1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4: */ 3/** 4 * PHP Version 5 5 * 6 * Copyright (c) 2001-2015, The PEAR developers 7 * 8 * This source file is subject to the BSD-2-Clause license, 9 * that is bundled with this package in the file LICENSE, and is 10 * available through the world-wide-web at the following url: 11 * http://opensource.org/licenses/bsd-license.php. 12 * 13 * @category Console 14 * @package Console_Getopt 15 * @author Andrei Zmievski <andrei@php.net> 16 * @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause 17 * @version CVS: $Id$ 18 * @link http://pear.php.net/package/Console_Getopt 19 */ 20 21require_once 'PEAR.php'; 22 23/** 24 * Command-line options parsing class. 25 * 26 * @category Console 27 * @package Console_Getopt 28 * @author Andrei Zmievski <andrei@php.net> 29 * @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause 30 * @link http://pear.php.net/package/Console_Getopt 31 */ 32class Console_Getopt 33{ 34 35 /** 36 * Parses the command-line options. 37 * 38 * The first parameter to this function should be the list of command-line 39 * arguments without the leading reference to the running program. 40 * 41 * The second parameter is a string of allowed short options. Each of the 42 * option letters can be followed by a colon ':' to specify that the option 43 * requires an argument, or a double colon '::' to specify that the option 44 * takes an optional argument. 45 * 46 * The third argument is an optional array of allowed long options. The 47 * leading '--' should not be included in the option name. Options that 48 * require an argument should be followed by '=', and options that take an 49 * option argument should be followed by '=='. 50 * 51 * The return value is an array of two elements: the list of parsed 52 * options and the list of non-option command-line arguments. Each entry in 53 * the list of parsed options is a pair of elements - the first one 54 * specifies the option, and the second one specifies the option argument, 55 * if there was one. 56 * 57 * Long and short options can be mixed. 58 * 59 * Most of the semantics of this function are based on GNU getopt_long(). 60 * 61 * @param array $args an array of command-line arguments 62 * @param string $short_options specifies the list of allowed short options 63 * @param array $long_options specifies the list of allowed long options 64 * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option 65 * 66 * @return array two-element array containing the list of parsed options and 67 * the non-option arguments 68 */ 69 public static function getopt2($args, $short_options, $long_options = null, $skip_unknown = false) 70 { 71 return Console_Getopt::doGetopt(2, $args, $short_options, $long_options, $skip_unknown); 72 } 73 74 /** 75 * This function expects $args to start with the script name (POSIX-style). 76 * Preserved for backwards compatibility. 77 * 78 * @param array $args an array of command-line arguments 79 * @param string $short_options specifies the list of allowed short options 80 * @param array $long_options specifies the list of allowed long options 81 * 82 * @see getopt2() 83 * @return array two-element array containing the list of parsed options and 84 * the non-option arguments 85 */ 86 public static function getopt($args, $short_options, $long_options = null, $skip_unknown = false) 87 { 88 return Console_Getopt::doGetopt(1, $args, $short_options, $long_options, $skip_unknown); 89 } 90 91 /** 92 * The actual implementation of the argument parsing code. 93 * 94 * @param int $version Version to use 95 * @param array $args an array of command-line arguments 96 * @param string $short_options specifies the list of allowed short options 97 * @param array $long_options specifies the list of allowed long options 98 * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option 99 * 100 * @return array 101 */ 102 public static function doGetopt($version, $args, $short_options, $long_options = null, $skip_unknown = false) 103 { 104 // in case you pass directly readPHPArgv() as the first arg 105 if (PEAR::isError($args)) { 106 return $args; 107 } 108 109 if (empty($args)) { 110 return array(array(), array()); 111 } 112 113 $non_opts = $opts = array(); 114 115 settype($args, 'array'); 116 117 if ($long_options) { 118 sort($long_options); 119 } 120 121 /* 122 * Preserve backwards compatibility with callers that relied on 123 * erroneous POSIX fix. 124 */ 125 if ($version < 2) { 126 if (isset($args[0][0]) && $args[0][0] != '-') { 127 array_shift($args); 128 } 129 } 130 131 for ($i = 0; $i < count($args); $i++) { 132 $arg = $args[$i]; 133 /* The special element '--' means explicit end of 134 options. Treat the rest of the arguments as non-options 135 and end the loop. */ 136 if ($arg == '--') { 137 $non_opts = array_merge($non_opts, array_slice($args, $i + 1)); 138 break; 139 } 140 141 if ($arg[0] != '-' || (strlen($arg) > 1 && $arg[1] == '-' && !$long_options)) { 142 $non_opts = array_merge($non_opts, array_slice($args, $i)); 143 break; 144 } elseif (strlen($arg) > 1 && $arg[1] == '-') { 145 $error = Console_Getopt::_parseLongOption(substr($arg, 2), 146 $long_options, 147 $opts, 148 $i, 149 $args, 150 $skip_unknown); 151 if (PEAR::isError($error)) { 152 return $error; 153 } 154 } elseif ($arg == '-') { 155 // - is stdin 156 $non_opts = array_merge($non_opts, array_slice($args, $i)); 157 break; 158 } else { 159 $error = Console_Getopt::_parseShortOption(substr($arg, 1), 160 $short_options, 161 $opts, 162 $i, 163 $args, 164 $skip_unknown); 165 if (PEAR::isError($error)) { 166 return $error; 167 } 168 } 169 } 170 171 return array($opts, $non_opts); 172 } 173 174 /** 175 * Parse short option 176 * 177 * @param string $arg Argument 178 * @param string[] $short_options Available short options 179 * @param string[][] &$opts 180 * @param int &$argIdx 181 * @param string[] $args 182 * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option 183 * 184 * @return void 185 */ 186 protected static function _parseShortOption($arg, $short_options, &$opts, &$argIdx, $args, $skip_unknown) 187 { 188 for ($i = 0; $i < strlen($arg); $i++) { 189 $opt = $arg[$i]; 190 $opt_arg = null; 191 192 /* Try to find the short option in the specifier string. */ 193 if (($spec = strstr($short_options, $opt)) === false || $arg[$i] == ':') { 194 if ($skip_unknown === true) { 195 break; 196 } 197 198 $msg = "Console_Getopt: unrecognized option -- $opt"; 199 return PEAR::raiseError($msg); 200 } 201 202 if (strlen($spec) > 1 && $spec[1] == ':') { 203 if (strlen($spec) > 2 && $spec[2] == ':') { 204 if ($i + 1 < strlen($arg)) { 205 /* Option takes an optional argument. Use the remainder of 206 the arg string if there is anything left. */ 207 $opts[] = array($opt, substr($arg, $i + 1)); 208 break; 209 } 210 } else { 211 /* Option requires an argument. Use the remainder of the arg 212 string if there is anything left. */ 213 if ($i + 1 < strlen($arg)) { 214 $opts[] = array($opt, substr($arg, $i + 1)); 215 break; 216 } else if (isset($args[++$argIdx])) { 217 $opt_arg = $args[$argIdx]; 218 /* Else use the next argument. */; 219 if (Console_Getopt::_isShortOpt($opt_arg) 220 || Console_Getopt::_isLongOpt($opt_arg)) { 221 $msg = "option requires an argument --$opt"; 222 return PEAR::raiseError("Console_Getopt: " . $msg); 223 } 224 } else { 225 $msg = "option requires an argument --$opt"; 226 return PEAR::raiseError("Console_Getopt: " . $msg); 227 } 228 } 229 } 230 231 $opts[] = array($opt, $opt_arg); 232 } 233 } 234 235 /** 236 * Checks if an argument is a short option 237 * 238 * @param string $arg Argument to check 239 * 240 * @return bool 241 */ 242 protected static function _isShortOpt($arg) 243 { 244 return strlen($arg) == 2 && $arg[0] == '-' 245 && preg_match('/[a-zA-Z]/', $arg[1]); 246 } 247 248 /** 249 * Checks if an argument is a long option 250 * 251 * @param string $arg Argument to check 252 * 253 * @return bool 254 */ 255 protected static function _isLongOpt($arg) 256 { 257 return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' && 258 preg_match('/[a-zA-Z]+$/', substr($arg, 2)); 259 } 260 261 /** 262 * Parse long option 263 * 264 * @param string $arg Argument 265 * @param string[] $long_options Available long options 266 * @param string[][] &$opts 267 * @param int &$argIdx 268 * @param string[] $args 269 * 270 * @return void|PEAR_Error 271 */ 272 protected static function _parseLongOption($arg, $long_options, &$opts, &$argIdx, $args, $skip_unknown) 273 { 274 @list($opt, $opt_arg) = explode('=', $arg, 2); 275 276 $opt_len = strlen($opt); 277 278 for ($i = 0; $i < count($long_options); $i++) { 279 $long_opt = $long_options[$i]; 280 $opt_start = substr($long_opt, 0, $opt_len); 281 282 $long_opt_name = str_replace('=', '', $long_opt); 283 284 /* Option doesn't match. Go on to the next one. */ 285 if ($long_opt_name != $opt) { 286 continue; 287 } 288 289 $opt_rest = substr($long_opt, $opt_len); 290 291 /* Check that the options uniquely matches one of the allowed 292 options. */ 293 if ($i + 1 < count($long_options)) { 294 $next_option_rest = substr($long_options[$i + 1], $opt_len); 295 } else { 296 $next_option_rest = ''; 297 } 298 299 if ($opt_rest != '' && $opt[0] != '=' && 300 $i + 1 < count($long_options) && 301 $opt == substr($long_options[$i+1], 0, $opt_len) && 302 $next_option_rest != '' && 303 $next_option_rest[0] != '=') { 304 305 $msg = "Console_Getopt: option --$opt is ambiguous"; 306 return PEAR::raiseError($msg); 307 } 308 309 if (substr($long_opt, -1) == '=') { 310 if (substr($long_opt, -2) != '==') { 311 /* Long option requires an argument. 312 Take the next argument if one wasn't specified. */; 313 if (!strlen($opt_arg)) { 314 if (!isset($args[++$argIdx])) { 315 $msg = "Console_Getopt: option requires an argument --$opt"; 316 return PEAR::raiseError($msg); 317 } 318 $opt_arg = $args[$argIdx]; 319 } 320 321 if (Console_Getopt::_isShortOpt($opt_arg) 322 || Console_Getopt::_isLongOpt($opt_arg)) { 323 $msg = "Console_Getopt: option requires an argument --$opt"; 324 return PEAR::raiseError($msg); 325 } 326 } 327 } else if ($opt_arg) { 328 $msg = "Console_Getopt: option --$opt doesn't allow an argument"; 329 return PEAR::raiseError($msg); 330 } 331 332 $opts[] = array('--' . $opt, $opt_arg); 333 return; 334 } 335 336 if ($skip_unknown === true) { 337 return; 338 } 339 340 return PEAR::raiseError("Console_Getopt: unrecognized option --$opt"); 341 } 342 343 /** 344 * Safely read the $argv PHP array across different PHP configurations. 345 * Will take care on register_globals and register_argc_argv ini directives 346 * 347 * @return mixed the $argv PHP array or PEAR error if not registered 348 */ 349 public static function readPHPArgv() 350 { 351 global $argv; 352 if (!is_array($argv)) { 353 if (!@is_array($_SERVER['argv'])) { 354 if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { 355 $msg = "Could not read cmd args (register_argc_argv=Off?)"; 356 return PEAR::raiseError("Console_Getopt: " . $msg); 357 } 358 return $GLOBALS['HTTP_SERVER_VARS']['argv']; 359 } 360 return $_SERVER['argv']; 361 } 362 return $argv; 363 } 364 365} 366