1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-12-28 08:20:56 +01:00

[Wilds] Make "arc liberate" run in the untamed wilds

Summary:
Depends on D19677. Ref T13098. This gets enough of the new workflow/toolset stuff running to run `arc liberate` (without arguments) so that the new `arc` can bootstrap itself to continue development.

This change also moves some workflows which will be shared across toolsets (shell completion, versions, help, aliases) out of being `arc`-specific, to varying degrees of success. These workflows will need to be revisited and cleaned up so they work properly. They mostly fatal when run right now.

The Conduit flags have moved to the new `arc` parent workflow, but they'll likely move to some kind of support object so `my-company-thing megadiff` can accept Conduit flags. This is a more involved change, however.

Test Plan: Ran `arc liberate`, got the Wilds `arc` to rebuild its own map. Everything else crashes!

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13098

Differential Revision: https://secure.phabricator.com/D19678
This commit is contained in:
epriestley 2018-09-14 11:33:40 -07:00
parent 7d05dbec15
commit 8e51f89c79
14 changed files with 251 additions and 683 deletions

View file

@ -15,9 +15,11 @@ phutil_register_library_map(array(
'ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase.php',
'ArcanistAliasFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAliasFunctionXHPASTLinterRule.php',
'ArcanistAliasFunctionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAliasFunctionXHPASTLinterRuleTestCase.php',
'ArcanistAliasWorkflow' => 'workflow/ArcanistAliasWorkflow.php',
'ArcanistAliasWorkflow' => 'toolset/ArcanistAliasWorkflow.php',
'ArcanistAmendWorkflow' => 'workflow/ArcanistAmendWorkflow.php',
'ArcanistAnoidWorkflow' => 'workflow/ArcanistAnoidWorkflow.php',
'ArcanistArcToolset' => 'toolset/ArcanistArcToolset.php',
'ArcanistArcWorkflow' => 'workflow/ArcanistArcWorkflow.php',
'ArcanistArrayCombineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayCombineXHPASTLinterRule.php',
'ArcanistArrayCombineXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistArrayCombineXHPASTLinterRuleTestCase.php',
'ArcanistArrayIndexSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayIndexSpacingXHPASTLinterRule.php',
@ -96,7 +98,6 @@ phutil_register_library_map(array(
'ArcanistConcatenationOperatorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistConcatenationOperatorXHPASTLinterRuleTestCase.php',
'ArcanistConduitCall' => 'conduit/ArcanistConduitCall.php',
'ArcanistConduitEngine' => 'conduit/ArcanistConduitEngine.php',
'ArcanistConfiguration' => 'configuration/ArcanistConfiguration.php',
'ArcanistConfigurationDrivenLintEngine' => 'lint/engine/ArcanistConfigurationDrivenLintEngine.php',
'ArcanistConfigurationDrivenUnitTestEngine' => 'unit/engine/ArcanistConfigurationDrivenUnitTestEngine.php',
'ArcanistConfigurationManager' => 'configuration/ArcanistConfigurationManager.php',
@ -186,7 +187,6 @@ phutil_register_library_map(array(
'ArcanistHLintLinter' => 'lint/linter/ArcanistHLintLinter.php',
'ArcanistHLintLinterTestCase' => 'lint/linter/__tests__/ArcanistHLintLinterTestCase.php',
'ArcanistHardpointLoader' => 'loader/ArcanistHardpointLoader.php',
'ArcanistHelpWorkflow' => 'workflow/ArcanistHelpWorkflow.php',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule.php',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase.php',
'ArcanistHgClientChannel' => 'hgdaemon/ArcanistHgClientChannel.php',
@ -310,6 +310,7 @@ phutil_register_library_map(array(
'ArcanistParseStrUseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistParseStrUseXHPASTLinterRuleTestCase.php',
'ArcanistPasteWorkflow' => 'workflow/ArcanistPasteWorkflow.php',
'ArcanistPatchWorkflow' => 'workflow/ArcanistPatchWorkflow.php',
'ArcanistPhageToolset' => 'toolset/ArcanistPhageToolset.php',
'ArcanistPhpLinter' => 'lint/linter/ArcanistPhpLinter.php',
'ArcanistPhpLinterTestCase' => 'lint/linter/__tests__/ArcanistPhpLinterTestCase.php',
'ArcanistPhpcsLinter' => 'lint/linter/ArcanistPhpcsLinter.php',
@ -317,6 +318,7 @@ phutil_register_library_map(array(
'ArcanistPhpunitTestResultParser' => 'unit/parser/ArcanistPhpunitTestResultParser.php',
'ArcanistPhrequentWorkflow' => 'workflow/ArcanistPhrequentWorkflow.php',
'ArcanistPhutilLibraryLinter' => 'lint/linter/ArcanistPhutilLibraryLinter.php',
'ArcanistPhutilWorkflow' => 'toolset/ArcanistPhutilWorkflow.php',
'ArcanistPhutilXHPASTLinterStandard' => 'lint/linter/standards/phutil/ArcanistPhutilXHPASTLinterStandard.php',
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPlusOperatorOnStringsXHPASTLinterRule.php',
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase.php',
@ -361,7 +363,7 @@ phutil_register_library_map(array(
'ArcanistSetConfigWorkflow' => 'workflow/ArcanistSetConfigWorkflow.php',
'ArcanistSetting' => 'configuration/ArcanistSetting.php',
'ArcanistSettings' => 'configuration/ArcanistSettings.php',
'ArcanistShellCompleteWorkflow' => 'workflow/ArcanistShellCompleteWorkflow.php',
'ArcanistShellCompleteWorkflow' => 'toolset/ArcanistShellCompleteWorkflow.php',
'ArcanistSingleLintEngine' => 'lint/engine/ArcanistSingleLintEngine.php',
'ArcanistSlownessXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSlownessXHPASTLinterRule.php',
'ArcanistSlownessXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSlownessXHPASTLinterRuleTestCase.php',
@ -389,6 +391,7 @@ phutil_register_library_map(array(
'ArcanistTodoCommentXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistTodoCommentXHPASTLinterRule.php',
'ArcanistTodoCommentXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistTodoCommentXHPASTLinterRuleTestCase.php',
'ArcanistTodoWorkflow' => 'workflow/ArcanistTodoWorkflow.php',
'ArcanistToolset' => 'toolset/ArcanistToolset.php',
'ArcanistUSEnglishTranslation' => 'internationalization/ArcanistUSEnglishTranslation.php',
'ArcanistUnableToParseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnableToParseXHPASTLinterRule.php',
'ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRule.php',
@ -425,10 +428,10 @@ phutil_register_library_map(array(
'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase.php',
'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php',
'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableVariableXHPASTLinterRuleTestCase.php',
'ArcanistVersionWorkflow' => 'workflow/ArcanistVersionWorkflow.php',
'ArcanistVersionWorkflow' => 'toolset/ArcanistVersionWorkflow.php',
'ArcanistWeldWorkflow' => 'workflow/ArcanistWeldWorkflow.php',
'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php',
'ArcanistWorkflow' => 'workflow/ArcanistWorkflow.php',
'ArcanistWorkflow' => 'toolset/ArcanistWorkflow.php',
'ArcanistWorkingCopyIdentity' => 'workingcopyidentity/ArcanistWorkingCopyIdentity.php',
'ArcanistWorkingCopyStateRef' => 'ref/ArcanistWorkingCopyStateRef.php',
'ArcanistXHPASTLintNamingHook' => 'lint/linter/xhpast/ArcanistXHPASTLintNamingHook.php',
@ -467,6 +470,8 @@ phutil_register_library_map(array(
'ArcanistAliasWorkflow' => 'ArcanistWorkflow',
'ArcanistAmendWorkflow' => 'ArcanistWorkflow',
'ArcanistAnoidWorkflow' => 'ArcanistWorkflow',
'ArcanistArcToolset' => 'ArcanistToolset',
'ArcanistArcWorkflow' => 'ArcanistWorkflow',
'ArcanistArrayCombineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistArrayCombineXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistArrayIndexSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -545,7 +550,6 @@ phutil_register_library_map(array(
'ArcanistConcatenationOperatorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistConduitCall' => 'Phobject',
'ArcanistConduitEngine' => 'Phobject',
'ArcanistConfiguration' => 'Phobject',
'ArcanistConfigurationDrivenLintEngine' => 'ArcanistLintEngine',
'ArcanistConfigurationDrivenUnitTestEngine' => 'ArcanistUnitTestEngine',
'ArcanistConfigurationManager' => 'Phobject',
@ -635,7 +639,6 @@ phutil_register_library_map(array(
'ArcanistHLintLinter' => 'ArcanistExternalLinter',
'ArcanistHLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistHardpointLoader' => 'Phobject',
'ArcanistHelpWorkflow' => 'ArcanistWorkflow',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistHgClientChannel' => 'PhutilProtocolChannel',
@ -759,6 +762,7 @@ phutil_register_library_map(array(
'ArcanistParseStrUseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistPasteWorkflow' => 'ArcanistWorkflow',
'ArcanistPatchWorkflow' => 'ArcanistWorkflow',
'ArcanistPhageToolset' => 'ArcanistToolset',
'ArcanistPhpLinter' => 'ArcanistExternalLinter',
'ArcanistPhpLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistPhpcsLinter' => 'ArcanistExternalLinter',
@ -766,6 +770,7 @@ phutil_register_library_map(array(
'ArcanistPhpunitTestResultParser' => 'ArcanistTestResultParser',
'ArcanistPhrequentWorkflow' => 'ArcanistWorkflow',
'ArcanistPhutilLibraryLinter' => 'ArcanistLinter',
'ArcanistPhutilWorkflow' => 'PhutilArgumentWorkflow',
'ArcanistPhutilXHPASTLinterStandard' => 'ArcanistLinterStandard',
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@ -838,6 +843,7 @@ phutil_register_library_map(array(
'ArcanistTodoCommentXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistTodoCommentXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistTodoWorkflow' => 'ArcanistWorkflow',
'ArcanistToolset' => 'Phobject',
'ArcanistUSEnglishTranslation' => 'PhutilTranslation',
'ArcanistUnableToParseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',

View file

@ -1,187 +0,0 @@
<?php
/**
* Runtime workflow configuration. In Arcanist, commands you type like
* "arc diff" or "arc lint" are called "workflows". This class allows you to add
* new workflows (and extend existing workflows) by subclassing it and then
* pointing to your subclass in your project configuration.
*
* When specified as the **arcanist_configuration** class in your project's
* ##.arcconfig##, your subclass will be instantiated (instead of this class)
* and be able to handle all the method calls. In particular, you can:
*
* - create, replace, or disable workflows by overriding buildWorkflow()
* and buildAllWorkflows();
* - add additional steps before or after workflows run by overriding
* willRunWorkflow() or didRunWorkflow() or didAbortWorkflow(); and
* - add new flags to existing workflows by overriding
* getCustomArgumentsForCommand().
*
* @concrete-extensible
*/
class ArcanistConfiguration extends Phobject {
public function buildWorkflow($command) {
if ($command == '--help') {
// Special-case "arc --help" to behave like "arc help" instead of telling
// you to type "arc help" without being helpful.
$command = 'help';
} else if ($command == '--version') {
// Special-case "arc --version" to behave like "arc version".
$command = 'version';
}
$workflow = idx($this->buildAllWorkflows(), $command);
if (!$workflow) {
return null;
}
return clone $workflow;
}
public function buildAllWorkflows() {
return id(new PhutilClassMapQuery())
->setAncestorClass('ArcanistWorkflow')
->setUniqueMethod('getWorkflowName')
->execute();
}
final public function isValidWorkflow($workflow) {
return (bool)$this->buildWorkflow($workflow);
}
public function willRunWorkflow($command, ArcanistWorkflow $workflow) {
// This is a hook.
}
public function didRunWorkflow($command, ArcanistWorkflow $workflow, $err) {
// This is a hook.
}
public function didAbortWorkflow($command, $workflow, Exception $ex) {
// This is a hook.
}
public function getCustomArgumentsForCommand($command) {
return array();
}
final public function selectWorkflow(
&$command,
array &$args,
ArcanistConfigurationManager $configuration_manager,
PhutilConsole $console) {
// First, try to build a workflow with the exact name provided. We always
// pick an exact match, and do not allow aliases to override it.
$workflow = $this->buildWorkflow($command);
if ($workflow) {
return $workflow;
}
// If the user has an alias, like 'arc alias dhelp diff help', look it up
// and substitute it. We do this only after trying to resolve the workflow
// normally to prevent you from doing silly things like aliasing 'alias'
// to something else.
$aliases = ArcanistAliasWorkflow::getAliases($configuration_manager);
list($new_command, $args) = ArcanistAliasWorkflow::resolveAliases(
$command,
$this,
$args,
$configuration_manager);
$full_alias = idx($aliases, $command, array());
$full_alias = implode(' ', $full_alias);
// Run shell command aliases.
if (ArcanistAliasWorkflow::isShellCommandAlias($new_command)) {
$shell_cmd = substr($full_alias, 1);
$console->writeLog(
"[%s: 'arc %s' -> $ %s]",
pht('alias'),
$command,
$shell_cmd);
if ($args) {
$err = phutil_passthru('%C %Ls', $shell_cmd, $args);
} else {
$err = phutil_passthru('%C', $shell_cmd);
}
exit($err);
}
// Run arc command aliases.
if ($new_command) {
$workflow = $this->buildWorkflow($new_command);
if ($workflow) {
$console->writeLog(
"[%s: 'arc %s' -> 'arc %s']\n",
pht('alias'),
$command,
$full_alias);
$command = $new_command;
return $workflow;
}
}
$all = array_keys($this->buildAllWorkflows());
// We haven't found a real command or an alias, so try to locate a command
// by unique prefix.
$prefixes = $this->expandCommandPrefix($command, $all);
if (count($prefixes) == 1) {
$command = head($prefixes);
return $this->buildWorkflow($command);
} else if (count($prefixes) > 1) {
$this->raiseUnknownCommand($command, $prefixes);
}
// We haven't found a real command, alias, or unique prefix. Try similar
// spellings.
$corrected = PhutilArgumentSpellingCorrector::newCommandCorrector()
->correctSpelling($command, $all);
if (count($corrected) == 1) {
$console->writeErr(
pht(
"(Assuming '%s' is the British spelling of '%s'.)",
$command,
head($corrected))."\n");
$command = head($corrected);
return $this->buildWorkflow($command);
} else if (count($corrected) > 1) {
$this->raiseUnknownCommand($command, $corrected);
}
$this->raiseUnknownCommand($command);
}
private function raiseUnknownCommand($command, array $maybe = array()) {
$message = pht("Unknown command '%s'. Try '%s'.", $command, 'arc help');
if ($maybe) {
$message .= "\n\n".pht('Did you mean:')."\n";
sort($maybe);
foreach ($maybe as $other) {
$message .= " ".$other."\n";
}
}
throw new ArcanistUsageException($message);
}
private function expandCommandPrefix($command, array $options) {
$is_prefix = array();
foreach ($options as $option) {
if (strncmp($option, $command, strlen($command)) == 0) {
$is_prefix[$option] = true;
}
}
return array_keys($is_prefix);
}
}

View file

@ -146,6 +146,10 @@ final class ArcanistConfigurationManager extends Phobject {
return $this;
}
public function getRuntimeConfig($key, $default = null) {
return idx($this->runtimeConfig, $key, $default);
}
/* -( Read/write config )--------------------------------------------------- */
public function readLocalArcConfig() {

View file

@ -9,46 +9,50 @@ final class ArcanistAliasWorkflow extends ArcanistWorkflow {
return 'alias';
}
public function getCommandSynopses() {
return phutil_console_format(<<<EOTEXT
**alias**
**alias** __command__
**alias** __command__ __target__ -- [__options__]
EOTEXT
);
public function supportsToolset(ArcanistToolset $toolset) {
return true;
}
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
Supports: cli
Create an alias from __command__ to __target__ (optionally, with
__options__). For example:
public function getWorkflowSynopses() {
return array(
pht('**alias**'),
pht('**alias** __command__'),
pht('**alias** __command__ __target__ -- [__options__]'),
);
}
arc alias fpatch patch -- --force
public function getWorkflowHelp() {
return pht(<<<EOTEXT
Supports: cli
Create an alias from __command__ to __target__ (optionally, with __options__).
For example:
...will create a new 'arc' command, 'arc fpatch', which invokes
'arc patch --force ...' when run. NOTE: use "--" before specifying
options!
%s alias fpatch patch -- --force
If you start an alias with "!", the remainder of the alias will be
invoked as a shell command. For example, if you want to implement
'arc ls', you can do so like this:
...will create a new 'arc' command, 'arc fpatch', which invokes
'arc patch --force ...' when run. NOTE: use "--" before specifying
options!
arc alias ls '!ls'
If you start an alias with "!", the remainder of the alias will be
invoked as a shell command. For example, if you want to implement
'arc ls', you can do so like this:
You can now run "arc ls" and it will behave like "ls". Of course, this
example is silly and would make your life worse.
%s alias ls '!ls'
You can not overwrite builtins, including 'alias' itself. The builtin
will always execute, even if it was added after your alias.
You can now run "arc ls" and it will behave like "ls". Of course, this
example is silly and would make your life worse.
To remove an alias, run:
You can not overwrite builtins, including 'alias' itself. The builtin
will always execute, even if it was added after your alias.
arc alias fpatch
To remove an alias, run:
Without any arguments, 'arc alias' will list aliases.
arc alias fpatch
Without any arguments, 'arc alias' will list aliases.
EOTEXT
);
,
$this->getToolsetName());
}
public function getArguments() {
@ -75,7 +79,7 @@ EOTEXT
$this->getConfigurationManager()->writeUserConfigurationFile($config);
}
public function run() {
public function runWorkflow() {
$aliases = self::getAliases($this->getConfigurationManager());
$argv = $this->getArgument('argv');

View file

@ -0,0 +1,26 @@
<?php
final class ArcanistArcToolset extends ArcanistToolset {
const TOOLSETKEY = 'arc';
public function getToolsetArguments() {
return array(
array(
'name' => 'conduit-uri',
'param' => 'uri',
'help' => pht('Connect to Phabricator install specified by __uri__.'),
),
array(
'name' => 'conduit-token',
'param' => 'token',
'help' => pht('Use a specific authentication token.'),
),
array(
'name' => 'anonymous',
'help' => pht('Run workflow as a public user, without authenticating.'),
),
);
}
}

View file

@ -0,0 +1,8 @@
<?php
final class ArcanistPhageToolset extends ArcanistToolset {
const TOOLSETKEY = 'phage';
}

View file

@ -0,0 +1,24 @@
<?php
final class ArcanistPhutilWorkflow extends PhutilArgumentWorkflow {
private $workflow;
public function setWorkflow(ArcanistWorkflow $workflow) {
$this->workflow = $workflow;
return $this;
}
public function getWorkflow() {
return $this->workflow;
}
public function isExecutable() {
return true;
}
public function execute(PhutilArgumentParser $args) {
return $this->getWorkflow()->executeWorkflow($args);
}
}

View file

@ -5,22 +5,24 @@
*/
final class ArcanistShellCompleteWorkflow extends ArcanistWorkflow {
public function supportsToolset(ArcanistToolset $toolset) {
return true;
}
public function getWorkflowName() {
return 'shell-complete';
}
public function getCommandSynopses() {
return phutil_console_format(<<<EOTEXT
**shell-complete** __--current__ __N__ -- [__argv__]
EOTEXT
);
public function getWorkflowSynopses() {
return array(
pht('**shell-complete** __--current__ __N__ -- [__argv__]'),
);
}
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
Supports: bash, etc.
Implements shell completion. To use shell completion, source the
appropriate script from 'resources/shell/' in your .shellrc.
public function getWorkflowHelp() {
return pht(<<<EOTEXT
Implements shell completion. To use shell completion, source the appropriate
script from 'resources/shell/' in your .shellrc.
EOTEXT
);
}

View file

@ -0,0 +1,22 @@
<?php
abstract class ArcanistToolset extends Phobject {
final public function getToolsetKey() {
return $this->getPhobjectClassConstant('TOOLSETKEY');
}
final public static function newToolsetMap() {
$toolsets = id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getToolsetKey')
->execute();
return $toolsets;
}
public function getToolsetArguments() {
return array();
}
}

View file

@ -5,26 +5,31 @@
*/
final class ArcanistVersionWorkflow extends ArcanistWorkflow {
public function supportsToolset(ArcanistToolset $toolset) {
return true;
}
public function getWorkflowName() {
return 'version';
}
public function getCommandSynopses() {
return phutil_console_format(<<<EOTEXT
**version** [__options__]
EOTEXT
);
public function getWorkflowSynopses() {
return array(
'**version**',
);
}
public function getCommandHelp() {
return phutil_console_format(pht(<<<EOTEXT
Supports: cli
Shows the current version of arcanist.
public function getWorkflowHelp() {
return pht(<<<EOTEXT
Shows the current version of **%s**.
EOTEXT
));
,
$this->getToolsetKey());
}
public function run() {
// TODO: Show the toolset version, not just the "arc" version.
$console = PhutilConsole::getConsole();
if (!Filesystem::binaryExists('git')) {

View file

@ -0,0 +1,88 @@
<?php
abstract class ArcanistWorkflow extends Phobject {
private $toolset;
private $arguments;
/**
* Return the command used to invoke this workflow from the command like,
* e.g. "help" for @{class:ArcanistHelpWorkflow}.
*
* @return string The command a user types to invoke this workflow.
*/
abstract public function getWorkflowName();
protected function runWorkflow() {
// TOOLSETS: Temporary to get this working.
throw new PhutilMethodNotImplementedException();
}
protected function runWorkflowCleanup() {
// TOOLSETS: Do we need this?
return;
}
/**
* Return true if this workflow belongs to the given toolset. Toolsets let
* you move a set of "arc" commands under some other command.
*
* @param ArcanistToolset Current selected toolset.
* @return bool True if this command supports the provided toolset.
*/
public function supportsToolset(ArcanistToolset $toolset) {
// TOOLSETS: Temporary!
return true;
}
public function newPhutilWorkflow() {
return id(new ArcanistPhutilWorkflow())
->setName($this->getWorkflowName())
->setWorkflow($this);
}
final public function getToolset() {
return $this->toolset;
}
final public function setToolset(ArcanistToolset $toolset) {
$this->toolset = $toolset;
return $this;
}
final protected function getToolsetKey() {
return $this->getToolset()->getToolsetKey();
}
final public function executeWorkflow(PhutilArgumentParser $args) {
$this->arguments = $args;
$caught = null;
try {
$err = $this->runWorkflow($args);
} catch (Exception $ex) {
$caught = $ex;
}
try {
$this->runWorkflowCleanup();
} catch (Exception $ex) {
phlog($ex);
}
if ($caught) {
throw $caught;
}
return $err;
}
final public function getArgument($key, $default = null) {
// TOOLSETS: This is a stub for now.
return $default;
return $this->arguments->getArg($key, $default);
}
}

View file

@ -33,7 +33,7 @@
* @task scratch Scratch Files
* @task phabrep Phabricator Repositories
*/
abstract class ArcanistWorkflow extends Phobject {
abstract class ArcanistArcWorkflow extends ArcanistWorkflow {
const COMMIT_DISABLE = 0;
const COMMIT_ALLOW = 1;
@ -52,8 +52,6 @@ abstract class ArcanistWorkflow extends Phobject {
private $userName;
private $repositoryAPI;
private $configurationManager;
private $arguments = array();
private $passedArguments = array();
private $command;
private $stashed;
@ -72,11 +70,9 @@ abstract class ArcanistWorkflow extends Phobject {
private $changeCache = array();
private $conduitEngine;
public function __construct() {}
abstract public function run();
final public function supportsToolset(ArcanistToolset $toolset) {
return ($toolset->getToolsetKey() === 'arc');
}
/**
* Finalizes any cleanup operations that need to occur regardless of
@ -86,27 +82,6 @@ abstract class ArcanistWorkflow extends Phobject {
$this->finalizeWorkingCopy();
}
/**
* Return the command used to invoke this workflow from the command like,
* e.g. "help" for @{class:ArcanistHelpWorkflow}.
*
* @return string The command a user types to invoke this workflow.
*/
abstract public function getWorkflowName();
/**
* Return console formatted string with all command synopses.
*
* @return string 6-space indented list of available command synopses.
*/
abstract public function getCommandSynopses();
/**
* Return console formatted string with command help printed in `arc help`.
*
* @return string 10-space indented help to use the command.
*/
abstract public function getCommandHelp();
/* -( Conduit )------------------------------------------------------------ */
@ -610,169 +585,6 @@ abstract class ArcanistWorkflow extends Phobject {
return $workflow;
}
final public function getArgument($key, $default = null) {
return idx($this->arguments, $key, $default);
}
final public function getPassedArguments() {
return $this->passedArguments;
}
final public function getCompleteArgumentSpecification() {
$spec = $this->getArguments();
$arc_config = $this->getArcanistConfiguration();
$command = $this->getCommand();
$spec += $arc_config->getCustomArgumentsForCommand($command);
return $spec;
}
final public function parseArguments(array $args) {
$this->passedArguments = $args;
$spec = $this->getCompleteArgumentSpecification();
$dict = array();
$more_key = null;
if (!empty($spec['*'])) {
$more_key = $spec['*'];
unset($spec['*']);
$dict[$more_key] = array();
}
$short_to_long_map = array();
foreach ($spec as $long => $options) {
if (!empty($options['short'])) {
$short_to_long_map[$options['short']] = $long;
}
}
foreach ($spec as $long => $options) {
if (!empty($options['repeat'])) {
$dict[$long] = array();
}
}
$more = array();
$size = count($args);
for ($ii = 0; $ii < $size; $ii++) {
$arg = $args[$ii];
$arg_name = null;
$arg_key = null;
if ($arg == '--') {
$more = array_merge(
$more,
array_slice($args, $ii + 1));
break;
} else if (!strncmp($arg, '--', 2)) {
$arg_key = substr($arg, 2);
$parts = explode('=', $arg_key, 2);
if (count($parts) == 2) {
list($arg_key, $val) = $parts;
array_splice($args, $ii, 1, array('--'.$arg_key, $val));
$size++;
}
if (!array_key_exists($arg_key, $spec)) {
$corrected = PhutilArgumentSpellingCorrector::newFlagCorrector()
->correctSpelling($arg_key, array_keys($spec));
if (count($corrected) == 1) {
PhutilConsole::getConsole()->writeErr(
pht(
"(Assuming '%s' is the British spelling of '%s'.)",
'--'.$arg_key,
'--'.head($corrected))."\n");
$arg_key = head($corrected);
} else {
throw new ArcanistUsageException(
pht(
"Unknown argument '%s'. Try '%s'.",
$arg_key,
'arc help'));
}
}
} else if (!strncmp($arg, '-', 1)) {
$arg_key = substr($arg, 1);
if (empty($short_to_long_map[$arg_key])) {
throw new ArcanistUsageException(
pht(
"Unknown argument '%s'. Try '%s'.",
$arg_key,
'arc help'));
}
$arg_key = $short_to_long_map[$arg_key];
} else {
$more[] = $arg;
continue;
}
$options = $spec[$arg_key];
if (empty($options['param'])) {
$dict[$arg_key] = true;
} else {
if ($ii == $size - 1) {
throw new ArcanistUsageException(
pht(
"Option '%s' requires a parameter.",
$arg));
}
if (!empty($options['repeat'])) {
$dict[$arg_key][] = $args[$ii + 1];
} else {
$dict[$arg_key] = $args[$ii + 1];
}
$ii++;
}
}
if ($more) {
if ($more_key) {
$dict[$more_key] = $more;
} else {
$example = reset($more);
throw new ArcanistUsageException(
pht(
"Unrecognized argument '%s'. Try '%s'.",
$example,
'arc help'));
}
}
foreach ($dict as $key => $value) {
if (empty($spec[$key]['conflicts'])) {
continue;
}
foreach ($spec[$key]['conflicts'] as $conflict => $more) {
if (isset($dict[$conflict])) {
if ($more) {
$more = ': '.$more;
} else {
$more = '.';
}
// TODO: We'll always display these as long-form, when the user might
// have typed them as short form.
throw new ArcanistUsageException(
pht(
"Arguments '%s' and '%s' are mutually exclusive",
"--{$key}",
"--{$conflict}").$more);
}
}
}
$this->arguments = $dict;
$this->didParseArguments();
return $this;
}
protected function didParseArguments() {
// Override this to customize workflow argument behavior.
}
final public function getWorkingCopy() {
$working_copy = $this->getConfigurationManager()->getWorkingCopyIdentity();
if (!$working_copy) {
@ -1301,31 +1113,6 @@ abstract class ArcanistWorkflow extends Phobject {
return $this->changeCache[$path];
}
final public function willRunWorkflow() {
$spec = $this->getCompleteArgumentSpecification();
foreach ($this->arguments as $arg => $value) {
if (empty($spec[$arg])) {
continue;
}
$options = $spec[$arg];
if (!empty($options['supports'])) {
$system_name = $this->getRepositoryAPI()->getSourceControlSystemName();
if (!in_array($system_name, $options['supports'])) {
$extended_info = null;
if (!empty($options['nosupport'][$system_name])) {
$extended_info = ' '.$options['nosupport'][$system_name];
}
throw new ArcanistUsageException(
pht(
"Option '%s' is not supported under %s.",
"--{$arg}",
$system_name).
$extended_info);
}
}
}
}
final protected function normalizeRevisionID($revision_id) {
return preg_replace('/^D/i', '', $revision_id);
}

View file

@ -1,221 +0,0 @@
<?php
/**
* Seduces the reader with majestic prose.
*/
final class ArcanistHelpWorkflow extends ArcanistWorkflow {
public function getWorkflowName() {
return 'help';
}
public function getCommandSynopses() {
return phutil_console_format(<<<EOTEXT
**help** [__command__]
**help** --full
EOTEXT
);
}
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
Supports: english
Shows this help. With __command__, shows help about a specific
command.
EOTEXT
);
}
public function getArguments() {
return array(
'full' => array(
'help' => pht('Print detailed information about each command.'),
),
'*' => 'command',
);
}
public function run() {
$arc_config = $this->getArcanistConfiguration();
$workflows = $arc_config->buildAllWorkflows();
ksort($workflows);
$target = null;
if ($this->getArgument('command')) {
$target = head($this->getArgument('command'));
if (empty($workflows[$target])) {
throw new ArcanistUsageException(
pht(
"Unrecognized command '%s'. Try '%s'.",
$target,
'arc help'));
}
}
$cmdref = array();
foreach ($workflows as $command => $workflow) {
if ($target && $target != $command) {
continue;
}
if (!$target && !$this->getArgument('full')) {
$cmdref[] = $workflow->getCommandSynopses();
continue;
}
$optref = array();
$arguments = $workflow->getArguments();
$config_arguments = $arc_config->getCustomArgumentsForCommand($command);
// This juggling is to put the extension arguments after the normal
// arguments, and make sure the normal arguments aren't overwritten.
ksort($arguments);
ksort($config_arguments);
foreach ($config_arguments as $argument => $spec) {
if (empty($arguments[$argument])) {
$arguments[$argument] = $spec;
}
}
foreach ($arguments as $argument => $spec) {
if ($argument == '*') {
continue;
}
if (!empty($spec['hide'])) {
continue;
}
if (isset($spec['param'])) {
if (isset($spec['short'])) {
$optref[] = phutil_console_format(
' __--%s__ __%s__, __-%s__ __%s__',
$argument,
$spec['param'],
$spec['short'],
$spec['param']);
} else {
$optref[] = phutil_console_format(
' __--%s__ __%s__',
$argument,
$spec['param']);
}
} else {
if (isset($spec['short'])) {
$optref[] = phutil_console_format(
' __--%s__, __-%s__',
$argument,
$spec['short']);
} else {
$optref[] = phutil_console_format(
' __--%s__',
$argument);
}
}
if (isset($config_arguments[$argument])) {
$optref[] = ' '.
pht('(This is a custom option for this project.)');
}
if (isset($spec['supports'])) {
$optref[] = ' '.
pht('Supports: %s', implode(', ', $spec['supports']));
}
if (isset($spec['help'])) {
$docs = $spec['help'];
} else {
$docs = pht('This option is not documented.');
}
$docs = phutil_console_wrap($docs, 14);
$optref[] = "{$docs}\n";
}
if ($optref) {
$optref = implode("\n", $optref);
$optref = "\n\n".$optref;
} else {
$optref = "\n";
}
$cmdref[] =
$workflow->getCommandSynopses()."\n".
$workflow->getCommandHelp().
$optref;
}
$cmdref = implode("\n\n", $cmdref);
if ($target) {
echo "\n".$cmdref."\n";
return;
}
$self = 'arc';
echo phutil_console_format(<<<EOTEXT
**NAME**
**{$self}** - arcanist, a code review and revision management utility
**SYNOPSIS**
**{$self}** __command__ [__options__] [__args__]
This help file provides a detailed command reference.
**COMMAND REFERENCE**
{$cmdref}
EOTEXT
);
if (!$this->getArgument('full')) {
echo pht(
"Run '%s' to get commands and options descriptions.\n",
'arc help --full');
return;
}
echo phutil_console_format(<<<EOTEXT
**OPTION REFERENCE**
__--trace__
Debugging command. Shows underlying commands as they are executed,
and full stack traces when exceptions are thrown.
__--no-ansi__
Output in plain ASCII text only, without color or style.
__--ansi__
Use formatting even in environments which probably don't support it.
Example: arc --ansi unit | less -r
__--load-phutil-library=/path/to/library__
Ignore libraries listed in .arcconfig and explicitly load specified
libraries instead. Mostly useful for Arcanist development.
__--conduit-uri__ __uri__
Ignore configured Conduit URI and use an explicit one instead. Mostly
useful for Arcanist development.
__--conduit-token__ __token__
Ignore configured credentials and use an explicit API token instead.
__--conduit-version__ __version__
Ignore software version and claim to be running some other version
instead. Mostly useful for Arcanist development. May cause bad things
to happen.
__--conduit-timeout__ __timeout__
Override the default Conduit timeout. Specified in seconds.
__--config__ __key=value__
Specify a runtime configuration value. This will take precedence
over static values, and only affect the current arcanist invocation.
__--skip-arcconfig__
Skip the working copy configuration file
__--arcrc-file__ __filename__
Use provided file instead of ~/.arcrc.
EOTEXT
);
}
}

View file

@ -68,7 +68,7 @@ EOTEXT
);
}
public function run() {
public function runWorkflow() {
$argv = $this->getArgument('argv');
if (count($argv) > 1) {
throw new ArcanistUsageException(