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