Chris@18: Chris@18: * @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause Chris@18: * @version CVS: $Id$ Chris@18: * @link http://pear.php.net/package/Console_Getopt Chris@18: */ Chris@18: Chris@18: require_once 'PEAR.php'; Chris@18: Chris@18: /** Chris@18: * Command-line options parsing class. Chris@18: * Chris@18: * @category Console Chris@18: * @package Console_Getopt Chris@18: * @author Andrei Zmievski Chris@18: * @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause Chris@18: * @link http://pear.php.net/package/Console_Getopt Chris@18: */ Chris@18: class Console_Getopt Chris@18: { Chris@18: Chris@18: /** Chris@18: * Parses the command-line options. Chris@18: * Chris@18: * The first parameter to this function should be the list of command-line Chris@18: * arguments without the leading reference to the running program. Chris@18: * Chris@18: * The second parameter is a string of allowed short options. Each of the Chris@18: * option letters can be followed by a colon ':' to specify that the option Chris@18: * requires an argument, or a double colon '::' to specify that the option Chris@18: * takes an optional argument. Chris@18: * Chris@18: * The third argument is an optional array of allowed long options. The Chris@18: * leading '--' should not be included in the option name. Options that Chris@18: * require an argument should be followed by '=', and options that take an Chris@18: * option argument should be followed by '=='. Chris@18: * Chris@18: * The return value is an array of two elements: the list of parsed Chris@18: * options and the list of non-option command-line arguments. Each entry in Chris@18: * the list of parsed options is a pair of elements - the first one Chris@18: * specifies the option, and the second one specifies the option argument, Chris@18: * if there was one. Chris@18: * Chris@18: * Long and short options can be mixed. Chris@18: * Chris@18: * Most of the semantics of this function are based on GNU getopt_long(). Chris@18: * Chris@18: * @param array $args an array of command-line arguments Chris@18: * @param string $short_options specifies the list of allowed short options Chris@18: * @param array $long_options specifies the list of allowed long options Chris@18: * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option Chris@18: * Chris@18: * @return array two-element array containing the list of parsed options and Chris@18: * the non-option arguments Chris@18: */ Chris@18: public static function getopt2($args, $short_options, $long_options = null, $skip_unknown = false) Chris@18: { Chris@18: return Console_Getopt::doGetopt(2, $args, $short_options, $long_options, $skip_unknown); Chris@18: } Chris@18: Chris@18: /** Chris@18: * This function expects $args to start with the script name (POSIX-style). Chris@18: * Preserved for backwards compatibility. Chris@18: * Chris@18: * @param array $args an array of command-line arguments Chris@18: * @param string $short_options specifies the list of allowed short options Chris@18: * @param array $long_options specifies the list of allowed long options Chris@18: * Chris@18: * @see getopt2() Chris@18: * @return array two-element array containing the list of parsed options and Chris@18: * the non-option arguments Chris@18: */ Chris@18: public static function getopt($args, $short_options, $long_options = null, $skip_unknown = false) Chris@18: { Chris@18: return Console_Getopt::doGetopt(1, $args, $short_options, $long_options, $skip_unknown); Chris@18: } Chris@18: Chris@18: /** Chris@18: * The actual implementation of the argument parsing code. Chris@18: * Chris@18: * @param int $version Version to use Chris@18: * @param array $args an array of command-line arguments Chris@18: * @param string $short_options specifies the list of allowed short options Chris@18: * @param array $long_options specifies the list of allowed long options Chris@18: * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option Chris@18: * Chris@18: * @return array Chris@18: */ Chris@18: public static function doGetopt($version, $args, $short_options, $long_options = null, $skip_unknown = false) Chris@18: { Chris@18: // in case you pass directly readPHPArgv() as the first arg Chris@18: if (PEAR::isError($args)) { Chris@18: return $args; Chris@18: } Chris@18: Chris@18: if (empty($args)) { Chris@18: return array(array(), array()); Chris@18: } Chris@18: Chris@18: $non_opts = $opts = array(); Chris@18: Chris@18: settype($args, 'array'); Chris@18: Chris@18: if ($long_options) { Chris@18: sort($long_options); Chris@18: } Chris@18: Chris@18: /* Chris@18: * Preserve backwards compatibility with callers that relied on Chris@18: * erroneous POSIX fix. Chris@18: */ Chris@18: if ($version < 2) { Chris@18: if (isset($args[0]{0}) && $args[0]{0} != '-') { Chris@18: array_shift($args); Chris@18: } Chris@18: } Chris@18: Chris@18: for ($i = 0; $i < count($args); $i++) { Chris@18: $arg = $args[$i]; Chris@18: /* The special element '--' means explicit end of Chris@18: options. Treat the rest of the arguments as non-options Chris@18: and end the loop. */ Chris@18: if ($arg == '--') { Chris@18: $non_opts = array_merge($non_opts, array_slice($args, $i + 1)); Chris@18: break; Chris@18: } Chris@18: Chris@18: if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) { Chris@18: $non_opts = array_merge($non_opts, array_slice($args, $i)); Chris@18: break; Chris@18: } elseif (strlen($arg) > 1 && $arg{1} == '-') { Chris@18: $error = Console_Getopt::_parseLongOption(substr($arg, 2), Chris@18: $long_options, Chris@18: $opts, Chris@18: $i, Chris@18: $args, Chris@18: $skip_unknown); Chris@18: if (PEAR::isError($error)) { Chris@18: return $error; Chris@18: } Chris@18: } elseif ($arg == '-') { Chris@18: // - is stdin Chris@18: $non_opts = array_merge($non_opts, array_slice($args, $i)); Chris@18: break; Chris@18: } else { Chris@18: $error = Console_Getopt::_parseShortOption(substr($arg, 1), Chris@18: $short_options, Chris@18: $opts, Chris@18: $i, Chris@18: $args, Chris@18: $skip_unknown); Chris@18: if (PEAR::isError($error)) { Chris@18: return $error; Chris@18: } Chris@18: } Chris@18: } Chris@18: Chris@18: return array($opts, $non_opts); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Parse short option Chris@18: * Chris@18: * @param string $arg Argument Chris@18: * @param string[] $short_options Available short options Chris@18: * @param string[][] &$opts Chris@18: * @param int &$argIdx Chris@18: * @param string[] $args Chris@18: * @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option Chris@18: * Chris@18: * @return void Chris@18: */ Chris@18: protected static function _parseShortOption($arg, $short_options, &$opts, &$argIdx, $args, $skip_unknown) Chris@18: { Chris@18: for ($i = 0; $i < strlen($arg); $i++) { Chris@18: $opt = $arg{$i}; Chris@18: $opt_arg = null; Chris@18: Chris@18: /* Try to find the short option in the specifier string. */ Chris@18: if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':') { Chris@18: if ($skip_unknown === true) { Chris@18: break; Chris@18: } Chris@18: Chris@18: $msg = "Console_Getopt: unrecognized option -- $opt"; Chris@18: return PEAR::raiseError($msg); Chris@18: } Chris@18: Chris@18: if (strlen($spec) > 1 && $spec{1} == ':') { Chris@18: if (strlen($spec) > 2 && $spec{2} == ':') { Chris@18: if ($i + 1 < strlen($arg)) { Chris@18: /* Option takes an optional argument. Use the remainder of Chris@18: the arg string if there is anything left. */ Chris@18: $opts[] = array($opt, substr($arg, $i + 1)); Chris@18: break; Chris@18: } Chris@18: } else { Chris@18: /* Option requires an argument. Use the remainder of the arg Chris@18: string if there is anything left. */ Chris@18: if ($i + 1 < strlen($arg)) { Chris@18: $opts[] = array($opt, substr($arg, $i + 1)); Chris@18: break; Chris@18: } else if (isset($args[++$argIdx])) { Chris@18: $opt_arg = $args[$argIdx]; Chris@18: /* Else use the next argument. */; Chris@18: if (Console_Getopt::_isShortOpt($opt_arg) Chris@18: || Console_Getopt::_isLongOpt($opt_arg)) { Chris@18: $msg = "option requires an argument --$opt"; Chris@18: return PEAR::raiseError("Console_Getopt: " . $msg); Chris@18: } Chris@18: } else { Chris@18: $msg = "option requires an argument --$opt"; Chris@18: return PEAR::raiseError("Console_Getopt: " . $msg); Chris@18: } Chris@18: } Chris@18: } Chris@18: Chris@18: $opts[] = array($opt, $opt_arg); Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * Checks if an argument is a short option Chris@18: * Chris@18: * @param string $arg Argument to check Chris@18: * Chris@18: * @return bool Chris@18: */ Chris@18: protected static function _isShortOpt($arg) Chris@18: { Chris@18: return strlen($arg) == 2 && $arg[0] == '-' Chris@18: && preg_match('/[a-zA-Z]/', $arg[1]); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Checks if an argument is a long option Chris@18: * Chris@18: * @param string $arg Argument to check Chris@18: * Chris@18: * @return bool Chris@18: */ Chris@18: protected static function _isLongOpt($arg) Chris@18: { Chris@18: return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' && Chris@18: preg_match('/[a-zA-Z]+$/', substr($arg, 2)); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Parse long option Chris@18: * Chris@18: * @param string $arg Argument Chris@18: * @param string[] $long_options Available long options Chris@18: * @param string[][] &$opts Chris@18: * @param int &$argIdx Chris@18: * @param string[] $args Chris@18: * Chris@18: * @return void|PEAR_Error Chris@18: */ Chris@18: protected static function _parseLongOption($arg, $long_options, &$opts, &$argIdx, $args, $skip_unknown) Chris@18: { Chris@18: @list($opt, $opt_arg) = explode('=', $arg, 2); Chris@18: Chris@18: $opt_len = strlen($opt); Chris@18: Chris@18: for ($i = 0; $i < count($long_options); $i++) { Chris@18: $long_opt = $long_options[$i]; Chris@18: $opt_start = substr($long_opt, 0, $opt_len); Chris@18: Chris@18: $long_opt_name = str_replace('=', '', $long_opt); Chris@18: Chris@18: /* Option doesn't match. Go on to the next one. */ Chris@18: if ($long_opt_name != $opt) { Chris@18: continue; Chris@18: } Chris@18: Chris@18: $opt_rest = substr($long_opt, $opt_len); Chris@18: Chris@18: /* Check that the options uniquely matches one of the allowed Chris@18: options. */ Chris@18: if ($i + 1 < count($long_options)) { Chris@18: $next_option_rest = substr($long_options[$i + 1], $opt_len); Chris@18: } else { Chris@18: $next_option_rest = ''; Chris@18: } Chris@18: Chris@18: if ($opt_rest != '' && $opt{0} != '=' && Chris@18: $i + 1 < count($long_options) && Chris@18: $opt == substr($long_options[$i+1], 0, $opt_len) && Chris@18: $next_option_rest != '' && Chris@18: $next_option_rest{0} != '=') { Chris@18: Chris@18: $msg = "Console_Getopt: option --$opt is ambiguous"; Chris@18: return PEAR::raiseError($msg); Chris@18: } Chris@18: Chris@18: if (substr($long_opt, -1) == '=') { Chris@18: if (substr($long_opt, -2) != '==') { Chris@18: /* Long option requires an argument. Chris@18: Take the next argument if one wasn't specified. */; Chris@18: if (!strlen($opt_arg)) { Chris@18: if (!isset($args[++$argIdx])) { Chris@18: $msg = "Console_Getopt: option requires an argument --$opt"; Chris@18: return PEAR::raiseError($msg); Chris@18: } Chris@18: $opt_arg = $args[$argIdx]; Chris@18: } Chris@18: Chris@18: if (Console_Getopt::_isShortOpt($opt_arg) Chris@18: || Console_Getopt::_isLongOpt($opt_arg)) { Chris@18: $msg = "Console_Getopt: option requires an argument --$opt"; Chris@18: return PEAR::raiseError($msg); Chris@18: } Chris@18: } Chris@18: } else if ($opt_arg) { Chris@18: $msg = "Console_Getopt: option --$opt doesn't allow an argument"; Chris@18: return PEAR::raiseError($msg); Chris@18: } Chris@18: Chris@18: $opts[] = array('--' . $opt, $opt_arg); Chris@18: return; Chris@18: } Chris@18: Chris@18: if ($skip_unknown === true) { Chris@18: return; Chris@18: } Chris@18: Chris@18: return PEAR::raiseError("Console_Getopt: unrecognized option --$opt"); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Safely read the $argv PHP array across different PHP configurations. Chris@18: * Will take care on register_globals and register_argc_argv ini directives Chris@18: * Chris@18: * @return mixed the $argv PHP array or PEAR error if not registered Chris@18: */ Chris@18: public static function readPHPArgv() Chris@18: { Chris@18: global $argv; Chris@18: if (!is_array($argv)) { Chris@18: if (!@is_array($_SERVER['argv'])) { Chris@18: if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { Chris@18: $msg = "Could not read cmd args (register_argc_argv=Off?)"; Chris@18: return PEAR::raiseError("Console_Getopt: " . $msg); Chris@18: } Chris@18: return $GLOBALS['HTTP_SERVER_VARS']['argv']; Chris@18: } Chris@18: return $_SERVER['argv']; Chris@18: } Chris@18: return $argv; Chris@18: } Chris@18: Chris@18: }