diff --git a/Tests/getoptsTest.php b/Tests/getoptsTest.php new file mode 100644 index 0000000..90b1860 --- /dev/null +++ b/Tests/getoptsTest.php @@ -0,0 +1,157 @@ +help (); + $this->assertSame ($res, "No option defined\n"); + } + + public function test_simu1 () + { + $getopts = new getopts (); + $res = $getopts->simulate ("sim\ ulate -h -d -d -f ii -o 1\ 2 -o \"2 2\" -o 3 -- -bla final"); + $this->assertSame (is_object ($res), true); + } + + public function test_add1 () + { + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate -h -d -d -f ii -o 1\ 2 -o \"2 2\" -o 3 -- -bla final"); + $res = $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $this->assertSame (is_object ($res), true); + } + + public function test_add2 () + { + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate -h -d -d -f ii -o 1\ 2 -o \"2 2\" -o 3 -- -bla final"); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $res = $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $this->assertSame (is_object ($res), true); + } + + public function test_scan1 () + { + $this->expectException ("Exception", "Provided tokens are not known: -f,ii,-o,1 2,-o,2 2,-o,3"); + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate -h -d -d -f ii -o 1\ 2 -o \"2 2\" -o 3 -- -bla final"); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $res = $getopts->scan (); + } + + public function test_getShort1 () + { + // One unique value (-h -> true/false) + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate -h -d -d "); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $res = $getopts->get ("Help"); + $this->assertSame ($res, true); + } + + public function test_getShort2 () + { + // Multiple values, two set (-d -d) + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate -h -d -d "); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $res = $getopts->get ("Debug"); + $this->assertSame ($res, array (true,true)); + } + + public function test_getShort3 () + { + // Multiple values, one set (-d) + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate -h -d "); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $res = $getopts->get ("Debug"); + $this->assertSame ($res, array (true)); + } + + public function test_getShort4 () + { + // Multiple values, None set (-d) + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate -h "); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $res = $getopts->get ("Debug"); + $this->assertSame ($res, array ()); + } + + public function test_getLong1 () + { + // One unique value (--help -> true/false) + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate --help -d -d "); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $res = $getopts->get ("Help"); + $this->assertSame ($res, true); + } + + public function test_getLong2 () + { + // Multiple values, two set (-debug --debug) + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate -h --debug --debug "); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $res = $getopts->get ("Debug"); + $this->assertSame ($res, array (true,true)); + } + + public function test_getLong3 () + { + // Multiple values, one set (-d) + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate --help -d "); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $res = $getopts->get ("Debug"); + $this->assertSame ($res, array (true)); + } + + public function test_getLong4 () + { + // Multiple values, None set (-d) + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate -h"); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $res = $getopts->get ("Debug"); + $this->assertSame ($res, array ()); + } + + public function test_restOfLine1 () + { + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate -h -d -d -- -bla final"); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $res = $getopts->restOfLine (); + $this->assertSame ($res, array ("-bla", "final")); + } + + public function test_programName1 () + { + $getopts = new getopts (); + $getopts->simulate ("sim\ ulate -h -d -d -- -bla final"); + $getopts->add ("Help", "?h", array ("help","help2"), "Help of the software"); + $getopts->add ("Debug", "d", "debug", "Debug", "DebugLevel", 2); + $res = $getopts->programName (); + $this->assertSame ($res, "sim ulate"); + } +} diff --git a/getopts.php b/getopts.php new file mode 100644 index 0000000..8ab0604 --- /dev/null +++ b/getopts.php @@ -0,0 +1,453 @@ +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)) + $long = explode (",", $long); + if ($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) !== ":") + 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 + */ + public function scan () + { + global $argv; + if ($this->simulate !== null) + $commandLine = $this->simulate; + else + $commandLine = implode (" ", $argv); + $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 + 1; + continue; + } + } + // Word analysis + $end = strpos ($commandLine, " ", $offset); + if ($end === false) + $end = strlen ($commandLine); + $nbchars = $end - $offset; + if ($debug) echo "WORD FOUND (Start $offset with $nbchars chars)\n"; + $token = substr ($commandLine, $offset, $nbchars); + if ($token === "--") + { + $this->restOfLine = array (); + if ($debug) echo "RESTOFLINE=$this->restOfLine\n"; + $offset = $end + 1; + continue; + } + // 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 = ""; + 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\n"; + $keys = array_keys ($tokens, str_replace (":", "", $opt)); + if (count ($keys) === 0) + 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) && + 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) || + 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 "CHECK $opt MULTIPLE\n"; + $keys = array_keys ($tokens, str_replace (":", "", $opt)); + if (count ($keys) === 0) + 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]); + } + } + } + } + } + } + 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 (); + } + + /** 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 .= $option; + if ($param) + { + $d .= " "; + if ($paramOptional) $d .= "["; + $d .= $opt["paramName"]; + if ($paramOptional) $d .= "]"; + } + $i++; + } + } + $d .= "\n\t".$opt["description"]; + $d .= "\n"; + } + return $d; + } +}