public static function newSpecsFromList(array $specs) { foreach ($specs as $key => $spec) { if (is_array($spec)) { $specs[$key] = PhutilArgumentSpecification::newQuickSpec($spec); } } return $specs; }
public function testSpecs() { $good_specs = array(array('name' => 'verbose'), array('name' => 'verbose', 'short' => 'v', 'help' => 'Derp.', 'param' => 'level', 'default' => 'y', 'conflicts' => array('quiet' => true), 'wildcard' => false), array('name' => 'files', 'wildcard' => true)); $bad_specs = array(array(), array('alias' => 'v'), array('name' => 'derp', 'fruit' => 'apple'), array('name' => 'x', 'default' => 'y'), array('name' => 'x', 'param' => 'y', 'default' => 'z', 'repeat' => true), array('name' => 'x', 'wildcard' => true, 'repeat' => true), array('name' => 'x', 'param' => 'y', 'wildcard' => true)); $cases = array(array(true, $good_specs), array(false, $bad_specs)); foreach ($cases as $case) { list($expect, $specs) = $case; foreach ($specs as $spec) { $caught = null; try { PhutilArgumentSpecification::newQuickSpec($spec); } catch (PhutilArgumentSpecificationException $ex) { $caught = $ex; } $this->assertEqual(!$expect, $caught instanceof Exception, "Spec validity for: " . print_r($spec, true)); } } }
public final function setArguments(array $specs) { $specs = PhutilArgumentSpecification::newSpecsFromList($specs); $this->specs = $specs; return $this; }
/** * Parse and consume a list of arguments, removing them from the argument * vector but leaving unparsed arguments for later consumption. You can * retrieve unconsumed arguments directly with * @{method:getUnconsumedArgumentVector}. Doing a partial parse can make it * easier to share common flags across scripts or workflows. * * @param list List of argument specs, see * @{class:PhutilArgumentSpecification}. * @return this * @task parse */ public function parsePartial(array $specs) { $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; 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; } 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; } 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; }
/** * Convenience constructor for building an argument specification from a * dictionary. This just wraps all the setter methods, but allows you to * define things a little more compactly. Pass an array of properties: * * $spec = PhutilArgumentSpecification::newQuickSpec( * array( * 'name' => 'verbose', * 'short' => 'v', * )); * * Recognized keys and equivalent verbose methods are: * * name setName() * help setHelp() * short setShortAlias() * param setParamName() * default setDefault() * conflicts setConflicts() * wildcard setWildcard() * repeat setRepeatable() * * @param dict Dictionary of quick parameter definitions. * @return PhutilArgumentSpecification Constructed argument specification. */ public static function newQuickSpec(array $spec) { $recognized_keys = array('name', 'help', 'short', 'param', 'default', 'conflicts', 'wildcard', 'repeat', 'standard'); $unrecognized = array_diff_key($spec, array_fill_keys($recognized_keys, true)); foreach ($unrecognized as $key => $ignored) { throw new PhutilArgumentSpecificationException(pht("Unrecognized key '%s' in argument specification. Recognized keys " . "are: %s.", $key, implode(', ', $recognized_keys))); } $obj = new PhutilArgumentSpecification(); foreach ($spec as $key => $value) { switch ($key) { case 'name': $obj->setName($value); break; case 'help': $obj->setHelp($value); break; case 'short': $obj->setShortAlias($value); break; case 'param': $obj->setParamName($value); break; case 'default': $obj->setDefault($value); break; case 'conflicts': $obj->setConflicts($value); break; case 'wildcard': $obj->setWildcard($value); break; case 'repeat': $obj->setRepeatable($value); break; case 'standard': $obj->setStandard($value); break; } } $obj->validate(); return $obj; }
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; }