* * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /** * Getopt.PHP allows for easy processing of command-line arguments. * It is a more powerful, object-oriented alternative to PHP's built-in getopt() function. * * @version 1.0 * @link https://github.com/ulrichsg/getopt-php */ class Getopt { const NO_ARGUMENT = 0; const REQUIRED_ARGUMENT = 1; const OPTIONAL_ARGUMENT = 2; /** @var string */ protected $scriptName; /** @var array */ protected $optionList = array(); /** @var array */ protected $options = array(); /** @var array */ protected $operands = array(); /** * Create a new Getopt object. * * The argument $options can be either a string in the format accepted by the PHP library * function getopt() or an array * * @param mixed $options Array of options, a String, or null * * @link https://www.gnu.org/s/hello/manual/libc/Getopt.html GNU Getopt manual */ public function __construct($options = null) { if ($options !== null) { $this->addOptions($options); } } /** * Evaluate the given arguments. These can be passed either as a string or as an array. * If nothing is passed, the running script's command line arguments are used. * * A {@link \UnexpectedValueException} or {@link \InvalidArgumentException} is thrown * when the arguments are not well-formed or do not conform to the options passed by the user. * * @param mixed $arguments optional ARGV array or space separated string * * @return void */ public function parse($arguments = null) { $this->options = array(); if (!isset($arguments)) { global $argv; $arguments = $argv; $this->scriptName = array_shift($arguments); // $argv[0] is the script's name } elseif (is_string($arguments)) { $this->scriptName = $_SERVER['PHP_SELF']; $arguments = explode(' ', $arguments); } $num_args = count($arguments); for ($i = 0; $i < $num_args; ++$i) { $arg = trim($arguments[$i]); if (empty($arg)) { continue; } if ($arg == '--' || mb_substr($arg, 0, 1) != '-') { // no more options, treat the remaining arguments as operands $first_operand_index = $arg == '--' ? $i + 1 : $i; $this->operands = array_slice($arguments, $first_operand_index); break; } if (mb_substr($arg, 0, 2) == '--') { // long option $option = mb_substr($arg, 2); if (strpos($option, '=') === false) { if ($i < $num_args - 1 && mb_substr($arguments[$i + 1], 0, 1) != '-' && $this->optionHasArgument($option, true)) { $value = $arguments[$i + 1]; ++$i; } else { $value = null; } } else { list($option, $value) = explode('=', $option, 2); } $this->addOption($option, $value, true); } else { // short option $option = mb_substr($arg, 1); if (mb_strlen($option) > 1) { // multiple options strung together foreach ($this->mb_str_split($option, 1) as $ch) { $this->addOption($ch, null, false); } } else { if ($i < $num_args - 1 && mb_substr($arguments[$i + 1], 0, 1) != '-' && $this->optionHasArgument($option, false)) { $value = $arguments[$i + 1]; ++$i; } else { $value = null; } $this->addOption($option, $value, false); } } } // endfor // remove '--' from operands array $operands = array(); foreach($this->operands as $operand) { if ($operand !== '--') { $operands[] = $operand; } } $this->operands = $operands; } /** * Return the value of the given option. Must be invoked after parse(). * * The return value can be any of the following: * * * @param string $name The (short or long) option. * * @return mixed */ public function getOption($name) { return isset($this->options[$name]) ? $this->options[$name] : null; } /** * Return all the options. Must be invoked after parse(). * * Will return an empty array if called before parse() is called. * * @return array */ public function getOptions() { return $this->options; } /** * Prints help message based on the available options. * * @param int $padding Number of characters to pad output of options to */ public function showHelp($padding = 25) { echo $this->getHelpText($padding); } /** * @param int $padding Number of characters to pad output of options to * * @return string help message for given options. */ public function getHelpText($padding = 25) { $help_text = sprintf("Usage: php %s [options] [operands]\n", $this->scriptName); $help_text .= "Options:\n"; foreach ($this->optionList as $name => $option) { @list($short, $long, $arg, $description) = $option; switch ($arg) { case self::NO_ARGUMENT: $arg = ''; break; case self::REQUIRED_ARGUMENT: $arg = ""; break; case self::OPTIONAL_ARGUMENT: $arg = "[]"; break; } $short = ($short) ? '-'.$short : ''; $long = ($long) ? '--'.$long : ''; if ($short && $long) { $options = $short.', '.$long; } else if ($short) { $options = $short; } else { $options = $long; } $padded = str_pad(sprintf(" %s %s", $options, $arg), $padding); $help_text .= sprintf("%s %s\n", $padded, $description); } return $help_text; } /** * Return the list of operands. Must be invoked after parse(). * * @return array */ public function getOperands() { return $this->operands; } /** * Parse an option string. * * @param string $string the option string * * @throws \InvalidArgumentException * @return array * @internal */ protected function parseOptionString($string) { if (!mb_strlen($string)) { throw new \InvalidArgumentException('Option string must not be empty'); } $option_list = array(); $eol = mb_strlen($string) - 1; $next_can_be_colon = false; for ($i = 0; $i <= $eol; ++$i) { $ch = $string[$i]; if (!preg_match('/^[A-Za-z]$/', $ch)) { $colon = $next_can_be_colon ? " or ':'" : ''; throw new \InvalidArgumentException("Option string is not well formed: " . "expected a letter$colon, found '$ch' at position " . ($i + 1)); } if ($i == $eol || $string[$i + 1] != ':') { $option_list[] = array($ch, null, self::NO_ARGUMENT); $next_can_be_colon = true; } elseif ($i < $eol - 1 && $string[$i + 2] == ':') { $option_list[] = array($ch, null, self::OPTIONAL_ARGUMENT); $i += 2; $next_can_be_colon = false; } else { $option_list[] = array($ch, null, self::REQUIRED_ARGUMENT); ++$i; $next_can_be_colon = true; } } return $option_list; } /** * Check that the argument conforms to Getopt.PHP's option array rules. * Throws an exception on failure. * * @param array $options the option list * * @throws \InvalidArgumentException * @return array the validated options array * @internal */ protected function validateOptions(array $options) { $valid_argument_specs = array( self::NO_ARGUMENT, self::OPTIONAL_ARGUMENT, self::REQUIRED_ARGUMENT ); if (empty($options)) { throw new \InvalidArgumentException('No options given'); } foreach ($options as $option) { if (!is_array($option) || count($option) < 3) { throw new \InvalidArgumentException("Too few fields in argument, must be 3 (short/long/type)"); } if (!(is_null($option[0]) || preg_match("/^[a-zA-Z]$/", $option[0]))) { throw new \InvalidArgumentException("First component of option must be " . "null or a letter, found '" . $option[0] . "'"); } if (!(is_null($option[1]) || preg_match("/^[a-zA-Z0-9_-]*$/", $option[1]))) { throw new \InvalidArgumentException("Second component of option must be " . "null or an alphanumeric string, found '" . $option[1] . "'"); } if (!mb_strlen($option[0]) && !mb_strlen($option[1])) { throw new \InvalidArgumentException("The short and long name of an option must not both be empty"); } if (!in_array($option[2], $valid_argument_specs, true)) { throw new \InvalidArgumentException("Third component of option must be one of " . "Getopt::NO_ARGUMENT, Getopt::OPTIONAL_ARGUMENT and Getopt::REQUIRED_ARGUMENT"); } if (!isset($option[3])) { $option[3] = ""; // description } } return $options; } /** * Add an option to the list of known options. * * @param string $option the option's name * @param string $value the option's value (or null) * @param boolean $is_long whether the option name is long or short * * @throws \UnexpectedValueException * @return void * @internal */ protected function addOption($option, $value, $is_long) { foreach ($this->optionList as $opt) { if (($is_long && $opt[1] == $option) || (!$is_long && $opt[0] == $option)) { if ($opt[2] == self::REQUIRED_ARGUMENT && !mb_strlen($value)) { throw new \UnexpectedValueException("Option '$option' must have a value"); } // for no-argument options, check if they are duplicate if ($opt[2] == self::NO_ARGUMENT) { $old_value = $this->getOption($option); $value = is_null($old_value) ? 1 : $old_value + 1; } // for optional-argument options, set value to 1 if none was given if (!mb_strlen($value)) { $value = 1; } // add both long and short names (if they exist) to the option array to facilitate lookup if (mb_strlen($opt[0]) > 0) { $this->options[$opt[0]] = $value; } if (mb_strlen($opt[1]) > 0) { $this->options[$opt[1]] = $value; } return; } } throw new \UnexpectedValueException("Option '$option' is unknown"); } /** * Return true if the given option can take an argument, false if it can't or is unknown. * * @param string $name the option's name * @param boolean $is_long whether it is a long option * * @return boolean * @internal */ protected function optionHasArgument($name, $is_long) { foreach ($this->optionList as $option) { if ((!$is_long && $option[0] == $name) || ($is_long && $option[1] == $name)) { return $option[2] != self::NO_ARGUMENT; } } return false; } /** * Return the option list used by this Getopt. * * This function is used for testing. It is not meant for production use. * * @return array * @internal */ public function getOptionList() { return $this->optionList; } /** * Parses and Adds options * The argument $options can be either a string in the format accepted by the PHP library * function getopt() or an array * * @param mixed $options Array of options, a String, or null * @throws \InvalidArgumentException * @return array */ public function addOptions($options) { if (is_string($options)) { return $this->addParsedOptions($this->parseOptionString($options)); } if (is_array($options)) { return $this->addParsedOptions($this->validateOptions($options)); } throw new \InvalidArgumentException("Getopt(): argument must be string or array"); } /** * Merges new options with the ones already in the Getopt optionList. * * @param array $options The array from parsing from parseOptionString() or validateOptions() * * @return array * @internal */ protected function addParsedOptions (array $options) { return $this->optionList = array_merge($this->optionList, $options); } /** * @param string $str string to split * @param int $l * * @return string * @internal */ protected function mb_str_split($str, $l = 0) { if ($l > 0) { $ret = array(); $len = mb_strlen($str, "UTF-8"); for ($i = 0; $i < $len; $i += $l) { $ret[] = mb_substr($str, $i, $l, "UTF-8"); } return $ret; } return preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY); } }