@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Modularize parser for "Closes task X as Y"

Summary:
Ref T3886. Ref T3872. Ref T1812. We have several parsers which look for textual references to other objects, like:

Closes Tx.
Depends on Dy.
Reverts Dz.

Currently, these are pretty hard coded, don't get all the edge cases right, and don't generalize well. They're also implemented in the middle of Differential's field code. So I want to:

- Share more code so that, e.g., "Tx, Ty" always works (only some rules support it right now);
- fix bugs in the parser, like T3872;
- make this a modular, extensible process which runs against custom fields, not a builtin part of fields;
- make the internals more flexible to accommodate custom stuff like T1812.

This implements the "Verbs optional-noun Object, Optional Other Objects optional-as-something." grammar in a general way so subclasses can just plug in their keywords. Runtime code doesn't touch this yet.

Test Plan: Ran unit tests.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T3872, T1812, T3886

Differential Revision: https://secure.phabricator.com/D8261

+173
+6
src/__phutil_library_map__.php
··· 355 355 'DifferentialCommitsFieldSpecification' => 'applications/differential/field/specification/DifferentialCommitsFieldSpecification.php', 356 356 'DifferentialConflictsFieldSpecification' => 'applications/differential/field/specification/DifferentialConflictsFieldSpecification.php', 357 357 'DifferentialController' => 'applications/differential/controller/DifferentialController.php', 358 + 'DifferentialCustomFieldDependsOnParser' => 'applications/differential/field/parser/DifferentialCustomFieldDependsOnParser.php', 359 + 'DifferentialCustomFieldDependsOnParserTestCase' => 'applications/differential/field/parser/__tests__/DifferentialCustomFieldDependsOnParserTestCase.php', 358 360 'DifferentialCustomFieldNumericIndex' => 'applications/differential/storage/DifferentialCustomFieldNumericIndex.php', 359 361 'DifferentialCustomFieldStorage' => 'applications/differential/storage/DifferentialCustomFieldStorage.php', 360 362 'DifferentialCustomFieldStringIndex' => 'applications/differential/storage/DifferentialCustomFieldStringIndex.php', ··· 1369 1371 'PhabricatorCustomFieldIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldIndexStorage.php', 1370 1372 'PhabricatorCustomFieldInterface' => 'infrastructure/customfield/interface/PhabricatorCustomFieldInterface.php', 1371 1373 'PhabricatorCustomFieldList' => 'infrastructure/customfield/field/PhabricatorCustomFieldList.php', 1374 + 'PhabricatorCustomFieldMonogramParser' => 'infrastructure/customfield/parser/PhabricatorCustomFieldMonogramParser.php', 1372 1375 'PhabricatorCustomFieldNotAttachedException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotAttachedException.php', 1373 1376 'PhabricatorCustomFieldNotProxyException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotProxyException.php', 1374 1377 'PhabricatorCustomFieldNumericIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldNumericIndexStorage.php', ··· 2870 2873 'DifferentialCommitsFieldSpecification' => 'DifferentialFieldSpecification', 2871 2874 'DifferentialConflictsFieldSpecification' => 'DifferentialFieldSpecification', 2872 2875 'DifferentialController' => 'PhabricatorController', 2876 + 'DifferentialCustomFieldDependsOnParser' => 'PhabricatorCustomFieldMonogramParser', 2877 + 'DifferentialCustomFieldDependsOnParserTestCase' => 'PhabricatorTestCase', 2873 2878 'DifferentialCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 2874 2879 'DifferentialCustomFieldStorage' => 'PhabricatorCustomFieldStorage', 2875 2880 'DifferentialCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', ··· 4053 4058 'PhabricatorCustomFieldImplementationIncompleteException' => 'Exception', 4054 4059 'PhabricatorCustomFieldIndexStorage' => 'PhabricatorLiskDAO', 4055 4060 'PhabricatorCustomFieldList' => 'Phobject', 4061 + 'PhabricatorCustomFieldMonogramParser' => 'Phobject', 4056 4062 'PhabricatorCustomFieldNotAttachedException' => 'Exception', 4057 4063 'PhabricatorCustomFieldNotProxyException' => 'Exception', 4058 4064 'PhabricatorCustomFieldNumericIndexStorage' => 'PhabricatorCustomFieldIndexStorage',
+32
src/applications/differential/field/parser/DifferentialCustomFieldDependsOnParser.php
··· 1 + <?php 2 + 3 + final class DifferentialCustomFieldDependsOnParser 4 + extends PhabricatorCustomFieldMonogramParser { 5 + 6 + protected function getPrefixes() { 7 + return array( 8 + 'depends on', 9 + ); 10 + } 11 + 12 + protected function getInfixes() { 13 + return array( 14 + 'diff', 15 + 'diffs', 16 + 'change', 17 + 'changes', 18 + 'rev', 19 + 'revs', 20 + 'revision', 21 + ); 22 + } 23 + 24 + protected function getSuffixes() { 25 + return array(); 26 + } 27 + 28 + protected function getMonogramPattern() { 29 + return 'D\d+'; 30 + } 31 + 32 + }
+64
src/applications/differential/field/parser/__tests__/DifferentialCustomFieldDependsOnParserTestCase.php
··· 1 + <?php 2 + 3 + final class DifferentialCustomFieldDependsOnParserTestCase 4 + extends PhabricatorTestCase { 5 + 6 + public function testParser() { 7 + $map = array( 8 + 'quack quack quack' => array(), 9 + 'D123' => array(), 10 + 'depends on D123' => array( 11 + array( 12 + 'match' => 'depends on D123', 13 + 'prefix' => 'depends on', 14 + 'infix' => '', 15 + 'monograms' => array('D123'), 16 + 'suffix' => '', 17 + 'offset' => 0, 18 + ), 19 + ), 20 + 'depends on D123.' => array( 21 + array( 22 + 'match' => 'depends on D123', 23 + 'prefix' => 'depends on', 24 + 'infix' => '', 25 + 'monograms' => array('D123'), 26 + 'suffix' => '', 27 + 'offset' => 0, 28 + ), 29 + ), 30 + 'depends on D123, D124' => array( 31 + array( 32 + 'match' => 'depends on D123, D124', 33 + 'prefix' => 'depends on', 34 + 'infix' => '', 35 + 'monograms' => array('D123', 'D124'), 36 + 'suffix' => '', 37 + 'offset' => 0, 38 + ), 39 + ), 40 + 'depends on rev D123' => array( 41 + array( 42 + 'match' => 'depends on rev D123', 43 + 'prefix' => 'depends on', 44 + 'infix' => 'rev', 45 + 'monograms' => array('D123'), 46 + 'suffix' => '', 47 + 'offset' => 0, 48 + ), 49 + ), 50 + 'depends on duck' => array( 51 + ), 52 + 'depends on D123abc' => array( 53 + ), 54 + ); 55 + 56 + foreach ($map as $input => $expect) { 57 + $parser = new DifferentialCustomFieldDependsOnParser(); 58 + $output = $parser->parseCorpus($input); 59 + 60 + $this->assertEqual($expect, $output, $input); 61 + } 62 + } 63 + 64 + }
+71
src/infrastructure/customfield/parser/PhabricatorCustomFieldMonogramParser.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorCustomFieldMonogramParser 4 + extends Phobject { 5 + 6 + abstract protected function getPrefixes(); 7 + abstract protected function getSuffixes(); 8 + abstract protected function getInfixes(); 9 + abstract protected function getMonogramPattern(); 10 + 11 + public function parseCorpus($corpus) { 12 + $prefixes = $this->getPrefixes(); 13 + $suffixes = $this->getSuffixes(); 14 + $infixes = $this->getInfixes(); 15 + 16 + $prefix_regex = $this->buildRegex($prefixes); 17 + $infix_regex = $this->buildRegex($infixes, true); 18 + $suffix_regex = $this->buildRegex($suffixes, true, true); 19 + 20 + $monogram_pattern = $this->getMonogramPattern(); 21 + 22 + $pattern = 23 + '/'. 24 + '(?:^|\b)'. 25 + $prefix_regex. 26 + $infix_regex. 27 + '((?:'.$monogram_pattern.'[,\s]*)+)'. 28 + $suffix_regex. 29 + '(?:$|\b)'. 30 + '/i'; 31 + 32 + $matches = null; 33 + $ok = preg_match_all( 34 + $pattern, 35 + $corpus, 36 + $matches, 37 + PREG_SET_ORDER | PREG_OFFSET_CAPTURE); 38 + 39 + if ($ok === false) { 40 + throw new Exception(pht('Regular expression "%s" is invalid!', $pattern)); 41 + } 42 + 43 + $results = array(); 44 + foreach ($matches as $set) { 45 + $results[] = array( 46 + 'match' => $set[0][0], 47 + 'prefix' => $set[1][0], 48 + 'infix' => $set[2][0], 49 + 'monograms' => array_filter(preg_split('/[,\s]+/', $set[3][0])), 50 + 'suffix' => $set[4][0], 51 + 'offset' => $set[0][1], 52 + ); 53 + } 54 + 55 + return $results; 56 + } 57 + 58 + private function buildRegex(array $list, $optional = false, $final = false) { 59 + $parts = array(); 60 + foreach ($list as $string) { 61 + $parts[] = preg_quote($string, '/'); 62 + } 63 + $parts = implode('|', $parts); 64 + 65 + $maybe_tail = $final ? '' : '\s+'; 66 + $maybe_optional = $optional ? '?' : ''; 67 + 68 + return '(?:('.$parts.')'.$maybe_tail.')'.$maybe_optional; 69 + } 70 + 71 + }