mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-25 16:22:42 +01:00
Correct spelling for "arc" commands
Summary: I type "arc brnach" about 300 times per day. - Allow arc commands to be specified by unique prefix ("exp" for "export", "lib" for "liberate"). - Allow arc commands to be specified by unique levenshtein edit distance <= 2 ("brnach" for "branch", "halp" for "help", "ptach" for "patch"). - Reorganize code out of "arcanist.php". I think this will be uncontentious because arc commands are rarely destructive, but if people complain we can either require certain commands be typed exactly (maybe "land"?) or allow this feature to be disabled in configuration. Test Plan: $ arc br Usage Exception: Unknown command 'br'. Try 'arc help'. Did you mean: branch browse $ arc brnachh Usage Exception: Unknown command 'brnachh'. Try 'arc help'. $ arc brnach (Assuming 'brnach' is the British spelling of 'branch'.) doc-security No Revision security sms Needs Revision D319: Add SMS support to Phabricator phxtag No Revision derp arantag No Revision derp ... Reviewers: btrahan, vrana Reviewed By: btrahan CC: aran Differential Revision: https://secure.phabricator.com/D4305
This commit is contained in:
parent
0b833c2f02
commit
59e13f666f
2 changed files with 137 additions and 55 deletions
|
@ -49,6 +49,7 @@ $args = array_values($argv);
|
||||||
$working_directory = getcwd();
|
$working_directory = getcwd();
|
||||||
$console = PhutilConsole::getConsole();
|
$console = PhutilConsole::getConsole();
|
||||||
$config = null;
|
$config = null;
|
||||||
|
$workflow = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
@ -131,61 +132,7 @@ try {
|
||||||
|
|
||||||
$command = strtolower($args[0]);
|
$command = strtolower($args[0]);
|
||||||
$args = array_slice($args, 1);
|
$args = array_slice($args, 1);
|
||||||
$workflow = $config->buildWorkflow($command);
|
$workflow = $config->selectWorkflow($command, $args, $working_copy, $console);
|
||||||
if (!$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($working_copy);
|
|
||||||
list($new_command, $args) = ArcanistAliasWorkflow::resolveAliases(
|
|
||||||
$command,
|
|
||||||
$config,
|
|
||||||
$args,
|
|
||||||
$working_copy);
|
|
||||||
|
|
||||||
$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(
|
|
||||||
"[alias: 'arc %s' -> $ %s]",
|
|
||||||
$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 = $config->buildWorkflow($new_command);
|
|
||||||
if ($workflow) {
|
|
||||||
$console->writeLog(
|
|
||||||
"[alias: 'arc %s' -> 'arc %s']\n",
|
|
||||||
$command,
|
|
||||||
$full_alias);
|
|
||||||
$command = $new_command;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$workflow) {
|
|
||||||
throw new ArcanistUsageException(
|
|
||||||
"Unknown command '{$command}'. Try 'arc help'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$workflow->setArcanistConfiguration($config);
|
$workflow->setArcanistConfiguration($config);
|
||||||
$workflow->setCommand($command);
|
$workflow->setCommand($command);
|
||||||
$workflow->setWorkingDirectory($working_directory);
|
$workflow->setWorkingDirectory($working_directory);
|
||||||
|
@ -605,3 +552,4 @@ function reenter_if_this_is_arcanist_or_libphutil(
|
||||||
|
|
||||||
exit($err);
|
exit($err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,4 +78,138 @@ class ArcanistConfiguration {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final public function selectWorkflow(
|
||||||
|
&$command,
|
||||||
|
array &$args,
|
||||||
|
ArcanistWorkingCopyIdentity $working_copy,
|
||||||
|
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($working_copy);
|
||||||
|
list($new_command, $args) = ArcanistAliasWorkflow::resolveAliases(
|
||||||
|
$command,
|
||||||
|
$this,
|
||||||
|
$args,
|
||||||
|
$working_copy);
|
||||||
|
|
||||||
|
$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(
|
||||||
|
"[alias: 'arc %s' -> $ %s]",
|
||||||
|
$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(
|
||||||
|
"[alias: 'arc %s' -> 'arc %s']\n",
|
||||||
|
$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 = $this->correctCommandSpelling($command, $all, 2);
|
||||||
|
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 'arc help'.", $command);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function correctCommandSpelling(
|
||||||
|
$command,
|
||||||
|
array $options,
|
||||||
|
$max_distance) {
|
||||||
|
|
||||||
|
$distances = array();
|
||||||
|
foreach ($options as $option) {
|
||||||
|
$distances[$option] = levenshtein($option, $command);
|
||||||
|
}
|
||||||
|
|
||||||
|
asort($distances);
|
||||||
|
$best = min($max_distance, reset($distances));
|
||||||
|
foreach ($distances as $option => $distance) {
|
||||||
|
if ($distance > $best) {
|
||||||
|
unset($distances[$option]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_keys($distances);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue