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;
 }