about summary refs log tree commit diff stats
path: root/wiki/vendor/splitbrain/php-cli/src/Options.php
diff options
context:
space:
mode:
Diffstat (limited to 'wiki/vendor/splitbrain/php-cli/src/Options.php')
-rw-r--r--wiki/vendor/splitbrain/php-cli/src/Options.php470
1 files changed, 470 insertions, 0 deletions
diff --git a/wiki/vendor/splitbrain/php-cli/src/Options.php b/wiki/vendor/splitbrain/php-cli/src/Options.php
new file mode 100644
index 0000000..4d8b44d
--- /dev/null
+++ b/wiki/vendor/splitbrain/php-cli/src/Options.php
@@ -0,0 +1,470 @@
+<?php
+
+namespace splitbrain\phpcli;
+
+/**
+ * Class Options
+ *
+ * Parses command line options passed to the CLI script. Allows CLI scripts to easily register all accepted options and
+ * commands and even generates a help text from this setup.
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @license MIT
+ */
+class Options
+{
+    /** @var  array keeps the list of options to parse */
+    protected $setup;
+
+    /** @var  array store parsed options */
+    protected $options = array();
+
+    /** @var string current parsed command if any */
+    protected $command = '';
+
+    /** @var  array passed non-option arguments */
+    protected $args = array();
+
+    /** @var  string the executed script */
+    protected $bin;
+
+    /** @var  Colors for colored help output */
+    protected $colors;
+
+    /**
+     * Constructor
+     *
+     * @param Colors $colors optional configured color object
+     * @throws Exception when arguments can't be read
+     */
+    public function __construct(Colors $colors = null)
+    {
+        if (!is_null($colors)) {
+            $this->colors = $colors;
+        } else {
+            $this->colors = new Colors();
+        }
+
+        $this->setup = array(
+            '' => array(
+                'opts' => array(),
+                'args' => array(),
+                'help' => ''
+            )
+        ); // default command
+
+        $this->args = $this->readPHPArgv();
+        $this->bin = basename(array_shift($this->args));
+
+        $this->options = array();
+    }
+
+    /**
+     * Sets the help text for the tool itself
+     *
+     * @param string $help
+     */
+    public function setHelp($help)
+    {
+        $this->setup['']['help'] = $help;
+    }
+
+    /**
+     * Register the names of arguments for help generation and number checking
+     *
+     * This has to be called in the order arguments are expected
+     *
+     * @param string $arg argument name (just for help)
+     * @param string $help help text
+     * @param bool $required is this a required argument
+     * @param string $command if theses apply to a sub command only
+     * @throws Exception
+     */
+    public function registerArgument($arg, $help, $required = true, $command = '')
+    {
+        if (!isset($this->setup[$command])) {
+            throw new Exception("Command $command not registered");
+        }
+
+        $this->setup[$command]['args'][] = array(
+            'name' => $arg,
+            'help' => $help,
+            'required' => $required
+        );
+    }
+
+    /**
+     * This registers a sub command
+     *
+     * Sub commands have their own options and use their own function (not main()).
+     *
+     * @param string $command
+     * @param string $help
+     * @throws Exception
+     */
+    public function registerCommand($command, $help)
+    {
+        if (isset($this->setup[$command])) {
+            throw new Exception("Command $command already registered");
+        }
+
+        $this->setup[$command] = array(
+            'opts' => array(),
+            'args' => array(),
+            'help' => $help
+        );
+
+    }
+
+    /**
+     * Register an option for option parsing and help generation
+     *
+     * @param string $long multi character option (specified with --)
+     * @param string $help help text for this option
+     * @param string|null $short one character option (specified with -)
+     * @param bool|string $needsarg does this option require an argument? give it a name here
+     * @param string $command what command does this option apply to
+     * @throws Exception
+     */
+    public function registerOption($long, $help, $short = null, $needsarg = false, $command = '')
+    {
+        if (!isset($this->setup[$command])) {
+            throw new Exception("Command $command not registered");
+        }
+
+        $this->setup[$command]['opts'][$long] = array(
+            'needsarg' => $needsarg,
+            'help' => $help,
+            'short' => $short
+        );
+
+        if ($short) {
+            if (strlen($short) > 1) {
+                throw new Exception("Short options should be exactly one ASCII character");
+            }
+
+            $this->setup[$command]['short'][$short] = $long;
+        }
+    }
+
+    /**
+     * Checks the actual number of arguments against the required number
+     *
+     * Throws an exception if arguments are missing.
+     *
+     * This is run from CLI automatically and usually does not need to be called directly
+     *
+     * @throws Exception
+     */
+    public function checkArguments()
+    {
+        $argc = count($this->args);
+
+        $req = 0;
+        foreach ($this->setup[$this->command]['args'] as $arg) {
+            if (!$arg['required']) {
+                break;
+            } // last required arguments seen
+            $req++;
+        }
+
+        if ($req > $argc) {
+            throw new Exception("Not enough arguments", Exception::E_OPT_ARG_REQUIRED);
+        }
+    }
+
+    /**
+     * Parses the given arguments for known options and command
+     *
+     * The given $args array should NOT contain the executed file as first item anymore! The $args
+     * array is stripped from any options and possible command. All found otions can be accessed via the
+     * getOpt() function
+     *
+     * Note that command options will overwrite any global options with the same name
+     *
+     * This is run from CLI automatically and usually does not need to be called directly
+     *
+     * @throws Exception
+     */
+    public function parseOptions()
+    {
+        $non_opts = array();
+
+        $argc = count($this->args);
+        for ($i = 0; $i < $argc; $i++) {
+            $arg = $this->args[$i];
+
+            // The special element '--' means explicit end of options. Treat the rest of the arguments as non-options
+            // and end the loop.
+            if ($arg == '--') {
+                $non_opts = array_merge($non_opts, array_slice($this->args, $i + 1));
+                break;
+            }
+
+            // '-' is stdin - a normal argument
+            if ($arg == '-') {
+                $non_opts = array_merge($non_opts, array_slice($this->args, $i));
+                break;
+            }
+
+            // first non-option
+            if ($arg{0} != '-') {
+                $non_opts = array_merge($non_opts, array_slice($this->args, $i));
+                break;
+            }
+
+            // long option
+            if (strlen($arg) > 1 && $arg{1} == '-') {
+                $arg = explode('=', substr($arg, 2), 2);
+                $opt = array_shift($arg);
+                $val = array_shift($arg);
+
+                if (!isset($this->setup[$this->command]['opts'][$opt])) {
+                    throw new Exception("No such option '$opt'", Exception::E_UNKNOWN_OPT);
+                }
+
+                // argument required?
+                if ($this->setup[$this->command]['opts'][$opt]['needsarg']) {
+                    if (is_null($val) && $i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) {
+                        $val = $this->args[++$i];
+                    }
+                    if (is_null($val)) {
+                        throw new Exception("Option $opt requires an argument",
+                            Exception::E_OPT_ARG_REQUIRED);
+                    }
+                    $this->options[$opt] = $val;
+                } else {
+                    $this->options[$opt] = true;
+                }
+
+                continue;
+            }
+
+            // short option
+            $opt = substr($arg, 1);
+            if (!isset($this->setup[$this->command]['short'][$opt])) {
+                throw new Exception("No such option $arg", Exception::E_UNKNOWN_OPT);
+            } else {
+                $opt = $this->setup[$this->command]['short'][$opt]; // store it under long name
+            }
+
+            // argument required?
+            if ($this->setup[$this->command]['opts'][$opt]['needsarg']) {
+                $val = null;
+                if ($i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) {
+                    $val = $this->args[++$i];
+                }
+                if (is_null($val)) {
+                    throw new Exception("Option $arg requires an argument",
+                        Exception::E_OPT_ARG_REQUIRED);
+                }
+                $this->options[$opt] = $val;
+            } else {
+                $this->options[$opt] = true;
+            }
+        }
+
+        // parsing is now done, update args array
+        $this->args = $non_opts;
+
+        // if not done yet, check if first argument is a command and reexecute argument parsing if it is
+        if (!$this->command && $this->args && isset($this->setup[$this->args[0]])) {
+            // it is a command!
+            $this->command = array_shift($this->args);
+            $this->parseOptions(); // second pass
+        }
+    }
+
+    /**
+     * Get the value of the given option
+     *
+     * Please note that all options are accessed by their long option names regardless of how they were
+     * specified on commandline.
+     *
+     * Can only be used after parseOptions() has been run
+     *
+     * @param mixed $option
+     * @param bool|string $default what to return if the option was not set
+     * @return bool|string|string[]
+     */
+    public function getOpt($option = null, $default = false)
+    {
+        if ($option === null) {
+            return $this->options;
+        }
+
+        if (isset($this->options[$option])) {
+            return $this->options[$option];
+        }
+        return $default;
+    }
+
+    /**
+     * Return the found command if any
+     *
+     * @return string
+     */
+    public function getCmd()
+    {
+        return $this->command;
+    }
+
+    /**
+     * Get all the arguments passed to the script
+     *
+     * This will not contain any recognized options or the script name itself
+     *
+     * @return array
+     */
+    public function getArgs()
+    {
+        return $this->args;
+    }
+
+    /**
+     * Builds a help screen from the available options. You may want to call it from -h or on error
+     *
+     * @return string
+     *
+     * @throws Exception
+     */
+    public function help()
+    {
+        $tf = new TableFormatter($this->colors);
+        $text = '';
+
+        $hascommands = (count($this->setup) > 1);
+        foreach ($this->setup as $command => $config) {
+            $hasopts = (bool)$this->setup[$command]['opts'];
+            $hasargs = (bool)$this->setup[$command]['args'];
+
+            // usage or command syntax line
+            if (!$command) {
+                $text .= $this->colors->wrap('USAGE:', Colors::C_BROWN);
+                $text .= "\n";
+                $text .= '   ' . $this->bin;
+                $mv = 2;
+            } else {
+                $text .= "\n";
+                $text .= $this->colors->wrap('   ' . $command, Colors::C_PURPLE);
+                $mv = 4;
+            }
+
+            if ($hasopts) {
+                $text .= ' ' . $this->colors->wrap('<OPTIONS>', Colors::C_GREEN);
+            }
+
+            if (!$command && $hascommands) {
+                $text .= ' ' . $this->colors->wrap('<COMMAND> ...', Colors::C_PURPLE);
+            }
+
+            foreach ($this->setup[$command]['args'] as $arg) {
+                $out = $this->colors->wrap('<' . $arg['name'] . '>', Colors::C_CYAN);
+
+                if (!$arg['required']) {
+                    $out = '[' . $out . ']';
+                }
+                $text .= ' ' . $out;
+            }
+            $text .= "\n";
+
+            // usage or command intro
+            if ($this->setup[$command]['help']) {
+                $text .= "\n";
+                $text .= $tf->format(
+                    array($mv, '*'),
+                    array('', $this->setup[$command]['help'] . "\n")
+                );
+            }
+
+            // option description
+            if ($hasopts) {
+                if (!$command) {
+                    $text .= "\n";
+                    $text .= $this->colors->wrap('OPTIONS:', Colors::C_BROWN);
+                }
+                $text .= "\n";
+                foreach ($this->setup[$command]['opts'] as $long => $opt) {
+
+                    $name = '';
+                    if ($opt['short']) {
+                        $name .= '-' . $opt['short'];
+                        if ($opt['needsarg']) {
+                            $name .= ' <' . $opt['needsarg'] . '>';
+                        }
+                        $name .= ', ';
+                    }
+                    $name .= "--$long";
+                    if ($opt['needsarg']) {
+                        $name .= ' <' . $opt['needsarg'] . '>';
+                    }
+
+                    $text .= $tf->format(
+                        array($mv, '30%', '*'),
+                        array('', $name, $opt['help']),
+                        array('', 'green', '')
+                    );
+                    $text .= "\n";
+                }
+            }
+
+            // argument description
+            if ($hasargs) {
+                if (!$command) {
+                    $text .= "\n";
+                    $text .= $this->colors->wrap('ARGUMENTS:', Colors::C_BROWN);
+                }
+                $text .= "\n";
+                foreach ($this->setup[$command]['args'] as $arg) {
+                    $name = '<' . $arg['name'] . '>';
+
+                    $text .= $tf->format(
+                        array($mv, '30%', '*'),
+                        array('', $name, $arg['help']),
+                        array('', 'cyan', '')
+                    );
+                }
+            }
+
+            // head line and intro for following command documentation
+            if (!$command && $hascommands) {
+                $text .= "\n";
+                $text .= $this->colors->wrap('COMMANDS:', Colors::C_BROWN);
+                $text .= "\n";
+                $text .= $tf->format(
+                    array($mv, '*'),
+                    array('', 'This tool accepts a command as first parameter as outlined below:')
+                );
+                $text .= "\n";
+            }
+        }
+
+        return $text;
+    }
+
+    /**
+     * Safely read the $argv PHP array across different PHP configurations.
+     * Will take care on register_globals and register_argc_argv ini directives
+     *
+     * @throws Exception
+     * @return array the $argv PHP array or PEAR error if not registered
+     */
+    private function readPHPArgv()
+    {
+        global $argv;
+        if (!is_array($argv)) {
+            if (!@is_array($_SERVER['argv'])) {
+                if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
+                    throw new Exception(
+                        "Could not read cmd args (register_argc_argv=Off?)",
+                        Exception::E_ARG_READ
+                    );
+                }
+                return $GLOBALS['HTTP_SERVER_VARS']['argv'];
+            }
+            return $_SERVER['argv'];
+        }
+        return $argv;
+    }
+}
+