%PDF- %PDF-
Direktori : /proc/self/root/home/infra/fusioninventory/scripts/ |
Current File : //proc/self/root/home/infra/fusioninventory/scripts/docopt.php |
<?php /** * Command-line interface parser that will make you smile. * * - http://docopt.org * - Repository and issue-tracker: https://github.com/docopt/docopt.php * - Licensed under terms of MIT license (see LICENSE-MIT) * - Copyright (c) 2013 Vladimir Keleshev, vladimir@keleshev.com * Blake Williams, <code@shabbyrobe.org> */ namespace Docopt; /** * Return true if all cased characters in the string are uppercase and there is * at least one cased character, false otherwise. * Python method with no known equivalent in PHP. */ function is_upper($string) { return preg_match('/[A-Z]/', $string) && !preg_match('/[a-z]/', $string); } /** * Return True if any element of the iterable is true. If the iterable is empty, return False. * Python method with no known equivalent in PHP. */ function any($iterable) { foreach ($iterable as $element) { if ($element) { return true; } } return false; } /** * The PHP version of this function doesn't work properly if the values aren't scalar. */ function array_count_values($array) { $counts = []; foreach ($array as $v) { if ($v && is_scalar($v)) { $key = $v; } else if (is_object($v)) { $key = spl_object_hash($v); } else { $key = serialize($v); } if (!isset($counts[$key])) { $counts[$key] = [$v, 1]; } else { $counts[$key][1]++; } } return $counts; } /** * The PHP version of this doesn't support array iterators */ function array_filter($input, $callback, $reKey = false) { if ($input instanceof \ArrayIterator) { $input = $input->getArrayCopy(); } $filtered = \array_filter($input, $callback); if ($reKey) { $filtered = array_values($filtered); } return $filtered; } /** * The PHP version of this doesn't support array iterators */ function array_merge() { $values = func_get_args(); $resolved = []; foreach ($values as $v) { if ($v instanceof \ArrayIterator) { $resolved[] = $v->getArrayCopy(); } else { $resolved[] = $v; } } return call_user_func_array('array_merge', $resolved); } function ends_with($str, $test) { $len = strlen($test); return substr_compare($str, $test, -$len, $len) === 0; } function get_class_name($obj) { $cls = get_class($obj); return substr($cls, strpos($cls, '\\')+1); } function dump($val) { if (is_array($val) || $val instanceof \Traversable) { echo '['; $cur = []; foreach ($val as $i) { $cur[] = $i->dump(); } echo implode(', ', $cur); echo ']'; } else { echo $val->dump(); } } function dump_scalar($scalar) { if ($scalar === null) { return 'None'; } else if ($scalar === false) { return 'False'; } else if ($scalar === true) { return 'True'; } else if (is_int($scalar) || is_float($scalar)) { return $scalar; } else { return "'$scalar'"; } } /** * Error in construction of usage-message by developer */ class LanguageError extends \Exception { } /** * Exit in case user invoked program with incorrect arguments. * DocoptExit equivalent. */ class ExitException extends \RuntimeException { public static $usage; public $status; public function __construct($message = null, $status = 1) { parent::__construct(trim($message.PHP_EOL.static::$usage)); $this->status = $status; } } class Pattern { public function __toString() { return serialize($this); } public function hash() { return crc32((string)$this); } public function fix() { $this->fixIdentities(); $this->fixRepeatingArguments(); return $this; } /** * Make pattern-tree tips point to same object if they are equal. */ public function fixIdentities($uniq = null) { if (!isset($this->children) || !$this->children) { return $this; } if (!$uniq) { $uniq = array_unique($this->flat()); } foreach ($this->children as $i=>$c) { if (!$c instanceof ParentPattern) { if (!in_array($c, $uniq)) { // Not sure if this is a true substitute for 'assert c in uniq' throw new \UnexpectedValueException(); } $this->children[$i] = $uniq[array_search($c, $uniq)]; } else { $c->fixIdentities($uniq); } } } /** * Fix elements that should accumulate/increment values. */ public function fixRepeatingArguments() { $either = []; foreach ($this->either()->children as $c) { $either[] = $c->children; } foreach ($either as $case) { $case = array_map( function($value) { return $value[0]; }, array_filter(array_count_values($case), function($value) { return $value[1] > 1; }) ); foreach ($case as $e) { if ($e instanceof Argument || ($e instanceof Option && $e->argcount)) { if (!$e->value) { $e->value = []; } else if (!is_array($e->value) && !$e->value instanceof \Traversable) { $e->value = preg_split('/\s+/', $e->value); } } if ($e instanceof Command || ($e instanceof Option && $e->argcount == 0)) { $e->value = 0; } } } return $this; } /** * Transform pattern into an equivalent, with only top-level Either. */ public function either() { // Currently the pattern will not be equivalent, but more "narrow", // although good enough to reason about list arguments. $ret = []; $groups = [[$this]]; while ($groups) { $children = array_pop($groups); $types = []; foreach ($children as $c) { if (is_object($c)) { $cls = get_class($c); $types[] = substr($cls, strrpos($cls, '\\')+1); } } if (in_array('Either', $types)) { $either = null; foreach ($children as $c) { if ($c instanceof Either) { $either = $c; break; } } unset($children[array_search($either, $children)]); foreach ($either->children as $c) { $groups[] = array_merge([$c], $children); } } else if (in_array('Required', $types)) { $required = null; foreach ($children as $c) { if ($c instanceof Required) { $required = $c; break; } } unset($children[array_search($required, $children)]); $groups[] = array_merge($required->children, $children); } else if (in_array('Optional', $types)) { $optional = null; foreach ($children as $c) { if ($c instanceof Optional) { $optional = $c; break; } } unset($children[array_search($optional, $children)]); $groups[] = array_merge($optional->children, $children); } else if (in_array('AnyOptions', $types)) { $optional = null; foreach ($children as $c) { if ($c instanceof AnyOptions) { $optional = $c; break; } } unset($children[array_search($optional, $children)]); $groups[] = array_merge($optional->children, $children); } else if (in_array('OneOrMore', $types)) { $oneormore = null; foreach ($children as $c) { if ($c instanceof OneOrMore) { $oneormore = $c; break; } } unset($children[array_search($oneormore, $children)]); $groups[] = array_merge($oneormore->children, $oneormore->children, $children); } else { $ret[] = $children; } } $rs = []; foreach ($ret as $e) { $rs[] = new Required($e); } return new Either($rs); } public function name() { } public function __get($name) { if ($name == 'name') { return $this->name(); } else { throw new \BadMethodCallException("Unknown property $name"); } } } class ChildPattern extends Pattern { public function flat($types = []) { $types = is_array($types) ? $types : [$types]; if (!$types || in_array(get_class_name($this), $types)) { return [$this]; } else { return []; } } public function match($left, $collected = null) { if (!$collected) { $collected = []; } list ($pos, $match) = $this->singleMatch($left); if (!$match) { return [false, $left, $collected]; } $left_ = $left; unset($left_[$pos]); $left_ = array_values($left_); $name = $this->name; $sameName = array_filter($collected, function ($a) use ($name) { return $name == $a->name; }, true); if (is_int($this->value) || is_array($this->value) || $this->value instanceof \Traversable) { if (is_int($this->value)) { $increment = 1; } else { $increment = is_string($match->value) ? [$match->value] : $match->value; } if (!$sameName) { $match->value = $increment; return [true, $left_, array_merge($collected, [$match])]; } if (is_array($increment) || $increment instanceof \Traversable) { $sameName[0]->value = array_merge($sameName[0]->value, $increment); } else { $sameName[0]->value += $increment; } return [true, $left_, $collected]; } return [true, $left_, array_merge($collected, [$match])]; } } class ParentPattern extends Pattern { public $children = []; public function __construct($children = null) { if (!$children) { $children = []; } else if ($children instanceof Pattern) { $children = [$children]; } foreach ($children as $c) { $this->children[] = $c; } } public function flat($types = []) { $types = is_array($types) ? $types : [$types]; if (in_array(get_class_name($this), $types)) { return [$this]; } $flat = []; foreach ($this->children as $c) { $flat = array_merge($flat, $c->flat($types)); } return $flat; } public function dump() { $out = get_class_name($this).'('; $cd = []; foreach ($this->children as $c) { $cd[] = $c->dump(); } $out .= implode(', ', $cd).')'; return $out; } } class Argument extends ChildPattern { public $name; public $value; public function __construct($name, $value = null) { $this->name = $name; $this->value = $value; } public function singleMatch($left) { foreach ($left as $n=>$p) { if ($p instanceof Argument) { return [$n, new Argument($this->name, $p->value)]; } } return [null, null]; } public static function parse($source) { $name = null; $value = null; if (preg_match_all('@(<\S*?>)@', $source, $matches)) { $name = $matches[0][0]; } if (preg_match_all('@\[default: (.*)\]@i', $source, $matches)) { $value = $matches[0][1]; } return new static($name, $value); } public function dump() { return "Argument('".dump_scalar($this->name)."', ".dump_scalar($this->value)."')"; } } class Command extends Argument { public $name; public $value; public function __construct($name, $value = false) { $this->name = $name; $this->value = $value; } function singleMatch($left) { foreach ($left as $n=>$p) { if ($p instanceof Argument) { if ($p->value == $this->name) { return [$n, new Command($this->name, true)]; } else { break; } } } return [null, null]; } } class Option extends ChildPattern { public $short; public $long; public function __construct($short = null, $long = null, $argcount = 0, $value = false) { if ($argcount != 0 && $argcount != 1) { throw new \InvalidArgumentException(); } $this->short = $short; $this->long = $long; $this->argcount = $argcount; $this->value = $value; // Python checks "value is False". maybe we should check "$value === false" if (!$value && $argcount) { $this->value = null; } } public static function parse($optionDescription) { $short = null; $long = null; $argcount = 0; $value = false; $exp = explode(' ', trim($optionDescription), 2); $options = $exp[0]; $description = isset($exp[1]) ? $exp[1] : ''; $options = str_replace(',', ' ', str_replace('=', ' ', $options)); foreach (preg_split('/\s+/', $options) as $s) { if (strpos($s, '--')===0) { $long = $s; } else if ($s && $s[0] == '-') { $short = $s; } else { $argcount = 1; } } if ($argcount) { $value = null; if (preg_match('@\[default: (.*)\]@i', $description, $match)) { $value = $match[1]; } } return new static($short, $long, $argcount, $value); } public function singleMatch($left) { foreach ($left as $n=>$p) { if ($this->name == $p->name) { return [$n, $p]; } } return [null, null]; } public function name() { return $this->long ?: $this->short; } public function dump() { return "Option('{$this->short}', ".dump_scalar($this->long).", ".dump_scalar($this->argcount).", ".dump_scalar($this->value).")"; } } class Required extends ParentPattern { public function match($left, $collected = null) { if (!$collected) { $collected = []; } $l = $left; $c = $collected; foreach ($this->children as $p) { list ($matched, $l, $c) = $p->match($l, $c); if (!$matched) { return [false, $left, $collected]; } } return [true, $l, $c]; } } class Optional extends ParentPattern { public function match($left, $collected = null) { if (!$collected) { $collected = []; } foreach ($this->children as $p) { list($m, $left, $collected) = $p->match($left, $collected); } return [true, $left, $collected]; } } /** * Marker/placeholder for [options] shortcut. */ class AnyOptions extends Optional { } class OneOrMore extends ParentPattern { public function match($left, $collected = null) { if (count($this->children) != 1) { throw new \UnexpectedValueException(); } if (!$collected) { $collected = []; } $l = $left; $c = $collected; $lnew = []; $matched = true; $times = 0; while ($matched) { // could it be that something didn't match but changed l or c? list ($matched, $l, $c) = $this->children[0]->match($l, $c); if ($matched) { $times += 1; } if ($lnew == $l) { break; } $lnew = $l; } if ($times >= 1) { return [true, $l, $c]; } else { return [false, $left, $collected]; } } } class Either extends ParentPattern { public function match($left, $collected = null) { if (!$collected) { $collected = []; } $outcomes = []; foreach ($this->children as $p) { list ($matched, $dump1, $dump2) = $outcome = $p->match($left, $collected); if ($matched) { $outcomes[] = $outcome; } } if ($outcomes) { // return min(outcomes, key=lambda outcome: len(outcome[1])) $min = null; $ret = null; foreach ($outcomes as $o) { $cnt = count($o[1]); if ($min === null || $cnt < $min) { $min = $cnt; $ret = $o; } } return $ret; } else { return [false, $left, $collected]; } } } class TokenStream extends \ArrayIterator { public $error; public function __construct($source, $error) { if (!is_array($source)) { $source = preg_split('/\s+/', trim($source)); } parent::__construct($source); $this->error = $error; } function move() { $item = $this->current(); $this->next(); return $item; } function raiseException($message) { $class = __NAMESPACE__.'\\'.$this->error; throw new $class($message); } } /** * long ::= '--' chars [ ( ' ' | '=' ) chars ] ; */ function parse_long($tokens, \ArrayIterator $options) { $token = $tokens->move(); $exploded = explode('=', $token, 2); if (count($exploded) == 2) { $long = $exploded[0]; $eq = '='; $value = $exploded[1]; } else { $long = $token; $eq = null; $value = null; } if (strpos($long, '--') !== 0) { throw new \UnexpectedValueExeption(); } if (!$value) { $value = null; } $similar = array_filter($options, function($o) use ($long) { return $o->long && $o->long == $long; }, true); if ('ExitException' == $tokens->error && !$similar) { $similar = array_filter($options, function($o) use ($long) { return $o->long && strpos($o->long, $long)===0; }, true); } if (count($similar) > 1) { // might be simply specified ambiguously 2+ times? $tokens->raiseException("$long is not a unique prefix: ".implode(', ', array_map(function($o) { return $o->long; }, $similar))); } else if (count($similar) < 1) { $argcount = $eq == '=' ? 1 : 0; $o = new Option(null, $long, $argcount); $options[] = $o; if ($tokens->error == 'ExitException') { $o = new Option(null, $long, $argcount, $argcount ? $value : true); } } else { $o = new Option($similar[0]->short, $similar[0]->long, $similar[0]->argcount, $similar[0]->value); if ($o->argcount == 0) { if ($value !== null) { $tokens->raiseException("{$o->long} must not have an argument"); } } else { if ($value === null) { if ($tokens->current() === null) { $tokens->raiseException("{$o->long} requires argument"); } $value = $tokens->move(); } } if ($tokens->error == 'ExitException') { $o->value = $value !== null ? $value : true; } } return [$o]; } /** * shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ; */ function parse_shorts($tokens, \ArrayIterator $options) { $token = $tokens->move(); if (strpos($token, '-') !== 0 || strpos($token, '--') === 0) { throw new \UnexpectedValueExeption(); } $left = ltrim($token, '-'); $parsed = []; while ($left != '') { $short = '-'.$left[0]; $left = substr($left, 1); $similar = []; foreach ($options as $o) { if ($o->short == $short) { $similar[] = $o; } } $similarCnt = count($similar); if ($similarCnt > 1) { $tokens->raiseException("$short is specified ambiguously $similarCnt times"); } else if ($similarCnt < 1) { $o = new Option($short, null, 0); $options[] = $o; if ($tokens->error == 'ExitException') { $o = new Option($short, null, 0, true); } } else { $o = new Option($short, $similar[0]->long, $similar[0]->argcount, $similar[0]->value); $value = null; if ($o->argcount != 0) { if ($left == '') { if ($tokens->current() === null) { $tokens->raiseException("$short requires argument"); } $value = $tokens->move(); } else { $value = $left; $left = ''; } } if ($tokens->error == 'ExitException') { $o->value = $value !== null ? $value : true; } } $parsed[] = $o; } return $parsed; } function parse_pattern($source, \ArrayIterator $options) { $tokens = new TokenStream(preg_replace('@([\[\]\(\)\|]|\.\.\.)@', ' $1 ', $source), 'LanguageError'); $result = parse_expr($tokens, $options); if ($tokens->current() != null) { $tokens->raiseException('unexpected ending: '.implode(' ', $tokens)); } return new Required($result); } /** * expr ::= seq ( '|' seq )* ; */ function parse_expr($tokens, \ArrayIterator $options) { $seq = parse_seq($tokens, $options); if ($tokens->current() != '|') { return $seq; } $result = null; if (count($seq) > 1) { $result = [new Required($seq)]; } else { $result = $seq; } while ($tokens->current() == '|') { $tokens->move(); $seq = parse_seq($tokens, $options); if (count($seq) > 1) { $result[] = new Required($seq); } else { $result = array_merge($result, $seq); } } if (count($result) > 1) { return new Either($result); } else { return $result; } } /** * seq ::= ( atom [ '...' ] )* ; */ function parse_seq($tokens, \ArrayIterator $options) { $result = []; $not = [null, '', ']', ')', '|']; while (!in_array($tokens->current(), $not, true)) { $atom = parse_atom($tokens, $options); if ($tokens->current() == '...') { $atom = [new OneOrMore($atom)]; $tokens->move(); } if ($atom instanceof \ArrayIterator) { $atom = $atom->getArrayCopy(); } if ($atom) { $result = array_merge($result, $atom); } } return $result; } /** * atom ::= '(' expr ')' | '[' expr ']' | 'options' * | long | shorts | argument | command ; */ function parse_atom($tokens, \ArrayIterator $options) { $token = $tokens->current(); $result = []; if ($token == '(' || $token == '[') { $tokens->move(); static $index; if (!$index) { $index = ['('=>[')', __NAMESPACE__.'\Required'], '['=>[']', __NAMESPACE__.'\Optional']]; } list ($matching, $pattern) = $index[$token]; $result = new $pattern(parse_expr($tokens, $options)); if ($tokens->move() != $matching) { $tokens->raiseException("Unmatched '$token'"); } return [$result]; } else if ($token == 'options') { $tokens->move(); return [new AnyOptions]; } else if (strpos($token, '--') === 0 && $token != '--') { return parse_long($tokens, $options); } else if (strpos($token, '-') === 0 && $token != '-' && $token != '--') { return parse_shorts($tokens, $options); } else if (strpos($token, '<') === 0 && ends_with($token, '>') || is_upper($token)) { return [new Argument($tokens->move())]; } else { return [new Command($tokens->move())]; } } /** * Parse command-line argument vector. * * If options_first: * argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ; * else: * argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ; */ function parse_argv($tokens, \ArrayIterator $options, $optionsFirst = false) { $parsed = []; while ($tokens->current() !== null) { if ($tokens->current() == '--') { foreach ($tokens as $v) { $parsed[] = new Argument(null, $v); } return $parsed; } else if (strpos($tokens->current(), '--')===0) { $parsed = array_merge($parsed, parse_long($tokens, $options)); } else if (strpos($tokens->current(), '-')===0 && $tokens->current() != '-') { $parsed = array_merge($parsed, parse_shorts($tokens, $options)); } else if ($optionsFirst) { return array_merge($parsed, array_map(function($v) { return new Argument(null, $v); }, $tokens)); } else { $parsed[] = new Argument(null, $tokens->move()); } } return $parsed; } function parse_defaults($doc) { $splitTmp = array_slice(preg_split('@\n[ ]*(<\S+?>|-\S+?)@', $doc, null, PREG_SPLIT_DELIM_CAPTURE), 1); $split = []; for ($cnt = count($splitTmp), $i=0; $i < $cnt; $i+=2) { $split[] = $splitTmp[$i] . (isset($splitTmp[$i+1]) ? $splitTmp[$i+1] : ''); } $options = new \ArrayIterator(); foreach ($split as $s) { if (strpos($s, '-') === 0) { $options[] = Option::parse($s); } } return $options; } function printable_usage($doc) { $usageSplit = preg_split("@([Uu][Ss][Aa][Gg][Ee]:)@", $doc, null, PREG_SPLIT_DELIM_CAPTURE); if (count($usageSplit) < 3) { throw new LanguageError('"usage:" (case-insensitive) not found.'); } else if (count($usageSplit) > 3) { throw new LanguageError('More than one "usage:" (case-insensitive).'); } $split = preg_split("@\n\s*\n@", implode('', array_slice($usageSplit, 1))); return trim($split[0]); } function formal_usage($printableUsage) { $pu = array_slice(preg_split('/\s+/', $printableUsage), 1); $ret = []; foreach (array_slice($pu, 1) as $s) { if ($s == $pu[0]) { $ret[] = ') | ('; } else { $ret[] = $s; } } return '( '.implode(' ', $ret).' )'; } function extras($help, $version, $options, $doc) { $ofound = false; $vfound = false; foreach ($options as $o) { if ($o->value && ($o->name == '-h' || $o->name == '--help')) { $ofound = true; } if ($o->value && $o->name == '--version') { $vfound = true; } } if ($help && $ofound) { \ExitException::$usage = null; throw new \ExitException($doc, 0); } if ($version && $vfound) { \ExitException::$usage = null; throw new \ExitException($version, 0); } } /** * API compatibility with python docopt */ function docopt($doc, $params = []) { $argv = []; if (isset($params['argv'])) { $argv = $params['argv']; unset($params['argv']); } $h = new Handler($params); return $h->handle($doc, $argv); } /** * Use a class in PHP because we can't autoload functions yet. */ class Handler { public $exit = true; public $help = true; public $optionsFirst = false; public $version; public function __construct($options = []) { foreach ($options as $k=>$v) { $this->$k = $v; } } function handle($doc, $argv = null) { try { if (!$argv && isset($_SERVER['argv'])) { $argv = array_slice($_SERVER['argv'], 1); } \ExitException::$usage = printable_usage($doc); $options = parse_defaults($doc); $formalUse = formal_usage(\ExitException::$usage); $pattern = parse_pattern($formalUse, $options); $argv = parse_argv(new TokenStream($argv, 'ExitException'), $options, $this->optionsFirst); foreach ($pattern->flat('AnyOptions') as $ao) { $docOptions = parse_defaults($doc); $ao->children = array_diff((array)$docOptions, $pattern->flat('Option')); } extras($this->help, $this->version, $argv, $doc); list($matched, $left, $collected) = $pattern->fix()->match($argv); if ($matched && !$left) { $return = []; foreach (array_merge($pattern->flat(), $collected) as $a) { $name = $a->name; if ($name) { $return[$name] = $a->value; } } return new Response($return); } throw new \ExitException(); } catch (\ExitException $ex) { $this->handleExit($ex); return new Response(null, $ex->status, $ex->getMessage()); } } function handleExit(\ExitException $ex) { if ($this->exit) { echo $ex->getMessage().PHP_EOL; exit($ex->status); } } } class Response implements \ArrayAccess, \IteratorAggregate { public $status; public $output; public $args; public function __construct($args, $status = 0, $output = '') { $this->args = $args ?: []; $this->status = $status; $this->output = $output; } public function __get($name) { if ($name == 'success') { return $this->status === 0; } else { throw new \BadMethodCallException("Unknown property $name"); } } public function offsetExists($offset) { return isset($this->args[$offset]); } public function offsetGet($offset) { return $this->args[$offset]; } public function offsetSet($offset, $value) { $this->args[$offset] = $value; } public function offsetUnset($offset) { unset($this->args[$offset]); } public function getIterator () { return new \ArrayIterator($this->args); } }