├── .gitignore
├── examples
├── copy
├── echo
├── echo.php
└── copy.php
├── composer.json
├── LICENSE
├── README.markdown
├── tests
└── OptionParser.php
└── lib
└── OptionParser.php
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/examples/copy:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | addHead("Echo the parameters of each flag as determined by OptionParser.\n");
7 | $parser->addHead("Usage: " . basename($argv[0]) . " [ options ]\n");
8 | $parser->addRule('a', "A short flag with no parameter");
9 | $parser->addRule('b:', "A short flag with an optional parameter");
10 | $parser->addRule('c::', "A short flag with a required parameter");
11 | $parser->addRule('long-a', "A long flag with no parameter");
12 | $parser->addRule('long-b:', "A long flag with an optional parameter");
13 | $parser->addRule('long-c::', "A long flag with a required parameter");
14 |
15 | $args = $argv;
16 |
17 | try {
18 | $parser->parse($args);
19 | } catch (Exception $e) {
20 | die($parser->getUsage());
21 | }
22 |
23 | $flagNames = explode(' ', 'a b c long-a long-b long-c');
24 |
25 | echo "Parsed arguments:\n";
26 | foreach ($flagNames as $flag) {
27 | $param = var_export($parser->getOption($flag), true);
28 | echo sprintf(' %6s %s', $flag, $param) . "\n";
29 | }
30 |
31 | echo "\nRemaining arguments: " . implode(' ', $args) . "\n";
32 |
--------------------------------------------------------------------------------
/examples/copy.php:
--------------------------------------------------------------------------------
1 | addHead("Copies the contents of one file (or stream) to another.\n");
14 | $parser->addHead("Usage: " . basename($argv[0]) . " -i [ options ]\n");
15 | $parser->addRule('d|debug', 'enable_debugging', "Enable debug mode");
16 | $parser->addRule('i|input::', 'The input file to read, use "-" for stdin');
17 | $parser->addRule('o|output:', '(optional) The output file to write to, defaults to stdout');
18 | $parser->addRule('help', 'Display a help message and exit');
19 | $parser->addTail("\nThis program brought to you by me!\n");
20 |
21 | try {
22 | $parser->parse();
23 | } catch (Exception $e) {
24 | die($parser->getUsage());
25 | }
26 |
27 | if ($parser->help) {
28 | die($parser->getUsage());
29 | }
30 | if (!isset($parser->input)) {
31 | die("You must specify an input file!\n");
32 | }
33 |
34 | if ($parser->input == '-') {
35 | $input = 'php://stdin';
36 | } else {
37 | $input = $parser->input;
38 | }
39 |
40 | $in = @fopen($input, 'rb');
41 | if ($in === false) {
42 | die("Unable to open input stream: $input\n");
43 | }
44 |
45 | if (isset($parser->output)) {
46 | if ($parser->output === true) {
47 | $output = 'php://stdout';
48 | } else {
49 | $output = $parser->output;
50 | }
51 | } else {
52 | $output = 'php://stdout';
53 | }
54 |
55 | $out = @fopen($output, 'w');
56 | if ($out === false) {
57 | die("Unable to open output stream: $output\n");
58 | }
59 |
60 | while (($data = fread($in, 512)) !== '') {
61 | fwrite($out, $data);
62 | }
63 |
64 | fclose($in);
65 | fclose($out);
66 |
67 | if ($debug) {
68 | // let the user know we're done
69 | echo "Finished!\n";
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | ### Overview
2 |
3 | OptionParser is a parser for command-line options for PHP. It supports both short and long options, optional and/or required parameter checking, automatic callback execution, and pretty printing of usage messages.
4 |
5 | ### Usage
6 |
7 | First create a parser object and then use it to parse your arguments. Examples explain it best:
8 |
9 | ```php
10 | $parser = new OptionParser;
11 | // add a rule that looks for the short "a" flag
12 | $parser->addRule('a');
13 | // add a rule that looks for the long "long-option" flag
14 | $parser->addRule('long-option');
15 | // add a rule that looks for the short "b" flag or long "big" flag
16 | $parser->addRule('b|big');
17 | // to indicate an optional parameter, use a colon after the flag name
18 | $parser->addRule('c:');
19 | // likewise, to indicate a required parameter use two colons
20 | $parser->addRule('d|debug::');
21 | // add a description for a rule that can be used later in a usage message
22 | $parser->addRule('e', 'The description for flag e');
23 | // or use a user-defined callback function that will be called when that
24 | // flag is used. the function will be passed the parameter that was given
25 | // to the flag, or true if the flag is optional and no parameter was used
26 | $parser->addRule('error-reporting', 'set_error_reporting');
27 | ```
28 |
29 | Next, parse your arguments. This function can be called multiple times with the same parser to parse multiple sets of arguments if desired. Note: This function will throw an exception if the user has specified invalid flags. Also, if no arguments are specified here the global `$argv` argument will be used.
30 |
31 | ```php
32 | try {
33 | $parser->parse();
34 | } catch (Exception $e) {
35 | die("Error parsing arguments: " . $e->getMessage());
36 | }
37 | ```
38 |
39 | A more helpful error message might be to show the user the options that she can use to run your program:
40 |
41 | ```php
42 | $parser->addHead("Usage: myprog [ options ]\n");
43 |
44 | try {
45 | $parser->parse();
46 | } catch (Exception $e) {
47 | die($parser->getUsage());
48 | }
49 | ```
50 |
51 | ### Examples
52 |
53 | Scripts in the examples directory may be invoked using the PHP interpreter on the command line, like so:
54 |
55 | $ php echo.php
56 |
57 | A Unix-style executable is also provided for *nix users:
58 |
59 | $ ./echo
60 |
61 | ### Tests
62 |
63 | OptionParser uses the [PHPUnit](http://www.phpunit.de/) unit testing framework to test the code. In order to run the tests, run the following command from the project root directory:
64 |
65 | $ phpunit tests/OptionParser.php
66 |
67 | ### Credits
68 |
69 | OptionParser draws inspiration from several other option parsers including [GNU getopt](http://www.gnu.org/software/libc/manual/html_node/Getopt.html), Ruby's [OptionParser](http://raa.ruby-lang.org/project/optionparser/), and [Zend_Console_Getopt](http://framework.zend.com/manual/en/zend.console.getopt.html).
70 |
71 | ### Requirements
72 |
73 | OptionParser requires PHP version 5 or greater.
74 |
75 | ### License
76 |
77 | OptionParser is released under the terms of the MIT license. Please read the LICENSE file for further information.
78 |
--------------------------------------------------------------------------------
/tests/OptionParser.php:
--------------------------------------------------------------------------------
1 | setExpectedException('Exception');
15 |
16 | $args = array('-', '-c');
17 | $op->parse($args);
18 | }
19 |
20 | public function testArguments()
21 | {
22 | $op = new OptionParser;
23 | $op->addRule('a:');
24 |
25 | $args = array('progname', 'word', '-a', 'a string');
26 | $op->parse($args);
27 |
28 | $this->assertEquals($op->getProgramName(), 'progname');
29 | $this->assertEquals($op->a, 'a string');
30 | $this->assertEquals($args, array('word'));
31 |
32 | $op->setConfig(OptionParser::CONF_DASHDASH);
33 |
34 | $args = array('progname', '-a', '--', '-a', 'word');
35 | $op->parse($args);
36 |
37 | $this->assertTrue($op->a === true);
38 | $this->assertEquals($args, array('--', '-a', 'word'));
39 | }
40 |
41 | public function testShortOption()
42 | {
43 | $op = new OptionParser;
44 | $op->addRule('a');
45 | $op->addRule('b');
46 |
47 | $this->assertFalse(isset($op->a));
48 | $this->assertFalse(isset($op->b));
49 |
50 | $args = array("-", "-a", "-b");
51 | $op->parse($args);
52 |
53 | $this->assertTrue(isset($op->a));
54 | $this->assertTrue(isset($op->b));
55 | $this->assertTrue($op->a);
56 | $this->assertTrue($op->b);
57 | }
58 |
59 | public function testShortOptionCluster()
60 | {
61 | $op = new OptionParser;
62 | $op->addRule('a');
63 | $op->addRule('b:');
64 | $op->addRule('c::');
65 |
66 | $this->assertFalse(isset($op->a));
67 | $this->assertFalse(isset($op->b));
68 |
69 | $args = array("-", "-ab");
70 | $op->parse($args);
71 |
72 | $this->assertTrue(isset($op->a));
73 | $this->assertTrue(isset($op->b));
74 | $this->assertFalse(isset($op->c));
75 | $this->assertTrue($op->a);
76 | $this->assertTrue($op->b);
77 | $this->assertFalse($op->c);
78 |
79 | $args = array('-', '-abc', 'bvalue', 'cvalue');
80 | $op->parse($args);
81 |
82 | $this->assertTrue($op->a);
83 | $this->assertEquals($op->b, 'bvalue');
84 | $this->assertEquals($op->c, 'cvalue');
85 | }
86 |
87 | public function testShortOptionWithParameter()
88 | {
89 | $op = new OptionParser;
90 | $op->addRule('a|b:');
91 | $op->addRule('c::');
92 |
93 | $this->assertTrue($op->isOptional('a'));
94 | $this->assertTrue($op->isOptional('b'));
95 | $this->assertTrue($op->isRequired('c'));
96 |
97 | $args = array("-", "-a", "1", "-c", "string");
98 | $op->parse($args);
99 |
100 | $this->assertEquals($op->a, 1);
101 | $this->assertEquals($op->b, 1);
102 | $this->assertEquals($op->c, 'string');
103 |
104 | $this->setExpectedException('Exception');
105 |
106 | $args = array('-', '-c');
107 | $op->parse($args);
108 | }
109 |
110 | public function testLongOption()
111 | {
112 | $op = new OptionParser;
113 | $op->addRule('verbose');
114 | $op->addRule('quiet');
115 |
116 | $args = array("-", "--verbose");
117 | $op->parse($args);
118 |
119 | $this->assertTrue($op->verbose);
120 | $this->assertFalse($op->quiet);
121 | }
122 |
123 | public function testLongOptionWithParameter()
124 | {
125 | $op = new OptionParser;
126 | $op->addRule('verbose');
127 | $op->addRule('dir-name::');
128 | $op->addRule('long-req::');
129 | $op->addRule('long-opt:');
130 |
131 | $args = array("-", "--verbose=yes", '--dir-name', 'lib/test/dir', '--long-req=hi there', '--long-opt=-a');
132 | $op->parse($args);
133 |
134 | $this->assertEquals($op->verbose, 'yes');
135 | $this->assertEquals($op->getOption('dir-name'), 'lib/test/dir');
136 | $this->assertEquals($op->getOption('long-req'), 'hi there');
137 | $this->assertEquals($op->getOption('long-opt'), '-a');
138 |
139 | $this->setExpectedException('Exception');
140 |
141 | $args = array('-', '--dir-name');
142 | $op->parse($args);
143 | }
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/lib/OptionParser.php:
--------------------------------------------------------------------------------
1 |
10 | */
11 | class OptionParser
12 | {
13 |
14 | /**
15 | * The current version of OptionParser.
16 | *
17 | * @var string
18 | */
19 | const VERSION = '0.4';
20 |
21 | /**#@+
22 | * Configuration constant.
23 | *
24 | * @var int
25 | */
26 | /**
27 | * Use to stop parsing arguments when a double hyphen (--) is found.
28 | */
29 | const CONF_DASHDASH = 1;
30 |
31 | /**
32 | * Use to ignore case when parsing flags.
33 | */
34 | const CONF_IGNORECASE = 2;
35 |
36 | /**
37 | * All configuration options.
38 | */
39 | const CONF_ALL = 3;
40 | /**#@-*/
41 |
42 | /**#@+
43 | * Parameter constant.
44 | *
45 | * @var int
46 | */
47 | /**
48 | * Used for a parameter that is not required.
49 | */
50 | const PARAM_NOTREQUIRED = 0;
51 |
52 | /**
53 | * Used for a parameter that is required.
54 | */
55 | const PARAM_REQUIRED = 1;
56 |
57 | /**
58 | * Used for a parameter that is optional.
59 | */
60 | const PARAM_OPTIONAL = 2;
61 | /**#@-*/
62 |
63 | /**
64 | * The configuration flag.
65 | *
66 | * @var int
67 | */
68 | protected $_config = 0;
69 |
70 | /**
71 | * The name of the program, as parsed from the arguments.
72 | *
73 | * @var string
74 | */
75 | protected $_programName;
76 |
77 | /**
78 | * Contains usage messages that should go at the beginning of the usage message.
79 | *
80 | * @var array
81 | */
82 | protected $_head = array();
83 |
84 | /**
85 | * Contains usage messages that should go at the end of the usage message.
86 | *
87 | * @var array
88 | */
89 | protected $_tail = array();
90 |
91 | /**
92 | * The parsed options. This is only available after {@link parse()} is called.
93 | *
94 | * @var array
95 | */
96 | protected $_options = array();
97 |
98 | /**
99 | * The rules for this parser. Each rule is an array with three keys: description,
100 | * callback, and required.
101 | *
102 | * @var array
103 | */
104 | protected $_rules = array();
105 |
106 | /**
107 | * A map of flags to the index in the {@link $_rules} array that contains the
108 | * corresponding rule for that flag.
109 | *
110 | * @var array
111 | */
112 | protected $_flags = array();
113 |
114 | /**
115 | * Constructor.
116 | *
117 | * @param int $config An optional configuration flag
118 | */
119 | public function __construct($config=null)
120 | {
121 | if (is_int($config)) {
122 | $this->setConfig($config);
123 | }
124 | }
125 |
126 | /**
127 | * Alias for {@link setOption()}.
128 | *
129 | * @return void
130 | */
131 | public function __set($flag, $value)
132 | {
133 | $this->setOption($flag, $value);
134 | }
135 |
136 | /**
137 | * Alias for {@link getOption()}.
138 | *
139 | * @return mixed
140 | */
141 | public function __get($flag)
142 | {
143 | return $this->getOption($flag);
144 | }
145 |
146 | /**
147 | * Returns true if an option for the given flag is set.
148 | *
149 | * @return bool
150 | */
151 | public function __isset($flag)
152 | {
153 | try {
154 | $this->checkFlag($flag);
155 | } catch (Exception $e) {
156 | return false;
157 | }
158 |
159 | $ruleIndex = $this->_flags[$flag];
160 |
161 | return isset($this->_options[$ruleIndex]);
162 | }
163 |
164 | /**
165 | * Unsets the given flag in this parser. Options for this flag
166 | * will no longer be available.
167 | *
168 | * @return bool
169 | */
170 | public function __unset($flag)
171 | {
172 | unset($this->_flags[$flag]);
173 | }
174 |
175 | /**
176 | * Sets the configuration flag.
177 | *
178 | * @param int
179 | */
180 | public function setConfig($config)
181 | {
182 | $this->_config = $config;
183 | }
184 |
185 | /**
186 | * Gets the configuration flag.
187 | *
188 | * @return int
189 | */
190 | public function getConfig()
191 | {
192 | return $this->_config;
193 | }
194 |
195 | /**
196 | * Gets the program name that was used to execute this script.
197 | *
198 | * @return string
199 | */
200 | public function getProgramName()
201 | {
202 | return $this->_programName;
203 | }
204 |
205 | /**
206 | * Sets the $value of the option for the given $flag.
207 | *
208 | * @param string $flag The name of the flag
209 | * @param mixed $value The new value for the option
210 | * @return void
211 | */
212 | public function setOption($flag, $value)
213 | {
214 | $this->checkFlag($flag);
215 | $ruleIndex = $this->_flags[$flag];
216 | $this->_options[$ruleIndex] = $value;
217 | }
218 |
219 | /**
220 | * Gets the value of the option for the given $flag.
221 | *
222 | * @param string $flag The name of the flag
223 | * @return mixed
224 | */
225 | public function getOption($flag)
226 | {
227 | $this->checkFlag($flag);
228 | $ruleIndex = $this->_flags[$flag];
229 |
230 | if (array_key_exists($ruleIndex, $this->_options)) {
231 | return $this->_options[$ruleIndex];
232 | } else {
233 | return false;
234 | }
235 | }
236 |
237 | /**
238 | * Gets the values of all options as an associative array.
239 | *
240 | * @return Array
241 | */
242 | public function getAllOptions() {
243 | $options = array();
244 |
245 | foreach($this->_flags as $option => $index) {
246 | if(array_key_exists($index, $this->_options)) {
247 | $options[$option] = $this->_options[$index];
248 | }
249 | }
250 |
251 | return $options;
252 | }
253 |
254 | /**
255 | * Add a usage message that will be displayed before the list of flags in the
256 | * output of {@link getUsage()}.
257 | *
258 | * @param string $message The usage message
259 | * @return void
260 | */
261 | public function addHead($message)
262 | {
263 | $this->_head[] = $message;
264 | }
265 |
266 | /**
267 | * Add a usage message that will be displayed after the list of flags in the
268 | * output of {@link getUsage()}.
269 | *
270 | * @param string $message The usage message
271 | * @return void
272 | */
273 | public function addTail($message)
274 | {
275 | $this->_tail[] = $message;
276 | }
277 |
278 | /**
279 | * Gets a usage message for this parser. The optional $maxWidth parameter can be
280 | * used to specify with width at which the message should wrap.
281 | *
282 | * @param int
283 | * @return string
284 | */
285 | public function getUsage($maxWidth=80)
286 | {
287 | $head = wordwrap(implode('', $this->_head), $maxWidth);
288 | $tail = wordwrap(implode('', $this->_tail), $maxWidth);
289 |
290 | # determine if there are any short flags
291 | $hasShortFlag = false;
292 | $allFlags = array_keys($this->_flags);
293 | foreach ($allFlags as $flag) {
294 | if (strlen($flag) == 1) {
295 | $hasShortFlag = true;
296 | break;
297 | }
298 | }
299 |
300 | $maxFlagsWidth = 0;
301 | $flagDescriptions = array();
302 | foreach ($this->_rules as $ruleIndex => $rule) {
303 | $flagList = array_keys($this->_flags, $ruleIndex);
304 | if (empty($flagList)) {
305 | continue;
306 | }
307 | usort($flagList, array($this, 'compareFlags'));
308 | $flags = array();
309 | $short = false;
310 | foreach ($flagList as $flag) {
311 | if (strlen($flag) == 1) {
312 | $flags[] = "-$flag";
313 | $short = true;
314 | } else {
315 | $flags[] = "--$flag";
316 | }
317 | }
318 | $flags = implode(', ', $flags);
319 | if ($hasShortFlag && !$short) {
320 | $flags = " $flags";
321 | }
322 | $maxFlagsWidth = max(strlen($flags), $maxFlagsWidth);
323 | $flagDescriptions[$flags] = $rule['description'];
324 | }
325 |
326 | $options = array();
327 | $format = " %-{$maxFlagsWidth}s ";
328 | foreach ($flagDescriptions as $flags => $description) {
329 | $wrap = wordwrap($description, $maxWidth - ($maxFlagsWidth + 3));
330 | $lines = explode("\n", $wrap);
331 | $option = array(sprintf($format, $flags) . array_shift($lines));
332 | foreach ($lines as $line) {
333 | $option[] = str_repeat(' ', $maxFlagsWidth + 3) . $line;
334 | }
335 | $options[] = implode("\n", $option);
336 | }
337 |
338 | $usage = $head . "\n" . implode("\n", $options) . "\n" . $tail;
339 |
340 | return $usage;
341 | }
342 |
343 | /**
344 | * Adds a rule to this option parser. A rule consists of some flags that have
345 | * an optional description and/or callback function associated with them. The first
346 | * argument to this function should be the rule flags in a string seperated by a
347 | * pipe (|) character. The string may end in a colon (:) to indicate that the flag
348 | * accepts an optional parameter, or two colons (::) for a required parameter.
349 | * Remaining arguments may be a description, a callback function, both, or neither.
350 | *
351 | * If a description is given it will be used in the usage message for this parser
352 | * when describing this rule. If a callback function is given, it will be called when
353 | * any of the flags for this option are encountered in the arguments. Some examples
354 | * follow:
355 | *
356 | *
357 | * $parser = new OptionParser;
358 | * $parser->addRule('a');
359 | * $parser->addRule('long-option');
360 | * $parser->addRule('b::'); // "b" with required parameter
361 | * $parser->addRule('q|quiet', "Don't output to the console"); // "q" or "quiet" with description
362 | * $parser->addRule('c', 'my_callback'); // my_callback will be called if the "c" flag is used
363 | * $parser->addRule('o|output:'); // "o" or "output" with optional parameter
364 | *
365 | *
366 | * @param string $flags The rule flags
367 | * @param mixed ... The rule description or callback
368 | * @return void
369 | */
370 | public function addRule()
371 | {
372 | $args = func_get_args();
373 | $rule = array(
374 | 'description' => '',
375 | 'callback' => null,
376 | 'required' => self::PARAM_NOTREQUIRED
377 | );
378 |
379 | $flags = array_shift($args);
380 |
381 | if ($this->_config & self::CONF_IGNORECASE) {
382 | $flags = strtolower($flags);
383 | }
384 |
385 | if (preg_match('/::?$/', $flags, $match)) {
386 | if (strlen($match[0]) == 2) {
387 | $rule['required'] = self::PARAM_REQUIRED;
388 | } else {
389 | $rule['required'] = self::PARAM_OPTIONAL;
390 | }
391 | $flags = rtrim($flags, ':');
392 | }
393 |
394 | # consume the remaining arguments
395 | while (count($args)) {
396 | $arg = array_pop($args);
397 | if (is_callable($arg)) {
398 | $rule['callback'] = $arg;
399 | } elseif (is_string($arg)) {
400 | $rule['description'] = $arg;
401 | }
402 | }
403 |
404 | $ruleIndex = count($this->_rules);
405 |
406 | foreach (explode('|', $flags) as $flag) {
407 | if ($flag) {
408 | $this->_flags[$flag] = $ruleIndex;
409 | }
410 | }
411 |
412 | $this->_rules[] = $rule;
413 | }
414 |
415 | /**
416 | * Gets the rule associated with the given flag.
417 | *
418 | * @return array
419 | */
420 | public function getRule($flag)
421 | {
422 | try {
423 | $this->checkFlag($flag);
424 | } catch (Exception $e) {
425 | return null;
426 | }
427 |
428 | $ruleIndex = $this->_flags[$flag];
429 |
430 | return $this->_rules[$ruleIndex];
431 | }
432 |
433 | /**
434 | * Gets all rules of this parser.
435 | *
436 | * @return array
437 | */
438 | public function getRules()
439 | {
440 | return $this->_rules;
441 | }
442 |
443 | /**
444 | * Returns true if the given flag expects a parameter.
445 | *
446 | * @return bool
447 | */
448 | public function expectsParameter($flag)
449 | {
450 | $rule = $this->getRule($flag);
451 | return $rule && $rule['required'] !== self::PARAM_NOTREQUIRED;
452 | }
453 |
454 | /**
455 | * Returns true if the given flag is required.
456 | *
457 | * @return bool
458 | */
459 | public function isRequired($flag)
460 | {
461 | $rule = $this->getRule($flag);
462 | return $rule && $rule['required'] === self::PARAM_REQUIRED;
463 | }
464 |
465 | /**
466 | * Returns true if the given flag is optional.
467 | *
468 | * @return bool
469 | */
470 | public function isOptional($flag)
471 | {
472 | $rule = $this->getRule($flag);
473 | return $rule && $rule['required'] === self::PARAM_OPTIONAL;
474 | }
475 |
476 | /**
477 | * Parses the given arguments according to this parser's rules. An exception will be
478 | * thrown if any rule is violated.
479 | *
480 | * Note: This function uses a reference to modify the given $argv array. If no argument
481 | * values are provided a copy of the global $argv array will be used.
482 | *
483 | * @param array $argv The command line arguments
484 | * @return array Anything still present in $argv that wasn't matched by a rule
485 | * @throws Exception
486 | */
487 | public function parse(array &$argv=null)
488 | {
489 | $this->_options = array();
490 |
491 | if ($argv === null) {
492 | if (isset($_SERVER['argv'])) {
493 | $argv = $_SERVER['argv'];
494 | } else {
495 | $argv = array();
496 | }
497 | }
498 |
499 | $this->_programName = array_shift($argv);
500 |
501 | for ($i = 0; $i < count($argv); $i++) {
502 | if (preg_match('/^(--?)([a-z][a-z\-]*)(?:=(.+)?)?$/i', $argv[$i], $matches)) {
503 | if (isset($matches[3])) {
504 | # put parameter back on stack of arguments
505 | array_splice($argv, $i, 1, $matches[3]);
506 | $paramGiven = true;
507 | } else {
508 | # throw away the flag
509 | array_splice($argv, $i, 1);
510 | $paramGiven = false;
511 | }
512 |
513 | $flag = $matches[2];
514 | if ($this->_config & self::CONF_IGNORECASE) {
515 | $flag = strtolower($flag);
516 | }
517 |
518 | if ($matches[1] == '--') {
519 | # long flag
520 | $this->parseOption($flag, $argv, $i, $paramGiven);
521 | } else {
522 | # short flag
523 | foreach (str_split($flag) as $shortFlag) {
524 | $this->parseOption($shortFlag, $argv, $i, $paramGiven);
525 | }
526 | }
527 |
528 | # decrement the index for the flag that was taken
529 | $i--;
530 | } elseif ($argv[$i] == '--') {
531 | if ($this->_config & self::CONF_DASHDASH) {
532 | # stop processing arguments
533 | break;
534 | }
535 | }
536 | }
537 |
538 | return $argv;
539 | }
540 |
541 | /**
542 | * Extracts the option value for the given $flag from the arguments array.
543 | *
544 | * @param string $flag The flag being parsed
545 | * @param array $argv The argument values
546 | * @param int $i The current index in the arguments array
547 | * @param bool $paramGiven True if a parameter was given using the --flag=param syntax
548 | * @return void
549 | */
550 | protected function parseOption($flag, array &$argv, $i, $paramGiven)
551 | {
552 | $this->checkFlag($flag);
553 |
554 | $ruleIndex = $this->_flags[$flag];
555 | $rule = $this->_rules[$ruleIndex];
556 |
557 | if ($rule['required'] == self::PARAM_REQUIRED) {
558 | if (isset($argv[$i]) && ($paramGiven || $this->isParam($argv[$i]))) {
559 | $slice = array_splice($argv, $i, 1);
560 | $param = $slice[0];
561 | } else {
562 | throw new Exception("Option \"$flag\" requires a parameter");
563 | }
564 | } elseif ($rule['required'] == self::PARAM_OPTIONAL) {
565 | if (isset($argv[$i]) && ($paramGiven || $this->isParam($argv[$i]))) {
566 | $slice = array_splice($argv, $i, 1);
567 | $param = $slice[0];
568 | } else {
569 | $param = true;
570 | }
571 | } else {
572 | $param = true;
573 | }
574 |
575 | if (is_callable($rule['callback'])) {
576 | call_user_func($rule['callback'], $param);
577 | }
578 |
579 | $this->_options[$ruleIndex] = $param;
580 | }
581 |
582 | /**
583 | * Returns true if the given string is considered a parameter.
584 | *
585 | * @param string
586 | * @return bool
587 | */
588 | protected function isParam($string)
589 | {
590 | if ($this->_config & self::CONF_DASHDASH && $string == '--') {
591 | return false;
592 | }
593 |
594 | return !$this->isFlag($string);
595 | }
596 |
597 | /**
598 | * Returns true if the given string is considered a flag.
599 | *
600 | * @param string
601 | * @return bool
602 | */
603 | protected function isFlag($string)
604 | {
605 | return (bool) preg_match('/^--?[a-z][a-z\-]*$/i', $string);
606 | }
607 |
608 | /**
609 | * Ensures the given flag is able to be recognized by this parser.
610 | *
611 | * @return void
612 | * @throws Exception
613 | */
614 | protected function checkFlag($flag)
615 | {
616 | if (!array_key_exists($flag, $this->_flags)) {
617 | throw new Exception("Option \"$flag\" is not recognized");
618 | }
619 | }
620 |
621 | /**
622 | * Used as a callback function for {@link usort()} when comparing two
623 | * flags. Flags are compared for length. If the two flags being compared are
624 | * of equal length, the flag that appeared first in the specification will
625 | * return first.
626 | *
627 | * @param string
628 | * @param string
629 | * @return int
630 | */
631 | protected function compareFlags($a, $b)
632 | {
633 | $lena = strlen($a);
634 | $lenb = strlen($b);
635 |
636 | if ($lena == $lenb) {
637 | # returning -1 here will keep strings of the same length in the
638 | # same order they originally were in
639 | return -1;
640 | }
641 |
642 | return $lena > $lenb ? 1 : -1;
643 | }
644 |
645 | /**
646 | * Alias for {@link getUsage()}.
647 | *
648 | * @return string
649 | */
650 | public function toString()
651 | {
652 | return $this->getUsage();
653 | }
654 |
655 | /**
656 | * Alias for {@link toString()}.
657 | *
658 | * @return string
659 | */
660 | public function __toString()
661 | {
662 | return $this->toString();
663 | }
664 |
665 | }
666 |
--------------------------------------------------------------------------------