xref: /dokuwiki/vendor/splitbrain/php-cli/src/Options.php (revision 6cb05674d342c24351edf98467e681cbaabf9d0b)
1cbeaa4a0SAndreas Gohr<?php
2cbeaa4a0SAndreas Gohr
3cbeaa4a0SAndreas Gohrnamespace splitbrain\phpcli;
4cbeaa4a0SAndreas Gohr
5cbeaa4a0SAndreas Gohr/**
6cbeaa4a0SAndreas Gohr * Class Options
7cbeaa4a0SAndreas Gohr *
8cbeaa4a0SAndreas Gohr * Parses command line options passed to the CLI script. Allows CLI scripts to easily register all accepted options and
9cbeaa4a0SAndreas Gohr * commands and even generates a help text from this setup.
10cbeaa4a0SAndreas Gohr *
11cbeaa4a0SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org>
12cbeaa4a0SAndreas Gohr * @license MIT
13cbeaa4a0SAndreas Gohr */
14cbeaa4a0SAndreas Gohrclass Options
15cbeaa4a0SAndreas Gohr{
16cbeaa4a0SAndreas Gohr    /** @var  array keeps the list of options to parse */
17cbeaa4a0SAndreas Gohr    protected $setup;
18cbeaa4a0SAndreas Gohr
19cbeaa4a0SAndreas Gohr    /** @var  array store parsed options */
20cbeaa4a0SAndreas Gohr    protected $options = array();
21cbeaa4a0SAndreas Gohr
22cbeaa4a0SAndreas Gohr    /** @var string current parsed command if any */
23cbeaa4a0SAndreas Gohr    protected $command = '';
24cbeaa4a0SAndreas Gohr
25cbeaa4a0SAndreas Gohr    /** @var  array passed non-option arguments */
26cbeaa4a0SAndreas Gohr    protected $args = array();
27cbeaa4a0SAndreas Gohr
28cbeaa4a0SAndreas Gohr    /** @var  string the executed script */
29cbeaa4a0SAndreas Gohr    protected $bin;
30cbeaa4a0SAndreas Gohr
31cbeaa4a0SAndreas Gohr    /** @var  Colors for colored help output */
32cbeaa4a0SAndreas Gohr    protected $colors;
33cbeaa4a0SAndreas Gohr
34*6cb05674SAndreas Gohr    /** @var string newline used for spacing help texts */
35*6cb05674SAndreas Gohr    protected $newline = "\n";
36*6cb05674SAndreas Gohr
37cbeaa4a0SAndreas Gohr    /**
38cbeaa4a0SAndreas Gohr     * Constructor
39cbeaa4a0SAndreas Gohr     *
40cbeaa4a0SAndreas Gohr     * @param Colors $colors optional configured color object
41cbeaa4a0SAndreas Gohr     * @throws Exception when arguments can't be read
42cbeaa4a0SAndreas Gohr     */
43cbeaa4a0SAndreas Gohr    public function __construct(Colors $colors = null)
44cbeaa4a0SAndreas Gohr    {
45cbeaa4a0SAndreas Gohr        if (!is_null($colors)) {
46cbeaa4a0SAndreas Gohr            $this->colors = $colors;
47cbeaa4a0SAndreas Gohr        } else {
48cbeaa4a0SAndreas Gohr            $this->colors = new Colors();
49cbeaa4a0SAndreas Gohr        }
50cbeaa4a0SAndreas Gohr
51cbeaa4a0SAndreas Gohr        $this->setup = array(
52cbeaa4a0SAndreas Gohr            '' => array(
53cbeaa4a0SAndreas Gohr                'opts' => array(),
54cbeaa4a0SAndreas Gohr                'args' => array(),
55*6cb05674SAndreas Gohr                'help' => '',
56*6cb05674SAndreas Gohr                'commandhelp' => 'This tool accepts a command as first parameter as outlined below:'
57cbeaa4a0SAndreas Gohr            )
58cbeaa4a0SAndreas Gohr        ); // default command
59cbeaa4a0SAndreas Gohr
60cbeaa4a0SAndreas Gohr        $this->args = $this->readPHPArgv();
61cbeaa4a0SAndreas Gohr        $this->bin = basename(array_shift($this->args));
62cbeaa4a0SAndreas Gohr
63cbeaa4a0SAndreas Gohr        $this->options = array();
64cbeaa4a0SAndreas Gohr    }
65ab0a8902SAndreas Gohr
66ab0a8902SAndreas Gohr    /**
67ab0a8902SAndreas Gohr     * Gets the bin value
68ab0a8902SAndreas Gohr     */
69ab0a8902SAndreas Gohr    public function getBin()
70ab0a8902SAndreas Gohr    {
71ab0a8902SAndreas Gohr        return $this->bin;
72ab0a8902SAndreas Gohr    }
73cbeaa4a0SAndreas Gohr
74cbeaa4a0SAndreas Gohr    /**
75cbeaa4a0SAndreas Gohr     * Sets the help text for the tool itself
76cbeaa4a0SAndreas Gohr     *
77cbeaa4a0SAndreas Gohr     * @param string $help
78cbeaa4a0SAndreas Gohr     */
79cbeaa4a0SAndreas Gohr    public function setHelp($help)
80cbeaa4a0SAndreas Gohr    {
81cbeaa4a0SAndreas Gohr        $this->setup['']['help'] = $help;
82cbeaa4a0SAndreas Gohr    }
83cbeaa4a0SAndreas Gohr
84cbeaa4a0SAndreas Gohr    /**
85*6cb05674SAndreas Gohr     * Sets the help text for the tools commands itself
86*6cb05674SAndreas Gohr     *
87*6cb05674SAndreas Gohr     * @param string $help
88*6cb05674SAndreas Gohr     */
89*6cb05674SAndreas Gohr    public function setCommandHelp($help)
90*6cb05674SAndreas Gohr    {
91*6cb05674SAndreas Gohr        $this->setup['']['commandhelp'] = $help;
92*6cb05674SAndreas Gohr    }
93*6cb05674SAndreas Gohr
94*6cb05674SAndreas Gohr    /**
95*6cb05674SAndreas Gohr     * Use a more compact help screen with less new lines
96*6cb05674SAndreas Gohr     *
97*6cb05674SAndreas Gohr     * @param bool $set
98*6cb05674SAndreas Gohr     */
99*6cb05674SAndreas Gohr    public function useCompactHelp($set = true)
100*6cb05674SAndreas Gohr    {
101*6cb05674SAndreas Gohr        $this->newline = $set ? '' : "\n";
102*6cb05674SAndreas Gohr    }
103*6cb05674SAndreas Gohr
104*6cb05674SAndreas Gohr    /**
105cbeaa4a0SAndreas Gohr     * Register the names of arguments for help generation and number checking
106cbeaa4a0SAndreas Gohr     *
107cbeaa4a0SAndreas Gohr     * This has to be called in the order arguments are expected
108cbeaa4a0SAndreas Gohr     *
109cbeaa4a0SAndreas Gohr     * @param string $arg argument name (just for help)
110cbeaa4a0SAndreas Gohr     * @param string $help help text
111cbeaa4a0SAndreas Gohr     * @param bool $required is this a required argument
112cbeaa4a0SAndreas Gohr     * @param string $command if theses apply to a sub command only
113cbeaa4a0SAndreas Gohr     * @throws Exception
114cbeaa4a0SAndreas Gohr     */
115cbeaa4a0SAndreas Gohr    public function registerArgument($arg, $help, $required = true, $command = '')
116cbeaa4a0SAndreas Gohr    {
117cbeaa4a0SAndreas Gohr        if (!isset($this->setup[$command])) {
118cbeaa4a0SAndreas Gohr            throw new Exception("Command $command not registered");
119cbeaa4a0SAndreas Gohr        }
120cbeaa4a0SAndreas Gohr
121cbeaa4a0SAndreas Gohr        $this->setup[$command]['args'][] = array(
122cbeaa4a0SAndreas Gohr            'name' => $arg,
123cbeaa4a0SAndreas Gohr            'help' => $help,
124cbeaa4a0SAndreas Gohr            'required' => $required
125cbeaa4a0SAndreas Gohr        );
126cbeaa4a0SAndreas Gohr    }
127cbeaa4a0SAndreas Gohr
128cbeaa4a0SAndreas Gohr    /**
129cbeaa4a0SAndreas Gohr     * This registers a sub command
130cbeaa4a0SAndreas Gohr     *
131cbeaa4a0SAndreas Gohr     * Sub commands have their own options and use their own function (not main()).
132cbeaa4a0SAndreas Gohr     *
133cbeaa4a0SAndreas Gohr     * @param string $command
134cbeaa4a0SAndreas Gohr     * @param string $help
135cbeaa4a0SAndreas Gohr     * @throws Exception
136cbeaa4a0SAndreas Gohr     */
137cbeaa4a0SAndreas Gohr    public function registerCommand($command, $help)
138cbeaa4a0SAndreas Gohr    {
139cbeaa4a0SAndreas Gohr        if (isset($this->setup[$command])) {
140cbeaa4a0SAndreas Gohr            throw new Exception("Command $command already registered");
141cbeaa4a0SAndreas Gohr        }
142cbeaa4a0SAndreas Gohr
143cbeaa4a0SAndreas Gohr        $this->setup[$command] = array(
144cbeaa4a0SAndreas Gohr            'opts' => array(),
145cbeaa4a0SAndreas Gohr            'args' => array(),
146cbeaa4a0SAndreas Gohr            'help' => $help
147cbeaa4a0SAndreas Gohr        );
148cbeaa4a0SAndreas Gohr
149cbeaa4a0SAndreas Gohr    }
150cbeaa4a0SAndreas Gohr
151cbeaa4a0SAndreas Gohr    /**
152cbeaa4a0SAndreas Gohr     * Register an option for option parsing and help generation
153cbeaa4a0SAndreas Gohr     *
154cbeaa4a0SAndreas Gohr     * @param string $long multi character option (specified with --)
155cbeaa4a0SAndreas Gohr     * @param string $help help text for this option
156cbeaa4a0SAndreas Gohr     * @param string|null $short one character option (specified with -)
157cbeaa4a0SAndreas Gohr     * @param bool|string $needsarg does this option require an argument? give it a name here
158cbeaa4a0SAndreas Gohr     * @param string $command what command does this option apply to
159cbeaa4a0SAndreas Gohr     * @throws Exception
160cbeaa4a0SAndreas Gohr     */
161cbeaa4a0SAndreas Gohr    public function registerOption($long, $help, $short = null, $needsarg = false, $command = '')
162cbeaa4a0SAndreas Gohr    {
163cbeaa4a0SAndreas Gohr        if (!isset($this->setup[$command])) {
164cbeaa4a0SAndreas Gohr            throw new Exception("Command $command not registered");
165cbeaa4a0SAndreas Gohr        }
166cbeaa4a0SAndreas Gohr
167cbeaa4a0SAndreas Gohr        $this->setup[$command]['opts'][$long] = array(
168cbeaa4a0SAndreas Gohr            'needsarg' => $needsarg,
169cbeaa4a0SAndreas Gohr            'help' => $help,
170cbeaa4a0SAndreas Gohr            'short' => $short
171cbeaa4a0SAndreas Gohr        );
172cbeaa4a0SAndreas Gohr
173cbeaa4a0SAndreas Gohr        if ($short) {
174cbeaa4a0SAndreas Gohr            if (strlen($short) > 1) {
175cbeaa4a0SAndreas Gohr                throw new Exception("Short options should be exactly one ASCII character");
176cbeaa4a0SAndreas Gohr            }
177cbeaa4a0SAndreas Gohr
178cbeaa4a0SAndreas Gohr            $this->setup[$command]['short'][$short] = $long;
179cbeaa4a0SAndreas Gohr        }
180cbeaa4a0SAndreas Gohr    }
181cbeaa4a0SAndreas Gohr
182cbeaa4a0SAndreas Gohr    /**
183cbeaa4a0SAndreas Gohr     * Checks the actual number of arguments against the required number
184cbeaa4a0SAndreas Gohr     *
185cbeaa4a0SAndreas Gohr     * Throws an exception if arguments are missing.
186cbeaa4a0SAndreas Gohr     *
187cbeaa4a0SAndreas Gohr     * This is run from CLI automatically and usually does not need to be called directly
188cbeaa4a0SAndreas Gohr     *
189cbeaa4a0SAndreas Gohr     * @throws Exception
190cbeaa4a0SAndreas Gohr     */
191cbeaa4a0SAndreas Gohr    public function checkArguments()
192cbeaa4a0SAndreas Gohr    {
193cbeaa4a0SAndreas Gohr        $argc = count($this->args);
194cbeaa4a0SAndreas Gohr
195cbeaa4a0SAndreas Gohr        $req = 0;
196cbeaa4a0SAndreas Gohr        foreach ($this->setup[$this->command]['args'] as $arg) {
197cbeaa4a0SAndreas Gohr            if (!$arg['required']) {
198cbeaa4a0SAndreas Gohr                break;
199cbeaa4a0SAndreas Gohr            } // last required arguments seen
200cbeaa4a0SAndreas Gohr            $req++;
201cbeaa4a0SAndreas Gohr        }
202cbeaa4a0SAndreas Gohr
203cbeaa4a0SAndreas Gohr        if ($req > $argc) {
204cbeaa4a0SAndreas Gohr            throw new Exception("Not enough arguments", Exception::E_OPT_ARG_REQUIRED);
205cbeaa4a0SAndreas Gohr        }
206cbeaa4a0SAndreas Gohr    }
207cbeaa4a0SAndreas Gohr
208cbeaa4a0SAndreas Gohr    /**
209cbeaa4a0SAndreas Gohr     * Parses the given arguments for known options and command
210cbeaa4a0SAndreas Gohr     *
211cbeaa4a0SAndreas Gohr     * The given $args array should NOT contain the executed file as first item anymore! The $args
212cbeaa4a0SAndreas Gohr     * array is stripped from any options and possible command. All found otions can be accessed via the
213cbeaa4a0SAndreas Gohr     * getOpt() function
214cbeaa4a0SAndreas Gohr     *
215cbeaa4a0SAndreas Gohr     * Note that command options will overwrite any global options with the same name
216cbeaa4a0SAndreas Gohr     *
217cbeaa4a0SAndreas Gohr     * This is run from CLI automatically and usually does not need to be called directly
218cbeaa4a0SAndreas Gohr     *
219cbeaa4a0SAndreas Gohr     * @throws Exception
220cbeaa4a0SAndreas Gohr     */
221cbeaa4a0SAndreas Gohr    public function parseOptions()
222cbeaa4a0SAndreas Gohr    {
223cbeaa4a0SAndreas Gohr        $non_opts = array();
224cbeaa4a0SAndreas Gohr
225cbeaa4a0SAndreas Gohr        $argc = count($this->args);
226cbeaa4a0SAndreas Gohr        for ($i = 0; $i < $argc; $i++) {
227cbeaa4a0SAndreas Gohr            $arg = $this->args[$i];
228cbeaa4a0SAndreas Gohr
229cbeaa4a0SAndreas Gohr            // The special element '--' means explicit end of options. Treat the rest of the arguments as non-options
230cbeaa4a0SAndreas Gohr            // and end the loop.
231cbeaa4a0SAndreas Gohr            if ($arg == '--') {
232cbeaa4a0SAndreas Gohr                $non_opts = array_merge($non_opts, array_slice($this->args, $i + 1));
233cbeaa4a0SAndreas Gohr                break;
234cbeaa4a0SAndreas Gohr            }
235cbeaa4a0SAndreas Gohr
236cbeaa4a0SAndreas Gohr            // '-' is stdin - a normal argument
237cbeaa4a0SAndreas Gohr            if ($arg == '-') {
238cbeaa4a0SAndreas Gohr                $non_opts = array_merge($non_opts, array_slice($this->args, $i));
239cbeaa4a0SAndreas Gohr                break;
240cbeaa4a0SAndreas Gohr            }
241cbeaa4a0SAndreas Gohr
242cbeaa4a0SAndreas Gohr            // first non-option
24364604f4eSAndreas Gohr            if ($arg[0] != '-') {
244cbeaa4a0SAndreas Gohr                $non_opts = array_merge($non_opts, array_slice($this->args, $i));
245cbeaa4a0SAndreas Gohr                break;
246cbeaa4a0SAndreas Gohr            }
247cbeaa4a0SAndreas Gohr
248cbeaa4a0SAndreas Gohr            // long option
24964604f4eSAndreas Gohr            if (strlen($arg) > 1 && $arg[1] === '-') {
250cbeaa4a0SAndreas Gohr                $arg = explode('=', substr($arg, 2), 2);
251cbeaa4a0SAndreas Gohr                $opt = array_shift($arg);
252cbeaa4a0SAndreas Gohr                $val = array_shift($arg);
253cbeaa4a0SAndreas Gohr
254cbeaa4a0SAndreas Gohr                if (!isset($this->setup[$this->command]['opts'][$opt])) {
255cbeaa4a0SAndreas Gohr                    throw new Exception("No such option '$opt'", Exception::E_UNKNOWN_OPT);
256cbeaa4a0SAndreas Gohr                }
257cbeaa4a0SAndreas Gohr
258cbeaa4a0SAndreas Gohr                // argument required?
259cbeaa4a0SAndreas Gohr                if ($this->setup[$this->command]['opts'][$opt]['needsarg']) {
260cbeaa4a0SAndreas Gohr                    if (is_null($val) && $i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) {
261cbeaa4a0SAndreas Gohr                        $val = $this->args[++$i];
262cbeaa4a0SAndreas Gohr                    }
263cbeaa4a0SAndreas Gohr                    if (is_null($val)) {
264cbeaa4a0SAndreas Gohr                        throw new Exception("Option $opt requires an argument",
265cbeaa4a0SAndreas Gohr                            Exception::E_OPT_ARG_REQUIRED);
266cbeaa4a0SAndreas Gohr                    }
267cbeaa4a0SAndreas Gohr                    $this->options[$opt] = $val;
268cbeaa4a0SAndreas Gohr                } else {
269cbeaa4a0SAndreas Gohr                    $this->options[$opt] = true;
270cbeaa4a0SAndreas Gohr                }
271cbeaa4a0SAndreas Gohr
272cbeaa4a0SAndreas Gohr                continue;
273cbeaa4a0SAndreas Gohr            }
274cbeaa4a0SAndreas Gohr
275cbeaa4a0SAndreas Gohr            // short option
276cbeaa4a0SAndreas Gohr            $opt = substr($arg, 1);
277cbeaa4a0SAndreas Gohr            if (!isset($this->setup[$this->command]['short'][$opt])) {
278cbeaa4a0SAndreas Gohr                throw new Exception("No such option $arg", Exception::E_UNKNOWN_OPT);
279cbeaa4a0SAndreas Gohr            } else {
280cbeaa4a0SAndreas Gohr                $opt = $this->setup[$this->command]['short'][$opt]; // store it under long name
281cbeaa4a0SAndreas Gohr            }
282cbeaa4a0SAndreas Gohr
283cbeaa4a0SAndreas Gohr            // argument required?
284cbeaa4a0SAndreas Gohr            if ($this->setup[$this->command]['opts'][$opt]['needsarg']) {
285cbeaa4a0SAndreas Gohr                $val = null;
286cbeaa4a0SAndreas Gohr                if ($i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) {
287cbeaa4a0SAndreas Gohr                    $val = $this->args[++$i];
288cbeaa4a0SAndreas Gohr                }
289cbeaa4a0SAndreas Gohr                if (is_null($val)) {
290cbeaa4a0SAndreas Gohr                    throw new Exception("Option $arg requires an argument",
291cbeaa4a0SAndreas Gohr                        Exception::E_OPT_ARG_REQUIRED);
292cbeaa4a0SAndreas Gohr                }
293cbeaa4a0SAndreas Gohr                $this->options[$opt] = $val;
294cbeaa4a0SAndreas Gohr            } else {
295cbeaa4a0SAndreas Gohr                $this->options[$opt] = true;
296cbeaa4a0SAndreas Gohr            }
297cbeaa4a0SAndreas Gohr        }
298cbeaa4a0SAndreas Gohr
299cbeaa4a0SAndreas Gohr        // parsing is now done, update args array
300cbeaa4a0SAndreas Gohr        $this->args = $non_opts;
301cbeaa4a0SAndreas Gohr
302cbeaa4a0SAndreas Gohr        // if not done yet, check if first argument is a command and reexecute argument parsing if it is
303cbeaa4a0SAndreas Gohr        if (!$this->command && $this->args && isset($this->setup[$this->args[0]])) {
304cbeaa4a0SAndreas Gohr            // it is a command!
305cbeaa4a0SAndreas Gohr            $this->command = array_shift($this->args);
306cbeaa4a0SAndreas Gohr            $this->parseOptions(); // second pass
307cbeaa4a0SAndreas Gohr        }
308cbeaa4a0SAndreas Gohr    }
309cbeaa4a0SAndreas Gohr
310cbeaa4a0SAndreas Gohr    /**
311cbeaa4a0SAndreas Gohr     * Get the value of the given option
312cbeaa4a0SAndreas Gohr     *
313cbeaa4a0SAndreas Gohr     * Please note that all options are accessed by their long option names regardless of how they were
314cbeaa4a0SAndreas Gohr     * specified on commandline.
315cbeaa4a0SAndreas Gohr     *
316cbeaa4a0SAndreas Gohr     * Can only be used after parseOptions() has been run
317cbeaa4a0SAndreas Gohr     *
318cbeaa4a0SAndreas Gohr     * @param mixed $option
319cbeaa4a0SAndreas Gohr     * @param bool|string $default what to return if the option was not set
32043d3f077SAndreas Gohr     * @return bool|string|string[]
321cbeaa4a0SAndreas Gohr     */
322cbeaa4a0SAndreas Gohr    public function getOpt($option = null, $default = false)
323cbeaa4a0SAndreas Gohr    {
324cbeaa4a0SAndreas Gohr        if ($option === null) {
325cbeaa4a0SAndreas Gohr            return $this->options;
326cbeaa4a0SAndreas Gohr        }
327cbeaa4a0SAndreas Gohr
328cbeaa4a0SAndreas Gohr        if (isset($this->options[$option])) {
329cbeaa4a0SAndreas Gohr            return $this->options[$option];
330cbeaa4a0SAndreas Gohr        }
331cbeaa4a0SAndreas Gohr        return $default;
332cbeaa4a0SAndreas Gohr    }
333cbeaa4a0SAndreas Gohr
334cbeaa4a0SAndreas Gohr    /**
335cbeaa4a0SAndreas Gohr     * Return the found command if any
336cbeaa4a0SAndreas Gohr     *
337cbeaa4a0SAndreas Gohr     * @return string
338cbeaa4a0SAndreas Gohr     */
339cbeaa4a0SAndreas Gohr    public function getCmd()
340cbeaa4a0SAndreas Gohr    {
341cbeaa4a0SAndreas Gohr        return $this->command;
342cbeaa4a0SAndreas Gohr    }
343cbeaa4a0SAndreas Gohr
344cbeaa4a0SAndreas Gohr    /**
345cbeaa4a0SAndreas Gohr     * Get all the arguments passed to the script
346cbeaa4a0SAndreas Gohr     *
347cbeaa4a0SAndreas Gohr     * This will not contain any recognized options or the script name itself
348cbeaa4a0SAndreas Gohr     *
349cbeaa4a0SAndreas Gohr     * @return array
350cbeaa4a0SAndreas Gohr     */
351cbeaa4a0SAndreas Gohr    public function getArgs()
352cbeaa4a0SAndreas Gohr    {
353cbeaa4a0SAndreas Gohr        return $this->args;
354cbeaa4a0SAndreas Gohr    }
355cbeaa4a0SAndreas Gohr
356cbeaa4a0SAndreas Gohr    /**
357cbeaa4a0SAndreas Gohr     * Builds a help screen from the available options. You may want to call it from -h or on error
358cbeaa4a0SAndreas Gohr     *
359cbeaa4a0SAndreas Gohr     * @return string
36043d3f077SAndreas Gohr     *
36143d3f077SAndreas Gohr     * @throws Exception
362cbeaa4a0SAndreas Gohr     */
363cbeaa4a0SAndreas Gohr    public function help()
364cbeaa4a0SAndreas Gohr    {
365cbeaa4a0SAndreas Gohr        $tf = new TableFormatter($this->colors);
366cbeaa4a0SAndreas Gohr        $text = '';
367cbeaa4a0SAndreas Gohr
368cbeaa4a0SAndreas Gohr        $hascommands = (count($this->setup) > 1);
369*6cb05674SAndreas Gohr        $commandhelp = $this->setup['']["commandhelp"];
370*6cb05674SAndreas Gohr
371cbeaa4a0SAndreas Gohr        foreach ($this->setup as $command => $config) {
372cbeaa4a0SAndreas Gohr            $hasopts = (bool)$this->setup[$command]['opts'];
373cbeaa4a0SAndreas Gohr            $hasargs = (bool)$this->setup[$command]['args'];
374cbeaa4a0SAndreas Gohr
375cbeaa4a0SAndreas Gohr            // usage or command syntax line
376cbeaa4a0SAndreas Gohr            if (!$command) {
377cbeaa4a0SAndreas Gohr                $text .= $this->colors->wrap('USAGE:', Colors::C_BROWN);
378cbeaa4a0SAndreas Gohr                $text .= "\n";
379cbeaa4a0SAndreas Gohr                $text .= '   ' . $this->bin;
380cbeaa4a0SAndreas Gohr                $mv = 2;
381cbeaa4a0SAndreas Gohr            } else {
382*6cb05674SAndreas Gohr                $text .= $this->newline;
383cbeaa4a0SAndreas Gohr                $text .= $this->colors->wrap('   ' . $command, Colors::C_PURPLE);
384cbeaa4a0SAndreas Gohr                $mv = 4;
385cbeaa4a0SAndreas Gohr            }
386cbeaa4a0SAndreas Gohr
387cbeaa4a0SAndreas Gohr            if ($hasopts) {
388cbeaa4a0SAndreas Gohr                $text .= ' ' . $this->colors->wrap('<OPTIONS>', Colors::C_GREEN);
389cbeaa4a0SAndreas Gohr            }
390cbeaa4a0SAndreas Gohr
391cbeaa4a0SAndreas Gohr            if (!$command && $hascommands) {
392cbeaa4a0SAndreas Gohr                $text .= ' ' . $this->colors->wrap('<COMMAND> ...', Colors::C_PURPLE);
393cbeaa4a0SAndreas Gohr            }
394cbeaa4a0SAndreas Gohr
395cbeaa4a0SAndreas Gohr            foreach ($this->setup[$command]['args'] as $arg) {
396cbeaa4a0SAndreas Gohr                $out = $this->colors->wrap('<' . $arg['name'] . '>', Colors::C_CYAN);
397cbeaa4a0SAndreas Gohr
398cbeaa4a0SAndreas Gohr                if (!$arg['required']) {
399cbeaa4a0SAndreas Gohr                    $out = '[' . $out . ']';
400cbeaa4a0SAndreas Gohr                }
401cbeaa4a0SAndreas Gohr                $text .= ' ' . $out;
402cbeaa4a0SAndreas Gohr            }
403*6cb05674SAndreas Gohr            $text .= $this->newline;
404cbeaa4a0SAndreas Gohr
405cbeaa4a0SAndreas Gohr            // usage or command intro
406cbeaa4a0SAndreas Gohr            if ($this->setup[$command]['help']) {
407cbeaa4a0SAndreas Gohr                $text .= "\n";
408cbeaa4a0SAndreas Gohr                $text .= $tf->format(
409cbeaa4a0SAndreas Gohr                    array($mv, '*'),
410*6cb05674SAndreas Gohr                    array('', $this->setup[$command]['help'] . $this->newline)
411cbeaa4a0SAndreas Gohr                );
412cbeaa4a0SAndreas Gohr            }
413cbeaa4a0SAndreas Gohr
414cbeaa4a0SAndreas Gohr            // option description
415cbeaa4a0SAndreas Gohr            if ($hasopts) {
416cbeaa4a0SAndreas Gohr                if (!$command) {
417cbeaa4a0SAndreas Gohr                    $text .= "\n";
418cbeaa4a0SAndreas Gohr                    $text .= $this->colors->wrap('OPTIONS:', Colors::C_BROWN);
419cbeaa4a0SAndreas Gohr                }
420cbeaa4a0SAndreas Gohr                $text .= "\n";
421cbeaa4a0SAndreas Gohr                foreach ($this->setup[$command]['opts'] as $long => $opt) {
422cbeaa4a0SAndreas Gohr
423cbeaa4a0SAndreas Gohr                    $name = '';
424cbeaa4a0SAndreas Gohr                    if ($opt['short']) {
425cbeaa4a0SAndreas Gohr                        $name .= '-' . $opt['short'];
426cbeaa4a0SAndreas Gohr                        if ($opt['needsarg']) {
427cbeaa4a0SAndreas Gohr                            $name .= ' <' . $opt['needsarg'] . '>';
428cbeaa4a0SAndreas Gohr                        }
429cbeaa4a0SAndreas Gohr                        $name .= ', ';
430cbeaa4a0SAndreas Gohr                    }
431cbeaa4a0SAndreas Gohr                    $name .= "--$long";
432cbeaa4a0SAndreas Gohr                    if ($opt['needsarg']) {
433cbeaa4a0SAndreas Gohr                        $name .= ' <' . $opt['needsarg'] . '>';
434cbeaa4a0SAndreas Gohr                    }
435cbeaa4a0SAndreas Gohr
436cbeaa4a0SAndreas Gohr                    $text .= $tf->format(
437cbeaa4a0SAndreas Gohr                        array($mv, '30%', '*'),
438cbeaa4a0SAndreas Gohr                        array('', $name, $opt['help']),
439cbeaa4a0SAndreas Gohr                        array('', 'green', '')
440cbeaa4a0SAndreas Gohr                    );
441*6cb05674SAndreas Gohr                    $text .= $this->newline;
442cbeaa4a0SAndreas Gohr                }
443cbeaa4a0SAndreas Gohr            }
444cbeaa4a0SAndreas Gohr
445cbeaa4a0SAndreas Gohr            // argument description
446cbeaa4a0SAndreas Gohr            if ($hasargs) {
447cbeaa4a0SAndreas Gohr                if (!$command) {
448cbeaa4a0SAndreas Gohr                    $text .= "\n";
449cbeaa4a0SAndreas Gohr                    $text .= $this->colors->wrap('ARGUMENTS:', Colors::C_BROWN);
450cbeaa4a0SAndreas Gohr                }
451*6cb05674SAndreas Gohr                $text .= $this->newline;
452cbeaa4a0SAndreas Gohr                foreach ($this->setup[$command]['args'] as $arg) {
453cbeaa4a0SAndreas Gohr                    $name = '<' . $arg['name'] . '>';
454cbeaa4a0SAndreas Gohr
455cbeaa4a0SAndreas Gohr                    $text .= $tf->format(
456cbeaa4a0SAndreas Gohr                        array($mv, '30%', '*'),
457cbeaa4a0SAndreas Gohr                        array('', $name, $arg['help']),
458cbeaa4a0SAndreas Gohr                        array('', 'cyan', '')
459cbeaa4a0SAndreas Gohr                    );
460cbeaa4a0SAndreas Gohr                }
461cbeaa4a0SAndreas Gohr            }
462cbeaa4a0SAndreas Gohr
463cbeaa4a0SAndreas Gohr            // head line and intro for following command documentation
464cbeaa4a0SAndreas Gohr            if (!$command && $hascommands) {
465cbeaa4a0SAndreas Gohr                $text .= "\n";
466cbeaa4a0SAndreas Gohr                $text .= $this->colors->wrap('COMMANDS:', Colors::C_BROWN);
467cbeaa4a0SAndreas Gohr                $text .= "\n";
468cbeaa4a0SAndreas Gohr                $text .= $tf->format(
469cbeaa4a0SAndreas Gohr                    array($mv, '*'),
470*6cb05674SAndreas Gohr                    array('', $commandhelp)
471cbeaa4a0SAndreas Gohr                );
472*6cb05674SAndreas Gohr                $text .= $this->newline;
473cbeaa4a0SAndreas Gohr            }
474cbeaa4a0SAndreas Gohr        }
475cbeaa4a0SAndreas Gohr
476cbeaa4a0SAndreas Gohr        return $text;
477cbeaa4a0SAndreas Gohr    }
478cbeaa4a0SAndreas Gohr
479cbeaa4a0SAndreas Gohr    /**
480cbeaa4a0SAndreas Gohr     * Safely read the $argv PHP array across different PHP configurations.
481cbeaa4a0SAndreas Gohr     * Will take care on register_globals and register_argc_argv ini directives
482cbeaa4a0SAndreas Gohr     *
483cbeaa4a0SAndreas Gohr     * @throws Exception
484cbeaa4a0SAndreas Gohr     * @return array the $argv PHP array or PEAR error if not registered
485cbeaa4a0SAndreas Gohr     */
486cbeaa4a0SAndreas Gohr    private function readPHPArgv()
487cbeaa4a0SAndreas Gohr    {
488cbeaa4a0SAndreas Gohr        global $argv;
489cbeaa4a0SAndreas Gohr        if (!is_array($argv)) {
490cbeaa4a0SAndreas Gohr            if (!@is_array($_SERVER['argv'])) {
491cbeaa4a0SAndreas Gohr                if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
492cbeaa4a0SAndreas Gohr                    throw new Exception(
493cbeaa4a0SAndreas Gohr                        "Could not read cmd args (register_argc_argv=Off?)",
494cbeaa4a0SAndreas Gohr                        Exception::E_ARG_READ
495cbeaa4a0SAndreas Gohr                    );
496cbeaa4a0SAndreas Gohr                }
497cbeaa4a0SAndreas Gohr                return $GLOBALS['HTTP_SERVER_VARS']['argv'];
498cbeaa4a0SAndreas Gohr            }
499cbeaa4a0SAndreas Gohr            return $_SERVER['argv'];
500cbeaa4a0SAndreas Gohr        }
501cbeaa4a0SAndreas Gohr        return $argv;
502cbeaa4a0SAndreas Gohr    }
503cbeaa4a0SAndreas Gohr}
504cbeaa4a0SAndreas Gohr
505