Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
 
3
/**
4
 * Console Getopt
5
 *
6
 * All rights reserved.
7
 * Redistribution and use in source and binary forms, with or without modification,
8
 * are permitted provided that the following conditions are met:
9
 * + Redistributions of source code must retain the above copyright notice,
10
 * this list of conditions and the following disclaimer.
11
 * + Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation and/or
13
 * other materials provided with the distribution.
14
 * + The names of its contributors may not be used to endorse or promote
15
 * products derived from this software without specific prior written permission.
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 *
28
 * @category  Console
29
 * @package   Console_GetoptPlus
30
 * @author    Michel Corne <mcorne@yahoo.com>
31
 * @copyright 2008 Michel Corne
32
 * @license   http://www.opensource.org/licenses/bsd-license.php The BSD License
33
 * @version   SVN: $Id: Getopt.php 48 2008-01-10 15:32:56Z mcorne $
34
 * @link      http://pear.php.net/package/Console_GetoptPlus
35
 */
36
 
37
require_once 'Console/GetoptPlus/Exception.php';
38
 
39
/**
40
 * Parsing of a command line.
41
 *
42
 * See more examples in docs/examples.
43
 *
44
 * Code Example 1:
45
 * <code>
46
 * require_once 'Console/GetoptPlus.php';
47
 *
48
 * try {
49
 *    $shortOptions = "b:c::";
50
 *    $longOptions = array("foo", "bar=");
51
 *    $options = Console_Getoptplus::getopt($config, $shortOptions, $longOptions);
52
 *    // some processing here...
53
 *    print_r($options);
54
 * }
55
 * catch(Console_GetoptPlus_Exception $e) {
56
 *    $error = array($e->getCode(), $e->getMessage());
57
 *    print_r($error);
58
 * }
59
 * </code>
60
 *
61
 * Code Example 2:
62
 * <code>
63
 * require_once 'Console/GetoptPlus/Getopt.php';
64
 *
65
 * try {
66
 *    $shortOptions = "b:c::";
67
 *    $longOptions = array("foo", "bar=");
68
 *    $options = Console_GetoptPlus_Getopt::getopt($config, $shortOptions, $longOptions);
69
 *    // some processing here...
70
 *    print_r($options);
71
 * }
72
 * catch(Console_GetoptPlus_Exception $e) {
73
 *    $error = array($e->getCode(), $e->getMessage());
74
 *    print_r($error);
75
 * }
76
 * </code>
77
 *
78
 * Run:
79
 * <pre>
80
 * #xyz --foo -b car -c
81
 * #xyz --foo -b car -c param1
82
 * #xyz --foo -b car -cbus param1
83
 * #xyz --foo -b car -c=bus param1
84
 * #xyz --bar car param1 param2
85
 * #xyz --bar car -- param1 param2
86
 * #xyz --bar=car param1 param2
87
 * </pre>
88
 *
89
 * @category  Console
90
 * @package   Console_GetoptPlus
91
 * @author    Michel Corne <mcorne@yahoo.com>
92
 * @copyright 2008 Michel Corne
93
 * @license   http://www.opensource.org/licenses/bsd-license.php The BSD License
94
 * @version   Release:
95
 * @package   _version@
96
 * @link      http://pear.php.net/package/Console_GetoptPlus
97
 * @see       Console_Getopt
98
 */
99
class Console_GetoptPlus_Getopt
100
{
101
    /**
102
     * The list of ambigous option names
103
     *
104
     * @var    array
105
     * @access private
106
     */
107
    private $ambigous;
108
 
109
    /**
110
     * The command arguments
111
     *
112
     * @var    array
113
     * @access private
114
     */
115
    private $args;
116
 
117
    /**
118
     * The long option names
119
     *
120
     * @var    array
121
     * @access private
122
     */
123
    private $longOptionsDef;
124
 
125
    /**
126
     * The parsed options
127
     *
128
     * @var    array
129
     * @access private
130
     */
131
    private $options;
132
 
133
    /**
134
     * The option shortcut names
135
     *
136
     * @var    array
137
     * @access private
138
     */
139
    private $shortcuts;
140
 
141
    /**
142
     * The short option names and their definition
143
     *
144
     * @var    array
145
     * @access private
146
     */
147
    private $shortOptionsDef;
148
 
149
    /**
150
     * The option types
151
     *
152
     * @var    array
153
     * @access private
154
     */
155
    private $type = array(// /
156
        false => 'noarg',
157
        '=' => 'mandatory', ':' => 'mandatory',
158
        '==' => 'optional', '::' => 'optional',
159
        );
160
 
161
    /**
162
     * Creates the option shorcut names
163
     *
164
     * @param  array  $longOptionsDef the long option names
165
     * @param  string $ambiguity      directive to handle option names ambiguity
166
     * @return array  the option shorcuts and the ambigous options
167
     * @access public
168
     */
169
    public function createShorcuts($longOptionsDef, $ambiguity)
170
    {
171
        $shortcuts = array();
172
        $ambigous = array();
173
 
174
        if ($ambiguity == 'shortcuts') {
175
            foreach(array_keys($longOptionsDef) as $name) {
176
                // splits the option name in characters to build the name
177
                // substring combinations, e.g. foo => f, fo, foo
178
                $subName = '';
179
                foreach(str_split($name) as $char) {
180
                    $subName .= $char;
181
 
182
                    if (isset($ambigous[$subName])) {
183
                        // adds the shortcut to the list of ambigous shortcuts
184
                        $ambigous[$subName][] = $name;
185
                    } else if (isset($shortcuts[$subName])) {
186
                        // there is already a shortcut, adds the previous one
187
                        // and the current one in the list of ambigous shortcuts
188
                        $ambigous[$subName] = array($shortcuts[$subName], $name);
189
                        unset($shortcuts[$subName]);
190
                    } else {
191
                        // creates the shorcut entry
192
                        $shortcuts[$subName] = $name;
193
                    }
194
                }
195
            }
196
            // checks if some options are ambigous, e.g. --foo --foobar
197
            $names = array_intersect_key($longOptionsDef, $ambigous) and
198
            self::exception('ambigous', key($names));
199
        }
200
 
201
        return array($shortcuts, $ambigous);
202
    }
203
 
204
    /**
205
     * Parses the command line
206
     *
207
     * See getopt() for a complete description.
208
     *
209
     * @param  numeric $version      the getopt version: 1 or 2
210
     * @param  array   $args         the arguments
211
     * @param  string  $shortOptions the short options definition, e.g. "ab:c::"
212
     * @param  array   $longOptions  the long options definition
213
     * @param  string  $ambiguity    directive to handle option names ambiguity
214
     * @return array   the parsed options, their arguments and parameters
215
     * @access public
216
     * @static
217
     */
218
    public static function doGetopt($version = null, $args = array(),
219
        $shortOptions = '', $longOptions = array(), $ambiguity = '')
220
    {
221
        $getopt = new self;
222
 
223
        return $getopt->process($args, $shortOptions, $longOptions,
224
            $ambiguity, $version);
225
    }
226
 
227
    /**
228
     * Wraps the exception call
229
     *
230
     * @return void
231
     * @access private
232
     * @throws Console_GetoptPlus_Exception Exception
233
     * @static
234
     */
235
    private static function exception()
236
    {
237
        $error = func_get_args();
238
        throw new Console_GetoptPlus_Exception($error);
239
    }
240
 
241
    /**
242
     * Parses the command line
243
     *
244
     * See the definition/example in the class Doc Block.
245
     *
246
     * Example: returning an index array
247
     * <code>
248
     * array(
249
     *      [0] => array("foo" => null, "bar" => "car", "c" => null),
250
     *      [1] => array([0] => "param1", [1] => "param2")
251
     * );
252
     * </code>
253
     *
254
     * @param  array  $args         the arguments
255
     * @param  string $shortOptions the short options definition, e.g. "ab:c::"
256
     *                              <ul>
257
     *                              <li>":" : the option requires an argument</li>
258
     *                              <li>"::" : the option accepts an optional argument</li>
259
     *                              <li>otherwise the option accepts no argument</li>
260
     *                              </ul>
261
     * @param  array  $longOptions  the long options definition,
262
     *                              e.g. array("art", "bar=", "car==)
263
     *                              <ul>
264
     *                              <li>"=" : the option requires an argument</li>
265
     *                              <li>"==" : the option accepts an optional argument</li>
266
     *                              <li>otherwise the option accepts no argument</li>
267
     *                              </ul>
268
     * @param  string $ambiguity    directive to handle option names ambiguity,
269
     *                              e.g. "--foo" and "--foobar":
270
     *                              <ul>
271
     *                              <li>"loose": allowed if "--foo" does not
272
     *                              accept an argument, this is the default
273
     *                              behaviour</li>
274
     *                              <li>"strict": no ambiguity allowed</li>
275
     *                              <li>"shortcuts": implies "strict", the use of
276
     *                              partial option names is allowed,
277
     *                              e.g. "--f" or "--fo" instead of "--foo"</li>
278
     *                              </ul>
279
     * @return array  the parsed options, their arguments and parameters
280
     * @access public
281
     * @static
282
     */
283
    public static function getopt($args = array(), $shortOptions = '',
284
        $longOptions = array(), $ambiguity = '')
285
    {
286
        return self::doGetopt(1, $args, $shortOptions, $longOptions, $ambiguity);
287
    }
288
 
289
    /**
290
     * Parses the command line
291
     *
292
     * See getopt() for a complete description.
293
     *
294
     * @param  array  $args         the arguments
295
     * @param  string $shortOptions the short options definition, e.g. "ab:c::"
296
     * @param  array  $longOptions  the long options definition
297
     * @param  string $ambiguity    directive to handle option names ambiguity
298
     * @return array  the parsed options, their arguments and parameters
299
     * @access public
300
     * @static
301
     */
302
    public static function getopt2($args = array(), $shortOptions = '',
303
        $longOptions = array(), $ambiguity = '')
304
    {
305
        return self::doGetopt(2, $args, $shortOptions, $longOptions, $ambiguity);
306
    }
307
 
308
    /**
309
     * Checks if the argument is an option
310
     *
311
     * @param  string  $argument the argument, e.g. "-f" or "--foo"
312
     * @return boolean true if an option, false otherwise
313
     * @access public
314
     */
315
    public function isOption($argument)
316
    {
317
        return (bool)preg_match('~^(-\w|--\w+)$~', $argument);
318
    }
319
 
320
    /**
321
     * Parses a long option
322
     *
323
     * @param  string $argument the option and argument (excluding the "--" prefix),
324
     *                          e.g. "file=foo.php", "file foo.php", "bar"
325
     * @return void
326
     * @access public
327
     */
328
    public function parseLongOption($argument)
329
    {
330
        $option = explode('=', $argument, 2);
331
        $name = current($option);
332
        $arg = next($option) or $arg = null;
333
        // verifies the option is valid
334
        isset($this->ambigous[$name]) and self::exception('ambigous', $name);
335
        isset($this->shortcuts[$name]) and $name = $this->shortcuts[$name] or
336
        isset($this->longOptionsDef[$name]) or self::exception('unrecognized', $name);
337
 
338
        if ($this->longOptionsDef[$name] == 'mandatory') {
339
            // the option requires an argument, e.g. --file=foo.php
340
            // tries the next argument if necessary, e.g. --file foo.php
341
            is_null($arg) and list(, $arg) = each($this->args);
342
            is_null($arg) and self::exception('mandatory', $name);
343
            // verifies the argument is not an option itself
344
            $this->isOption($arg) and self::exception('mandatory', $name);
345
        } else if ($this->longOptionsDef[$name] == 'noarg' and !is_null($arg)) {
346
            // the option may not take an optional argument
347
            self::exception('noargument', $name);
348
        }
349
        // capture the option and its argument
350
        $this->options[] = array('--' . $name, $arg);
351
    }
352
 
353
    /**
354
     * Parses the long option names and types
355
     *
356
     * @param  array  $options the long options, e.g. array("foo", "bar=")
357
     * @return array  the options name and type,
358
     *                e.g. array("foo"=>"noarg", "bar"=>"mandatory")
359
     * @access public
360
     */
361
    public function parseLongOptionsDef($options)
362
    {
363
        // converts to an array if there is only one option
364
        settype($options, 'array');
365
 
366
        $longOptionsDef = array();
367
        foreach($options as $option) {
368
            if ($option = trim($option)) {
369
                // extracts the option name and type:
370
                // optional argument (==), mandatory (=), or none (null)
371
                // verifies the option syntax is correct
372
                preg_match("~^(\w+)(==|=)?$~", $option, $match) or
373
                self::exception('invalid', $option);
374
                $name = next($match);
375
                $type = next($match);
376
                // verifies the option is not a duplicate
377
                isset($longOptionsDef[$name]) and self::exception('duplicate', $name);
378
                // captures the option name and type
379
                $longOptionsDef[$name] = $this->type[$type];
380
            }
381
        }
382
 
383
        return $longOptionsDef;
384
    }
385
 
386
    /**
387
     * Parses a short option
388
     *
389
     * @param  string $argument the option and argument (excluding the "-" prefix),
390
     *                          e.g. "zfoo.php", "z foo.php", "z".
391
     * @return void
392
     * @access public
393
     */
394
    public function parseShortOption($argument)
395
    {
396
        for ($i = 0; $i < strlen($argument); $i++) {
397
            $name = $argument{$i};
398
            $arg = null;
399
            // verifies the option is valid
400
            isset($this->shortOptionsDef[$name]) or self::exception('unrecognized', $name);
401
 
402
            if ($this->shortOptionsDef[$name] == 'optional') {
403
                // the option may take an optional argument, e.g. -zfoo.php or -z
404
                if (($arg = substr($argument, $i + 1)) !== false) {
405
                    // the remainder of the string is the option argument
406
                    $this->options[] = array($name, $arg);
407
                    return;
408
                }
409
            } else if ($this->shortOptionsDef[$name] == 'mandatory') {
410
                // the option requires an argument, -zfoo.php or -z foo.php
411
                if (($arg = substr($argument, $i + 1)) === false) {
412
                    // nothing left to use as the option argument
413
                    // the next argument is expected to be the option argument
414
                    // verifies there is one and it is not an option itself
415
                    list(, $arg) = each($this->args);
416
                    (is_null($arg) or $this->isOption($arg)) and
417
                    self::exception('mandatory', $name);
418
                }
419
                $this->options[] = array($name, $arg);
420
                return;
421
            }
422
            // else: the option is not expecting an argument, e.g. -h
423
            // TODO: verify that if followed by a non option which is interpreted
424
            // as the end of options, there is indeed no option after until
425
            // possibly -- or -
426
            // capture the option and its argument
427
            $this->options[] = array($name, $arg);
428
        }
429
    }
430
 
431
    /**
432
     * Parses the short option names and types
433
     *
434
     * @param  string $options the short options, e.g. array("ab:c::)
435
     * @return array  the options name and type,
436
     *                e.g. array("a"=>"noarg", "b"=>"mandatory", "c"=>"optional")
437
     * @access public
438
     */
439
    public function parseShortOptionsDef($options)
440
    {
441
        // expecting a string for a the short options definition
442
        is_array($options) and self::exception('string');
443
        // trims and extracts the options name and type
444
        // optional argument (::), mandatory (:), or none (null)
445
        $options = trim($options);
446
        preg_match_all("~(\w)(::|:)?~", $options, $matches, PREG_SET_ORDER);
447
 
448
        $check = '';
449
        $shortOptionsDef = array();
450
        foreach($matches as $match) {
451
            $check .= current($match);
452
            $name = next($match);
453
            $type = next($match);
454
            // verifies the option is not a duplicate
455
            isset($shortOptionsDef[$name]) and self::exception('duplicate', $name);
456
            // captures the option name and type
457
            $shortOptionsDef[$name] = $this->type[$type];
458
        }
459
        // checks there is no syntax error the short options definition
460
        $check == $options or self::exception('syntax', $name);
461
 
462
        return $shortOptionsDef;
463
    }
464
 
465
    /**
466
     * Parses the command line
467
     *
468
     * See getopt() for a complete description.
469
     *
470
     * @param  array   $args         the arguments
471
     * @param  string  $shortOptions the short options definition, e.g. "ab:c::"
472
     * @param  array   $longOptions  the long options definition, e.g. array("foo", "bar=")
473
     * @param  string  $ambiguity    directive to handle option names ambiguity
474
     * @param  numeric $version      the getopt version: 1 or 2
475
     * @return array   the parsed options, their arguments and parameters
476
     * @access public
477
     */
478
    public function process($args = array(), $shortOptions, $longOptions,
479
        $ambiguity = '', $version = 2)
480
    {
481
        settype($args, 'array');
482
        in_array($ambiguity, array('loose', 'strict', 'shortcuts')) or
483
        $ambiguity = 'loose';
484
 
485
        if ($version < 2) {
486
            // preserve backwards compatibility with callers
487
            // that relied on erroneous POSIX fix
488
            // note: ported from Console/Getopt
489
            isset($args[0]) and substr($args[0], 0, 1) != '-' and array_shift($args);
490
            settype($args, 'array');
491
        }
492
        $this->args = $args;
493
        // parses the options definitions, create shorcuts or check ambiguities
494
        $this->shortOptionsDef = $this->parseShortOptionsDef($shortOptions);
495
        $this->longOptionsDef = $this->parseLongOptionsDef($longOptions);
496
        list($this->shortcuts, $this->ambigous) = $this->createShorcuts($this->longOptionsDef, $ambiguity);
497
        $this->verifyNoAmbiguity($this->longOptionsDef, $ambiguity);
498
 
499
        $this->options = array();
500
        $parameters = array();
501
        while (list($i, $arg) = each($this->args)) {
502
            if ($arg == '--') {
503
                // end of options
504
                // the remaining arguments are parameters excluding this one
505
                $parameters = array_slice($this->args, $i + 1);
506
                break;
507
            } else if ($arg == '-') {
508
                // the stdin flag
509
                // the remaining arguments are parameters including this one
510
                $parameters = array_slice($this->args, $i);
511
                break;
512
            } else if (substr($arg, 0, 2) == '--') {
513
                // a long option, e.g. --foo
514
                if ($this->longOptionsDef) {
515
                    $this->parseLongOption(substr($arg, 2));
516
                } else {
517
                    // not expecting long options, the remaining arguments are
518
                    // parameters including this one stripped off of --
519
                    $parameters = array_slice($this->args, $i);
520
                    $parameters[0] = substr($parameters[0], 2);
521
                    break;
522
                }
523
            } else if ($arg{0} == '-') {
524
                // a short option, e.g. -h
525
                $this->parseShortOption(substr($arg, 1));
526
            } else {
527
                // the first non option
528
                // the remaining arguments are parameters including this one
529
                $parameters = array_slice($this->args, $i);
530
                break;
531
            }
532
        }
533
 
534
        return array($this->options, $parameters);
535
    }
536
 
537
    /**
538
     * Reads the command arguments
539
     *
540
     * @return array  the arguments
541
     * @access public
542
     * @static
543
     */
544
    public static function readPHPArgv()
545
    {
546
        global $argv;
547
 
548
        is_array($args = $argv) or
549
        is_array($args = $_SERVER['argv']) or
550
        is_array($args = $GLOBALS['HTTP_SERVER_VARS']['argv']) or
551
        self::exception('noargs');
552
 
553
        return $args;
554
    }
555
 
556
    /**
557
     * Verifies there is no ambiguity with option names
558
     *
559
     * @param  array   $longOptionsDef the long options names and their types
560
     * @param  string  $ambiguity      directive to handle option names ambiguity,
561
     *                                 See getopt() for a complete description
562
     * @return boolean no ambiguity if true, false otherwise
563
     * @access public
564
     */
565
    public function verifyNoAmbiguity($longOptionsDef, $ambiguity)
566
    {
567
        settype($longOptionsDef, 'array');
568
 
569
        foreach($longOptionsDef as $name => $type) {
570
            foreach($longOptionsDef as $name2 => $type2) {
571
                if ($name != $name2) {
572
                    if ($ambiguity == 'loose' and $type == 'noarg') {
573
                        // according to Getopt.php, CVS v 1.4 2007/06/12,
574
                        // _parseLongOption(), line #236, the possible
575
                        // ambiguity of a long option name with another one is
576
                        // ignored if this option does not expect an argument!
577
                        continue;
578
                    }
579
                    // checks options are not ambigous, e.g. --foo --foobar
580
                    strpos($name2, $name) === false or self::exception('ambigous', $name);
581
                }
582
                // else: there is no ambiguity between an option and itself!
583
            }
584
        }
585
 
586
        return true;
587
    }
588
}
589
 
590
?>