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:
15: /**
16: * Class for better work with PHP comand-line options
17: *
18: * @author Viktor Mašíček <viktor@masicek.net>
19: */
20: class Options
21: {
22:
23: /**
24: * Version of PhpOptions
25: */
26: const VERSION = '1.3.0';
27:
28: /**
29: * Max length of one line
30: */
31: const HELP_MAX_LENGTH = 75;
32:
33: /**
34: * List of possible options
35: *
36: * @var array of Option
37: */
38: private $options = array();
39:
40: /**
41: * List of value of deffined options
42: *
43: * @var array ([name of option] => [value of option])
44: */
45: private $optionsValues = array();
46:
47: /**
48: * Mapping short variants to name of options
49: *
50: * @var array ([short variant] => [name of option])
51: */
52: private $optionsShorts = array();
53:
54: /**
55: * Mapping long variants to name of options
56: *
57: * @var array ([long variant] => [name of option])
58: */
59: private $optionsLongs = array();
60:
61: /**
62: * Default option and its value
63: *
64: * @var array ('name' => [name of option], 'value' => [value of option])
65: */
66: private $defaults = NULL;
67:
68: /**
69: * Common description show in help
70: *
71: * @var string
72: */
73: private $description = '';
74:
75: /**
76: * List groups with names of options that belong to groups
77: *
78: * @var array ([name of group] => array of names of options)
79: */
80: private $groups = array();
81:
82:
83: /**
84: * Control that script run from command line
85: *
86: * @throws UserBadCallException Script have to run from command line.
87: */
88: public function __construct()
89: {
90: if (php_sapi_name() !== 'cli')
91: { // @codeCoverageIgnoreStart
92: throw new UserBadCallException('Script have to run from command line.');
93: } // @codeCoverageIgnoreEnd
94: }
95:
96:
97: /**
98: * Return array of arguments in command-line.
99: *
100: * @return array
101: */
102: public static function arguments()
103: {
104: return Arguments::getArguments();
105: }
106:
107:
108: /**
109: * Add option
110: *
111: * @param array|Option $options Added options
112: *
113: * @return Options
114: */
115: public function add($options = array())
116: {
117: if (!is_array($options))
118: {
119: $options = array($options);
120: }
121:
122: foreach ($options as $option)
123: {
124: $this->addOne($option);
125: }
126:
127: return $this;
128: }
129:
130:
131: /**
132: * Set default option if any options is set
133: *
134: * @param string $name Name of option
135: *
136: * @throws InvalidArgumentException Unknown option.
137: * @return Options
138: */
139: public function defaults($name)
140: {
141: if (!isset($this->options[$name]))
142: {
143: throw new InvalidArgumentException($name . ': Unknown option.');
144: }
145:
146: $value = $this->options[$name]->getDefaults();
147: $this->defaults['name'] = $name;
148: $this->defaults['value'] = $value;
149: if (is_null($value))
150: {
151: $this->defaults['value'] = TRUE;
152: }
153:
154: return $this;
155: }
156:
157:
158: /**
159: * Set text of common description in generated help
160: *
161: * @param string $description Text of common description
162: *
163: * @return Options
164: */
165: public function description($description)
166: {
167: $this->description = $description;
168: return $this;
169: }
170:
171:
172: /**
173: * Return value of option.
174: * For set option without value return TRUE.
175: * If option is not set, return FALSE.
176: *
177: * @param string $name Name of option or short or long option with prefix ('-' or '--')
178: *
179: * @throws InvalidArgumentException Unknown option
180: * @return mixed
181: */
182: public function get($name)
183: {
184: $nameInput = $name;
185:
186: // long variant to name
187: if (substr($name, 0, 2) == '--')
188: {
189: $long = substr($name, 2);
190: $name = isset($this->optionsLongs[$long]) ? $this->optionsLongs[$long] : NULL;
191: }
192: // short variant to name
193: elseif (substr($name, 0, 1) == '-')
194: {
195: $short = substr($name, 1);
196: $name = isset($this->optionsShorts[$short]) ? $this->optionsShorts[$short] : NULL;
197: }
198:
199: if (!isset($this->optionsValues[$name]))
200: {
201: throw new InvalidArgumentException($nameInput . ': Unknown option.');
202: }
203:
204: // default option
205: if (!is_null($this->defaults) && ($this->defaults['name'] == $name) && (count(Arguments::options()) == 0))
206: {
207: return $this->defaults['value'];
208: }
209:
210: return $this->optionsValues[$name];
211: }
212:
213:
214: /**
215: * Define dependences of options.
216: *
217: * @param string $main Main option for which we define needed options
218: * @param string|array $needed Needed options for main option
219: * @param string $groupName Name of group into which put all options (main + needed)
220: *
221: * @throws InvalidArgumentException Unknown option
222: * @throws UserBadCallException Some option need some another option
223: * @return Options
224: */
225: public function dependences($main, $needed, $groupName = NULL)
226: {
227: if (!is_array($needed))
228: {
229: $needed = array($needed);
230: }
231:
232: if (!isset($this->optionsValues[$main]))
233: {
234: throw new InvalidArgumentException($main . ': Unknown option.');
235: }
236:
237: $neededOptions = array();
238: foreach ($needed as $name)
239: {
240: if (!isset($this->optionsValues[$name]))
241: {
242: throw new InvalidArgumentException($name . ': Unknown option.');
243: }
244: $neededOptions[] = $this->options[$name];
245: }
246:
247: // if main option is defined, check needed options
248: if ($this->get($main) !== FALSE)
249: {
250: foreach ($needed as $name)
251: {
252: if ($this->get($name) === FALSE)
253: {
254: $mainOptions = $this->options[$main]->getOptions();
255: $nameOptions = $this->options[$name]->getOptions();
256: throw new UserBadCallException('Option "' . $mainOptions . '" needs option "'. $nameOptions . '".');
257: }
258: }
259: }
260:
261: $this->options[$main]->dependences($neededOptions);
262:
263: // make group
264: if ($groupName)
265: {
266: array_unshift($needed, $main);
267: $this->group($groupName, $needed);
268: }
269:
270: return $this;
271: }
272:
273:
274: /**
275: * Define groups of options
276: *
277: * @param string $name Name of group
278: * @param string|array $options List of options names
279: *
280: * @return Options
281: */
282: public function group($name, $options)
283: {
284: if (in_array($name, array_keys($this->groups)))
285: {
286: throw new LogicException($name . ': Group already exists.');
287: }
288:
289: if (!is_array($options))
290: {
291: $options = array($options);
292: }
293:
294: foreach ($options as $optionName)
295: {
296: if (!isset($this->options[$optionName]))
297: {
298: throw new LogicException($optionName . ': Option does not exist.');
299: }
300: }
301:
302: $this->groups[$name] = $options;
303:
304: return $this;
305: }
306:
307:
308: /**
309: * Return "help" made from descriptions of options
310: *
311: * @return string
312: */
313: public function getHelp($maxLength = self::HELP_MAX_LENGTH)
314: {
315: $help = $this->wordwrap($this->description, $maxLength) . PHP_EOL . PHP_EOL;
316:
317: $optionsNongroup = $this->options;
318:
319: $indent = 0;
320: if ($this->groups)
321: {
322: foreach ($this->groups as $groupName => $optionsNames)
323: {
324: $help .= $this->wordwrap($groupName, $maxLength) . PHP_EOL;
325: foreach ($optionsNames as $optionName)
326: {
327: $help .= $this->options[$optionName]->getHelp($maxLength, 1) . PHP_EOL;
328: if (isset($optionsNongroup[$optionName]))
329: {
330: unset($optionsNongroup[$optionName]);
331: }
332: }
333: }
334:
335: if (count($optionsNongroup) > 0)
336: {
337: $help .= PHP_EOL . $this->wordwrap('NON GROUP OPTIONS:', $maxLength) . PHP_EOL;
338: }
339: $indent = 1;
340: }
341:
342: // print non group options
343: foreach ($optionsNongroup as $option)
344: {
345: $help .= $option->getHelp($maxLength, $indent) . PHP_EOL;
346: }
347:
348: return $help;
349: }
350:
351:
352: // ---- private ----
353:
354:
355: /**
356: * Add one option
357: *
358: * @param Option $option Added option
359: *
360: * @return void
361: */
362: private function addOne(Option $option)
363: {
364: $this->checkConflicts($option);
365: $name = $option->getName();
366: $this->options[$name] = $option;
367: $this->optionsValues[$name] = $option->getValue((bool)($this->defaults['value']));
368: if ($option->getShort())
369: {
370: $this->optionsShorts[$option->getShort()] = $name;
371: }
372: if ($option->getLong())
373: {
374: $this->optionsLongs[$option->getLong()] = $name;
375: }
376: }
377:
378:
379: /**
380: * Check conflict between added option and input option
381: *
382: * @param Option $option Checked option
383: *
384: * @throws LogicException Option already exist.
385: * @throws LogicException Option with set short variant already exist.
386: * @throws LogicException Option with set long variant already exist.
387: * @return void
388: */
389: private function checkConflicts(Option $option)
390: {
391: $name = $option->getName();
392: if (isset($this->options[$name]))
393: {
394: throw new LogicException($name . ': Option already exists.');
395: }
396:
397: $short = $option->getShort();
398: if (in_array($short, array_keys($this->optionsShorts)))
399: {
400: throw new LogicException($name . ': Option with short variant "' . $short . '" already exists.');
401: }
402:
403: $long = $option->getLong();
404: if (in_array($long, array_keys($this->optionsLongs)))
405: {
406: throw new LogicException($name . ': Option with long variant "' . $long . '" already exists.');
407: }
408: }
409:
410:
411: /**
412: * Wrapping input sring on maximal length
413: *
414: * @param string $string Wrapped string
415: * @param int $maxLength Maximla length of one line
416: *
417: * @return string
418: */
419: private function wordwrap($string, $maxLength)
420: {
421: return wordwrap($string, $maxLength, PHP_EOL, TRUE);
422: }
423:
424:
425: }
426: