6 * Command line arguments
8 class CliArgs
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
72 public function __construct(array $spec = null) {
73 $this->compile($spec);
77 * Compile the original spec
79 * @return pharext\CliArgs self
81 public function compile(array $spec = null) {
84 foreach ((array) $spec as $arg) {
85 $this->spec
["-".$arg[0]] = $arg;
86 $this->spec
["--".$arg[1]] = $arg;
92 * Parse command line arguments according to the compiled spec.
94 * The Generator yields any parsing errors.
95 * Parsing will stop when all arguments are processed or the first option
96 * flagged CliArgs::HALT was encountered.
102 public function parse($argc, array $argv) {
103 for ($i = 0; $i < $argc; ++
$i) {
106 if ($o{0} === '-' && strlen($o) > 1 && $o{1} !== '-') {
107 // multiple short opts, .e.g -vps
108 $argc +
= strlen($o) - 2;
109 array_splice($argv, $i, 1, array_map(function($s) {
111 }, str_split(substr($o, 1))));
115 if (!isset($this->spec
[$o])) {
116 yield
sprintf("Unknown option %s", $argv[$i]);
117 } elseif (!$this->optAcceptsArg($o)) {
119 } elseif ($i+
1 < $argc && !isset($this->spec
[$argv[$i+
1]])) {
120 $this[$o] = $argv[++
$i];
121 } elseif ($this->optNeedsArg($o)) {
122 yield
sprintf("Option --%s needs an argument", $this->optLongName($o));
125 $this[$o] = $this->optDefaultArg($o);
128 if ($this->optHalts($o)) {
135 * Validate that all required options were given.
137 * The Generator yields any validation errors.
141 public function validate() {
142 $required = array_filter($this->orig
, function($spec) {
143 return $spec[3] & self
::REQUIRED
;
145 foreach ($required as $req) {
146 if (!isset($this[$req[0]])) {
147 yield
sprintf("Option --%s is required", $req[1]);
153 * Output command line help message
154 * @param string $prog
156 public function help($prog) {
157 printf("\nUsage:\n\n $ %s", $prog);
161 foreach ($this->orig
as $spec) {
162 if ($spec[3] & self
::REQARG
) {
163 if ($spec[3] & self
::REQUIRED
) {
174 printf(" [-%s]", implode("|-", array_column($flags, 0)));
176 foreach ($required as $req) {
177 printf(" -%s <arg>", $req[0]);
180 printf(" [-%s <arg>]", implode("|-", array_column($optional, 0)));
183 foreach ($this->orig
as $spec) {
184 printf(" -%s|--%s %s", $spec[0], $spec[1], ($spec[3] & self
::REQARG
) ?
"<arg> " : (($spec[3] & self
::OPTARG
) ?
"[<arg>]" : " "));
185 printf("%s%s %s", str_repeat(" ", 16-strlen($spec[1])), $spec[2], ($spec[3] & self
::REQUIRED
) ?
"(REQUIRED)" : "");
186 if (isset($spec[4])) {
187 printf(" [%s]", $spec[4]);
195 * Retreive the default argument of an option
199 private function optDefaultArg($o) {
201 if (isset($this->spec
[$o][4])) {
202 return $this->spec
[$o][4];
208 * Retrieve the help message of an option
212 private function optHelp($o) {
214 if (isset($this->spec
[$o][2])) {
215 return $this->spec
[$o][2];
221 * Check whether an option is flagged for halting argument processing
225 private function optHalts($o) {
227 return $this->spec
[$o][3] & self
::HALT
;
231 * Check whether an option needs an argument
235 private function optNeedsArg($o) {
237 return $this->spec
[$o][3] & self
::REQARG
;
241 * Check wether an option accepts any argument
245 private function optAcceptsArg($o) {
247 return $this->spec
[$o][3] & 0xf00;
251 * Check whether an option can be used more than once
255 private function optIsMulti($o) {
257 return $this->spec
[$o][3] & self
::MULTI
;
261 * Retreive the long name of an option
265 private function optLongName($o) {
267 return $this->spec
[$o][1];
271 * Retreive the short name of an option
275 private function optShortName($o) {
277 return $this->spec
[$o][0];
281 * Retreive the canonical name (--long-name) of an option
285 private function opt($o) {
287 if (strlen($o) > 1) {
296 * Implements ArrayAccess and virtual properties
298 function offsetExists($o) {
300 return isset($this->args
[$o]);
302 function __isset($o) {
303 return $this->offsetExists($o);
305 function offsetGet($o) {
307 if (isset($this->args
[$o])) {
308 return $this->args
[$o];
310 return $this->optDefaultArg($o);
313 return $this->offsetGet($o);
315 function offsetSet($o, $v) {
316 if ($this->optIsMulti($o)) {
317 $this->args
["-".$this->optShortName($o)][] = $v;
318 $this->args
["--".$this->optLongName($o)][] = $v;
320 $this->args
["-".$this->optShortName($o)] = $v;
321 $this->args
["--".$this->optLongName($o)] = $v;
324 function __set($o, $v) {
325 $this->offsetSet($o, $v);
327 function offsetUnset($o) {
328 unset($this->args
["-".$this->optShortName($o)]);
329 unset($this->args
["--".$this->optLongName($o)]);
331 function __unset($o) {
332 $this->offsetUnset($o);