664 lines
24 KiB
PHP
664 lines
24 KiB
PHP
<?php
|
|
|
|
/** DomFramework
|
|
* @package domframework
|
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
|
* @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;
|
|
}
|
|
}
|