From 4818892841347e373c4b2aad1773508db9f0c215 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 14 Jan 2011 20:00:11 -0800 Subject: [PATCH] Basic 'shell-complete' workflow. Summary: Adds data-driven shell completion help to arcanist. Test Plan: ran various commands in git and svn working copies, output seemed reasonable Differential Revision: 201754 Reviewed By: adonohue Reviewers: mroch, adonohue Commenters: crackerjack CC: epriestley, adonohue, achao Revert Plan: OK --- resources/shell/bash-completion | 24 +++ src/__phutil_library_map__.php | 2 + src/configuration/ArcanistConfiguration.php | 12 ++ src/workflow/amend/ArcanistAmendWorkflow.php | 5 + src/workflow/base/ArcanistBaseWorkflow.php | 12 ++ .../commit/ArcanistCommitWorkflow.php | 4 + .../ArcanistGitHookPreReceiveWorkflow.php | 4 + src/workflow/patch/ArcanistPatchWorkflow.php | 8 + .../ArcanistShellCompleteWorkflow.php | 144 ++++++++++++++++++ src/workflow/shell-complete/__init__.php | 17 +++ .../ArcanistSvnHookPreCommitWorkflow.php | 4 + 11 files changed, 236 insertions(+) create mode 100644 resources/shell/bash-completion create mode 100644 src/workflow/shell-complete/ArcanistShellCompleteWorkflow.php create mode 100644 src/workflow/shell-complete/__init__.php diff --git a/resources/shell/bash-completion b/resources/shell/bash-completion new file mode 100644 index 00000000..c87230d8 --- /dev/null +++ b/resources/shell/bash-completion @@ -0,0 +1,24 @@ +_arc() +{ + + CUR="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=() + OPTS=$(arc shell-complete --current ${COMP_CWORD} -- ${COMP_WORDS[@]}) + + if [ $? -ne 0 ]; then + return $? + fi + + if [ "$OPTS" = "FILE" ]; then + COMPREPLY=( $(compgen -f -- ${CUR}) ) + return 0 + fi + + if [ "$OPTS" = "ARGUMENT" ]; then + return 0 + fi + + COMPREPLY=( $(compgen -W "${OPTS}" -- ${CUR}) ) + +} +complete -F _arc -o filenames arc diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8d49e238..623caff0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -51,6 +51,7 @@ phutil_register_library_map(array( 'ArcanistPhutilTestCase' => 'unit/engine/phutil/testcase', 'ArcanistPhutilTestTerminatedException' => 'unit/engine/phutil/testcase/exception', 'ArcanistRepositoryAPI' => 'repository/api/base', + 'ArcanistShellCompleteWorkflow' => 'workflow/shell-complete', 'ArcanistSubversionAPI' => 'repository/api/subversion', 'ArcanistSvnHookPreCommitWorkflow' => 'workflow/svn-hook-pre-commit', 'ArcanistTextLinter' => 'lint/linter/text', @@ -92,6 +93,7 @@ phutil_register_library_map(array( 'ArcanistPEP8Linter' => 'ArcanistLinter', 'ArcanistPatchWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistPhutilModuleLinter' => 'ArcanistLinter', + 'ArcanistShellCompleteWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistSubversionAPI' => 'ArcanistRepositoryAPI', 'ArcanistSvnHookPreCommitWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistTextLinter' => 'ArcanistLinter', diff --git a/src/configuration/ArcanistConfiguration.php b/src/configuration/ArcanistConfiguration.php index 9d04ef09..1f5c2389 100644 --- a/src/configuration/ArcanistConfiguration.php +++ b/src/configuration/ArcanistConfiguration.php @@ -62,6 +62,14 @@ class ArcanistConfiguration { foreach ($symbols as $symbol) { $class = $symbol['name']; $name = preg_replace('/^Arcanist(\w+)Workflow$/', '\1', $class); + $name[0] = strtolower($name[0]); + $name = preg_replace_callback( + '/[A-Z]/', + array( + 'ArcanistConfiguration', + 'replaceClassnameUppers', + ), + $name); $name = strtolower($name); $workflows[$name] = newv($class, array()); } @@ -85,4 +93,8 @@ class ArcanistConfiguration { return strtoupper($m[1]); } + public static function replaceClassnameUppers($m) { + return '-'.strtolower($m[0]); + } + } diff --git a/src/workflow/amend/ArcanistAmendWorkflow.php b/src/workflow/amend/ArcanistAmendWorkflow.php index fd29962e..0d124da1 100644 --- a/src/workflow/amend/ArcanistAmendWorkflow.php +++ b/src/workflow/amend/ArcanistAmendWorkflow.php @@ -124,4 +124,9 @@ EOTEXT return 0; } + + protected function getSupportedRevisionControlSystems() { + return array('git'); + } + } diff --git a/src/workflow/base/ArcanistBaseWorkflow.php b/src/workflow/base/ArcanistBaseWorkflow.php index 3ca14ff9..a9544766 100644 --- a/src/workflow/base/ArcanistBaseWorkflow.php +++ b/src/workflow/base/ArcanistBaseWorkflow.php @@ -543,4 +543,16 @@ class ArcanistBaseWorkflow { return ltrim(strtoupper($revision_id), 'D'); } + protected function shouldShellComplete() { + return true; + } + + protected function getShellCompletions(array $argv) { + return array(); + } + + protected function getSupportedRevisionControlSystems() { + return array('any'); + } + } diff --git a/src/workflow/commit/ArcanistCommitWorkflow.php b/src/workflow/commit/ArcanistCommitWorkflow.php index c486d464..7631df72 100644 --- a/src/workflow/commit/ArcanistCommitWorkflow.php +++ b/src/workflow/commit/ArcanistCommitWorkflow.php @@ -248,4 +248,8 @@ EOTEXT } } + protected function getSupportedRevisionControlSystems() { + return array('svn'); + } + } diff --git a/src/workflow/git-hook-pre-receive/ArcanistGitHookPreReceiveWorkflow.php b/src/workflow/git-hook-pre-receive/ArcanistGitHookPreReceiveWorkflow.php index 47bcf20b..72c2601a 100644 --- a/src/workflow/git-hook-pre-receive/ArcanistGitHookPreReceiveWorkflow.php +++ b/src/workflow/git-hook-pre-receive/ArcanistGitHookPreReceiveWorkflow.php @@ -40,6 +40,10 @@ EOTEXT return true; } + public function shouldShellComplete() { + return false; + } + public function run() { $working_copy = $this->getWorkingCopy(); if (!$working_copy->getProjectID()) { diff --git a/src/workflow/patch/ArcanistPatchWorkflow.php b/src/workflow/patch/ArcanistPatchWorkflow.php index 11c63dad..29ceefa0 100644 --- a/src/workflow/patch/ArcanistPatchWorkflow.php +++ b/src/workflow/patch/ArcanistPatchWorkflow.php @@ -40,6 +40,7 @@ EOTEXT return array( 'revision' => array( 'param' => 'revision_id', + 'paramtype' => 'complete', 'help' => "Apply changes from a Differential revision, using the most recent ". "diff that has been attached to it.", @@ -54,11 +55,13 @@ EOTEXT ), 'arcbundle' => array( 'param' => 'bundlefile', + 'paramtype' => 'file', 'help' => "Apply changes from an arc bundle generated with 'arc export'.", ), 'patch' => array( 'param' => 'patchfile', + 'paramtype' => 'file', 'help' => "Apply changes from a git patchfile or unified patchfile.", ), @@ -323,4 +326,9 @@ EOTEXT return 0; } + + public function getShellCompletions(array $argv) { + // TODO: Pull open diffs from 'arc list'? + return array('ARGUMENT'); + } } diff --git a/src/workflow/shell-complete/ArcanistShellCompleteWorkflow.php b/src/workflow/shell-complete/ArcanistShellCompleteWorkflow.php new file mode 100644 index 00000000..6b117761 --- /dev/null +++ b/src/workflow/shell-complete/ArcanistShellCompleteWorkflow.php @@ -0,0 +1,144 @@ + array( + 'help' => 'Current term in the argument list being completed.', + 'param' => 'cursor_position', + ), + '*' => 'argv', + ); + } + + public function shouldShellComplete() { + return false; + } + + public function run() { + + $pos = $this->getArgument('current'); + $argv = $this->getArgument('argv', array()); + $argc = count($argv); + if ($pos === null) { + $pos = $argc - 1; + } + + // Determine which revision control system the working copy uses, so we + // can filter out commands and flags which aren't supported. If we can't + // figure it out, just return all flags/commands. + $vcs = null; + + // We have to build our own because if we requiresWorkingCopy() we'll throw + // if we aren't in a .arcconfig directory. We probably still can't do much, + // but commands can raise more detailed errors. + $working_copy = ArcanistWorkingCopyIdentity::newFromPath($_SERVER['PWD']); + if ($working_copy->getProjectRoot()) { + $repository_api = ArcanistRepositoryAPI::newAPIFromWorkingCopyIdentity( + $working_copy); + $vcs = $repository_api->getSourceControlSystemName(); + } + + $arc_config = $this->getArcanistConfiguration(); + + if ($pos == 1) { + $workflows = $arc_config->buildAllWorkflows(); + + $complete = array(); + foreach ($workflows as $name => $workflow) { + if (!$workflow->shouldShellComplete()) { + continue; + } + + $supported = $workflow->getSupportedRevisionControlSystems(); + + $ok = (in_array('any', $supported)) || + (in_array($vcs, $supported)); + if (!$ok) { + continue; + } + + $complete[] = $name; + } + + echo implode(' ', $complete)."\n"; + return 0; + } else { + $workflow = $arc_config->buildWorkflow($argv[1]); + if (!$workflow) { + return 1; + } + $arguments = $workflow->getArguments(); + + $prev = idx($argv, $pos - 1, null); + if (!strncmp($prev, '--', 2)) { + $prev = substr($prev, 2); + } else { + $prev = null; + } + + if ($prev !== null && + isset($arguments[$prev]) && + isset($arguments[$prev]['param'])) { + + $type = idx($arguments[$prev], 'paramtype'); + switch ($type) { + case 'file': + echo "FILE\n"; + break; + case 'complete': + echo implode(' ', $workflow->getShellCompletions($argv))."\n"; + break; + default: + echo "ARGUMENT\n"; + break; + } + return 0; + } else { + $output = array(); + foreach ($arguments as $argument => $spec) { + if ($argument == '*') { + continue; + } + if ($vcs && + isset($spec['supports']) && + !in_array($vcs, $spec['supports'])) { + continue; + } + $output[] = '--'.$argument; + } + + echo implode(' ', $output)."\n"; + return 0; + } + } + } +} diff --git a/src/workflow/shell-complete/__init__.php b/src/workflow/shell-complete/__init__.php new file mode 100644 index 00000000..dbb4ac02 --- /dev/null +++ b/src/workflow/shell-complete/__init__.php @@ -0,0 +1,17 @@ +getArgument('svnargs');