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\CliArgs self
84 public function compile($spec) {
85 foreach ($spec as $arg) {
87 $this->spec
["-".$arg[0]] = $arg;
89 $this->spec
["--".$arg[1]] = $arg;
99 public function getSpec() {
107 public function getCompiledSpec() {
112 * Parse command line arguments according to the compiled spec.
114 * The Generator yields any parsing errors.
115 * Parsing will stop when all arguments are processed or the first option
116 * flagged CliArgs::HALT was encountered.
122 public function parse($argc, array $argv) {
123 for ($i = 0; $i < $argc; ++
$i) {
126 if ($o{0} === '-' && strlen($o) > 2 && $o{1} !== '-') {
127 // multiple short opts, .e.g -vps
128 $argc +
= strlen($o) - 2;
129 array_splice($argv, $i, 1, array_map(function($s) {
131 }, str_split(substr($o, 1))));
133 } elseif ($o{0} === '-' && strlen($o) > 2 && $o{1} === '-' && 0 < ($eq = strpos($o, "="))) {
135 array_splice($argv, $i, 1, [
136 substr($o, 0, $eq++
),
142 if (!isset($this->spec
[$o])) {
143 yield
sprintf("Unknown option %s", $o);
144 } elseif (!$this->optAcceptsArg($o)) {
146 } elseif ($i+
1 < $argc && !isset($this->spec
[$argv[$i+
1]])) {
147 $this[$o] = $argv[++
$i];
148 } elseif ($this->optRequiresArg($o)) {
149 yield
sprintf("Option --%s requires an argument", $this->optLongName($o));
152 $this[$o] = $this->optDefaultArg($o);
155 if ($this->optHalts($o)) {
162 * Validate that all required options were given.
164 * The Generator yields any validation errors.
168 public function validate() {
169 $required = array_filter($this->orig
, function($spec) {
170 return $spec[3] & self
::REQUIRED
;
172 foreach ($required as $req) {
173 if (!strlen($this[$req[0]])) {
174 yield
sprintf("Option --%s is required", $req[1]);
180 * Retreive the default argument of an option
184 private function optDefaultArg($o) {
186 if (isset($this->spec
[$o][4])) {
187 return $this->spec
[$o][4];
193 * Retrieve the help message of an option
197 private function optHelp($o) {
199 if (isset($this->spec
[$o][2])) {
200 return $this->spec
[$o][2];
206 * Retrieve option's flags
210 private function optFlags($o) {
212 if (isset($this->spec
[$o])) {
213 return $this->spec
[$o][3];
219 * Check whether an option is flagged for halting argument processing
223 private function optHalts($o) {
224 return $this->optFlags($o) & self
::HALT
;
228 * Check whether an option needs an argument
232 private function optRequiresArg($o) {
233 return $this->optFlags($o) & self
::REQARG
;
237 * Check wether an option accepts any argument
241 private function optAcceptsArg($o) {
242 return $this->optFlags($o) & 0xf00;
246 * Check whether an option can be used more than once
250 private function optIsMulti($o) {
251 return $this->optFlags($o) & self
::MULTI
;
255 * Retreive the long name of an option
259 private function optLongName($o) {
261 return $this->spec
[$o][1];
265 * Retreive the short name of an option
269 private function optShortName($o) {
271 return $this->spec
[$o][0];
275 * Retreive the canonical name (--long-name) of an option
279 private function opt($o) {
281 if (strlen($o) > 1) {
290 * Implements ArrayAccess and virtual properties
292 function offsetExists($o) {
294 return isset($this->args
[$o]);
296 function __isset($o) {
297 return $this->offsetExists($o);
299 function offsetGet($o) {
301 if (isset($this->args
[$o])) {
302 return $this->args
[$o];
304 return $this->optDefaultArg($o);
307 return $this->offsetGet($o);
309 function offsetSet($o, $v) {
310 $osn = $this->optShortName($o);
311 $oln = $this->optLongName($o);
312 if ($this->optIsMulti($o)) {
314 $this->args
["-$osn"][] = $v;
316 $this->args
["--$oln"][] = $v;
319 $this->args
["-$osn"] = $v;
321 $this->args
["--$oln"] = $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);