* @license BSD */ namespace Domframework; /** Manage the options provided on the command line * Parameters can be : * - Manage -d -dd -ddd as -d -d -d * - Manage mandatory parameter to argument: -f file (-f: in getopt) * - Manage optional parameter to argument: -b (-b:: in getopt) * The long options start with two dashes, the short start with one dash and * can only be one letter * If the token is "--", the following value can start by dash */ class Getopts { /** The list of options to check */ private $options = array(); /** The simulate line entry */ private $simulate; /** The parameters scanned from the command line or from the simulate entry */ private $parameters; /** The rest of the line which is not analyzed */ private $restOfLine; /** The name of the program called ($argv[0]) */ private $programName; /** The constructor check the availability of the MB module */ public function __construct() { if (! function_exists("mb_strlen")) { throw new \Exception( "PHP don't have the MB Support. Please add it !", 500 ); } } /** Set/Get the simulate value * @param string|null $simulate The simulate to get/set */ public function simulate($simulate = null) { if ($simulate === null) { return $this->simulate; } if (! is_string($simulate)) { throw new \Exception( "Simulate provided to getopts is not a string", 500 ); } if ($simulate === "") { $simulate = null; } $this->simulate = $simulate; return $this; } /** Add a new option to check * @param string $identifier The identifier of the options * @param string $short The short options (one letter with/without * semi-colon) to check * @param array|string $long The long options without double dash, * with/without semi-colon to check. If string, use colon to separate the * options * @param string $description The description of the option * @param string $paramName The description (or name) of the parameter * @param integer $multiple The same identifier can be called multiple time. * Set $multiple to set the maximum number of values */ public function add( $identifier, $short, $long, $description, $paramName = "", $multiple = 0 ) { if (! is_string($identifier)) { throw new \Exception( "Identifier provided to getopts is not a string", 500 ); } if (strlen(trim($identifier)) === 0) { throw new \Exception("Identifier provided to getopts is too short", 500); } if (! is_string($short)) { throw new \Exception( "Short option provided to getopts is not a string", 500 ); } if (! is_array($long) && ! is_string($long)) { throw new \Exception( "Long option provided to getopts is not a string or an array", 500 ); } if (! is_string($description)) { throw new \Exception( "Description of option provided to getopts is not a string", 500 ); } if (! is_string($paramName)) { throw new \Exception( "paramName of option provided to getopts is not a string", 500 ); } if (is_string($long)) { if (trim($long) !== "") { $long = explode(",", $long); } else { $long = array(); } } if ( substr($short, 0, 1) !== false && substr($short, 0, 1) !== "" && $short[0] === ":" ) { throw new \Exception("Short option can't start by semi-colon", 500); } if ( (strpos($short, ":") !== false || strpos(implode($long), ":") !== false) && trim($paramName) === "" ) { throw new \Exception( "One parameter is waiting, but there is no paramName defined", 500 ); } if ( strspn($paramName, "abcdefghijklmnopqrstuvwxyz" . "ABCDEFGHIJKLMNOPQRSTUVWXYZ" . "0123456789-_.") !== strlen($paramName) ) { throw new \Exception( "paramName of option provided to getopts contains invalid chars", 500 ); } if ( ! is_int($multiple) && \verify::staticIs_integer("$multiple") === false ) { throw new \Exception("Multiple option must be an integer", 500); } preg_match_all("#[\S]:?#u", $short, $matches); if (! array_key_exists(0, $matches)) { throw new \Exception("Short parameter is invalid", 500); } $short = $matches[0]; foreach ($short as $key => $s) { if ( substr($s, 1) !== false && substr($s, 1) !== ":" && substr($s, 1) !== "" ) { throw new \Exception( "Short parameter is invalid : only one letter allowed", 500 ); } $short[$key] = "-" . $s; } foreach ($long as $key => $l) { $long[$key] = "--" . $l; } $optShort = array(); $optLong = array(); foreach ($this->options as $opt) { // Do not allow to add if the short is already defined (with : or :: or // without any) if ($opt["identifier"] === $identifier) { throw new \Exception("The identifier is already defined", 500); } // Do not allow to add if the short is already defined $optShort = array_merge($optShort, $opt["short"]); $optLong = array_merge($optLong, $opt["long"]); } foreach ($short as $s) { if ( count(preg_grep( "#^" . str_replace( ":", "", str_replace("?", "\\?", $s) ) . ":?:?$#", $optShort )) ) { throw new \Exception(sprintf( "The short option '%s' is already defined in '%s'", $s, $opt["identifier"] ), 500); } } // Do not allow to add if the long is already defined (with : or :: or // without any) foreach ($long as $l) { if ( count(preg_grep("#^" . str_replace( ":", "", str_replace("?", "\\?", $l) ) . ":?:?$#", $optLong)) ) { throw new \Exception(sprintf( "The long option '%s' is already defined in '%s'", $l, $opt["identifier"] ), 500); } } // Do not allow a parameter to be defined multiple times in the new option foreach ($short as $s) { if ( strspn(substr($s, 1), "abcdefghijklmnopqrstuvwxyz" . "ABCDEFGHIJKLMNOPQRSTUVWXYZ" . "0123456789?_.:") !== strlen($s) - 1 ) { throw new \Exception(sprintf( "Short option '%s' to getopts contains invalid chars", $s ), 500); } $regex = "#^" . str_replace(":", "", str_replace("?", "\\?", $s)) . ":?:?$#"; if (count(preg_grep($regex, $short)) !== 1) { throw new \Exception(sprintf( "The short option '%s' can not be defined multiple time in '%s'", str_replace(":", "", $s), $identifier ), 500); } } foreach ($long as $l) { if ( strspn(substr($l, 2), "abcdefghijklmnopqrstuvwxyz" . "ABCDEFGHIJKLMNOPQRSTUVWXYZ" . "0123456789?_.:") !== strlen($l) - 2 ) { throw new \Exception(sprintf( "Long option '%s' to getopts contains invalid chars", $l ), 500); } $regex = "#^" . str_replace(":", "", str_replace("?", "\\?", $l)) . ":?:?$#"; if (count(preg_grep($regex, $long)) !== 1) { throw new \Exception(sprintf( "The long option '%s' can not be defined multiple time in '%s'", str_replace(":", "", $l), $identifier ), 500); } } $this->options[] = array("identifier" => $identifier, "short" => $short, "long" => $long, "description" => trim($description), "paramName" => trim($paramName), "multiple" => $multiple); return $this; } /** Scan the command line and fill the parameters. * Use the simulate line if provided or use the $argv if not * Set the parameters property to an array * @return $this; */ public function scan() { global $argv; if ($argv === null) { // getopts is launched in WebServer. Can not analyze anything $this->programName = ""; $this->parameters = array(); $this->restOfLine = array(); return $this; } if ($this->simulate !== null) { $commandLine = $this->simulate; } else { $args = array(); foreach ($argv as $arg) { if (strpos($arg, " ") !== false || $arg === "") { $args[] = "\"$arg\""; } else { $args[] = $arg; } } $commandLine = implode(" ", $args); } $debug = false; $tokens = array(); $prevToken = ""; // Look for sentences in the arguments of the command line $offset = 0; if ($debug) { echo "\n012345678901234567890123456789\n$commandLine\n"; } while ($offset < mb_strlen($commandLine)) { if ($debug) { echo "OFFSET=$offset\n"; } $start = strpos($commandLine, "\"", $offset); if ($start === $offset) { // Sentence, see if there is a end $end = strpos($commandLine, "\"", $offset + 1); if ($end !== false) { // Complete sentence (with ending double quote) $nbchars = $end - $offset - 1; if ($debug) { echo "COMPLETE SENTENCE (Start " . ($offset + 1) . " with $nbchars chars)\n"; } $token = substr($commandLine, $offset + 1, $nbchars); $tokens[] = $token; $offset = $end + 2; continue; } } // Word analysis $end = strpos($commandLine, " ", $offset); if ($end === false) { $end = strlen($commandLine); } $nbchars = $end - $offset; $token = substr($commandLine, $offset, $nbchars); if ($debug) { echo "WORD FOUND (Start $offset with $nbchars chars): $token\n"; } // The '\ ' are token concatenation. Remove the \ in the parameters if (substr($token, -1) === "\\") { $prevToken .= substr($token, 0, -1) . " "; $offset = $end + 1; continue; } if ($prevToken !== "") { $token = "$prevToken$token"; $prevToken = ""; } //if ($this->restOfLine !== null) // $this->restOfLine[] = $token; //elseif (trim ($token) !== "") $tokens[] = $token; $offset = $end + 1; } if ($debug) { print_r($tokens); } // Analyze the tokens to fill the $this->parameters if ($this->restOfLine === null) { $this->restOfLine = array(); } if (! array_key_exists(0, $tokens)) { throw new \Exception("Can not find the program name (\$argv[0]", 500); } $this->programName = array_shift($tokens); foreach ($this->options as $option) { foreach (array("short", "long") as $len) { foreach ($option[$len] as $opt) { if ($option["multiple"] < 2) { if ($debug) { echo "CHECK $opt UNIQUE -> "; } $keys = array_keys($tokens, str_replace(":", "", $opt)); if (count($keys) === 0) { if ($debug) { echo "Option Not found\n"; } continue; } if (count($keys) > 1) { throw new \Exception(sprintf(dgettext( "domframework", "Too much identical parameters provided: %s" ), $opt), 500); } $pos = reset($keys); // Look for parameter in this option. Can't start by dash if (strpos($opt, ":") === false) { if ($debug) { echo "FOUND UNIQUE without param\n"; } $this->parameters[$option["identifier"]] = true; unset($tokens[$pos]); } elseif ( strpos($opt, "::") !== false && array_key_exists($pos + 1, $tokens) && array_key_exists($pos + 2, $tokens) && $tokens[$pos + 1] === "--" ) { if ($debug) { echo "FOUND UNIQUE with optional param filled " . "and double-dash\n"; } $this->parameters[$option["identifier"]] = $tokens[$pos + 2]; unset($tokens[$pos]); unset($tokens[$pos + 1]); unset($tokens[$pos + 2]); } elseif ( strpos($opt, "::") !== false && array_key_exists($pos + 1, $tokens) && substr($tokens[$pos + 1], 0, 1) !== "-" ) { if ($debug) { echo "FOUND UNIQUE with optional param filled\n"; } $this->parameters[$option["identifier"]] = $tokens[$pos + 1]; unset($tokens[$pos]); unset($tokens[$pos + 1]); } elseif (strpos($opt, ":") !== false) { if ( array_key_exists($pos + 1, $tokens) && $tokens[$pos + 1] === "--" && ! array_key_exists($pos + 2, $tokens) ) { throw new \Exception(sprintf( dgettext( "domframework", "Mandatory value for parameter '%s' is not provided after double-dash" ), $opt ), 500); } if ( ! array_key_exists($pos + 1, $tokens) || substr($tokens[$pos + 1], 0, 1) === "-" ) { throw new \Exception(dgettext( "domframework", "Mandatory value for parameter '$opt' is not provided" ), 500); } if ($debug) { echo "FOUND UNIQUE with mandatory param filled\n"; } $this->parameters[$option["identifier"]] = $tokens[$pos + 1]; unset($tokens[$pos]); unset($tokens[$pos + 1]); } else { if ($debug) { echo "Not found\n"; } } } else { if ($debug) { echo "CHECK $opt MULTIPLE -> "; } $keys = array_keys($tokens, str_replace(":", "", $opt)); if (count($keys) === 0) { if ($debug) { echo "Option Not found\n"; } continue; } if (count($keys) > $option["multiple"]) { throw new \Exception(sprintf(dgettext( "domframework", "Too much multiple parameters provided: %s" ), $opt), 500); } foreach ($keys as $pos) { // Look for parameter in this option. Can't start by dash if (strpos($opt, ":") === false) { if ($debug) { echo "FOUND MUTLIPLE without param\n"; } $this->parameters[$option["identifier"]][] = true; unset($tokens[$pos]); } elseif ( strpos($opt, "::") !== false && array_key_exists($pos + 1, $tokens) && substr($tokens[$pos + 1], 0, 1) !== "-" ) { if ($debug) { echo "FOUND MUTLIPLE with optional param filled\n"; } $this->parameters[$option["identifier"]][] = $tokens[$pos + 1]; unset($tokens[$pos]); unset($tokens[$pos + 1]); } elseif (strpos($opt, ":") !== false) { if ( ! array_key_exists($pos + 1, $tokens) || substr($tokens[$pos + 1], 0, 1) === "-" ) { throw new \Exception(sprintf( dgettext( "domframework", "Mandatory value for parameter '%s' is not provided" ), $opt ), 500); } if ($debug) { echo "FOUND MUTLIPLE with mandatory param filled\n"; } $this->parameters[$option["identifier"]][] = $tokens[$pos + 1]; unset($tokens[$pos]); unset($tokens[$pos + 1]); } else { if ($debug) { echo "Not found\n"; } } } } } } } // Manage the restOfLine foreach ($tokens as $key => $tok) { if ($tok === "--" && array_key_exists($key + 1, $tokens)) { $this->restOfLine[] = $tokens[$key + 1]; unset($tokens[$key]); unset($tokens[$key + 1]); continue; } if (substr($tok, 0, 1) === "-") { continue; } $this->restOfLine[] = $tok; unset($tokens[$key]); } if (count($tokens)) { throw new \Exception(sprintf(dgettext( "domframework", "Provided tokens are not known: %s" ), implode(",", $tokens)), 500); } if ($this->parameters === null) { $this->parameters = array(); } return $this; } /** Get the value of the option if set in the command line. If simulate is * defined, use it. * Return false if the option is not set * Return true if the option is set without parameter * Return the content of the parameter if the option is set and parameter is * defined * Throw an exception if the identifier is not set * @param string $identifier The identifier option to get */ public function get($identifier) { $exists = false; foreach ($this->options as $opt) { if ($opt["identifier"] === $identifier) { $exists = true; break; } } if ($exists === false) { throw new \Exception(sprintf(dgettext( "domframework", "Provided parameter is not known: %s" ), $identifier), 500); } if ($this->parameters === null) { $this->scan(); } if (array_key_exists($identifier, $this->parameters)) { return $this->parameters[$identifier]; } if ($opt["multiple"] > 1) { return array(); } return false; } /** Get the value found in the rest of line (after the double dashes) */ public function restOfLine() { if ($this->restOfLine === null) { $this->scan(); } return $this->restOfLine; } /** Get the name of the program found in the command line */ public function programName() { if ($this->programName === null) { $this->scan(); } return $this->programName; } /** Get the Help message with all the descriptions and options */ public function help() { if (count($this->options) === 0) { return dgettext("domframework", "No option defined") . "\n"; } $d = ""; foreach ($this->options as $opt) { $i = 0; foreach (array("short", "long") as $len) { foreach ($opt[$len] as $option) { if (strpos($option, ":") !== false) { $param = true; } else { $param = false; } if (strpos($option, "::") !== false) { $paramOptional = true; } else { $paramOptional = false; } if ($i > 0) { $d .= ", "; } $d .= str_replace(":", "", $option); if ($param) { $d .= " "; if ($paramOptional) { $d .= "["; } $d .= $opt["paramName"]; if ($paramOptional) { $d .= "]"; } } $i++; } } $d .= "\n\t" . $opt["description"]; $d .= "\n"; } return $d; } }