Overview
Namespaces
Classes
Exceptions
1: <?php
2:
3: /**
4: * PhpOptions
5: * @link https://github.com/masicek/PhpOptions
6: * @author Viktor Mašíček <viktor@masicek.net>
7: * @license "New" BSD License
8: */
9:
10: namespace PhpOptions;
11:
12: require_once __DIR__ . '/Exceptions.php';
13: require_once __DIR__ . '/Arguments.php';
14: require_once __DIR__ . '/Types/Types.php';
15:
16: /**
17: * Class for bettwer work with one PHP comman-line option
18: *
19: * @author Viktor Mašíček <viktor@masicek.net>
20: */
21: class Option
22: {
23:
24: /**
25: * Type of value requirement - no value
26: */
27: const VALUE_NO = 'value_no';
28:
29: /**
30: * Type of value requirement - require value
31: */
32: const VALUE_REQUIRE = 'value_require';
33:
34: /**
35: * Type of value requirement - optional value
36: */
37: const VALUE_OPTIONAL = 'value_optional';
38:
39: /**
40: * Length of indent in printed help
41: */
42: const HELP_INDENT_LENGTH = 4;
43:
44:
45: /**
46: * Object for making types objects
47: *
48: * @var Types
49: */
50: private static $types = NULL;
51:
52: /**
53: * Name of option
54: *
55: * @var string
56: */
57: private $name;
58:
59: /**
60: * Type of expected value
61: *
62: * @var AType
63: */
64: private $type = NULL;
65:
66: /**
67: * Value of options set in command-line
68: *
69: * @var mixed
70: */
71: private $value = NULL;
72:
73: /**
74: * Short variant of option in command-line
75: *
76: * @var string
77: */
78: private $short;
79:
80: /**
81: * Long variant of option in command-line
82: *
83: * @var string
84: */
85: private $long;
86:
87: /**
88: * Defualt value of option
89: *
90: * @var string
91: */
92: private $defaults = NULL;
93:
94: /**
95: * Flag of requirements of option
96: *
97: * @var bool
98: */
99: private $required = FALSE;
100:
101: /**
102: * Flag of requirement of option value
103: *
104: * @var string
105: */
106: private $valueRequired = self::VALUE_NO;
107:
108: /**
109: * Desription used in help generated for option
110: *
111: * @var string
112: */
113: private $description = '';
114:
115: /**
116: * Array of needed options by this option
117: *
118: * @var array of Option
119: */
120: private $needed = array();
121:
122:
123: // ---- MAKE NEW OPTION ----
124:
125:
126: /**
127: * Set name of option and default short and long variant in command-line
128: * @todo accept array as second parameere for setting all object
129: *
130: * @param string $name Name of option
131: *
132: * @throws InvalidArgumentException Name of option cannot be empty.
133: */
134: public function __construct($name)
135: {
136: if (!$name)
137: {
138: throw new InvalidArgumentException('Name of option cannot be empty.');
139: }
140:
141: $this->name = $name;
142:
143: $long = strtolower($name);
144: $long = preg_replace('/[^a-z0-9]+/', '-', $long);
145: $long = preg_replace('/-+/', '-', $long);
146: $this->long($long);
147:
148: $this->short(substr($long, 0, 1));
149: }
150:
151:
152: /**
153: * Create new option
154: * @see Option::__construct
155: *
156: * @param string $name Name of option
157: *
158: * @return Option
159: */
160: public static function make($name)
161: {
162: return new Option($name);
163: }
164:
165:
166: /**
167: * Create new option of specific type.
168: * The typed option have required value as default.
169: * @see Option::make
170: *
171: * @param string $name Name of option
172: * @param array $settings Specific settings for selected type
173: *
174: * @return Option
175: */
176: public static function __callStatic($type, $settings)
177: {
178: $name = (isset($settings[0])) ? array_shift($settings) : '';
179: $option = self::make($name);
180: $option->type = self::getTypes()->getType($type, $settings);
181: $option->value();
182: return $option;
183: }
184:
185:
186: /**
187: * Register new type. It has to by child of abstract class AType.
188: * It is possible rewrite already registered types (so defaults too).
189: *
190: * @param string $name Name of type
191: * @param string $className Name of class of type with namespace
192: * @param string $classPath Full path of file contains class of type
193: *
194: * @return void
195: */
196: public static function registerType($name, $className, $classPath)
197: {
198: self::getTypes()->register($name, $className, $classPath);
199: }
200:
201:
202: // ---- SET OPTION ----
203:
204:
205: /**
206: * Set short variant of option in command-line
207: *
208: * @param string $short
209: *
210: * @throws LogicException Short and long varint cannot be undefined together.
211: * @throws InvalidArgumentException Short variant have to be only one character.
212: * @return Option
213: */
214: public function short($short = NULL)
215: {
216: if (is_null($short) && is_null($this->long))
217: {
218: throw new LogicException($this->name . ': Short and long varint cannot be undefined together.');
219: }
220:
221: if (!is_null($short) && strlen($short) != 1)
222: {
223: throw new InvalidArgumentException($this->name . ': Short variant has to be only one character.');
224: }
225:
226: $this->short = $short;
227: return $this;
228: }
229:
230:
231: /**
232: * Set long variant of option in command-line
233: *
234: * @param string $long
235: *
236: * @throws LogicException Short and long varint cannot be undefined together.
237: * @return Option
238: */
239: public function long($long = NULL)
240: {
241: if (is_null($long) && is_null($this->short))
242: {
243: throw new LogicException($this->name . ': Short and long varint cannot be undefined together.');
244: }
245:
246: $this->long = $long;
247: return $this;
248: }
249:
250:
251: /**
252: * Set default value of option
253: *
254: * @param mixed $default
255: *
256: * @throws LogicException The default value makes sense only for options with optional value or optional option with optional or required value.
257: * @return Option
258: */
259: public function defaults($defaults = NULL)
260: {
261: if (!is_null($defaults) && !(
262: ($this->valueRequired == self::VALUE_OPTIONAL) ||
263: ($this->valueRequired == self::VALUE_REQUIRE && $this->required == FALSE)
264: ))
265: {
266: throw new LogicException($this->name . ': The default value makes sense only for options with optional value or optional option with optional or required value.');
267: }
268:
269: $this->defaults = $defaults;
270: return $this;
271: }
272:
273:
274: /**
275: * Set flag of requirement of option
276: *
277: * @param bool $required
278: *
279: * @throws LogicException The required option does not make sense for option with default value and required value
280: * @return Option
281: */
282: public function required($required = TRUE)
283: {
284: if ($this->valueRequired == self::VALUE_REQUIRE && !is_null($this->defaults))
285: {
286: throw new LogicException($this->name . ': The required option does not make sense for option with default value and required value.');
287: }
288:
289: $this->required = (bool)$required;
290: return $this;
291: }
292:
293:
294: /**
295: * Set type of requirement of value of option
296: * TRUE = require
297: * FALSE = optional
298: *
299: * @param bool $value
300: *
301: * @throws LogicException The require/non value make sense only for option without default value.
302: * @return Option
303: */
304: public function value($value = TRUE)
305: {
306: $value = ((bool)$value) ? self::VALUE_REQUIRE : self::VALUE_OPTIONAL;
307:
308: if (!is_null($this->defaults) && $value != self::VALUE_OPTIONAL)
309: {
310: throw new LogicException($this->name . ': The require/non value makes sense only for option without default value.');
311: }
312:
313: $this->valueRequired = $value;
314: return $this;
315: }
316:
317:
318: /**
319: * Set description of option used in help
320: *
321: * @param string $description
322: *
323: * @return Option
324: */
325: public function description($description = '')
326: {
327: $this->description = $description;
328: return $this;
329: }
330:
331:
332: /**
333: * Set needed options by this option
334: *
335: * @param array $needed Needed options
336: *
337: * @return void
338: */
339: public function dependences($needed)
340: {
341: $this->needed = $needed;
342: return $this;
343: }
344:
345:
346: // ---- GET OPTION SETTINGS ----
347:
348:
349: /**
350: * Return name of option
351: *
352: * @return string
353: */
354: public function getName()
355: {
356: return $this->name;
357: }
358:
359:
360: /**
361: * Return short varian of option in command-line
362: *
363: * @return string
364: */
365: public function getShort()
366: {
367: return $this->short;
368: }
369:
370:
371: /**
372: * Return long varian of option in command-line
373: *
374: * @return string
375: */
376: public function getLong()
377: {
378: return $this->long;
379: }
380:
381:
382: /**
383: * Return default value of option in command-line
384: *
385: * @return mixed
386: */
387: public function getDefaults()
388: {
389: return $this->defaults;
390: }
391:
392:
393: /**
394: * Return value of option
395: * FALSE = not set
396: * TRUE = set without value
397: * other = value form command-line of default value
398: *
399: * @param bool $isDefaiultSet Flag if default option is set
400: *
401: * @return mixed
402: */
403: public function getValue($isDefaultSet = FALSE)
404: {
405: if (is_null($this->value))
406: {
407: $this->value = $this->readValue($isDefaultSet);
408: }
409:
410: return $this->value;
411: }
412:
413:
414: // ---- HELP ----
415:
416:
417: /**
418: * Return short and long option in one string
419: *
420: * @param string $value Value of options (typically type of value)
421: *
422: * @return string
423: */
424: public function getOptions($value = '')
425: {
426: $options = '';
427: $options .= ($this->short ? '-' . $this->short . $value : '');
428: $options .= ($this->short && $this->long ? ', ' : '');
429: $options .= ($this->long ? '--' . $this->long . $value : '');
430: return $options;
431: }
432:
433:
434: /**
435: * Return help for option
436: *
437: * @param integer $indent Level of indent of text
438: *
439: * @return string
440: */
441: public function getHelp($maxLength, $indent = 0)
442: {
443: $indentString = str_repeat(' ', self::HELP_INDENT_LENGTH);
444:
445: // indent
446: if ($indent)
447: {
448: $indent = implode('', array_fill(0, $indent, $indentString));
449: }
450: else
451: {
452: $indent = '';
453: }
454:
455: // option value
456: $valueType = 'VALUE';
457: if ($this->type)
458: {
459: $valueType = $this->type->getName();
460: }
461: switch ($this->valueRequired)
462: {
463: case self::VALUE_NO:
464: $value = '';
465: break;
466: case self::VALUE_OPTIONAL:
467: $value = '[="' . $valueType . '"]';
468: break;
469: case self::VALUE_REQUIRE:
470: $value = '="' . $valueType . '"';
471: break;
472: }
473:
474: // options with value
475: $help = $this->getOptions($value);
476: if (!$this->required)
477: {
478: $help = '[' . $help . ']';
479: }
480: $help = $this->wordwrap($help, $maxLength, $indent);
481:
482:
483: // default value
484: if (!is_null($this->defaults))
485: {
486: switch (gettype($this->defaults))
487: {
488: case 'boolean':
489: $defaults = $this->defaults ? 'TRUE' : 'FALSE';
490: break;
491: case 'array':
492: $defaults = 'array(' . implode(',', $this->defaults) . ')';
493: break;
494: default:
495: $defaults = $this->defaults;
496: break;
497: }
498: $help .= PHP_EOL . $this->wordwrap('DEFAULT="' . $defaults . '"', $maxLength, $indentString . $indent);
499: }
500:
501: // needed options
502: if (count($this->needed) > 0)
503: {
504: $helpNeeded = array();
505: foreach ($this->needed as $neededOption)
506: {
507: $helpNeeded[] = $neededOption->getOptions();
508: }
509: $help .= PHP_EOL . $this->wordwrap('NEEDED: ' . implode('; ', $helpNeeded), $maxLength, $indentString . $indent);
510: }
511:
512: // descriptions
513: if ($this->description)
514: {
515: $help .= PHP_EOL . $this->wordwrap(str_replace(PHP_EOL, PHP_EOL . $indentString . $indent, $this->description), $maxLength, $indentString . $indent);
516: }
517:
518: return $help;
519: }
520:
521:
522: // ---- PRIVATE ----
523:
524:
525: /**
526: * Return object for work with types.
527: *
528: * @return Types\Types
529: */
530: private static function getTypes()
531: {
532: if (!self::$types)
533: {
534: self::$types = new Types\Types();
535: }
536:
537: return self::$types;
538: }
539:
540:
541: /**
542: * Read value of option
543: * FALSE = not set
544: * TRUE = set without value
545: * other = value form command-line of default value
546: *
547: * @param bool $isDefaiultSet Flag if default option is set
548: *
549: * @throws UserBadCallException Option has value set by short and long variant.
550: * @throws UserBadCallException Option has bad format
551: * @throws UserBadCallException Option is required.
552: * @throws UserBadCallException Option have value, but any is exected.
553: * @throws UserBadCallException Option has require value, but no set.
554: * @return mixed
555: */
556: private function readValue($isDefaultSet)
557: {
558: $options = Arguments::options();
559:
560: $short = isset($options[$this->short]) ? $options[$this->short] : FALSE;
561: $long = isset($options[$this->long]) ? $options[$this->long] : FALSE;
562:
563: // get value
564: if ($short !== FALSE && $long !== FALSE)
565: {
566: throw new UserBadCallException($this->getOptions() . ': Option has value set by short and long variant.');
567: }
568: elseif ($short === FALSE && $long === FALSE)
569: {
570: $value = FALSE;
571: }
572: elseif ($short !== FALSE)
573: {
574: $value = $short;
575: }
576: else
577: {
578: $value = $long;
579: }
580:
581: // required
582: if (!$isDefaultSet && $this->required && $value === FALSE)
583: {
584: throw new UserBadCallException($this->getOptions() . ': Option is required.');
585: }
586:
587: // value required
588: switch ($this->valueRequired)
589: {
590: case self::VALUE_NO:
591: // set argument as common argument
592: if (!is_bool($value))
593: {
594: throw new UserBadCallException($this->getOptions() . ': Option has value, but any is expected.');
595: }
596: break;
597:
598: case self::VALUE_OPTIONAL:
599: // use default value for not set option or empty set option
600: if (is_bool($value) && !is_null($this->defaults))
601: {
602: $value = $this->defaults;
603: }
604: break;
605:
606: case self::VALUE_REQUIRE:
607: // option has not value
608: if ($value === TRUE)
609: {
610: throw new UserBadCallException($this->getOptions() . ': Option has require value, but no set.');
611: }
612: else if ($value === FALSE && !is_null($this->defaults))
613: {
614: $value = $this->defaults;
615: }
616:
617: break;
618: }
619:
620: // check type of value + filter value
621: if (($value !== FALSE) && !is_null($this->type))
622: {
623: if (!$this->type->check($value))
624: {
625: throw new UserBadCallException($this->getOptions() . ': Option has bad format.');
626: }
627:
628: // do not filter optional value without set value
629: if (($this->valueRequired != self::VALUE_OPTIONAL) || !is_bool($value))
630: {
631: $value = $this->type->filter($value);
632: }
633: }
634:
635: return $value;
636: }
637:
638:
639: /**
640: * Wrapping input sring on maximal length and prepand prefix on each line
641: *
642: * @param string $string Wrapped string
643: * @param int $maxLength Maximla length of one line
644: * @param string $prefix Prepanded prefix
645: *
646: * @return string
647: */
648: private function wordwrap($string, $maxLength, $prefix)
649: {
650: $string = wordwrap($string, $maxLength - strlen($prefix), PHP_EOL, FALSE);
651: $strings = explode(PHP_EOL, $string);
652: array_walk($strings,
653: function (&$item, $key, $prefix) {
654: $item = $prefix . trim($item);
655: },
656: $prefix
657: );
658: $string = implode(PHP_EOL, $strings);
659: return $string;
660: }
661:
662:
663:
664: }
665: