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
 * Command-line option parser - Console Getopt+ (Getopt Plus)
5
 *
6
 * This package is (1) a PHP5 port/rewrite of Console_Getopt, (2) with added
7
 * functionalities, and (3) with a Web interface to run getopt-like shell
8
 * commands through a browser (not implemented yet).
9
 *
10
 * (1) Console_getoptPlus:getopt() is a replacement for Console_getopt:getopt().
11
 * Same for getopt2() and readPHPArgv(). It returns PEAR_Exception instead of
12
 * PEAR_Error. Error messages are the same.
13
 *
14
 * (2) GetoptPlus:getoptplus uses an array-based description of the command. It can
15
 * generates the command usage/help automaticly. It can return the parsed
16
 * options and parameters in an associative array. It can be set to accept
17
 * option shortcut names.
18
 *
19
 * Fully tested with phpUnit. Code coverage test close to 100%.
20
 *
21
 * Usage is fully documented in docs/examples files.
22
 *
23
 * PHP version 5
24
 *
25
 * All rights reserved.
26
 * Redistribution and use in source and binary forms, with or without modification,
27
 * are permitted provided that the following conditions are met:
28
 * + Redistributions of source code must retain the above copyright notice,
29
 * this list of conditions and the following disclaimer.
30
 * + Redistributions in binary form must reproduce the above copyright notice,
31
 * this list of conditions and the following disclaimer in the documentation and/or
32
 * other materials provided with the distribution.
33
 * + The names of its contributors may not be used to endorse or promote
34
 * products derived from this software without specific prior written permission.
35
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
39
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
40
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
41
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
42
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
43
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
44
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
45
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46
 *
47
 * @category  Console
48
 * @package   Console_GetoptPlus
49
 * @author    Michel Corne <mcorne@yahoo.com>
50
 * @copyright 2008 Michel Corne
51
 * @license   http://www.opensource.org/licenses/bsd-license.php The BSD License
52
 * @version   SVN: $Id: GetoptPlus.php 50 2008-01-11 09:01:57Z mcorne $
53
 * @link      http://pear.php.net/package/Console_GetoptPlus
54
 */
55
 
56
require_once 'Console/GetoptPlus/Getopt.php';
57
require_once 'Console/GetoptPlus/Help.php';
58
 
59
/**
60
 * Parsing of a command line based on the command description in an array.
61
 * See more examples in docs/examples.
62
 *
63
 * Code Example:
64
 * <code>
65
 * require_once 'Console/GetoptPlus.php';
66
 *
67
 * try {
68
 *    $config = array(
69
 *      'header' => array('The command xyz is used to...',
70
 *        'Note that the header and the usage are optional.'),
71
 *      'usage' => array('--foo', '--bar <arg> -c [arg]'),
72
 *      'options' => array(
73
 *        array('long' => 'foo', 'type' => 'noarg', 'desc' => array(
74
 *          'An option without argument with only the long',
75
 *          'name defined.')),
76
 *        array('long' => 'bar', 'type' => 'mandatory', 'short' => 'b',
77
 *          'desc' => array('arg',
78
 *            'A mandatory option with both the long and',
79
 *            'the short names defined.')),
80
 *        array('short' => 'c', 'type' => 'optional',
81
 *          'desc' => array('arg',
82
 *            'An option with an optional argument with only',
83
 *            'the short name defined.'))),
84
 *        'parameters' => array('[param1] [param2]',
85
 *          'Some additional parameters.'),
86
 *        'footer' => array('Some additional information.',
87
 *          'Note that the footer is optional.'),
88
 *    );
89
 *
90
 * $options = Console_Getoptplus::getoptplus($config);
91
 * // some processing here...
92
 * print_r($options);
93
 * }
94
 * catch(Console_GetoptPlus_Exception $e) {
95
 *    $error = array($e->getCode(), $e->getMessage());
96
 *    print_r($error);
97
 * }
98
 * </code>
99
 *
100
 * Run:
101
 * <pre>
102
 * #xyz --help
103
 * #xyz -h
104
 * #xyz --foo -b car -c
105
 * #xyz --foo -b car -c param1
106
 * #xyz --foo -b car -cbus param1
107
 * #xyz --foo -b car -c=bus param1
108
 * #xyz --bar car param1 param2
109
 * #xyz --bar car -- param1 param2
110
 * #xyz --bar=car param1 param2
111
 * </pre>
112
 *
113
 * @category  Console
114
 * @package   Console_GetoptPlus
115
 * @author    Michel Corne <mcorne@yahoo.com>
116
 * @copyright 2008 Michel Corne
117
 * @license   http://www.opensource.org/licenses/bsd-license.php The BSD License
118
 * @version   Release:
119
 * @package   _version@
120
 * @link      http://pear.php.net/package/Console_GetoptPlus
121
 * @todo      create a Web interface to getopt-like shell command
122
 * @todo      optionally split long usage lines to fit within 80 columns
123
 * @todo      optionally change the start position of the options usage
124
 */
125
class Console_GetoptPlus extends Console_GetoptPlus_Getopt
126
{
127
    /**
128
     * The long name to short option name cross-references
129
     *
130
     * @var    array
131
     * @access private
132
     */
133
    private $long2short = array();
134
 
135
    /**
136
     * The short name to long option name cross-references
137
     *
138
     * @var    array
139
     * @access private
140
     */
141
    private $short2long = array();
142
 
143
    /**
144
     * Verifies the option settings are valid
145
     *
146
     * Adds the "help" option if missing.
147
     *
148
     * @param  array  $config the command configuration, see the configuration
149
     *                        definition/example in the class Doc Block
150
     * @return array  the options configurations, the updated 'options' subarray,
151
     *                e.g. $config['options']
152
     * @access public
153
     */
154
    public function checkOptionsConfig($config)
155
    {
156
        $optionsConfig = empty($config['options'])? array() : $config['options'];
157
 
158
        $isHelp = false;
159
        $isH = false;
160
 
161
        foreach($optionsConfig as $idx => &$option) {
162
            // verifies there is at least a short or long option name
163
            isset($option['long']) or isset($option['short']) or
164
            self::exception('missing', $idx);
165
 
166
            if (isset($option['long']) and isset($option['short'])) {
167
                // creates cross-references between long and short options names
168
                $this->short2long[$option['short']] = $option['long'];
169
                $this->long2short[$option['long']] = $option['short'];
170
            }
171
 
172
            if (isset($option['type'])) {
173
                // the option has a type, checks it is valid
174
                $type = $option['type'];
175
                in_array($type, array('noarg', 'mandatory', 'optional')) or
176
                self::exception('type', $type);
177
            } else {
178
                // defaults to no argument
179
                $option['type'] = 'noarg';
180
            }
181
            // determines if --help is provided
182
            $isHelp or isset($option['long']) and
183
            $option['long'] == 'help' and $isHelp = true;
184
            // determines if -h is used
185
            $isH or isset($option['short']) and
186
            $option['short'] == 'h' and $isH = true;
187
        }
188
 
189
        if (!$isHelp) {
190
            // no help option, adds the default --help, and -h if unused
191
            $help = array('long' => 'help', 'type' => 'noarg', 'desc' => 'This help.');
192
            $isH or $help['short'] = 'h';
193
            $optionsConfig[] = $help;
194
 
195
            if (isset($help['long']) and isset($help['short'])) {
196
                $this->short2long[$help['short']] = $help['long'];
197
                $this->long2short[$help['long']] = $help['short'];
198
            }
199
        }
200
 
201
        return $optionsConfig;
202
    }
203
 
204
    /**
205
     * Extracts the long or short option names and types
206
     *
207
     * Validates the option name against the pattern. A short option name has
208
     * a single letter. A long option name has one of more alphanumerical
209
     * letters.
210
     *
211
     * @param  array  $optionsConfig the options configurations, see the
212
     *                               configuration definition/example in the
213
     *                               class Doc Block, e.g. $config['options']
214
     * @param  string $defType       the option name type: "short" or "long"
215
     * @param  string $pattern       the validation pattern
216
     * @return array  the option names list, e.g. array("a" => "noarg", ...)
217
     * @access public
218
     */
219
    public function createOptionsDef($optionsConfig, $defType, $pattern)
220
    {
221
        $optionsDef = array();
222
 
223
        foreach($optionsConfig as $option) {
224
            if (isset($option[$defType])) {
225
                // the option has a name
226
                $name = $option[$defType];
227
                // checks the option name syntax is valid and is not a duplicate
228
                preg_match($pattern, $name) or self::exception('invalid', $option);
229
                isset($duplicates[$name]) and self::exception('duplicate', $name);
230
 
231
                $duplicates[$name] = true;
232
                $optionsDef[$name] = $option['type'];
233
            }
234
        }
235
 
236
        return $optionsDef;
237
    }
238
 
239
    /**
240
     * Wraps the exception call
241
     *
242
     * @return void
243
     * @access private
244
     * @throws Console_GetoptPlus_Exception Exception
245
     * @static
246
     */
247
    private static function exception()
248
    {
249
        $error = func_get_args();
250
        throw new Console_GetoptPlus_Exception($error);
251
    }
252
 
253
    /**
254
     * Parses the command line
255
     *
256
     * See the configuration definition/example in the class Doc Block.
257
     *
258
     * Example 1: returning an index array
259
     * <code>
260
     * array(
261
     *    [0] => array(
262
     *      [0] => array([0] => "--foo", [1] => null),
263
     *      [1] => array([0] => "b", [1] => "car"),
264
     *      [2] => array([0] => "c", [1] => null)),
265
     *    [1] => array([0] => "param1", [1] => "param2")
266
     * );
267
     * </code>
268
     *
269
     * Example 2: returning an associative array
270
     * <code>
271
     * array(
272
     *    [0] => array("foo" => null, "bar" => "car", "c" => null),
273
     *    [1] => array([0] => "param1", [1] => "param2")
274
     * );
275
     * </code>
276
     *
277
     * @param  array   $config      the command configuration, see the configuration
278
     *                              definition/example in the class Doc Block
279
     * @param  string  $convertName returns short option names if set to
280
     *                              "long2short", long ones if set to "short2long",
281
     *                              as in the command line by default
282
     * @param  boolean $returnAssoc returns an associative array if true,
283
     *                              an index array if false
284
     * @param  string  $ambiguity   directive to handle option names ambiguity,
285
     *                              e.g. "--foo" and "--foobar":
286
     *                              <ul>
287
     *                              <li>"loose": allowed if "--foo" does not
288
     *                              accept an argument, this is the default
289
     *                              behaviour</li>
290
     *                              <li>"strict": no ambiguity allowed</li>
291
     *                              <li>"shortcuts": implies "strict", the use of
292
     *                              partial option names is allowed,
293
     *                              e.g. "--f" or "--fo" instead of "--foo"</li>
294
     *                              </ul>
295
     * @param  boolean $exitHelp    if "--help" is one of the options:
296
     *                              <ul>
297
     *                              <li>true: displays the command usage and exits</li>
298
     *                              <li>false: returns the command usage as:
299
     *                              <ul>
300
     *                              <li>an index array, e.g.
301
     *                              array([0] => array([0] => array("h", "Usage:...")))</li>
302
     *                              <li>an associative, e.g.
303
     *                              array([0] => array("h" => "Usage:..."))</li>
304
     *                              </ul></li>
305
     *                              </ul>
306
     * @return array   the parsed options, their arguments and parameters
307
     * @access public
308
     * @static
309
     */
310
    public static function getoptplus($config = array(), $convertName = '',
311
        $returnAssoc = false, $ambiguity = '', $exitHelp = true)
312
    {
313
        $getopt = new self;
314
 
315
        return $getopt->process($config, $convertName, $returnAssoc,
316
            $ambiguity, $exitHelp);
317
    }
318
 
319
    /**
320
     * Parses the long option names and types
321
     *
322
     * Verifies the option names have one of more alphanumerical characters.
323
     *
324
     * @param  array  $optionsConfig the options configurations, see the
325
     *                               configuration definition/example in the
326
     *                               class Doc Block, e.g. $config['options']
327
     * @return array  the option names list, e.g. array("foo" => "noarg", ...)
328
     * @access public
329
     */
330
    public function parseLongOptionsDef($optionsConfig)
331
    {
332
        return $this->createOptionsDef($optionsConfig, 'long', '~^\w+$~');
333
    }
334
 
335
    /**
336
     * Parses the short option names and types
337
     *
338
     * Verifies the option names have one alphanumerical character.
339
     *
340
     * @param  array  $optionsConfig the options configurations, see the
341
     *                               configuration definition/example in the
342
     *                               class Doc Block, e.g. $config['options']
343
     * @return array  the option names list, e.g. array("f" => "noarg", ...)
344
     * @access public
345
     */
346
    public function parseShortOptionsDef($optionsConfig)
347
    {
348
        return $this->createOptionsDef($optionsConfig, 'short', '~^\w$~');
349
    }
350
 
351
    /**
352
     * Parses the command line
353
     *
354
     * See getoptplus() for a complete description.
355
     *
356
     * @param  array   $config      the command configuration
357
     * @param  string  $convertName returns short option names if set to
358
     *                              "long2short", long ones if set to "short2long",
359
     *                              as in the command line by default
360
     * @param  boolean $returnAssoc returns an associative array if true,
361
     *                              an index array if false
362
     * @param  string  $ambiguity   directive to handle option names ambiguity:
363
     *                              "loose", "strict", or "shortcuts"
364
     * @param  boolean $exitHelp    same as getoptplus()
365
     * @return array   the parsed options, their arguments and parameters
366
     * @access public
367
     */
368
    public function process($config = array(), $convertName = '',
369
        $returnAssoc = false, $ambiguity = '', $exitHelp = true)
370
    {
371
        // extracts the command arguments, including the command name
372
        $args = self::readPHPArgv();
373
        $command = array_shift($args);
374
        // checks the options configurations, parses the command
375
        $config['options'] = $optionsConfig = $this->checkOptionsConfig($config);
376
        $options = parent::process($args, $optionsConfig, $optionsConfig, $ambiguity);
377
        // tidies the options
378
        $options[0] = $this->tidyOptions($options[0], $convertName, $returnAssoc);
379
 
380
        if (is_string($options[0])) {
381
            // a request for help, builds the command usage,
382
            $help = Console_GetoptPlus_Help::get($config, $command);
383
            // exits/displays the command usage or returns it
384
            $exitHelp and exit($help);
385
            $name = $options[0];
386
            $options[0] = $returnAssoc? array($name => $help) : array(array($name, $help));
387
        }
388
 
389
        return $options;
390
    }
391
 
392
    /**
393
     * Tidies the command arguments
394
     *
395
     * See getoptplus() for a complete description of the returned options.
396
     *
397
     * @param  array   $options     the parsed options arguments, e.g.
398
     *                              <pre>
399
     *                              array(
400
     *                              [0] => array([0] => "--foo", [1] => null),
401
     *                              [1] => array([0] => "b", [1] => "car"),
402
     *                              [2] => array([0] => "c", [1] => null))
403
     *                              >/pre>
404
     * @param  string  $convertName returns short option names if set to
405
     *                              "long2short", long ones if set to "short2long",
406
     *                              as in the command line by default
407
     * @param  boolean $returnAssoc returns an associative array if true,
408
     *                              an index array if false
409
     * @return array   the tidied options arguments
410
     * @access public
411
     */
412
    public function tidyOptions($options, $convertName = '', $returnAssoc = false)
413
    {
414
        // verifies the conversion is valid
415
        empty($convertName) or $convertName == 'long2short' or
416
        $convertName == 'short2long' or self::exception('convert', $convertName);
417
 
418
        $tidied = array();
419
        foreach($options as $option) {
420
            // extracs the option name and value, removes the long option prefix
421
            list($name, $value) = $option;
422
            $isLong = substr($name, 0, 2) == '--' and $name = substr($name, 2);
423
 
424
            if ($isLong) {
425
                // a long option
426
                if ($name == 'help') {
427
                    // the help option
428
                    if ($convertName == 'long2short' and isset($this->long2short['help'])) {
429
                        return $this->long2short['help'];
430
                    } else {
431
                        return '--help';
432
                    }
433
                }
434
                // converts to a short option if requested and possible
435
                if ($convertName == 'long2short' and isset($this->long2short[$name])) {
436
                    $name = $this->long2short[$name];
437
                    $isLong = false;
438
                }
439
            } else {
440
                // a short option
441
                if ($convertName == 'short2long') {
442
                    // converts to a long one if possible
443
                    if (isset($this->short2long[$name])) {
444
                        $name = $this->short2long[$name];
445
                        $isLong = true;
446
                    }
447
 
448
                    if ($isLong and $name == 'help') {
449
                        // the help option
450
                        return '--help';
451
                    }
452
                } else if (isset($this->short2long[$name]) and
453
                        $this->short2long[$name] == 'help') {
454
                    // the help option
455
                    return $name;
456
                }
457
            }
458
 
459
            if ($returnAssoc) {
460
                // converts the arguments to an associative array with
461
                // the argument name as the key, converts a NULL values to an
462
                // empty string to make it easier to use with isset()
463
                $tidied[$name] = is_null($value)? '' : $value;
464
            } else {
465
                // leaves the arguments as per per Console_Getopt::doGetopt()
466
                // format and prefixes long options with --
467
                $isLong and $name = '--' . $name;
468
                $tidied[] = array($name, $value);
469
            }
470
        }
471
 
472
        return $tidied;
473
    }
474
}
475
 
476
?>