From 088b1574440d154b3cfe308e35421395507acc04 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 12 Apr 2020 06:54:46 -0700 Subject: [PATCH] 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 --- src/__phutil_library_map__.php | 12 ++ src/conduit/ConduitSearchFuture.php | 103 ++++++++++++ src/conduit/FutureAgent.php | 38 +++++ src/ref/user/ArcanistUserRef.php | 33 ++++ .../user/ArcanistUserSymbolHardpointQuery.php | 155 ++++++++++++++++++ src/ref/user/ArcanistUserSymbolRef.php | 56 +++++++ .../user/ArcanistUserSymbolRefInspector.php | 22 +++ .../query/ArcanistWorkflowHardpointQuery.php | 24 ++- src/workflow/ArcanistInspectWorkflow.php | 2 +- 9 files changed, 443 insertions(+), 2 deletions(-) create mode 100644 src/conduit/ConduitSearchFuture.php create mode 100644 src/conduit/FutureAgent.php create mode 100644 src/ref/user/ArcanistUserRef.php create mode 100644 src/ref/user/ArcanistUserSymbolHardpointQuery.php create mode 100644 src/ref/user/ArcanistUserSymbolRef.php create mode 100644 src/ref/user/ArcanistUserSymbolRefInspector.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index fab4f853..1b49c2d2 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/conduit/ConduitSearchFuture.php b/src/conduit/ConduitSearchFuture.php new file mode 100644 index 00000000..935ffe9d --- /dev/null +++ b/src/conduit/ConduitSearchFuture.php @@ -0,0 +1,103 @@ +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'); + } + +} diff --git a/src/conduit/FutureAgent.php b/src/conduit/FutureAgent.php new file mode 100644 index 00000000..6000c6b2 --- /dev/null +++ b/src/conduit/FutureAgent.php @@ -0,0 +1,38 @@ +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; + } + +} diff --git a/src/ref/user/ArcanistUserRef.php b/src/ref/user/ArcanistUserRef.php new file mode 100644 index 00000000..010cd009 --- /dev/null +++ b/src/ref/user/ArcanistUserRef.php @@ -0,0 +1,33 @@ +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')); + } + +} diff --git a/src/ref/user/ArcanistUserSymbolHardpointQuery.php b/src/ref/user/ArcanistUserSymbolHardpointQuery.php new file mode 100644 index 00000000..6d37c510 --- /dev/null +++ b/src/ref/user/ArcanistUserSymbolHardpointQuery.php @@ -0,0 +1,155 @@ + $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); + } + +} diff --git a/src/ref/user/ArcanistUserSymbolRef.php b/src/ref/user/ArcanistUserSymbolRef.php new file mode 100644 index 00000000..527f0064 --- /dev/null +++ b/src/ref/user/ArcanistUserSymbolRef.php @@ -0,0 +1,56 @@ +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)); + } + +} diff --git a/src/ref/user/ArcanistUserSymbolRefInspector.php b/src/ref/user/ArcanistUserSymbolRefInspector.php new file mode 100644 index 00000000..9ba4621f --- /dev/null +++ b/src/ref/user/ArcanistUserSymbolRefInspector.php @@ -0,0 +1,22 @@ +setSymbol($argv[0]); + } + +} diff --git a/src/toolset/query/ArcanistWorkflowHardpointQuery.php b/src/toolset/query/ArcanistWorkflowHardpointQuery.php index 0246b2ce..9999ff21 100644 --- a/src/toolset/query/ArcanistWorkflowHardpointQuery.php +++ b/src/toolset/query/ArcanistWorkflowHardpointQuery.php @@ -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); } diff --git a/src/workflow/ArcanistInspectWorkflow.php b/src/workflow/ArcanistInspectWorkflow.php index a5dbeb99..8663400e 100644 --- a/src/workflow/ArcanistInspectWorkflow.php +++ b/src/workflow/ArcanistInspectWorkflow.php @@ -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(