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));
 }
예제 #2
0
 /**
  * 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;
     }
 }
예제 #3
0
 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);
 }
예제 #4
0
 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;
 }