1: <?php
2: /**
3: * This file is part of the PHPLucidFrame library.
4: * This class manages the process of a Command
5: *
6: * @package PHPLucidFrame\Console
7: * @since PHPLucidFrame v 1.11.0
8: * @copyright Copyright (c), PHPLucidFrame.
9: * @link http://phplucidframe.com
10: * @license http://www.opensource.org/licenses/mit-license.php MIT License
11: *
12: * This source file is subject to the MIT license that is bundled
13: * with this source code in the file LICENSE
14: */
15:
16: namespace LucidFrame\Console;
17:
18: use Closure;
19:
20: /**
21: * This class manages the process of a Command
22: */
23: class Command
24: {
25: /** @var string The command name */
26: protected $name;
27: /** @var string The description for the command */
28: protected $description;
29: /** @var string The help tip for the command */
30: protected $help;
31: /** @var array The options for the command such as --help etc. */
32: protected $options = array();
33: /** @var array The short options of the long options defined for the command such as -h for --help, etc. */
34: protected $shortcuts = array();
35: /** @var array The arguments for the command */
36: protected $arguments = array();
37: /** @var array Array of the argument names */
38: protected $argumentNames = array();
39: /** @var \Closure|string Anonymous function or class name that performs the job of the command */
40: protected $definition;
41: /** @var array Array of arguments passed to script */
42: private $argv;
43: /** @var array The parsed options from the command running */
44: private $parsedOptions = array();
45: /** @var array The parsed arguments from the command running */
46: private $parsedArguments = array();
47: /** @var string The longest option name */
48: private $longestArgument = '';
49: /** @var string The longest argument name */
50: private $longestOption = '';
51:
52: /**
53: * Constructor
54: * @param string $name The command name
55: */
56: public function __construct($name)
57: {
58: $this->setName($name);
59: $this->addOption('help', 'h', 'Display the help message', null, LC_CONSOLE_OPTION_NOVALUE);
60: }
61:
62: /**
63: * Setter for $name
64: * @param string $name The command name
65: * @return object LucidFrame\Console\Command
66: */
67: public function setName($name)
68: {
69: $this->name = $name;
70:
71: return $this;
72: }
73:
74: /**
75: * Getter for $name
76: * @return string
77: */
78: public function getName()
79: {
80: return $this->name;
81: }
82:
83: /**
84: * Setter for $description
85: * @param string $description The description for the command
86: * @return object LucidFrame\Console\Command
87: */
88: public function setDescription($description = null)
89: {
90: $this->description = $description;
91:
92: return $this;
93: }
94:
95: /**
96: * Setter for $description
97: * @return string
98: */
99: public function getDescription()
100: {
101: return $this->description;
102: }
103:
104: /**
105: * Setter for $help
106: * @param string $help The help tip for the command
107: * @return object LucidFrame\Console\Command`
108: */
109: public function setHelp($help = null)
110: {
111: $this->help = $help;
112:
113: return $this;
114: }
115:
116: /**
117: * Setter for $help
118: * @return string
119: */
120: public function getHelp()
121: {
122: return $this->help;
123: }
124:
125: /**
126: * Add an option for the command
127: *
128: * @param string $name The option name without the prefix `--`, i.e,. `help` for `--help`
129: * @param string $shortcut The short option name without the prefix `-`, i.e, `h` for `-h`
130: * @param string $description A short description for the option
131: * @param mixed $default The default value for the option
132: * @param int $type A constant: LC_CONSOLE_OPTION_REQUIRED, LC_CONSOLE_OPTION_OPTIONAL, LC_CONSOLE_OPTION_NOVALUE
133: *
134: * @return object LucidFrame\Console\Command
135: */
136: public function addOption($name, $shortcut = null, $description = '', $default = null, $type = LC_CONSOLE_OPTION_OPTIONAL)
137: {
138: $name = ltrim($name, '--');
139: if ($shortcut) {
140: $shortcut = ltrim($shortcut, '-');
141: }
142:
143: $this->options[$name] = array(
144: 'name' => $name,
145: 'shortcut' => $shortcut,
146: 'description' => $description,
147: 'default' => $default,
148: 'type' => $type
149: );
150:
151: $this->shortcuts[$shortcut] = $name;
152: $this->parsedOptions[$name] = $default;
153:
154: $key = ($shortcut ? "-{$shortcut}, " : _indent(4)) . "--{$name}";
155: $this->options[$name]['key'] = $key;
156: if (strlen($key) > strlen($this->longestOption)) {
157: $this->longestOption = $key;
158: }
159:
160: return $this;
161: }
162:
163: /**
164: * Add an argument for the command
165: *
166: * @param string $name The argument name
167: * @param string $description A short description for the argument
168: * @param mixed $default The default value for the option
169: *
170: * @return object LucidFrame\Console\Command
171: */
172: public function addArgument($name, $description = '', $default = null)
173: {
174: $this->arguments[] = array(
175: 'name' => $name,
176: 'description' => $description,
177: 'default' => $default,
178: );
179: $this->argumentNames[] = $name;
180:
181: if (strlen($name) > strlen($this->longestArgument)) {
182: $this->longestArgument = $name;
183: }
184:
185: return $this;
186: }
187:
188: /**
189: * Getter for $parsedArguments
190: */
191: public function getArguments()
192: {
193: return $this->parsedArguments;
194: }
195:
196: /**
197: * Getter for $parsedOptions
198: */
199: public function getOptions()
200: {
201: return $this->parsedOptions;
202: }
203:
204: /**
205: * Setter for $definition
206: * @param \Closure|string $function Anonymous function or class name that performs the job of the command
207: * @return object LucidFrame\Console\Command`
208: */
209: public function setDefinition($function)
210: {
211: $this->definition = $function;
212:
213: return $this;
214: }
215:
216: /**
217: * Register a command
218: * @return object LucidFrame\Console\Command`
219: */
220: public function register()
221: {
222: Console::registerCommand($this);
223:
224: return $this;
225: }
226:
227: /**
228: * Get an option from the command
229: *
230: * @param string $name The option name without the prefix `--`, i.e,. `help` for `--help`
231: * @return mixed
232: */
233: public function getOption($name)
234: {
235: if (!empty($this->parsedOptions[$name])) {
236: return $this->parsedOptions[$name];
237: } else {
238: if ($this->options[$name]['type'] == LC_CONSOLE_OPTION_REQUIRED) {
239: _writeln('The option "' . $name . '" is required.');
240: }
241: }
242:
243: return null;
244: }
245:
246: /**
247: * Get an argument from the command
248: *
249: * @param string $name The argument name
250: * @return mixed
251: */
252: public function getArgument($name)
253: {
254: return isset($this->parsedArguments[$name]) ? $this->parsedArguments[$name] : null;
255: }
256:
257: /**
258: * Getter for $parsedOptions
259: */
260: public function getParsedOptions()
261: {
262: return $this->parsedOptions;
263: }
264:
265: /**
266: * Getter for $parsedArguments
267: */
268: public function getParsedArguments()
269: {
270: return $this->parsedArguments;
271: }
272:
273: /**
274: * Reset default values to arguments and options
275: */
276: public function resetToDefaults()
277: {
278: foreach ($this->options as $name => $opt) {
279: $this->parsedOptions[$name] = $opt['default'];
280: }
281:
282: foreach ($this->arguments as $arg) {
283: $this->parsedArguments[$arg['name']] = $arg['default'];
284: }
285: }
286:
287: /**
288: * Run the command
289: * @param array $argv Array of arguments passed to script
290: * @return mixed
291: */
292: public function run($argv = array())
293: {
294: $this->parseArguments($argv);
295:
296: if ($this->getOption('help')) {
297: $this->showHelp();
298: return true;
299: }
300:
301: if (is_string($this->definition)) {
302: $cmd = new $this->definition;
303: $cmd->execute($this);
304: return true;
305: } else {
306: return call_user_func_array($this->definition, array($this));
307: }
308: }
309:
310: /**
311: * Display the help message
312: * @return void
313: */
314: public function showHelp()
315: {
316: $options = $this->getOptions();
317:
318: if (count($options)) {
319: _writeln('Usage:');
320: $usage = _indent() . $this->name . ' [options]';
321:
322: if (count($this->arguments)) {
323: $usage .= ' [<' . implode('>] [<', $this->argumentNames) . '>]';
324: }
325:
326: _writeln($usage);
327:
328: # Arguments
329: if (count($this->arguments)) {
330: _writeln();
331: _writeln('Arguments:');
332:
333: $table = new ConsoleTable();
334: $table->hideBorder()->setPadding(2);
335: foreach ($this->arguments as $arg) {
336: $table->addRow();
337: $table->addColumn($arg['name']);
338: $desc = $arg['description'];
339: if ($arg['default']) {
340: $desc .= ' [default: "' . $arg['default'] . '"]';
341: }
342: $table->addColumn($desc);
343: }
344: $table->display();
345: }
346:
347: # Options
348: if (count($options)) {
349: _writeln();
350: _writeln('Options:');
351:
352: $table = new ConsoleTable();
353: $table->hideBorder()->setPadding(2);
354: foreach ($this->options as $name => $opt) {
355: $table->addRow();
356: $table->addColumn($opt['key']);
357: $desc = $opt['description'];
358: if ($opt['default']) {
359: $desc .= ' [default: "' . $opt['default'] . '"]';
360: }
361: $table->addColumn($desc);
362: }
363: $table->display();
364: }
365:
366: if ($this->description) {
367: _writeln();
368: _writeln('Help:');
369: _writeln(_indent() . $this->description);
370: }
371: }
372: }
373:
374: /**
375: * Validate the option
376: * @param string $name
377: * @param string $type
378: * @return string|boolean
379: */
380: private function validateOption($name, $type)
381: {
382: if (!in_array($type, array('shortopt', 'longopt'))) {
383: return $name;
384: }
385:
386: if ($type === 'longopt') {
387: return isset($this->options[$name]) ? $name : false;
388: }
389:
390: if ($type === 'shortopt') {
391: return isset($this->shortcuts[$name]) ? $this->shortcuts[$name] : false;
392: }
393:
394: return false;
395: }
396:
397: /**
398: * Get the argument type and name
399: * @param integer $pos The position of argument
400: * @return array
401: *
402: * array(
403: * $type, // longopt, shortopt or value
404: * $name // the name without prefix `--` or `-`
405: * )
406: */
407: private function getArgTypeAndName($pos)
408: {
409: if (isset($this->argv[$pos])) {
410: $arg = $this->argv[$pos];
411: } else {
412: return array(null, null);
413: }
414:
415: $a = explode('=', $arg);
416:
417: if (substr($a[0], 0, 2) === '--') {
418: $type = 'longopt';
419: $name = ltrim($a[0], '--');
420: } elseif (substr($a[0], 0, 1) === '-') {
421: $type = 'shortopt';
422: $name = ltrim($a[0], '-');
423: } else {
424: $type = 'value';
425: $name = $a[0];
426: }
427:
428: return array($type, $name);
429: }
430:
431: /**
432: * Parse the arguments for the command
433: * @param array $argv Array of arguments passed to script
434: * @return array
435: */
436: public function parseArguments($argv = array())
437: {
438: $this->argv = $argv;
439: $this->resetToDefaults();
440: $parsedArguments = array();
441:
442: foreach ($argv as $pos => $arg) {
443: list($type, $name) = $this->getArgTypeAndName($pos);
444: list($lastType, $lastName) = $this->getArgTypeAndName($pos - 1);
445:
446: $name = $this->validateOption($name, $type);
447: if (!$name) {
448: continue;
449: }
450:
451: $a = explode('=', $arg);
452: if (count($a) === 2) {
453: // when there is '=' in the option
454: $value = $a[1];
455: if ($type === 'value') {
456: $parsedArguments[] = $value;
457: } else {
458: $this->parsedOptions[$name] = $value;
459: }
460: } else {
461: $value = $a[0];
462: if ($type === 'value') {
463: if (in_array($lastType, array('shortopt', 'longopt')) && $lastName = $this->validateOption($lastName, $lastType)) {
464: if ($this->options[$lastName]['type'] === LC_CONSOLE_OPTION_NOVALUE) {
465: $parsedArguments[] = $value;
466: } elseif ($this->parsedOptions[$lastName] === true) {
467: $this->parsedOptions[$lastName] = $value;
468: } else {
469: $parsedArguments[] = $value;
470: }
471: } else {
472: $parsedArguments[] = $value;
473: }
474: } else {
475: $this->parsedOptions[$name] = true;
476: }
477: }
478: }
479:
480: foreach ($parsedArguments as $key => $value) {
481: if (isset($this->arguments[$key])) {
482: $name = $this->arguments[$key]['name'];
483: $this->parsedArguments[$name] = $value;
484: }
485: }
486:
487: return array($this->parsedArguments, $this->parsedOptions);
488: }
489:
490: /**
491: * Console confirmation prompt
492: * @param string $message The confirmation message
493: * @param string|array $input The input to be allowed or to be checked against
494: * @return boolean TRUE if it is passed; otherwise FALSE
495: */
496: public function confirm($message = 'Are you sure? Type "yes" or "y" to continue:', $input = array('yes', 'y'))
497: {
498: _write(trim($message) . ' ');
499:
500: $handle = fopen("php://stdin", "r");
501: $line = fgets($handle);
502: $line = strtolower(trim($line));
503:
504: if (is_string($input) && $line == $input) {
505: fclose($handle);
506: return true;
507: }
508:
509: if (is_array($input) && in_array($line, $input)) {
510: fclose($handle);
511: return true;
512: }
513:
514: fclose($handle);
515: return false;
516: }
517: }
518: