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)); }
private function parseInternal(array $specs, $correct_spelling) { $specs = PhutilArgumentSpecification::newSpecsFromList($specs); $this->mergeSpecs($specs); $specs_by_name = mpull($specs, null, 'getName'); $specs_by_short = mpull($specs, null, 'getShortAlias'); unset($specs_by_short[null]); $argv = $this->argv; $len = count($argv); for ($ii = 0; $ii < $len; $ii++) { $arg = $argv[$ii]; $map = null; $options = null; if (!is_string($arg)) { // Non-string argument; pass it through as-is. } else { if ($arg == '--') { // This indicates "end of flags". break; } else { if ($arg == '-') { // This is a normal argument (e.g., stdin). continue; } else { if (!strncmp('--', $arg, 2)) { $pre = '--'; $arg = substr($arg, 2); $map = $specs_by_name; $options = array_keys($specs_by_name); } else { if (!strncmp('-', $arg, 1) && strlen($arg) > 1) { $pre = '-'; $arg = substr($arg, 1); $map = $specs_by_short; } } } } } if ($map) { $val = null; $parts = explode('=', $arg, 2); if (count($parts) == 2) { list($arg, $val) = $parts; } // Try to correct flag spelling for full flags, to allow users to make // minor mistakes. if ($correct_spelling && $options && !isset($map[$arg])) { $corrections = PhutilArgumentSpellingCorrector::newFlagCorrector()->correctSpelling($arg, $options); if (count($corrections) == 1) { $corrected = head($corrections); $this->logMessage(tsprintf("%s\n", pht('(Assuming "%s" is the British spelling of "%s".)', $pre . $arg, $pre . $corrected))); $arg = $corrected; } } if (isset($map[$arg])) { $spec = $map[$arg]; unset($argv[$ii]); $param_name = $spec->getParamName(); if ($val !== null) { if ($param_name === null) { throw new PhutilArgumentUsageException(pht("Argument '%s' does not take a parameter.", "{$pre}{$arg}")); } } else { if ($param_name !== null) { if ($ii + 1 < $len) { $val = $argv[$ii + 1]; unset($argv[$ii + 1]); $ii++; } else { throw new PhutilArgumentUsageException(pht("Argument '%s' requires a parameter.", "{$pre}{$arg}")); } } else { $val = true; } } if (!$spec->getRepeatable()) { if (array_key_exists($spec->getName(), $this->results)) { throw new PhutilArgumentUsageException(pht("Argument '%s' was provided twice.", "{$pre}{$arg}")); } } $conflicts = $spec->getConflicts(); foreach ($conflicts as $conflict => $reason) { if (array_key_exists($conflict, $this->results)) { if (!is_string($reason) || !strlen($reason)) { $reason = '.'; } else { $reason = ': ' . $reason . '.'; } throw new PhutilArgumentUsageException(pht("Argument '%s' conflicts with argument '%s'%s", "{$pre}{$arg}", "--{$conflict}", $reason)); } } if ($spec->getRepeatable()) { if ($spec->getParamName() === null) { if (empty($this->results[$spec->getName()])) { $this->results[$spec->getName()] = 0; } $this->results[$spec->getName()]++; } else { $this->results[$spec->getName()][] = $val; } } else { $this->results[$spec->getName()] = $val; } } } } foreach ($specs as $spec) { if ($spec->getWildcard()) { $this->results[$spec->getName()] = $this->filterWildcardArgv($argv); $argv = array(); break; } } $this->argv = array_values($argv); return $this; }
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; }