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;
95 public function getSpec() {
103 public function getCompiledSpec() {
108 * Parse command line arguments according to the compiled spec.
110 * The Generator yields any parsing errors.
111 * Parsing will stop when all arguments are processed or the first option
112 * flagged CliArgs::HALT was encountered.
118 public function parse($argc, array $argv) {
119 for ($i = 0; $i < $argc; ++
$i) {
122 if ($o{0} === '-' && strlen($o) > 2 && $o{1} !== '-') {
123 // multiple short opts, .e.g -vps
124 $argc +
= strlen($o) - 2;
125 array_splice($argv, $i, 1, array_map(function($s) {
127 }, str_split(substr($o, 1))));
131 if (!isset($this->spec
[$o])) {
132 yield
sprintf("Unknown option %s", $o);
133 } elseif (!$this->optAcceptsArg($o)) {
135 } elseif ($i+
1 < $argc && !isset($this->spec
[$argv[$i+
1]])) {
136 $this[$o] = $argv[++
$i];
137 } elseif ($this->optRequiresArg($o)) {
138 yield
sprintf("Option --%s requires an argument", $this->optLongName($o));
141 $this[$o] = $this->optDefaultArg($o);
144 if ($this->optHalts($o)) {
151 * Validate that all required options were given.
153 * The Generator yields any validation errors.
157 public function validate() {
158 $required = array_filter($this->orig
, function($spec) {
159 return $spec[3] & self
::REQUIRED
;
161 foreach ($required as $req) {
162 if (!isset($this[$req[0]])) {
163 yield
sprintf("Option --%s is required", $req[1]);
169 * Retreive the default argument of an option
173 private function optDefaultArg($o) {
175 if (isset($this->spec
[$o][4])) {
176 return $this->spec
[$o][4];
182 * Retrieve the help message of an option
186 private function optHelp($o) {
188 if (isset($this->spec
[$o][2])) {
189 return $this->spec
[$o][2];
195 * Retrieve option's flags
199 private function optFlags($o) {
201 if (isset($this->spec
[$o])) {
202 return $this->spec
[$o][3];
208 * Check whether an option is flagged for halting argument processing
212 private function optHalts($o) {
213 return $this->optFlags($o) & self
::HALT
;
217 * Check whether an option needs an argument
221 private function optRequiresArg($o) {
222 return $this->optFlags($o) & self
::REQARG
;
226 * Check wether an option accepts any argument
230 private function optAcceptsArg($o) {
231 return $this->optFlags($o) & 0xf00;
235 * Check whether an option can be used more than once
239 private function optIsMulti($o) {
240 return $this->optFlags($o) & self
::MULTI
;
244 * Retreive the long name of an option
248 private function optLongName($o) {
250 return $this->spec
[$o][1];
254 * Retreive the short name of an option
258 private function optShortName($o) {
260 return $this->spec
[$o][0];
264 * Retreive the canonical name (--long-name) of an option
268 private function opt($o) {
270 if (strlen($o) > 1) {
279 * Implements ArrayAccess and virtual properties
281 function offsetExists($o) {
283 return isset($this->args
[$o]);
285 function __isset($o) {
286 return $this->offsetExists($o);
288 function offsetGet($o) {
290 if (isset($this->args
[$o])) {
291 return $this->args
[$o];
293 return $this->optDefaultArg($o);
296 return $this->offsetGet($o);
298 function offsetSet($o, $v) {
299 if ($this->optIsMulti($o)) {
300 $this->args
["-".$this->optShortName($o)][] = $v;
301 $this->args
["--".$this->optLongName($o)][] = $v;
303 $this->args
["-".$this->optShortName($o)] = $v;
304 $this->args
["--".$this->optLongName($o)] = $v;
307 function __set($o, $v) {
308 $this->offsetSet($o, $v);
310 function offsetUnset($o) {
311 unset($this->args
["-".$this->optShortName($o)]);
312 unset($this->args
["--".$this->optLongName($o)]);
314 function __unset($o) {
315 $this->offsetUnset($o);