Page MenuHomePhabricator

D20996.id50025.diff
No OneTemporary

D20996.id50025.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -24,7 +24,7 @@
'ArcanistAliasEngine' => 'toolset/ArcanistAliasEngine.php',
'ArcanistAliasFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAliasFunctionXHPASTLinterRule.php',
'ArcanistAliasFunctionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAliasFunctionXHPASTLinterRuleTestCase.php',
- 'ArcanistAliasWorkflow' => 'workflow/ArcanistAliasWorkflow.php',
+ 'ArcanistAliasWorkflow' => 'toolset/workflow/ArcanistAliasWorkflow.php',
'ArcanistAliasesConfigOption' => 'config/option/ArcanistAliasesConfigOption.php',
'ArcanistAmendWorkflow' => 'workflow/ArcanistAmendWorkflow.php',
'ArcanistAnoidWorkflow' => 'workflow/ArcanistAnoidWorkflow.php',
diff --git a/src/config/option/ArcanistAliasesConfigOption.php b/src/config/option/ArcanistAliasesConfigOption.php
--- a/src/config/option/ArcanistAliasesConfigOption.php
+++ b/src/config/option/ArcanistAliasesConfigOption.php
@@ -22,7 +22,18 @@
protected function didReadStorageValueList(array $list) {
assert_instances_of($list, 'ArcanistConfigurationSourceValue');
- return mpull($list, 'getValue');
+
+ $results = array();
+ foreach ($list as $spec) {
+ $source = $spec->getConfigurationSource();
+ $value = $spec->getValue();
+
+ $value->setConfigurationSource($source);
+
+ $results[] = $value;
+ }
+
+ return $results;
}
public function getDisplayValueFromValue($value) {
diff --git a/src/configuration/ArcanistConfiguration.php b/src/configuration/ArcanistConfiguration.php
--- a/src/configuration/ArcanistConfiguration.php
+++ b/src/configuration/ArcanistConfiguration.php
@@ -81,53 +81,6 @@
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
diff --git a/src/configuration/ArcanistConfigurationManager.php b/src/configuration/ArcanistConfigurationManager.php
--- a/src/configuration/ArcanistConfigurationManager.php
+++ b/src/configuration/ArcanistConfigurationManager.php
@@ -270,7 +270,13 @@
}
public function readUserArcConfig() {
- return idx($this->readUserConfigurationFile(), 'config', array());
+ $config = $this->readUserConfigurationFile();
+
+ if (isset($config['config'])) {
+ $config = $config['config'];
+ }
+
+ return $config;
}
public function writeUserArcConfig(array $options) {
diff --git a/src/runtime/ArcanistRuntime.php b/src/runtime/ArcanistRuntime.php
--- a/src/runtime/ArcanistRuntime.php
+++ b/src/runtime/ArcanistRuntime.php
@@ -521,7 +521,7 @@
$message = $effect->getMessage();
if ($message !== null) {
- $log->writeInfo(pht('ALIAS'), $message);
+ $log->writeHint(pht('ALIAS'), $message);
}
if ($effect->getCommand()) {
diff --git a/src/toolset/ArcanistAlias.php b/src/toolset/ArcanistAlias.php
--- a/src/toolset/ArcanistAlias.php
+++ b/src/toolset/ArcanistAlias.php
@@ -36,7 +36,7 @@
$is_list = false;
$is_dict = false;
if ($value && is_array($value)) {
- if (array_keys($value) === range(0, count($value) - 1)) {
+ if (phutil_is_natural_list($value)) {
$is_list = true;
} else {
$is_dict = true;
diff --git a/src/toolset/ArcanistAliasEffect.php b/src/toolset/ArcanistAliasEffect.php
--- a/src/toolset/ArcanistAliasEffect.php
+++ b/src/toolset/ArcanistAliasEffect.php
@@ -17,6 +17,7 @@
const EFFECT_NOTFOUND = 'not-found';
const EFFECT_CYCLE = 'cycle';
const EFFECT_STACK = 'stack';
+ const EFFECT_IGNORED = 'ignored';
public function setType($type) {
$this->type = $type;
diff --git a/src/toolset/ArcanistAliasEngine.php b/src/toolset/ArcanistAliasEngine.php
--- a/src/toolset/ArcanistAliasEngine.php
+++ b/src/toolset/ArcanistAliasEngine.php
@@ -172,15 +172,31 @@
}
$alias = array_pop($toolset_matches);
- foreach ($toolset_matches as $ignored_match) {
+
+ if ($toolset_matches) {
+ $source = $alias->getConfigurationSource();
+
$results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_IGNORED)
->setMessage(
pht(
'Multiple configuration sources define an alias for "%s %s". '.
- 'The definition in "%s" will be ignored.',
+ 'The last definition in the most specific source ("%s") will '.
+ 'be used.',
$toolset_key,
$command,
- $ignored_match->getConfigurationSource()->getSourceDisplayName()));
+ $source->getSourceDisplayName()));
+
+ foreach ($toolset_matches as $ignored_match) {
+ $source = $ignored_match->getConfigurationSource();
+
+ $results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_IGNORED)
+ ->setMessage(
+ pht(
+ 'A definition of "%s %s" in "%s" will be ignored.',
+ $toolset_key,
+ $command,
+ $source->getSourceDisplayName()));
+ }
}
if ($alias->isShellCommandAlias()) {
@@ -227,14 +243,17 @@
return $results;
}
+ $display_argv = (string)csprintf('%LR', $alias_argv);
+
$results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_ALIAS)
->setMessage(
pht(
- '%s %s -> %s %s',
+ '%s %s -> %s %s %s',
$toolset_key,
$command,
$toolset_key,
- $alias_command));
+ $alias_command,
+ $display_argv));
$argv = array_merge($alias_argv, $argv);
diff --git a/src/toolset/workflow/ArcanistAliasWorkflow.php b/src/toolset/workflow/ArcanistAliasWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/toolset/workflow/ArcanistAliasWorkflow.php
@@ -0,0 +1,200 @@
+<?php
+
+/**
+ * Manages aliases for commands with options.
+ */
+final class ArcanistAliasWorkflow extends ArcanistWorkflow {
+
+ public function getWorkflowName() {
+ return 'alias';
+ }
+
+ public function supportsToolset(ArcanistToolset $toolset) {
+ return true;
+ }
+
+ public function getWorkflowInformation() {
+ $help = pht(<<<EOTEXT
+Create an alias from __command__ to __target__ (optionally, with __options__).
+
+Aliases allow you to create shorthands for commands and sets of flags you
+commonly use, like defining "arc draft" as a shorthand for "arc diff --draft".
+
+**Creating Aliases**
+
+You can define "arc draft" as a shorthand for "arc diff --draft" like this:
+
+ $ arc alias draft diff -- --draft
+
+Now, when you run "arc draft", the command will function like
+"arc diff --draft".
+
+<bg:yellow> NOTE: </bg> Make sure you use "--" before specifying any flags you
+want to pass to the command! Otherwise, the flags will be interpreted as flags
+to "arc alias".
+
+**Listing Aliases**
+
+Without any arguments, "arc alias" will list aliases.
+
+**Removing Aliases**
+
+To remove an alias, run:
+
+ $ arc alias <alias-name>
+
+You will be prompted to remove the alias.
+
+**Shell Commands**
+
+If you begin 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:
+
+ $ arc alias ls '!ls'
+
+When run, "arc ls" will now behave like "ls".
+
+**Multiple Toolsets**
+
+This workflow supports any toolset, even though the examples in this help text
+use "arc". If you are working with another toolset, use the binary for that
+toolset define aliases for it:
+
+ $ phage alias ...
+
+Aliases are bound to the toolset which was used to define them. If you define
+an "arc draft" alias, that does not also define a "phage draft" alias.
+
+**Builtins**
+
+You can not overwrite the behavior of builtin workflows, including "alias"
+itself, and if you install a new workflow it will take precedence over any
+existing aliases with the same name.
+EOTEXT
+);
+
+ return $this->newWorkflowInformation()
+ ->addExample(pht('**alias**'))
+ ->addExample(pht('**alias** __command__'))
+ ->addExample(pht('**alias** __command__ __target__ -- [__options__]'))
+ ->setHelp($help);
+ }
+
+ public function getWorkflowArguments() {
+ return array(
+ $this->newWorkflowArgument('json')
+ ->setHelp(pht('Output aliases in JSON format.')),
+ $this->newWorkflowArgument('argv')
+ ->setWildcard(true),
+ );
+ }
+
+ public function runWorkflow() {
+ $argv = $this->getArgument('argv');
+
+ $is_list = false;
+ $is_delete = false;
+
+ if (!$argv) {
+ $is_list = true;
+ } else if (count($argv) === 1) {
+ $is_delete = true;
+ }
+
+ $is_json = $this->getArgument('json');
+ if ($is_json && !$is_list) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'The "--json" argument may only be used when listing aliases.'));
+ }
+
+ if ($is_list) {
+ return $this->runListAliases();
+ }
+
+ if ($is_delete) {
+ return $this->runDeleteAlias($argv[0]);
+ }
+
+ return $this->runCreateAlias($argv);
+ }
+
+ private function runListAliases() {
+ // TOOLSETS: Actually list aliases.
+ return 1;
+ }
+
+ private function runDeleteAlias($alias) {
+ // TOOLSETS: Actually delete aliases.
+ return 1;
+ }
+
+ private function runCreateAlias(array $argv) {
+ $trigger = array_shift($argv);
+ $this->validateAliasTrigger($trigger);
+
+ $alias = id(new ArcanistAlias())
+ ->setToolset($this->getToolsetKey())
+ ->setTrigger($trigger)
+ ->setCommand($argv);
+
+ $aliases = $this->readAliasesForWrite();
+
+ // TOOLSETS: Check if the user already has an alias for this trigger, and
+ // prompt them to overwrite it. Needs prompting to work.
+
+ $aliases[] = $alias;
+
+ $this->writeAliases($aliases);
+
+ // TOOLSETS: Print out a confirmation that we added the alias.
+
+ return 0;
+ }
+
+ private function validateAliasTrigger($trigger) {
+ $workflows = $this->getRuntime()->getWorkflows();
+
+ if (isset($workflows[$trigger])) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You can not define an alias for "%s" because it is a builtin '.
+ 'workflow for the current toolset ("%s"). The "alias" workflow '.
+ 'can only define new commands as aliases; it can not redefine '.
+ 'existing commands to mean something else.',
+ $trigger,
+ $this->getToolsetKey()));
+ }
+ }
+
+ private function getEditScope() {
+ return ArcanistConfigurationSource::SCOPE_USER;
+ }
+
+ private function getAliasesConfigKey() {
+ return ArcanistArcConfigurationEngineExtension::KEY_ALIASES;
+ }
+
+ private function readAliasesForWrite() {
+ $key = $this->getAliasesConfigKey();
+ $scope = $this->getEditScope();
+ $source_list = $this->getConfigurationSourceList();
+
+ return $source_list->getConfigFromScopes($key, array($scope));
+ }
+
+ private function writeAliases(array $aliases) {
+ assert_instances_of($aliases, 'ArcanistAlias');
+
+ $key = $this->getAliasesConfigKey();
+ $scope = $this->getEditScope();
+
+ $source_list = $this->getConfigurationSourceList();
+ $source = $source_list->getWritableSourceFromScope($scope);
+ $option = $source_list->getConfigOption($key);
+
+ $option->writeValue($source, $aliases);
+ }
+
+}
diff --git a/src/workflow/ArcanistAliasWorkflow.php b/src/workflow/ArcanistAliasWorkflow.php
deleted file mode 100644
--- a/src/workflow/ArcanistAliasWorkflow.php
+++ /dev/null
@@ -1,240 +0,0 @@
-<?php
-
-/**
- * Manages aliases for commands with options.
- */
-final class ArcanistAliasWorkflow extends ArcanistWorkflow {
-
- public function getWorkflowName() {
- return 'alias';
- }
-
- public function getCommandSynopses() {
- return phutil_console_format(<<<EOTEXT
- **alias**
- **alias** __command__
- **alias** __command__ __target__ -- [__options__]
-EOTEXT
- );
- }
-
- public function getCommandHelp() {
- return phutil_console_format(<<<EOTEXT
- Supports: cli
- Create an alias from __command__ to __target__ (optionally, with
- __options__). For example:
-
- arc alias fpatch patch -- --force
-
- ...will create a new 'arc' command, 'arc fpatch', which invokes
- 'arc patch --force ...' when run. NOTE: use "--" before specifying
- options!
-
- 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:
-
- arc alias ls '!ls'
-
- You can now run "arc ls" and it will behave like "ls". Of course, this
- example is silly and would make your life worse.
-
- You can not overwrite builtins, including 'alias' itself. The builtin
- will always execute, even if it was added after your alias.
-
- To remove an alias, run:
-
- arc alias fpatch
-
- Without any arguments, 'arc alias' will list aliases.
-EOTEXT
- );
- }
-
- public function getArguments() {
- return array(
- '*' => 'argv',
- );
- }
-
- public static function getAliases(
- ArcanistConfigurationManager $configuration_manager) {
- $sources = $configuration_manager->getConfigFromAllSources('aliases');
-
- $aliases = array();
- foreach ($sources as $source) {
- $aliases += $source;
- }
-
- return $aliases;
- }
-
- private function writeAliases(array $aliases) {
- $config = $this->getConfigurationManager()->readUserConfigurationFile();
- $config['aliases'] = $aliases;
- $this->getConfigurationManager()->writeUserConfigurationFile($config);
- }
-
- public function run() {
- $aliases = self::getAliases($this->getConfigurationManager());
-
- $argv = $this->getArgument('argv');
- if (count($argv) == 0) {
- $this->printAliases($aliases);
- } else if (count($argv) == 1) {
- $this->removeAlias($aliases, $argv[0]);
- } else {
- $arc_config = $this->getArcanistConfiguration();
- $alias = $argv[0];
-
- if ($arc_config->buildWorkflow($alias)) {
- throw new ArcanistUsageException(
- pht(
- 'You can not create an alias for "%s" because it is a '.
- 'builtin command. "%s" can only create new commands.',
- "arc {$alias}",
- 'arc alias'));
- }
-
- $new_alias = array_slice($argv, 1);
-
- $command = implode(' ', $new_alias);
- if (self::isShellCommandAlias($command)) {
- echo tsprintf(
- "%s\n",
- pht(
- 'Aliased "%s" to shell command "%s".',
- "arc {$alias}",
- substr($command, 1)));
- } else {
- echo tsprintf(
- "%s\n",
- pht(
- 'Aliased "%s" to "%s".',
- "arc {$alias}",
- "arc {$command}"));
- }
-
- $aliases[$alias] = $new_alias;
- $this->writeAliases($aliases);
- }
-
- return 0;
- }
-
- public static function isShellCommandAlias($command) {
- return preg_match('/^!/', $command);
- }
-
- public static function resolveAliases(
- $command,
- ArcanistConfiguration $config,
- array $argv,
- ArcanistConfigurationManager $configuration_manager) {
-
- $aliases = self::getAliases($configuration_manager);
- if (!isset($aliases[$command])) {
- return array(null, $argv);
- }
-
- $new_command = head($aliases[$command]);
-
- if (self::isShellCommandAlias($new_command)) {
- return array($new_command, $argv);
- }
-
- $workflow = $config->buildWorkflow($new_command);
- if (!$workflow) {
- return array(null, $argv);
- }
-
- $alias_argv = array_slice($aliases[$command], 1);
- foreach (array_reverse($alias_argv) as $alias_arg) {
- if (!in_array($alias_arg, $argv)) {
- array_unshift($argv, $alias_arg);
- }
- }
-
- return array($new_command, $argv);
- }
-
- private function printAliases(array $aliases) {
- if (!$aliases) {
- echo tsprintf(
- "%s\n",
- pht('You have not defined any aliases yet.'));
- return;
- }
-
- $table = id(new PhutilConsoleTable())
- ->addColumn('input', array('title' => pht('Alias')))
- ->addColumn('command', array('title' => pht('Command')))
- ->addColumn('type', array('title' => pht('Type')));
-
- ksort($aliases);
-
- foreach ($aliases as $alias => $binding) {
- $command = implode(' ', $binding);
- if (self::isShellCommandAlias($command)) {
- $command = substr($command, 1);
- $type = pht('Shell Command');
- } else {
- $command = "arc {$command}";
- $type = pht('Arcanist Command');
- }
-
- $row = array(
- 'input' => "arc {$alias}",
- 'type' => $type,
- 'command' => $command,
- );
-
- $table->addRow($row);
- }
-
- $table->draw();
- }
-
- private function removeAlias(array $aliases, $alias) {
- if (empty($aliases[$alias])) {
- echo tsprintf(
- "%s\n",
- pht('No alias "%s" to remove.', $alias));
- return;
- }
-
- $command = implode(' ', $aliases[$alias]);
-
- if (self::isShellCommandAlias($command)) {
- echo tsprintf(
- "%s\n",
- pht(
- '"%s" is currently aliased to shell command "%s".',
- "arc {$alias}",
- substr($command, 1)));
- } else {
- echo tsprintf(
- "%s\n",
- pht(
- '"%s" is currently aliased to "%s".',
- "arc {$alias}",
- "arc {$command}"));
- }
-
-
- $ok = phutil_console_confirm(pht('Delete this alias?'));
- if (!$ok) {
- throw new ArcanistUserAbortException();
- }
-
- unset($aliases[$alias]);
- $this->writeAliases($aliases);
-
- echo tsprintf(
- "%s\n",
- pht(
- 'Removed alias "%s".',
- "arc {$alias}"));
- }
-
-}
diff --git a/src/workflow/ArcanistWorkflow.php b/src/workflow/ArcanistWorkflow.php
--- a/src/workflow/ArcanistWorkflow.php
+++ b/src/workflow/ArcanistWorkflow.php
@@ -2271,4 +2271,8 @@
return $this->repositoryRef;
}
+ final public function getToolsetKey() {
+ return $this->getToolset()->getToolsetKey();
+ }
+
}

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 24, 1:13 AM (2 d, 12 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7711922
Default Alt Text
D20996.id50025.diff (19 KB)

Event Timeline