c248a74dcc964c81c089d1b2204a8cacfe3dcfb0
6 * Command line arguments
8 class Args
implements \ArrayAccess
13 const OPTIONAL
= 0x000;
18 const REQUIRED
= 0x001;
21 * Only one value, even when used multiple times
26 * Aggregate an array, when used multiple times
31 * Option takes no argument
36 * Option requires an argument
41 * Option takes an optional argument
46 * Option halts processing
48 const HALT
= 0x10000000;
51 * Original option spec
69 * Compile the original spec
70 * @param array|Traversable $spec
72 public function __construct($spec = null) {
73 if (is_array($spec) ||
$spec instanceof Traversable
) {
74 $this->compile($spec);
80 * Compile the original spec
81 * @param array|Traversable $spec
82 * @return pharext\Cli\Args self
84 public function compile($spec) {
85 foreach ($spec as $arg) {
86 if (isset($arg[0]) && is_numeric($arg[0])) {
88 $this->spec
["--".$arg[0]] = $arg;
89 } elseif (isset($arg[0])) {
90 $this->spec
["-".$arg[0]] = $arg;
91 $this->spec
["--".$arg[1]] = $arg;
93 $this->spec
["--".$arg[1]] = $arg;
104 public function getSpec() {
112 public function getCompiledSpec() {
117 * Parse command line arguments according to the compiled spec.
119 * The Generator yields any parsing errors.
120 * Parsing will stop when all arguments are processed or the first option
121 * flagged Cli\Args::HALT was encountered.
127 public function parse($argc, array $argv) {
128 for ($f = false, $p = 0, $i = 0; $i < $argc; ++
$i) {
131 if ($o{0} === "-" && strlen($o) > 2 && $o{1} !== "-") {
132 // multiple short opts, e.g. -vps
133 $argc +
= strlen($o) - 2;
134 array_splice($argv, $i, 1, array_map(function($s) {
136 }, str_split(substr($o, 1))));
138 } elseif ($o{0} === "-" && strlen($o) > 2 && $o{1} === "-" && 0 < ($eq = strpos($o, "="))) {
139 // long opt with argument, e.g. --foo=bar
141 array_splice($argv, $i, 1, [
142 substr($o, 0, $eq++
),
146 } elseif ($o === "--") {
147 // only positional args following
152 if ($f ||
!isset($this->spec
[$o])) {
153 if ($o{0} !== "-" && isset($this->spec
["--$p"])) {
155 if (!$this->optIsMulti($p)) {
159 yield
sprintf("Unknown option %s", $o);
161 } elseif (!$this->optAcceptsArg($o)) {
163 } elseif ($i+
1 < $argc && !isset($this->spec
[$argv[$i+
1]])) {
164 $this[$o] = $argv[++
$i];
165 } elseif ($this->optRequiresArg($o)) {
166 yield
sprintf("Option --%s requires an argument", $this->optLongName($o));
169 $this[$o] = $this->optDefaultArg($o);
172 if ($this->optHalts($o)) {
179 * Validate that all required options were given.
181 * The Generator yields any validation errors.
185 public function validate() {
186 $required = array_filter($this->orig
, function($spec) {
187 return $spec[3] & self
::REQUIRED
;
189 foreach ($required as $req) {
190 if ($req[3] & self
::MULTI
) {
191 if (is_array($this[$req[0]])) {
194 } elseif (strlen($this[$req[0]])) {
197 if (is_numeric($req[0])) {
198 yield
sprintf("Argument <%s> is required", $req[1]);
200 yield
sprintf("Option --%s is required", $req[1]);
206 public function toArray() {
208 foreach ($this->spec
as $spec) {
209 $opt = $this->opt($spec[1]);
210 $args[$opt] = $this[$opt];
216 * Retreive the default argument of an option
220 private function optDefaultArg($o) {
222 if (isset($this->spec
[$o][4])) {
223 return $this->spec
[$o][4];
229 * Retrieve the help message of an option
233 private function optHelp($o) {
235 if (isset($this->spec
[$o][2])) {
236 return $this->spec
[$o][2];
242 * Retrieve option's flags
246 private function optFlags($o) {
248 if (isset($this->spec
[$o])) {
249 return $this->spec
[$o][3];
255 * Check whether an option is flagged for halting argument processing
259 private function optHalts($o) {
260 return $this->optFlags($o) & self
::HALT
;
264 * Check whether an option needs an argument
268 private function optRequiresArg($o) {
269 return $this->optFlags($o) & self
::REQARG
;
273 * Check wether an option accepts any argument
277 private function optAcceptsArg($o) {
278 return $this->optFlags($o) & 0xf00;
282 * Check whether an option can be used more than once
286 private function optIsMulti($o) {
287 return $this->optFlags($o) & self
::MULTI
;
291 * Retreive the long name of an option
295 private function optLongName($o) {
297 return is_numeric($this->spec
[$o][0]) ?
$this->spec
[$o][0] : $this->spec
[$o][1];
301 * Retreive the short name of an option
305 private function optShortName($o) {
307 return is_numeric($this->spec
[$o][0]) ?
null : $this->spec
[$o][0];
311 * Retreive the canonical name (--long-name) of an option
315 private function opt($o) {
316 if (is_numeric($o)) {
320 if (strlen($o) > 1) {
329 * Implements ArrayAccess and virtual properties
331 function offsetExists($o) {
333 return isset($this->args
[$o]);
335 function __isset($o) {
336 return $this->offsetExists($o);
338 function offsetGet($o) {
340 if (isset($this->args
[$o])) {
341 return $this->args
[$o];
343 return $this->optDefaultArg($o);
346 return $this->offsetGet($o);
348 function offsetSet($o, $v) {
349 $osn = $this->optShortName($o);
350 $oln = $this->optLongName($o);
351 if ($this->optIsMulti($o)) {
353 $this->args
["-$osn"][] = $v;
355 $this->args
["--$oln"][] = $v;
358 $this->args
["-$osn"] = $v;
360 $this->args
["--$oln"] = $v;
363 function __set($o, $v) {
364 $this->offsetSet($o, $v);
366 function offsetUnset($o) {
367 unset($this->args
["-".$this->optShortName($o)]);
368 unset($this->args
["--".$this->optLongName($o)]);
370 function __unset($o) {
371 $this->offsetUnset($o);