@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.

Support evaluation of complex tokenizer functions

Summary:
Depends on D19088. Ref T13079.

> Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
> - Greenspun's Tenth Rule

Move us a step closer to this noble goal.

This doesn't implement any `viewer(project())` stuff but it looks like the API doesn't need to change to do that in the future.

Test Plan: Grimmaced in pain.

Maniphest Tasks: T13079

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

+157 -5
+2
src/__phutil_library_map__.php
··· 4381 4381 'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php', 4382 4382 'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php', 4383 4383 'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php', 4384 + 'PhabricatorTypeaheadTestNumbersDatasource' => 'applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php', 4384 4385 'PhabricatorTypeaheadTokenView' => 'applications/typeahead/view/PhabricatorTypeaheadTokenView.php', 4385 4386 'PhabricatorUIConfigOptions' => 'applications/config/option/PhabricatorUIConfigOptions.php', 4386 4387 'PhabricatorUIExample' => 'applications/uiexample/examples/PhabricatorUIExample.php', ··· 10159 10160 'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource', 10160 10161 'PhabricatorTypeaheadResult' => 'Phobject', 10161 10162 'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 10163 + 'PhabricatorTypeaheadTestNumbersDatasource' => 'PhabricatorTypeaheadDatasource', 10162 10164 'PhabricatorTypeaheadTokenView' => 'AphrontTagView', 10163 10165 'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions', 10164 10166 'PhabricatorUIExample' => 'Phobject',
+46 -5
src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php
··· 406 406 $results = $this->evaluateValues($results); 407 407 408 408 foreach ($evaluate as $result_key => $function) { 409 - $function = self::parseFunction($function); 409 + $function = $this->parseFunction($function); 410 410 if (!$function) { 411 411 throw new PhabricatorTypeaheadInvalidTokenException(); 412 412 } ··· 459 459 /** 460 460 * @task functions 461 461 */ 462 - public function parseFunction($token, $allow_partial = false) { 462 + protected function parseFunction($token, $allow_partial = false) { 463 463 $matches = null; 464 464 465 465 if ($allow_partial) { 466 - $ok = preg_match('/^([^(]+)\((.*?)\)?$/', $token, $matches); 466 + $ok = preg_match('/^([^(]+)\((.*?)\)?\z/', $token, $matches); 467 467 } else { 468 - $ok = preg_match('/^([^(]+)\((.*)\)$/', $token, $matches); 468 + $ok = preg_match('/^([^(]+)\((.*)\)\z/', $token, $matches); 469 469 } 470 470 471 471 if (!$ok) { 472 + if (!$allow_partial) { 473 + throw new PhabricatorTypeaheadInvalidTokenException( 474 + pht( 475 + 'Unable to parse function and arguments for token "%s".', 476 + $token)); 477 + } 472 478 return null; 473 479 } 474 480 475 481 $function = trim($matches[1]); 476 482 477 483 if (!$this->canEvaluateFunction($function)) { 484 + if (!$allow_partial) { 485 + throw new PhabricatorTypeaheadInvalidTokenException( 486 + pht( 487 + 'This datasource ("%s") can not evaluate the function "%s(...)".', 488 + get_class($this), 489 + $function)); 490 + } 491 + 478 492 return null; 479 493 } 480 494 495 + // TODO: There is currently no way to quote characters in arguments, so 496 + // some characters can't be argument characters. Replace this with a real 497 + // parser once we get use cases. 498 + 499 + $argv = $matches[2]; 500 + $argv = trim($argv); 501 + if (!strlen($argv)) { 502 + $argv = array(); 503 + } else { 504 + $argv = preg_split('/,/', $matches[2]); 505 + foreach ($argv as $key => $arg) { 506 + $argv[$key] = trim($arg); 507 + } 508 + } 509 + 510 + foreach ($argv as $key => $arg) { 511 + if (self::isFunctionToken($arg)) { 512 + $subfunction = $this->parseFunction($arg); 513 + 514 + $results = $this->evaluateFunction( 515 + $subfunction['name'], 516 + array($subfunction['argv'])); 517 + 518 + $argv[$key] = head($results); 519 + } 520 + } 521 + 481 522 return array( 482 523 'name' => $function, 483 - 'argv' => array(trim($matches[2])), 524 + 'argv' => $argv, 484 525 ); 485 526 } 486 527
+42
src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadDatasourceTestCase.php
··· 48 48 pht('Tokenization of "%s"', $input)); 49 49 } 50 50 51 + public function testFunctionEvaluation() { 52 + $viewer = PhabricatorUser::getOmnipotentUser(); 53 + 54 + $datasource = id(new PhabricatorTypeaheadTestNumbersDatasource()) 55 + ->setViewer($viewer); 56 + 57 + $constraints = $datasource->evaluateTokens( 58 + array( 59 + 9, 60 + 'seven()', 61 + 12, 62 + 3, 63 + )); 64 + 65 + $this->assertEqual( 66 + array(9, 7, 12, 3), 67 + $constraints); 68 + 69 + $map = array( 70 + 'inc(3)' => 4, 71 + 'sum(3, 4)' => 7, 72 + 'inc(seven())' => 8, 73 + 'inc(inc(3))' => 5, 74 + 'inc(inc(seven()))' => 9, 75 + 'sum(seven(), seven())' => 14, 76 + 'sum(inc(seven()), inc(inc(9)))' => 19, 77 + ); 78 + 79 + foreach ($map as $input => $expect) { 80 + $constraints = $datasource->evaluateTokens( 81 + array( 82 + $input, 83 + )); 84 + 85 + $this->assertEqual( 86 + array($expect), 87 + $constraints, 88 + pht('Constraints for input "%s".', $input)); 89 + } 90 + } 91 + 92 + 51 93 }
+67
src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php
··· 1 + <?php 2 + 3 + final class PhabricatorTypeaheadTestNumbersDatasource 4 + extends PhabricatorTypeaheadDatasource { 5 + 6 + public function getBrowseTitle() { 7 + return pht('Browse Numbers'); 8 + } 9 + 10 + public function getPlaceholderText() { 11 + return null; 12 + } 13 + 14 + public function getDatasourceApplicationClass() { 15 + return 'PhabricatorPeopleApplication'; 16 + } 17 + 18 + public function getDatasourceFunctions() { 19 + return array( 20 + 'seven' => array(), 21 + 'inc' => array(), 22 + 'sum' => array(), 23 + ); 24 + } 25 + 26 + public function loadResults() { 27 + return array(); 28 + } 29 + 30 + public function renderFunctionTokens($function, array $argv_list) { 31 + return array(); 32 + } 33 + 34 + protected function evaluateFunction($function, array $argv_list) { 35 + $results = array(); 36 + 37 + foreach ($argv_list as $argv) { 38 + foreach ($argv as $k => $arg) { 39 + if (!is_scalar($arg) || !preg_match('/^\d+\z/', $arg)) { 40 + throw new PhabricatorTypeaheadInvalidTokenException( 41 + pht( 42 + 'All arguments to "%s(...)" must be integers, found '. 43 + '"%s" in position %d.', 44 + $function, 45 + (is_scalar($arg) ? $arg : gettype($arg)), 46 + $k + 1)); 47 + } 48 + $argv[$k] = (int)$arg; 49 + } 50 + 51 + switch ($function) { 52 + case 'seven': 53 + $results[] = 7; 54 + break; 55 + case 'inc': 56 + $results[] = $argv[0] + 1; 57 + break; 58 + case 'sum': 59 + $results[] = array_sum($argv); 60 + break; 61 + } 62 + } 63 + 64 + return $results; 65 + } 66 + 67 + }