Mereged updates from DokuWiki 38
[sudaraka-org:dokuwiki-mods.git] / inc / cliopts.php
1 <?php
2 /**
3  * Brutally chopped and modified from http://pear.php.net/package/Console_Getopts
4  *
5  * PHP Version 5
6  *
7  * Copyright (c) 1997-2004 The PHP Group
8  *
9  * LICENSE: This source file is subject to the New BSD license that is
10  * available through the world-wide-web at the following URI:
11  * http://www.opensource.org/licenses/bsd-license.php. If you did not receive
12  * a copy of the New BSD License and are unable to obtain it through the web,
13  * please send a note to license@php.net so we can mail you a copy immediately.
14  *
15  * @category Console
16  * @package  Console_Getopt
17  * @author   Andrei Zmievski <andrei@php.net>
18  * @modified Harry Fuecks hfuecks  gmail.com
19  * @modified Tanguy Ortolo <tanguy+dokuwiki@ortolo.eu>
20  * @license  http://www.opensource.org/licenses/bsd-license.php New BSD License
21  * @version  CVS: $Id$
22  * @link     http://pear.php.net/package/Console_Getopt
23  *
24  */
25
26 //------------------------------------------------------------------------------
27 /**
28  * Sets up CLI environment based on SAPI and PHP version
29  * Helps resolve some issues between the CGI and CLI SAPIs
30  * as well is inconsistencies between PHP 4.3+ and older versions
31  */
32 if (version_compare(phpversion(), '4.3.0', '<') || php_sapi_name() == 'cgi') {
33     // Handle output buffering
34     @ob_end_flush();
35     ob_implicit_flush(true);
36
37     // PHP ini settings
38     set_time_limit(0);
39     ini_set('track_errors', true);
40     ini_set('html_errors', false);
41     ini_set('magic_quotes_runtime', false);
42
43     // Define stream constants
44     define('STDIN', fopen('php://stdin', 'r'));
45     define('STDOUT', fopen('php://stdout', 'w'));
46     define('STDERR', fopen('php://stderr', 'w'));
47
48     // Close the streams on script termination
49     register_shutdown_function(
50         create_function('',
51         'fclose(STDIN); fclose(STDOUT); fclose(STDERR); return true;')
52         );
53 }
54
55 //------------------------------------------------------------------------------
56 /**
57 * Error codes
58 */
59 define('DOKU_CLI_OPTS_UNKNOWN_OPT',1); //Unrecognized option
60 define('DOKU_CLI_OPTS_OPT_ARG_REQUIRED',2); //Option requires argument
61 define('DOKU_CLI_OPTS_OPT_ARG_DENIED',3); //Option not allowed argument
62 define('DOKU_CLI_OPTS_OPT_ABIGUOUS',4);//Option abiguous
63 define('DOKU_CLI_OPTS_ARG_READ',5);//Could not read argv
64
65 //------------------------------------------------------------------------------
66 /**
67  * Command-line options parsing class.
68  *
69  * @author Andrei Zmievski <andrei@php.net>
70  *
71  */
72 class Doku_Cli_Opts {
73
74     /**
75      * <?php ?>
76      * @see http://www.sitepoint.com/article/php-command-line-1/3
77      * @param string executing file name - this MUST be passed the __FILE__ constant
78      * @param string short options
79      * @param array (optional) long options
80      * @return Doku_Cli_Opts_Container or Doku_Cli_Opts_Error
81      */
82     function & getOptions($bin_file, $short_options, $long_options = null) {
83         $args = Doku_Cli_Opts::readPHPArgv();
84
85         if ( Doku_Cli_Opts::isError($args) ) {
86             return $args;
87         }
88
89         // Compatibility between "php extensions.php" and "./extensions.php"
90         if ( realpath($_SERVER['argv'][0]) == $bin_file ) {
91             $options = Doku_Cli_Opts::getOpt($args,$short_options,$long_options);
92         } else {
93             $options = Doku_Cli_Opts::getOpt2($args,$short_options,$long_options);
94         }
95
96         if ( Doku_Cli_Opts::isError($options) ) {
97             return $options;
98         }
99
100         $container = new Doku_Cli_Opts_Container($options);
101         return $container;
102     }
103
104     /**
105      * Parses the command-line options.
106      *
107      * The first parameter to this function should be the list of command-line
108      * arguments without the leading reference to the running program.
109      *
110      * The second parameter is a string of allowed short options. Each of the
111      * option letters can be followed by a colon ':' to specify that the option
112      * requires an argument, or a double colon '::' to specify that the option
113      * takes an optional argument.
114      *
115      * The third argument is an optional array of allowed long options. The
116      * leading '--' should not be included in the option name. Options that
117      * require an argument should be followed by '=', and options that take an
118      * option argument should be followed by '=='.
119      *
120      * The return value is an array of two elements: the list of parsed
121      * options and the list of non-option command-line arguments. Each entry in
122      * the list of parsed options is a pair of elements - the first one
123      * specifies the option, and the second one specifies the option argument,
124      * if there was one.
125      *
126      * Long and short options can be mixed.
127      *
128      * Most of the semantics of this function are based on GNU getopt_long().
129      *
130      * @param array  $args          an array of command-line arguments
131      * @param string $short_options specifies the list of allowed short options
132      * @param array  $long_options  specifies the list of allowed long options
133      *
134      * @return array two-element array containing the list of parsed options and
135      * the non-option arguments
136      * @access public
137      */
138     function getopt2($args, $short_options, $long_options = null) {
139         return Doku_Cli_Opts::doGetopt(
140             2, $args, $short_options, $long_options
141             );
142     }
143
144     /**
145      * This function expects $args to start with the script name (POSIX-style).
146      * Preserved for backwards compatibility.
147      *
148      * @param array  $args          an array of command-line arguments
149      * @param string $short_options specifies the list of allowed short options
150      * @param array  $long_options  specifies the list of allowed long options
151      *
152      * @see getopt2()
153      * @return array two-element array containing the list of parsed options and
154      * the non-option arguments
155      */
156     function getopt($args, $short_options, $long_options = null) {
157         return Doku_Cli_Opts::doGetopt(
158             1, $args, $short_options, $long_options
159             );
160     }
161
162     /**
163      * The actual implementation of the argument parsing code.
164      *
165      * @param int    $version       Version to use
166      * @param array  $args          an array of command-line arguments
167      * @param string $short_options specifies the list of allowed short options
168      * @param array  $long_options  specifies the list of allowed long options
169      *
170      * @return array
171      */
172     function doGetopt($version, $args, $short_options, $long_options = null) {
173
174         // in case you pass directly readPHPArgv() as the first arg
175         if (Doku_Cli_Opts::isError($args)) {
176             return $args;
177         }
178         if (empty($args)) {
179             return array(array(), array());
180         }
181         $opts     = array();
182         $non_opts = array();
183
184         settype($args, 'array');
185
186         if ($long_options && is_array($long_options)) {
187             sort($long_options);
188         }
189
190         /*
191          * Preserve backwards compatibility with callers that relied on
192          * erroneous POSIX fix.
193          */
194         if ($version < 2) {
195             if (isset($args[0]{0}) && $args[0]{0} != '-') {
196                 array_shift($args);
197             }
198         }
199
200         reset($args);
201         while (list($i, $arg) = each($args)) {
202
203             /* The special element '--' means explicit end of
204                options. Treat the rest of the arguments as non-options
205                and end the loop. */
206             if ($arg == '--') {
207                 $non_opts = array_merge($non_opts, array_slice($args, $i + 1));
208                 break;
209             }
210
211             if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) {
212                 $non_opts = array_merge($non_opts, array_slice($args, $i));
213                 break;
214             } elseif (strlen($arg) > 1 && $arg{1} == '-') {
215                 $error = Doku_Cli_Opts::_parseLongOption(substr($arg, 2), $long_options, $opts, $args);
216                 if (Doku_Cli_Opts::isError($error))
217                     return $error;
218             } elseif ($arg == '-') {
219                 // - is stdin
220                 $non_opts = array_merge($non_opts, array_slice($args, $i));
221                 break;
222             } else {
223                 $error = Doku_Cli_Opts::_parseShortOption(substr($arg, 1), $short_options, $opts, $args);
224                 if (Doku_Cli_Opts::isError($error))
225                     return $error;
226             }
227         }
228
229         return array($opts, $non_opts);
230     }
231
232     /**
233      * Parse short option
234      *
235      * @param string     $arg           Argument
236      * @param string[]   $short_options Available short options
237      * @param string[][] &$opts
238      * @param string[]   &$args
239      *
240      * @access private
241      * @return void
242      */
243     function _parseShortOption($arg, $short_options, &$opts, &$args) {
244         $len = strlen($arg);
245         for ($i = 0; $i < $len; $i++) {
246             $opt = $arg{$i};
247             $opt_arg = null;
248
249             /* Try to find the short option in the specifier string. */
250             if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':')
251             {
252                 return Doku_Cli_Opts::raiseError(
253                     DOKU_CLI_OPTS_UNKNOWN_OPT,
254                     "Unrecognized option -- $opt"
255                     );
256             }
257
258             if (strlen($spec) > 1 && $spec{1} == ':') {
259                 if (strlen($spec) > 2 && $spec{2} == ':') {
260                     if ($i + 1 < strlen($arg)) {
261                         /* Option takes an optional argument. Use the remainder of
262                            the arg string if there is anything left. */
263                         $opts[] = array($opt, substr($arg, $i + 1));
264                         break;
265                     }
266                 } else {
267                     /* Option requires an argument. Use the remainder of the arg
268                        string if there is anything left. */
269                     if ($i + 1 < strlen($arg)) {
270                         $opts[] = array($opt,  substr($arg, $i + 1));
271                         break;
272                     } else if (list(, $opt_arg) = each($args)) {
273                         /* Else use the next argument. */;
274                         if (Doku_Cli_Opts::_isShortOpt($opt_arg) || Doku_Cli_Opts::_isLongOpt($opt_arg))
275                             return Doku_Cli_Opts::raiseError(
276                                 DOKU_CLI_OPTS_OPT_ARG_REQUIRED,
277                                 "option requires an argument --$opt"
278                                 );
279                     }
280                     else
281                         return Doku_Cli_Opts::raiseError(
282                             DOKU_CLI_OPTS_OPT_ARG_REQUIRED,
283                             "Option requires an argument -- $opt"
284                             );
285                 }
286             }
287
288             $opts[] = array($opt, $opt_arg);
289         }
290     }
291
292     /**
293      * Checks if an argument is a short option
294      *
295      * @param string $arg Argument to check
296      *
297      * @access private
298      * @return bool
299      */
300     function _isShortOpt($arg){
301         return strlen($arg) == 2 && $arg[0] == '-'
302                && preg_match('/[a-zA-Z]/', $arg[1]);
303     }
304
305     /**
306      * Checks if an argument is a long option
307      *
308      * @param string $arg Argument to check
309      *
310      * @access private
311      * @return bool
312      */
313     function _isLongOpt($arg){
314         return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' &&
315                preg_match('/[a-zA-Z]+$/', substr($arg, 2));
316     }
317
318     /**
319      * Parse long option
320      *
321      * @param string     $arg          Argument
322      * @param string[]   $long_options Available long options
323      * @param string[][] &$opts
324      * @param string[]   &$args
325      *
326      * @access private
327      * @return void|PEAR_Error
328      */
329     function _parseLongOption($arg, $long_options, &$opts, &$args) {
330         @list($opt, $opt_arg) = explode('=', $arg, 2);
331         $opt_len = strlen($opt);
332         $opt_cnt = count($long_options);
333
334         for ($i = 0; $i < $opt_cnt; $i++) {
335             $long_opt  = $long_options[$i];
336             $opt_start = substr($long_opt, 0, $opt_len);
337
338             $long_opt_name = str_replace('=', '', $long_opt);
339
340             /* Option doesn't match. Go on to the next one. */
341             if ($opt_start != $opt)
342                 continue;
343
344             $opt_rest = substr($long_opt, $opt_len);
345
346             /* Check that the options uniquely matches one of the allowed
347                options. */
348             if ($i + 1 < count($long_options)) {
349                 $next_option_rest = substr($long_options[$i + 1], $opt_len);
350             } else {
351                 $next_option_rest = '';
352             }
353
354             if ($opt_rest != '' && $opt{0} != '=' &&
355                 $i + 1 < $opt_cnt &&
356                 $opt == substr($long_options[$i+1], 0, $opt_len) &&
357                 $next_option_rest != '' &&
358                 $next_option_rest{0} != '=') {
359                 return Doku_Cli_Opts::raiseError(
360                     DOKU_CLI_OPTS_OPT_ABIGUOUS,
361                     "Option --$opt is ambiguous"
362                     );
363             }
364
365             if (substr($long_opt, -1) == '=') {
366                 if (substr($long_opt, -2) != '==') {
367                     /* Long option requires an argument.
368                        Take the next argument if one wasn't specified. */;
369                     if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) {
370                         return Doku_Cli_Opts::raiseError(
371                             DOKU_CLI_OPTS_OPT_ARG_REQUIRED,
372                             "Option --$opt requires an argument"
373                             );
374                     }
375
376                     if (Doku_Cli_Opts::_isShortOpt($opt_arg)
377                         || Doku_Cli_Opts::_isLongOpt($opt_arg))
378                         return Doku_Cli_Opts::raiseError(
379                             DOKU_CLI_OPTS_OPT_ARG_REQUIRED,
380                             "Option --$opt requires an argument"
381                             );
382                 }
383             } else if ($opt_arg) {
384                 return Doku_Cli_Opts::raiseError(
385                     DOKU_CLI_OPTS_OPT_ARG_DENIED,
386                     "Option --$opt doesn't allow an argument"
387                     );
388             }
389
390             $opts[] = array('--' . $opt, $opt_arg);
391             return;
392         }
393
394         return Doku_Cli_Opts::raiseError(
395             DOKU_CLI_OPTS_UNKNOWN_OPT,
396             "Unrecognized option --$opt"
397             );
398     }
399
400     /**
401      * Safely read the $argv PHP array across different PHP configurations.
402      * Will take care on register_globals and register_argc_argv ini directives
403      *
404      * @access public
405      * @return mixed the $argv PHP array or PEAR error if not registered
406      */
407     function readPHPArgv() {
408         global $argv;
409         if (!is_array($argv)) {
410             if (!@is_array($_SERVER['argv'])) {
411                 if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
412                     return Doku_Cli_Opts::raiseError(
413                         DOKU_CLI_OPTS_ARG_READ,
414                         "Could not read cmd args (register_argc_argv=Off?)"
415                         );
416                 }
417                 return $GLOBALS['HTTP_SERVER_VARS']['argv'];
418             }
419             return $_SERVER['argv'];
420         }
421         return $argv;
422     }
423
424     function raiseError($code, $msg) {
425         return new Doku_Cli_Opts_Error($code, $msg);
426     }
427
428     function isError($obj) {
429         return is_a($obj, 'Doku_Cli_Opts_Error');
430     }
431
432 }
433
434 //------------------------------------------------------------------------------
435 class Doku_Cli_Opts_Error {
436
437     var $code;
438     var $msg;
439
440     function Doku_Cli_Opts_Error($code, $msg) {
441         $this->code = $code;
442         $this->msg = $msg;
443     }
444
445     function getMessage() {
446         return $this->msg;
447     }
448
449     function isError() {
450         return true;
451     }
452
453 }
454
455 //------------------------------------------------------------------------------
456 class Doku_Cli_Opts_Container {
457
458     var $options = array();
459     var $args = array();
460
461     function Doku_Cli_Opts_Container($options) {
462         foreach ( $options[0] as $option ) {
463             if ( false !== ( strpos($option[0], '--') ) ) {
464                 $opt_name = substr($option[0], 2);
465             } else {
466                 $opt_name = $option[0];
467             }
468             $this->options[$opt_name] = $option[1];
469         }
470
471         $this->args = $options[1];
472     }
473
474     function has($option) {
475         return array_key_exists($option, $this->options);
476     }
477
478     function get($option) {
479         if ( isset($this->options[$option]) ) {
480             return ( $this->options[$option] ) ;
481         }
482     }
483
484     function arg($index) {
485         if ( isset($this->args[$index]) ) {
486             return $this->args[$index];
487         }
488     }
489
490     function numArgs() {
491         return count($this->args);
492     }
493
494     function hasArgs() {
495         return count($this->args) !== 0;
496     }
497
498     function isError() {
499         return false;
500     }
501
502 }