2022-02-11 15:48:06 +01:00

1209 lines
29 KiB
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);
}
}