統計
| ブランチ: | リビジョン:

pictcode / lib / Cake / Console / ConsoleOptionParser.php @ 635eef61

履歴 | 表示 | アノテート | ダウンロード (19.988 KB)

1
<?php
2
/**
3
 * ConsoleOptionParser file
4
 *
5
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7
 *
8
 * Licensed under The MIT License
9
 * For full copyright and license information, please see the LICENSE.txt
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13
 * @link          http://cakephp.org CakePHP(tm) Project
14
 * @since         CakePHP(tm) v 2.0
15
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
16
 */
17

    
18
App::uses('TaskCollection', 'Console');
19
App::uses('ConsoleOutput', 'Console');
20
App::uses('ConsoleInput', 'Console');
21
App::uses('ConsoleInputSubcommand', 'Console');
22
App::uses('ConsoleInputOption', 'Console');
23
App::uses('ConsoleInputArgument', 'Console');
24
App::uses('ConsoleOptionParser', 'Console');
25
App::uses('HelpFormatter', 'Console');
26

    
27
/**
28
 * Handles parsing the ARGV in the command line and provides support
29
 * for GetOpt compatible option definition. Provides a builder pattern implementation
30
 * for creating shell option parsers.
31
 *
32
 * ### Options
33
 *
34
 * Named arguments come in two forms, long and short. Long arguments are preceded
35
 * by two - and give a more verbose option name. i.e. `--version`. Short arguments are
36
 * preceded by one - and are only one character long. They usually match with a long option,
37
 * and provide a more terse alternative.
38
 *
39
 * ### Using Options
40
 *
41
 * Options can be defined with both long and short forms. By using `$parser->addOption()`
42
 * you can define new options. The name of the option is used as its long form, and you
43
 * can supply an additional short form, with the `short` option. Short options should
44
 * only be one letter long. Using more than one letter for a short option will raise an exception.
45
 *
46
 * Calling options can be done using syntax similar to most *nix command line tools. Long options
47
 * cane either include an `=` or leave it out.
48
 *
49
 * `cake myshell command --connection default --name=something`
50
 *
51
 * Short options can be defined signally or in groups.
52
 *
53
 * `cake myshell command -cn`
54
 *
55
 * Short options can be combined into groups as seen above. Each letter in a group
56
 * will be treated as a separate option. The previous example is equivalent to:
57
 *
58
 * `cake myshell command -c -n`
59
 *
60
 * Short options can also accept values:
61
 *
62
 * `cake myshell command -c default`
63
 *
64
 * ### Positional arguments
65
 *
66
 * If no positional arguments are defined, all of them will be parsed. If you define positional
67
 * arguments any arguments greater than those defined will cause exceptions. Additionally you can
68
 * declare arguments as optional, by setting the required param to false.
69
 *
70
 * `$parser->addArgument('model', array('required' => false));`
71
 *
72
 * ### Providing Help text
73
 *
74
 * By providing help text for your positional arguments and named arguments, the ConsoleOptionParser
75
 * can generate a help display for you. You can view the help for shells by using the `--help` or `-h` switch.
76
 *
77
 * @package       Cake.Console
78
 */
79
class ConsoleOptionParser {
80

    
81
/**
82
 * Description text - displays before options when help is generated
83
 *
84
 * @see ConsoleOptionParser::description()
85
 * @var string
86
 */
87
        protected $_description = null;
88

    
89
/**
90
 * Epilog text - displays after options when help is generated
91
 *
92
 * @see ConsoleOptionParser::epilog()
93
 * @var string
94
 */
95
        protected $_epilog = null;
96

    
97
/**
98
 * Option definitions.
99
 *
100
 * @see ConsoleOptionParser::addOption()
101
 * @var array
102
 */
103
        protected $_options = array();
104

    
105
/**
106
 * Map of short -> long options, generated when using addOption()
107
 *
108
 * @var string
109
 */
110
        protected $_shortOptions = array();
111

    
112
/**
113
 * Positional argument definitions.
114
 *
115
 * @see ConsoleOptionParser::addArgument()
116
 * @var array
117
 */
118
        protected $_args = array();
119

    
120
/**
121
 * Subcommands for this Shell.
122
 *
123
 * @see ConsoleOptionParser::addSubcommand()
124
 * @var array
125
 */
126
        protected $_subcommands = array();
127

    
128
/**
129
 * Command name.
130
 *
131
 * @var string
132
 */
133
        protected $_command = '';
134

    
135
/**
136
 * Construct an OptionParser so you can define its behavior
137
 *
138
 * @param string $command The command name this parser is for. The command name is used for generating help.
139
 * @param bool $defaultOptions Whether you want the verbose and quiet options set. Setting
140
 *  this to false will prevent the addition of `--verbose` & `--quiet` options.
141
 */
142
        public function __construct($command = null, $defaultOptions = true) {
143
                $this->command($command);
144

    
145
                $this->addOption('help', array(
146
                        'short' => 'h',
147
                        'help' => __d('cake_console', 'Display this help.'),
148
                        'boolean' => true
149
                ));
150

    
151
                if ($defaultOptions) {
152
                        $this->addOption('verbose', array(
153
                                'short' => 'v',
154
                                'help' => __d('cake_console', 'Enable verbose output.'),
155
                                'boolean' => true
156
                        ))->addOption('quiet', array(
157
                                'short' => 'q',
158
                                'help' => __d('cake_console', 'Enable quiet output.'),
159
                                'boolean' => true
160
                        ));
161
                }
162
        }
163

    
164
/**
165
 * Static factory method for creating new OptionParsers so you can chain methods off of them.
166
 *
167
 * @param string $command The command name this parser is for. The command name is used for generating help.
168
 * @param bool $defaultOptions Whether you want the verbose and quiet options set.
169
 * @return ConsoleOptionParser
170
 */
171
        public static function create($command, $defaultOptions = true) {
172
                return new ConsoleOptionParser($command, $defaultOptions);
173
        }
174

    
175
/**
176
 * Build a parser from an array. Uses an array like
177
 *
178
 * ```
179
 * $spec = array(
180
 *                'description' => 'text',
181
 *                'epilog' => 'text',
182
 *                'arguments' => array(
183
 *                        // list of arguments compatible with addArguments.
184
 *                ),
185
 *                'options' => array(
186
 *                        // list of options compatible with addOptions
187
 *                ),
188
 *                'subcommands' => array(
189
 *                        // list of subcommands to add.
190
 *                )
191
 * );
192
 * ```
193
 *
194
 * @param array $spec The spec to build the OptionParser with.
195
 * @return ConsoleOptionParser
196
 */
197
        public static function buildFromArray($spec) {
198
                $parser = new ConsoleOptionParser($spec['command']);
199
                if (!empty($spec['arguments'])) {
200
                        $parser->addArguments($spec['arguments']);
201
                }
202
                if (!empty($spec['options'])) {
203
                        $parser->addOptions($spec['options']);
204
                }
205
                if (!empty($spec['subcommands'])) {
206
                        $parser->addSubcommands($spec['subcommands']);
207
                }
208
                if (!empty($spec['description'])) {
209
                        $parser->description($spec['description']);
210
                }
211
                if (!empty($spec['epilog'])) {
212
                        $parser->epilog($spec['epilog']);
213
                }
214
                return $parser;
215
        }
216

    
217
/**
218
 * Get or set the command name for shell/task.
219
 *
220
 * @param string $text The text to set, or null if you want to read
221
 * @return string|$this If reading, the value of the command. If setting $this will be returned.
222
 */
223
        public function command($text = null) {
224
                if ($text !== null) {
225
                        $this->_command = Inflector::underscore($text);
226
                        return $this;
227
                }
228
                return $this->_command;
229
        }
230

    
231
/**
232
 * Get or set the description text for shell/task.
233
 *
234
 * @param string|array $text The text to set, or null if you want to read. If an array the
235
 *   text will be imploded with "\n"
236
 * @return string|$this If reading, the value of the description. If setting $this will be returned.
237
 */
238
        public function description($text = null) {
239
                if ($text !== null) {
240
                        if (is_array($text)) {
241
                                $text = implode("\n", $text);
242
                        }
243
                        $this->_description = $text;
244
                        return $this;
245
                }
246
                return $this->_description;
247
        }
248

    
249
/**
250
 * Get or set an epilog to the parser. The epilog is added to the end of
251
 * the options and arguments listing when help is generated.
252
 *
253
 * @param string|array $text Text when setting or null when reading. If an array the text will be imploded with "\n"
254
 * @return string|$this If reading, the value of the epilog. If setting $this will be returned.
255
 */
256
        public function epilog($text = null) {
257
                if ($text !== null) {
258
                        if (is_array($text)) {
259
                                $text = implode("\n", $text);
260
                        }
261
                        $this->_epilog = $text;
262
                        return $this;
263
                }
264
                return $this->_epilog;
265
        }
266

    
267
/**
268
 * Add an option to the option parser. Options allow you to define optional or required
269
 * parameters for your console application. Options are defined by the parameters they use.
270
 *
271
 * ### Options
272
 *
273
 * - `short` - The single letter variant for this option, leave undefined for none.
274
 * - `help` - Help text for this option. Used when generating help for the option.
275
 * - `default` - The default value for this option. Defaults are added into the parsed params when the
276
 *    attached option is not provided or has no value. Using default and boolean together will not work.
277
 *    are added into the parsed parameters when the option is undefined. Defaults to null.
278
 * - `boolean` - The option uses no value, its just a boolean switch. Defaults to false.
279
 *    If an option is defined as boolean, it will always be added to the parsed params. If no present
280
 *    it will be false, if present it will be true.
281
 * - `choices` A list of valid choices for this option. If left empty all values are valid..
282
 *   An exception will be raised when parse() encounters an invalid value.
283
 *
284
 * @param ConsoleInputOption|string $name The long name you want to the value to be parsed out as when options are parsed.
285
 *   Will also accept an instance of ConsoleInputOption
286
 * @param array $options An array of parameters that define the behavior of the option
287
 * @return $this
288
 */
289
        public function addOption($name, $options = array()) {
290
                if (is_object($name) && $name instanceof ConsoleInputOption) {
291
                        $option = $name;
292
                        $name = $option->name();
293
                } else {
294
                        $defaults = array(
295
                                'name' => $name,
296
                                'short' => null,
297
                                'help' => '',
298
                                'default' => null,
299
                                'boolean' => false,
300
                                'choices' => array()
301
                        );
302
                        $options += $defaults;
303
                        $option = new ConsoleInputOption($options);
304
                }
305
                $this->_options[$name] = $option;
306
                if ($option->short() !== null) {
307
                        $this->_shortOptions[$option->short()] = $name;
308
                }
309
                return $this;
310
        }
311

    
312
/**
313
 * Add a positional argument to the option parser.
314
 *
315
 * ### Params
316
 *
317
 * - `help` The help text to display for this argument.
318
 * - `required` Whether this parameter is required.
319
 * - `index` The index for the arg, if left undefined the argument will be put
320
 *   onto the end of the arguments. If you define the same index twice the first
321
 *   option will be overwritten.
322
 * - `choices` A list of valid choices for this argument. If left empty all values are valid..
323
 *   An exception will be raised when parse() encounters an invalid value.
324
 *
325
 * @param ConsoleInputArgument|string $name The name of the argument. Will also accept an instance of ConsoleInputArgument
326
 * @param array $params Parameters for the argument, see above.
327
 * @return $this
328
 */
329
        public function addArgument($name, $params = array()) {
330
                if (is_object($name) && $name instanceof ConsoleInputArgument) {
331
                        $arg = $name;
332
                        $index = count($this->_args);
333
                } else {
334
                        $defaults = array(
335
                                'name' => $name,
336
                                'help' => '',
337
                                'index' => count($this->_args),
338
                                'required' => false,
339
                                'choices' => array()
340
                        );
341
                        $options = $params + $defaults;
342
                        $index = $options['index'];
343
                        unset($options['index']);
344
                        $arg = new ConsoleInputArgument($options);
345
                }
346
                $this->_args[$index] = $arg;
347
                ksort($this->_args);
348
                return $this;
349
        }
350

    
351
/**
352
 * Add multiple arguments at once. Take an array of argument definitions.
353
 * The keys are used as the argument names, and the values as params for the argument.
354
 *
355
 * @param array $args Array of arguments to add.
356
 * @see ConsoleOptionParser::addArgument()
357
 * @return $this
358
 */
359
        public function addArguments(array $args) {
360
                foreach ($args as $name => $params) {
361
                        $this->addArgument($name, $params);
362
                }
363
                return $this;
364
        }
365

    
366
/**
367
 * Add multiple options at once. Takes an array of option definitions.
368
 * The keys are used as option names, and the values as params for the option.
369
 *
370
 * @param array $options Array of options to add.
371
 * @see ConsoleOptionParser::addOption()
372
 * @return $this
373
 */
374
        public function addOptions(array $options) {
375
                foreach ($options as $name => $params) {
376
                        $this->addOption($name, $params);
377
                }
378
                return $this;
379
        }
380

    
381
/**
382
 * Append a subcommand to the subcommand list.
383
 * Subcommands are usually methods on your Shell, but can also be used to document Tasks.
384
 *
385
 * ### Options
386
 *
387
 * - `help` - Help text for the subcommand.
388
 * - `parser` - A ConsoleOptionParser for the subcommand. This allows you to create method
389
 *    specific option parsers. When help is generated for a subcommand, if a parser is present
390
 *    it will be used.
391
 *
392
 * @param ConsoleInputSubcommand|string $name Name of the subcommand. Will also accept an instance of ConsoleInputSubcommand
393
 * @param array $options Array of params, see above.
394
 * @return $this
395
 */
396
        public function addSubcommand($name, $options = array()) {
397
                if (is_object($name) && $name instanceof ConsoleInputSubcommand) {
398
                        $command = $name;
399
                        $name = $command->name();
400
                } else {
401
                        $defaults = array(
402
                                'name' => $name,
403
                                'help' => '',
404
                                'parser' => null
405
                        );
406
                        $options += $defaults;
407
                        $command = new ConsoleInputSubcommand($options);
408
                }
409
                $this->_subcommands[$name] = $command;
410
                return $this;
411
        }
412

    
413
/**
414
 * Remove a subcommand from the option parser.
415
 *
416
 * @param string $name The subcommand name to remove.
417
 * @return $this
418
 */
419
        public function removeSubcommand($name) {
420
                unset($this->_subcommands[$name]);
421
                return $this;
422
        }
423

    
424
/**
425
 * Add multiple subcommands at once.
426
 *
427
 * @param array $commands Array of subcommands.
428
 * @return $this
429
 */
430
        public function addSubcommands(array $commands) {
431
                foreach ($commands as $name => $params) {
432
                        $this->addSubcommand($name, $params);
433
                }
434
                return $this;
435
        }
436

    
437
/**
438
 * Gets the arguments defined in the parser.
439
 *
440
 * @return array Array of argument descriptions
441
 */
442
        public function arguments() {
443
                return $this->_args;
444
        }
445

    
446
/**
447
 * Get the defined options in the parser.
448
 *
449
 * @return array
450
 */
451
        public function options() {
452
                return $this->_options;
453
        }
454

    
455
/**
456
 * Get the array of defined subcommands
457
 *
458
 * @return array
459
 */
460
        public function subcommands() {
461
                return $this->_subcommands;
462
        }
463

    
464
/**
465
 * Parse the argv array into a set of params and args. If $command is not null
466
 * and $command is equal to a subcommand that has a parser, that parser will be used
467
 * to parse the $argv
468
 *
469
 * @param array $argv Array of args (argv) to parse.
470
 * @param string $command The subcommand to use. If this parameter is a subcommand, that has a parser,
471
 *    That parser will be used to parse $argv instead.
472
 * @return Array array($params, $args)
473
 * @throws ConsoleException When an invalid parameter is encountered.
474
 */
475
        public function parse($argv, $command = null) {
476
                if (isset($this->_subcommands[$command]) && $this->_subcommands[$command]->parser()) {
477
                        return $this->_subcommands[$command]->parser()->parse($argv);
478
                }
479
                $params = $args = array();
480
                $this->_tokens = $argv;
481
                while (($token = array_shift($this->_tokens)) !== null) {
482
                        if (substr($token, 0, 2) === '--') {
483
                                $params = $this->_parseLongOption($token, $params);
484
                        } elseif (substr($token, 0, 1) === '-') {
485
                                $params = $this->_parseShortOption($token, $params);
486
                        } else {
487
                                $args = $this->_parseArg($token, $args);
488
                        }
489
                }
490
                foreach ($this->_args as $i => $arg) {
491
                        if ($arg->isRequired() && !isset($args[$i]) && empty($params['help'])) {
492
                                throw new ConsoleException(
493
                                        __d('cake_console', 'Missing required arguments. %s is required.', $arg->name())
494
                                );
495
                        }
496
                }
497
                foreach ($this->_options as $option) {
498
                        $name = $option->name();
499
                        $isBoolean = $option->isBoolean();
500
                        $default = $option->defaultValue();
501

    
502
                        if ($default !== null && !isset($params[$name]) && !$isBoolean) {
503
                                $params[$name] = $default;
504
                        }
505
                        if ($isBoolean && !isset($params[$name])) {
506
                                $params[$name] = false;
507
                        }
508
                }
509
                return array($params, $args);
510
        }
511

    
512
/**
513
 * Gets formatted help for this parser object.
514
 * Generates help text based on the description, options, arguments, subcommands and epilog
515
 * in the parser.
516
 *
517
 * @param string $subcommand If present and a valid subcommand that has a linked parser.
518
 *    That subcommands help will be shown instead.
519
 * @param string $format Define the output format, can be text or xml
520
 * @param int $width The width to format user content to. Defaults to 72
521
 * @return string Generated help.
522
 */
523
        public function help($subcommand = null, $format = 'text', $width = 72) {
524
                if (isset($this->_subcommands[$subcommand]) &&
525
                        $this->_subcommands[$subcommand]->parser() instanceof self
526
                ) {
527
                        $subparser = $this->_subcommands[$subcommand]->parser();
528
                        $subparser->command($this->command() . ' ' . $subparser->command());
529
                        return $subparser->help(null, $format, $width);
530
                }
531
                $formatter = new HelpFormatter($this);
532
                if ($format === 'text' || $format === true) {
533
                        return $formatter->text($width);
534
                } elseif ($format === 'xml') {
535
                        return $formatter->xml();
536
                }
537
        }
538

    
539
/**
540
 * Parse the value for a long option out of $this->_tokens. Will handle
541
 * options with an `=` in them.
542
 *
543
 * @param string $option The option to parse.
544
 * @param array $params The params to append the parsed value into
545
 * @return array Params with $option added in.
546
 */
547
        protected function _parseLongOption($option, $params) {
548
                $name = substr($option, 2);
549
                if (strpos($name, '=') !== false) {
550
                        list($name, $value) = explode('=', $name, 2);
551
                        array_unshift($this->_tokens, $value);
552
                }
553
                return $this->_parseOption($name, $params);
554
        }
555

    
556
/**
557
 * Parse the value for a short option out of $this->_tokens
558
 * If the $option is a combination of multiple shortcuts like -otf
559
 * they will be shifted onto the token stack and parsed individually.
560
 *
561
 * @param string $option The option to parse.
562
 * @param array $params The params to append the parsed value into
563
 * @return array Params with $option added in.
564
 * @throws ConsoleException When unknown short options are encountered.
565
 */
566
        protected function _parseShortOption($option, $params) {
567
                $key = substr($option, 1);
568
                if (strlen($key) > 1) {
569
                        $flags = str_split($key);
570
                        $key = $flags[0];
571
                        for ($i = 1, $len = count($flags); $i < $len; $i++) {
572
                                array_unshift($this->_tokens, '-' . $flags[$i]);
573
                        }
574
                }
575
                if (!isset($this->_shortOptions[$key])) {
576
                        throw new ConsoleException(__d('cake_console', 'Unknown short option `%s`', $key));
577
                }
578
                $name = $this->_shortOptions[$key];
579
                return $this->_parseOption($name, $params);
580
        }
581

    
582
/**
583
 * Parse an option by its name index.
584
 *
585
 * @param string $name The name to parse.
586
 * @param array $params The params to append the parsed value into
587
 * @return array Params with $option added in.
588
 * @throws ConsoleException
589
 */
590
        protected function _parseOption($name, $params) {
591
                if (!isset($this->_options[$name])) {
592
                        throw new ConsoleException(__d('cake_console', 'Unknown option `%s`', $name));
593
                }
594
                $option = $this->_options[$name];
595
                $isBoolean = $option->isBoolean();
596
                $nextValue = $this->_nextToken();
597
                $emptyNextValue = (empty($nextValue) && $nextValue !== '0');
598
                if (!$isBoolean && !$emptyNextValue && !$this->_optionExists($nextValue)) {
599
                        array_shift($this->_tokens);
600
                        $value = $nextValue;
601
                } elseif ($isBoolean) {
602
                        $value = true;
603
                } else {
604
                        $value = $option->defaultValue();
605
                }
606
                if ($option->validChoice($value)) {
607
                        $params[$name] = $value;
608
                        return $params;
609
                }
610
                return array();
611
        }
612

    
613
/**
614
 * Check to see if $name has an option (short/long) defined for it.
615
 *
616
 * @param string $name The name of the option.
617
 * @return bool
618
 */
619
        protected function _optionExists($name) {
620
                if (substr($name, 0, 2) === '--') {
621
                        return isset($this->_options[substr($name, 2)]);
622
                }
623
                if ($name{0} === '-' && $name{1} !== '-') {
624
                        return isset($this->_shortOptions[$name{1}]);
625
                }
626
                return false;
627
        }
628

    
629
/**
630
 * Parse an argument, and ensure that the argument doesn't exceed the number of arguments
631
 * and that the argument is a valid choice.
632
 *
633
 * @param string $argument The argument to append
634
 * @param array $args The array of parsed args to append to.
635
 * @return array Args
636
 * @throws ConsoleException
637
 */
638
        protected function _parseArg($argument, $args) {
639
                if (empty($this->_args)) {
640
                        $args[] = $argument;
641
                        return $args;
642
                }
643
                $next = count($args);
644
                if (!isset($this->_args[$next])) {
645
                        throw new ConsoleException(__d('cake_console', 'Too many arguments.'));
646
                }
647

    
648
                if ($this->_args[$next]->validChoice($argument)) {
649
                        $args[] = $argument;
650
                        return $args;
651
                }
652
        }
653

    
654
/**
655
 * Find the next token in the argv set.
656
 *
657
 * @return string next token or ''
658
 */
659
        protected function _nextToken() {
660
                return isset($this->_tokens[0]) ? $this->_tokens[0] : '';
661
        }
662

    
663
}