mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-12-22 21:40:54 +01:00
Add ref lookup for username symbols
Summary: Ref T13490. I'm attempting to update "arc amend", but it needs to fetch revision authors to raise an "amending a revision you don't own" error. Support user-symbol resolution. Along the way, this introduces some infrastructure for abstracting away iteration over a multi-page Conduit result set. Test Plan: - Ran "arc inspect ..." for various "user(...)" queries. - Set page size to 3 and issued a general query, saw the future page it away properly. Maniphest Tasks: T13490 Differential Revision: https://secure.phabricator.com/D21092
This commit is contained in:
parent
1f18f25fa5
commit
088b157444
9 changed files with 443 additions and 2 deletions
|
@ -487,6 +487,10 @@ phutil_register_library_map(array(
|
|||
'ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase.php',
|
||||
'ArcanistUserAbortException' => 'exception/usage/ArcanistUserAbortException.php',
|
||||
'ArcanistUserConfigurationSource' => 'config/source/ArcanistUserConfigurationSource.php',
|
||||
'ArcanistUserRef' => 'ref/user/ArcanistUserRef.php',
|
||||
'ArcanistUserSymbolHardpointQuery' => 'ref/user/ArcanistUserSymbolHardpointQuery.php',
|
||||
'ArcanistUserSymbolRef' => 'ref/user/ArcanistUserSymbolRef.php',
|
||||
'ArcanistUserSymbolRefInspector' => 'ref/user/ArcanistUserSymbolRefInspector.php',
|
||||
'ArcanistVariableReferenceSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableReferenceSpacingXHPASTLinterRule.php',
|
||||
'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase.php',
|
||||
'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php',
|
||||
|
@ -527,6 +531,7 @@ phutil_register_library_map(array(
|
|||
'ConduitClientException' => 'conduit/ConduitClientException.php',
|
||||
'ConduitClientTestCase' => 'conduit/__tests__/ConduitClientTestCase.php',
|
||||
'ConduitFuture' => 'conduit/ConduitFuture.php',
|
||||
'ConduitSearchFuture' => 'conduit/ConduitSearchFuture.php',
|
||||
'ExecFuture' => 'future/exec/ExecFuture.php',
|
||||
'ExecFutureTestCase' => 'future/exec/__tests__/ExecFutureTestCase.php',
|
||||
'ExecPassthruTestCase' => 'future/exec/__tests__/ExecPassthruTestCase.php',
|
||||
|
@ -537,6 +542,7 @@ phutil_register_library_map(array(
|
|||
'FilesystemException' => 'filesystem/FilesystemException.php',
|
||||
'FilesystemTestCase' => 'filesystem/__tests__/FilesystemTestCase.php',
|
||||
'Future' => 'future/Future.php',
|
||||
'FutureAgent' => 'conduit/FutureAgent.php',
|
||||
'FutureIterator' => 'future/FutureIterator.php',
|
||||
'FutureIteratorTestCase' => 'future/__tests__/FutureIteratorTestCase.php',
|
||||
'FuturePool' => 'future/FuturePool.php',
|
||||
|
@ -1455,6 +1461,10 @@ phutil_register_library_map(array(
|
|||
'ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistUserAbortException' => 'ArcanistUsageException',
|
||||
'ArcanistUserConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
|
||||
'ArcanistUserRef' => 'ArcanistRef',
|
||||
'ArcanistUserSymbolHardpointQuery' => 'ArcanistWorkflowHardpointQuery',
|
||||
'ArcanistUserSymbolRef' => 'ArcanistSymbolRef',
|
||||
'ArcanistUserSymbolRefInspector' => 'ArcanistRefInspector',
|
||||
'ArcanistVariableReferenceSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistVariableVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
|
@ -1495,6 +1505,7 @@ phutil_register_library_map(array(
|
|||
'ConduitClientException' => 'Exception',
|
||||
'ConduitClientTestCase' => 'PhutilTestCase',
|
||||
'ConduitFuture' => 'FutureProxy',
|
||||
'ConduitSearchFuture' => 'FutureAgent',
|
||||
'ExecFuture' => 'PhutilExecutableFuture',
|
||||
'ExecFutureTestCase' => 'PhutilTestCase',
|
||||
'ExecPassthruTestCase' => 'PhutilTestCase',
|
||||
|
@ -1505,6 +1516,7 @@ phutil_register_library_map(array(
|
|||
'FilesystemException' => 'Exception',
|
||||
'FilesystemTestCase' => 'PhutilTestCase',
|
||||
'Future' => 'Phobject',
|
||||
'FutureAgent' => 'Future',
|
||||
'FutureIterator' => array(
|
||||
'Phobject',
|
||||
'Iterator',
|
||||
|
|
103
src/conduit/ConduitSearchFuture.php
Normal file
103
src/conduit/ConduitSearchFuture.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
final class ConduitSearchFuture
|
||||
extends FutureAgent {
|
||||
|
||||
private $conduitEngine;
|
||||
private $method;
|
||||
private $constraints;
|
||||
|
||||
private $objects = array();
|
||||
private $cursor;
|
||||
|
||||
public function setConduitEngine(ArcanistConduitEngine $conduit_engine) {
|
||||
$this->conduitEngine = $conduit_engine;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getConduitEngine() {
|
||||
return $this->conduitEngine;
|
||||
}
|
||||
|
||||
public function setMethod($method) {
|
||||
$this->method = $method;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMethod() {
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function setConstraints($constraints) {
|
||||
$this->constraints = $constraints;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getConstraints() {
|
||||
return $this->constraints;
|
||||
}
|
||||
|
||||
public function isReady() {
|
||||
if ($this->hasResult()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$futures = $this->getFutures();
|
||||
$future = head($futures);
|
||||
|
||||
if (!$future) {
|
||||
$future = $this->newFuture();
|
||||
}
|
||||
|
||||
if (!$future->isReady()) {
|
||||
$this->setFutures(array($future));
|
||||
return false;
|
||||
} else {
|
||||
$this->setFutures(array());
|
||||
}
|
||||
|
||||
$result = $future->resolve();
|
||||
|
||||
foreach ($this->readResults($result) as $object) {
|
||||
$this->objects[] = $object;
|
||||
}
|
||||
|
||||
$cursor = idxv($result, array('cursor', 'after'));
|
||||
|
||||
if ($cursor === null) {
|
||||
$this->setResult($this->objects);
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->cursor = $cursor;
|
||||
$future = $this->newFuture();
|
||||
$this->setFutures(array($future));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function newFuture() {
|
||||
$engine = $this->getConduitEngine();
|
||||
|
||||
$method = $this->getMethod();
|
||||
$constraints = $this->getConstraints();
|
||||
|
||||
$parameters = array(
|
||||
'constraints' => $constraints,
|
||||
);
|
||||
|
||||
if ($this->cursor !== null) {
|
||||
$parameters['after'] = (string)$this->cursor;
|
||||
}
|
||||
|
||||
$conduit_call = $engine->newCall($method, $parameters);
|
||||
$conduit_future = $engine->newFuture($conduit_call);
|
||||
|
||||
return $conduit_future;
|
||||
}
|
||||
|
||||
private function readResults(array $data) {
|
||||
return idx($data, 'data');
|
||||
}
|
||||
|
||||
}
|
38
src/conduit/FutureAgent.php
Normal file
38
src/conduit/FutureAgent.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
abstract class FutureAgent
|
||||
extends Future {
|
||||
|
||||
private $futures = array();
|
||||
|
||||
final protected function setFutures(array $futures) {
|
||||
$this->futures = $futures;
|
||||
}
|
||||
|
||||
final protected function getFutures() {
|
||||
return $this->futures;
|
||||
}
|
||||
|
||||
final public function getReadSockets() {
|
||||
$sockets = array();
|
||||
foreach ($this->getFutures() as $future) {
|
||||
foreach ($future->getReadSockets() as $read_socket) {
|
||||
$sockets[] = $read_socket;
|
||||
}
|
||||
}
|
||||
|
||||
return $sockets;
|
||||
}
|
||||
|
||||
final public function getWriteSockets() {
|
||||
$sockets = array();
|
||||
foreach ($this->getFutures() as $future) {
|
||||
foreach ($future->getWriteSockets() as $read_socket) {
|
||||
$sockets[] = $read_socket;
|
||||
}
|
||||
}
|
||||
|
||||
return $sockets;
|
||||
}
|
||||
|
||||
}
|
33
src/ref/user/ArcanistUserRef.php
Normal file
33
src/ref/user/ArcanistUserRef.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistUserRef
|
||||
extends ArcanistRef {
|
||||
|
||||
private $parameters;
|
||||
|
||||
public function getRefDisplayName() {
|
||||
return pht('User "%s"', $this->getUsername());
|
||||
}
|
||||
|
||||
public static function newFromConduit(array $parameters) {
|
||||
$ref = new self();
|
||||
$ref->parameters = $parameters;
|
||||
return $ref;
|
||||
}
|
||||
|
||||
public static function newFromConduitWhoami(array $parameters) {
|
||||
// NOTE: The "user.whoami" call returns a different structure than
|
||||
// "user.search". Mangle the data so it looks similar.
|
||||
|
||||
$parameters['fields'] = array(
|
||||
'username' => idx($parameters, 'userName'),
|
||||
);
|
||||
|
||||
return self::newFromConduit($parameters);
|
||||
}
|
||||
|
||||
public function getUsername() {
|
||||
return idxv($this->parameters, array('fields', 'username'));
|
||||
}
|
||||
|
||||
}
|
155
src/ref/user/ArcanistUserSymbolHardpointQuery.php
Normal file
155
src/ref/user/ArcanistUserSymbolHardpointQuery.php
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistUserSymbolHardpointQuery
|
||||
extends ArcanistWorkflowHardpointQuery {
|
||||
|
||||
public function getHardpoints() {
|
||||
return array(
|
||||
ArcanistUserSymbolRef::HARDPOINT_OBJECT,
|
||||
);
|
||||
}
|
||||
|
||||
protected function canLoadRef(ArcanistRef $ref) {
|
||||
return ($ref instanceof ArcanistUserSymbolRef);
|
||||
}
|
||||
|
||||
public function loadHardpoint(array $refs, $hardpoint) {
|
||||
$id_map = array();
|
||||
$phid_map = array();
|
||||
$username_map = array();
|
||||
$function_map = array();
|
||||
|
||||
foreach ($refs as $key => $ref) {
|
||||
switch ($ref->getSymbolType()) {
|
||||
case ArcanistUserSymbolRef::TYPE_ID:
|
||||
$id_map[$key] = $ref->getSymbol();
|
||||
break;
|
||||
case ArcanistUserSymbolRef::TYPE_PHID:
|
||||
$phid_map[$key] = $ref->getSymbol();
|
||||
break;
|
||||
case ArcanistUserSymbolRef::TYPE_USERNAME:
|
||||
$username_map[$key] = $ref->getSymbol();
|
||||
break;
|
||||
case ArcanistUserSymbolRef::TYPE_FUNCTION:
|
||||
$symbol = $ref->getSymbol();
|
||||
if ($symbol !== 'viewer()') {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Only the function "viewer()" is supported.'));
|
||||
}
|
||||
$function_map[$key] = $symbol;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$futures = array();
|
||||
|
||||
if ($function_map) {
|
||||
// The only function we support is "viewer()".
|
||||
$function_future = $this->newConduit(
|
||||
'user.whoami',
|
||||
array());
|
||||
|
||||
$futures[] = $function_future;
|
||||
} else {
|
||||
$function_future = null;
|
||||
}
|
||||
|
||||
if ($id_map) {
|
||||
$id_future = $this->newConduitSearch(
|
||||
'user.search',
|
||||
array(
|
||||
'ids' => array_values(array_fuse($id_map)),
|
||||
));
|
||||
|
||||
$futures[] = $id_future;
|
||||
} else {
|
||||
$id_future = null;
|
||||
}
|
||||
|
||||
if ($phid_map) {
|
||||
$phid_future = $this->newConduitSearch(
|
||||
'user.search',
|
||||
array(
|
||||
'phids' => array_values(array_fuse($phid_map)),
|
||||
));
|
||||
|
||||
$futures[] = $phid_future;
|
||||
} else {
|
||||
$phid_future = null;
|
||||
}
|
||||
|
||||
if ($username_map) {
|
||||
$username_future = $this->newConduitSearch(
|
||||
'user.search',
|
||||
array(
|
||||
'usernames' => array_values(array_fuse($username_map)),
|
||||
));
|
||||
|
||||
$futures[] = $username_future;
|
||||
} else {
|
||||
$username_future = null;
|
||||
}
|
||||
|
||||
yield $this->yieldFutures($futures);
|
||||
|
||||
$result_map = array();
|
||||
|
||||
if ($id_future) {
|
||||
$id_results = $id_future->resolve();
|
||||
$id_results = ipull($id_results, null, 'id');
|
||||
|
||||
foreach ($id_map as $key => $id) {
|
||||
$result_map[$key] = idx($id_results, $id);
|
||||
}
|
||||
}
|
||||
|
||||
if ($phid_future) {
|
||||
$phid_results = $phid_future->resolve();
|
||||
$phid_results = ipull($phid_results, null, 'phid');
|
||||
|
||||
foreach ($phid_map as $key => $phid) {
|
||||
$result_map[$key] = idx($phid_results, $phid);
|
||||
}
|
||||
}
|
||||
|
||||
if ($username_future) {
|
||||
$raw_results = $username_future->resolve();
|
||||
|
||||
$username_results = array();
|
||||
foreach ($raw_results as $raw_result) {
|
||||
$username = idxv($raw_result, array('fields', 'username'));
|
||||
$username_results[$username] = $raw_result;
|
||||
}
|
||||
|
||||
foreach ($username_map as $key => $username) {
|
||||
$result_map[$key] = idx($username_results, $username);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($result_map as $key => $raw_result) {
|
||||
if ($raw_result === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result_map[$key] = ArcanistUserRef::newFromConduit($raw_result);
|
||||
}
|
||||
|
||||
if ($function_future) {
|
||||
$raw_result = $function_future->resolve();
|
||||
|
||||
if ($raw_result === null) {
|
||||
$function_ref = null;
|
||||
} else {
|
||||
$function_ref = ArcanistUserRef::newFromConduitWhoami($raw_result);
|
||||
}
|
||||
|
||||
foreach ($function_map as $key => $function) {
|
||||
$result_map[$key] = $function_ref;
|
||||
}
|
||||
}
|
||||
|
||||
yield $this->yieldMap($result_map);
|
||||
}
|
||||
|
||||
}
|
56
src/ref/user/ArcanistUserSymbolRef.php
Normal file
56
src/ref/user/ArcanistUserSymbolRef.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistUserSymbolRef
|
||||
extends ArcanistSymbolRef {
|
||||
|
||||
private $type;
|
||||
|
||||
const TYPE_ID = 'id';
|
||||
const TYPE_PHID = 'phid';
|
||||
const TYPE_USERNAME = 'username';
|
||||
const TYPE_FUNCTION = 'function';
|
||||
|
||||
public function getRefDisplayName() {
|
||||
return pht('User Symbol "%s"', $this->getSymbol());
|
||||
}
|
||||
|
||||
public function getSymbolType() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
protected function resolveSymbol($symbol) {
|
||||
$matches = null;
|
||||
|
||||
$is_id = preg_match('/^([1-9]\d*)\z/', $symbol, $matches);
|
||||
if ($is_id) {
|
||||
$this->type = self::TYPE_ID;
|
||||
return (int)$matches[1];
|
||||
}
|
||||
|
||||
$is_phid = preg_match('/^PHID-USER-\S+\z/', $symbol, $matches);
|
||||
if ($is_phid) {
|
||||
$this->type = self::TYPE_PHID;
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$is_function = preg_match('/^\S+\(\s*\)\s*\z/', $symbol, $matches);
|
||||
if ($is_function) {
|
||||
$this->type = self::TYPE_FUNCTION;
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$is_username = preg_match('/^@?(\S+)\z/', $symbol, $matches);
|
||||
if ($is_username) {
|
||||
$this->type = self::TYPE_USERNAME;
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'The format of user symbol "%s" is unrecognized. Expected a '.
|
||||
'username like "alice" or "@alice", or a user PHID, or a user '.
|
||||
'ID, or a special function like "viewer()".',
|
||||
$symbol));
|
||||
}
|
||||
|
||||
}
|
22
src/ref/user/ArcanistUserSymbolRefInspector.php
Normal file
22
src/ref/user/ArcanistUserSymbolRefInspector.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistUserSymbolRefInspector
|
||||
extends ArcanistRefInspector {
|
||||
|
||||
public function getInspectFunctionName() {
|
||||
return 'user';
|
||||
}
|
||||
|
||||
public function newInspectRef(array $argv) {
|
||||
if (count($argv) !== 1) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Expected exactly one argument to "user(...)" with a '.
|
||||
'user symbol.'));
|
||||
}
|
||||
|
||||
return id(new ArcanistUserSymbolRef())
|
||||
->setSymbol($argv[0]);
|
||||
}
|
||||
|
||||
}
|
|
@ -51,13 +51,35 @@ abstract class ArcanistWorkflowHardpointQuery
|
|||
|
||||
abstract protected function canLoadRef(ArcanistRef $ref);
|
||||
|
||||
final public function yieldConduit($method, array $parameters) {
|
||||
final public function newConduitSearch($method, $constraints) {
|
||||
$conduit_engine = $this->getWorkflow()
|
||||
->getConduitEngine();
|
||||
|
||||
$conduit_future = id(new ConduitSearchFuture())
|
||||
->setConduitEngine($conduit_engine)
|
||||
->setMethod($method)
|
||||
->setConstraints($constraints);
|
||||
|
||||
return $conduit_future;
|
||||
}
|
||||
|
||||
final public function yieldConduitSearch($method, $constraints) {
|
||||
$conduit_future = $this->newConduitSearch($method, $constraints);
|
||||
return $this->yieldFuture($conduit_future);
|
||||
}
|
||||
|
||||
final public function newConduit($method, $parameters) {
|
||||
$conduit_engine = $this->getWorkflow()
|
||||
->getConduitEngine();
|
||||
|
||||
$call_object = $conduit_engine->newCall($method, $parameters);
|
||||
$call_future = $conduit_engine->newFuture($call_object);
|
||||
|
||||
return $call_future;
|
||||
}
|
||||
|
||||
final public function yieldConduit($method, array $parameters) {
|
||||
$call_future = $this->newConduit($method, $parameters);
|
||||
return $this->yieldFuture($call_future);
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ EOTEXT
|
|||
$all_refs = array();
|
||||
foreach ($objects as $description) {
|
||||
$matches = null;
|
||||
$pattern = '/^([\w-]+)(?:\(([^)]+)\))?\z/';
|
||||
$pattern = '/^([\w-]+)(?:\((.*)\))?\z/';
|
||||
if (!preg_match($pattern, $description, $matches)) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
|
|
Loading…
Reference in a new issue