1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 05:50:55 +01:00

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
This commit is contained in:
epriestley 2014-02-17 15:53:47 -08:00
parent 7ff539b729
commit d016cac915
4 changed files with 173 additions and 0 deletions

View file

@ -355,6 +355,8 @@ phutil_register_library_map(array(
'DifferentialCommitsFieldSpecification' => 'applications/differential/field/specification/DifferentialCommitsFieldSpecification.php', 'DifferentialCommitsFieldSpecification' => 'applications/differential/field/specification/DifferentialCommitsFieldSpecification.php',
'DifferentialConflictsFieldSpecification' => 'applications/differential/field/specification/DifferentialConflictsFieldSpecification.php', 'DifferentialConflictsFieldSpecification' => 'applications/differential/field/specification/DifferentialConflictsFieldSpecification.php',
'DifferentialController' => 'applications/differential/controller/DifferentialController.php', 'DifferentialController' => 'applications/differential/controller/DifferentialController.php',
'DifferentialCustomFieldDependsOnParser' => 'applications/differential/field/parser/DifferentialCustomFieldDependsOnParser.php',
'DifferentialCustomFieldDependsOnParserTestCase' => 'applications/differential/field/parser/__tests__/DifferentialCustomFieldDependsOnParserTestCase.php',
'DifferentialCustomFieldNumericIndex' => 'applications/differential/storage/DifferentialCustomFieldNumericIndex.php', 'DifferentialCustomFieldNumericIndex' => 'applications/differential/storage/DifferentialCustomFieldNumericIndex.php',
'DifferentialCustomFieldStorage' => 'applications/differential/storage/DifferentialCustomFieldStorage.php', 'DifferentialCustomFieldStorage' => 'applications/differential/storage/DifferentialCustomFieldStorage.php',
'DifferentialCustomFieldStringIndex' => 'applications/differential/storage/DifferentialCustomFieldStringIndex.php', 'DifferentialCustomFieldStringIndex' => 'applications/differential/storage/DifferentialCustomFieldStringIndex.php',
@ -1369,6 +1371,7 @@ phutil_register_library_map(array(
'PhabricatorCustomFieldIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldIndexStorage.php', 'PhabricatorCustomFieldIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldIndexStorage.php',
'PhabricatorCustomFieldInterface' => 'infrastructure/customfield/interface/PhabricatorCustomFieldInterface.php', 'PhabricatorCustomFieldInterface' => 'infrastructure/customfield/interface/PhabricatorCustomFieldInterface.php',
'PhabricatorCustomFieldList' => 'infrastructure/customfield/field/PhabricatorCustomFieldList.php', 'PhabricatorCustomFieldList' => 'infrastructure/customfield/field/PhabricatorCustomFieldList.php',
'PhabricatorCustomFieldMonogramParser' => 'infrastructure/customfield/parser/PhabricatorCustomFieldMonogramParser.php',
'PhabricatorCustomFieldNotAttachedException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotAttachedException.php', 'PhabricatorCustomFieldNotAttachedException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotAttachedException.php',
'PhabricatorCustomFieldNotProxyException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotProxyException.php', 'PhabricatorCustomFieldNotProxyException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotProxyException.php',
'PhabricatorCustomFieldNumericIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldNumericIndexStorage.php', 'PhabricatorCustomFieldNumericIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldNumericIndexStorage.php',
@ -2870,6 +2873,8 @@ phutil_register_library_map(array(
'DifferentialCommitsFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialCommitsFieldSpecification' => 'DifferentialFieldSpecification',
'DifferentialConflictsFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialConflictsFieldSpecification' => 'DifferentialFieldSpecification',
'DifferentialController' => 'PhabricatorController', 'DifferentialController' => 'PhabricatorController',
'DifferentialCustomFieldDependsOnParser' => 'PhabricatorCustomFieldMonogramParser',
'DifferentialCustomFieldDependsOnParserTestCase' => 'PhabricatorTestCase',
'DifferentialCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 'DifferentialCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
'DifferentialCustomFieldStorage' => 'PhabricatorCustomFieldStorage', 'DifferentialCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
'DifferentialCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 'DifferentialCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
@ -4053,6 +4058,7 @@ phutil_register_library_map(array(
'PhabricatorCustomFieldImplementationIncompleteException' => 'Exception', 'PhabricatorCustomFieldImplementationIncompleteException' => 'Exception',
'PhabricatorCustomFieldIndexStorage' => 'PhabricatorLiskDAO', 'PhabricatorCustomFieldIndexStorage' => 'PhabricatorLiskDAO',
'PhabricatorCustomFieldList' => 'Phobject', 'PhabricatorCustomFieldList' => 'Phobject',
'PhabricatorCustomFieldMonogramParser' => 'Phobject',
'PhabricatorCustomFieldNotAttachedException' => 'Exception', 'PhabricatorCustomFieldNotAttachedException' => 'Exception',
'PhabricatorCustomFieldNotProxyException' => 'Exception', 'PhabricatorCustomFieldNotProxyException' => 'Exception',
'PhabricatorCustomFieldNumericIndexStorage' => 'PhabricatorCustomFieldIndexStorage', 'PhabricatorCustomFieldNumericIndexStorage' => 'PhabricatorCustomFieldIndexStorage',

View file

@ -0,0 +1,32 @@
<?php
final class DifferentialCustomFieldDependsOnParser
extends PhabricatorCustomFieldMonogramParser {
protected function getPrefixes() {
return array(
'depends on',
);
}
protected function getInfixes() {
return array(
'diff',
'diffs',
'change',
'changes',
'rev',
'revs',
'revision',
);
}
protected function getSuffixes() {
return array();
}
protected function getMonogramPattern() {
return 'D\d+';
}
}

View file

@ -0,0 +1,64 @@
<?php
final class DifferentialCustomFieldDependsOnParserTestCase
extends PhabricatorTestCase {
public function testParser() {
$map = array(
'quack quack quack' => array(),
'D123' => array(),
'depends on D123' => array(
array(
'match' => 'depends on D123',
'prefix' => 'depends on',
'infix' => '',
'monograms' => array('D123'),
'suffix' => '',
'offset' => 0,
),
),
'depends on D123.' => array(
array(
'match' => 'depends on D123',
'prefix' => 'depends on',
'infix' => '',
'monograms' => array('D123'),
'suffix' => '',
'offset' => 0,
),
),
'depends on D123, D124' => array(
array(
'match' => 'depends on D123, D124',
'prefix' => 'depends on',
'infix' => '',
'monograms' => array('D123', 'D124'),
'suffix' => '',
'offset' => 0,
),
),
'depends on rev D123' => array(
array(
'match' => 'depends on rev D123',
'prefix' => 'depends on',
'infix' => 'rev',
'monograms' => array('D123'),
'suffix' => '',
'offset' => 0,
),
),
'depends on duck' => array(
),
'depends on D123abc' => array(
),
);
foreach ($map as $input => $expect) {
$parser = new DifferentialCustomFieldDependsOnParser();
$output = $parser->parseCorpus($input);
$this->assertEqual($expect, $output, $input);
}
}
}

View file

@ -0,0 +1,71 @@
<?php
abstract class PhabricatorCustomFieldMonogramParser
extends Phobject {
abstract protected function getPrefixes();
abstract protected function getSuffixes();
abstract protected function getInfixes();
abstract protected function getMonogramPattern();
public function parseCorpus($corpus) {
$prefixes = $this->getPrefixes();
$suffixes = $this->getSuffixes();
$infixes = $this->getInfixes();
$prefix_regex = $this->buildRegex($prefixes);
$infix_regex = $this->buildRegex($infixes, true);
$suffix_regex = $this->buildRegex($suffixes, true, true);
$monogram_pattern = $this->getMonogramPattern();
$pattern =
'/'.
'(?:^|\b)'.
$prefix_regex.
$infix_regex.
'((?:'.$monogram_pattern.'[,\s]*)+)'.
$suffix_regex.
'(?:$|\b)'.
'/i';
$matches = null;
$ok = preg_match_all(
$pattern,
$corpus,
$matches,
PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
if ($ok === false) {
throw new Exception(pht('Regular expression "%s" is invalid!', $pattern));
}
$results = array();
foreach ($matches as $set) {
$results[] = array(
'match' => $set[0][0],
'prefix' => $set[1][0],
'infix' => $set[2][0],
'monograms' => array_filter(preg_split('/[,\s]+/', $set[3][0])),
'suffix' => $set[4][0],
'offset' => $set[0][1],
);
}
return $results;
}
private function buildRegex(array $list, $optional = false, $final = false) {
$parts = array();
foreach ($list as $string) {
$parts[] = preg_quote($string, '/');
}
$parts = implode('|', $parts);
$maybe_tail = $final ? '' : '\s+';
$maybe_optional = $optional ? '?' : '';
return '(?:('.$parts.')'.$maybe_tail.')'.$maybe_optional;
}
}