private function assertFlagCorrection($expect, $input, $flags) { $result = PhutilArgumentSpellingCorrector::newFlagCorrector()->correctSpelling($input, $flags); sort($result); sort($expect); $flags = implode(', ', $flags); $this->assertEqual($expect, $result, pht('Correction of %s against: %s', $input, $flags)); }
/** * Select a workflow. For commands that may operate in several modes, like * `arc`, the modes can be split into "workflows". Each workflow specifies * the arguments it accepts. This method takes a list of workflows, selects * the chosen workflow, parses its arguments, and either executes it (if it * is executable) or returns it for handling. * * See @{class:PhutilArgumentWorkflow} for details on using workflows. * * @param list List of @{class:PhutilArgumentWorkflow}s. * @return PhutilArgumentWorkflow|no Returns the chosen workflow if it is * not executable, or executes it and * exits with a return code if it is. * @task parse */ public function parseWorkflowsFull(array $workflows) { assert_instances_of($workflows, 'PhutilArgumentWorkflow'); // Clear out existing workflows. We need to do this to permit the // construction of sub-workflows. $this->workflows = array(); foreach ($workflows as $workflow) { $name = $workflow->getName(); if ($name === null) { throw new PhutilArgumentSpecificationException(pht('Workflow has no name!')); } if (isset($this->workflows[$name])) { throw new PhutilArgumentSpecificationException(pht("Two workflows with name '%s!", $name)); } $this->workflows[$name] = $workflow; } $argv = $this->argv; if (empty($argv)) { // TODO: this is kind of hacky / magical. if (isset($this->workflows['help'])) { $argv = array('help'); } else { throw new PhutilArgumentUsageException(pht('No workflow selected.')); } } $flow = array_shift($argv); if (empty($this->workflows[$flow])) { $corrected = PhutilArgumentSpellingCorrector::newCommandCorrector()->correctSpelling($flow, array_keys($this->workflows)); if (count($corrected) == 1) { $corrected = head($corrected); $this->logMessage(tsprintf("%s\n", pht('(Assuming "%s" is the British spelling of "%s".)', $flow, $corrected))); $flow = $corrected; } else { $this->raiseUnknownWorkflow($flow, $corrected); } } $workflow = $this->workflows[$flow]; if ($this->showHelp) { // Make "cmd flow --help" behave like "cmd help flow", not "cmd help". $help_flow = idx($this->workflows, 'help'); if ($help_flow) { if ($help_flow !== $workflow) { $workflow = $help_flow; $argv = array($flow); // Prevent parse() from dumping us back out to standard help. $this->showHelp = false; } } else { $this->printHelpAndExit(); } } $this->argv = array_values($argv); if ($workflow->shouldParsePartial()) { $this->parsePartial($workflow->getArguments()); } else { $this->parse($workflow->getArguments()); } if ($workflow->isExecutable()) { $workflow->setArgv($this); $err = $workflow->execute($this); exit($err); } else { return $workflow; } }
public final 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); }
public final 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; }