├── .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 | --------------------------------------------------------------------------------