1: <?php
2: 3: 4: 5: 6: 7: 8:
9:
10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:
32: class Horde_Argv_Option
33: {
34: const SUPPRESS_HELP = 'SUPPRESS HELP';
35: const SUPPRESS_USAGE = 'SUPPRESS USAGE';
36:
37: 38: 39: 40:
41: public static $NO_DEFAULT = array('NO', 'DEFAULT');
42:
43: public static function parseNumber($value)
44: {
45: if (!strlen($value)) {
46: return false;
47: }
48:
49:
50: $first = substr($value, 0, 1);
51: $prefix = substr($value, 0, 2);
52: $suffix = substr($value, 2);
53:
54:
55: if ($prefix == '0x' || $prefix == '0X') {
56: if (strspn($suffix, '0123456789abcdefABCDEF') != strlen($suffix)) {
57: return false;
58: }
59: return hexdec($value);
60: }
61:
62:
63: if ($prefix == '0b' || $prefix == '0B') {
64: if (strspn($suffix, '01') != strlen($suffix)) {
65: return false;
66: }
67: return bindec($value);
68: }
69:
70:
71: if ($first == '0') {
72: $suffix = substr($value, 1);
73: if (strspn($suffix, '01234567') != strlen($suffix)) {
74: return false;
75: }
76: return octdec($suffix);
77: }
78:
79:
80: if (!is_numeric($value)) {
81: return false;
82: }
83: return intval($value);
84: }
85:
86: public function checkBuiltin($opt, $value)
87: {
88: switch ($this->type) {
89: case 'int':
90: case 'long':
91: $number = self::parseNumber($value);
92: if ($number === false) {
93: $message = $this->type == 'int'
94: ? Horde_Argv_Translation::t("option %s: invalid integer value: '%s'")
95: : Horde_Argv_Translation::t("option %s: invalid long integer value: '%s'");
96: throw new Horde_Argv_OptionValueException(
97: sprintf($message, $opt, $value));
98: }
99: return $number;
100:
101: case 'float':
102: if (!is_numeric($value)) {
103: throw new Horde_Argv_OptionValueException(
104: sprintf(Horde_Argv_Translation::t("option %s: invalid floating-point value: '%s'"),
105: $opt, $value));
106: }
107: return floatval($value);
108: }
109: }
110:
111: public function checkChoice($opt, $value)
112: {
113: if (in_array($value, $this->choices)) {
114: return $value;
115: } else {
116: $choices = array();
117: foreach ($this->choices as $choice) {
118: $choices[] = (string)$choice;
119: }
120: $choices = "'" . implode("', '", $choices) . "'";
121: throw new Horde_Argv_OptionValueException(sprintf(
122: Horde_Argv_Translation::t("option %s: invalid choice: '%s' (choose from %s)"),
123: $opt, $value, $choices));
124: }
125: }
126:
127:
128: 129: 130: 131:
132: public $ATTRS = array(
133: 'action',
134: 'type',
135: 'dest',
136: 'default',
137: 'nargs',
138: 'const',
139: 'choices',
140: 'callback',
141: 'callbackArgs',
142: 'help',
143: 'metavar',
144: );
145:
146: 147: 148: 149:
150: public $ACTIONS = array(
151: 'store',
152: 'store_const',
153: 'store_true',
154: 'store_false',
155: 'append',
156: 'append_const',
157: 'count',
158: 'callback',
159: 'help',
160: 'version',
161: );
162:
163: 164: 165: 166: 167:
168: public $STORE_ACTIONS = array(
169: 'store',
170: 'store_const',
171: 'store_true',
172: 'store_false',
173: 'append',
174: 'append_const',
175: 'count',
176: );
177:
178: 179: 180: 181:
182: public $TYPED_ACTIONS = array(
183: 'store',
184: 'append',
185: 'callback',
186: );
187:
188: 189: 190: 191:
192: public $ALWAYS_TYPED_ACTIONS = array('store', 'append');
193:
194: 195: 196:
197: public $CONST_ACTIONS = array('store_const', 'append_const');
198:
199:
200:
201: public $TYPES = array('string', 'int', 'long', 'float', 'complex', 'choice');
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219: public $TYPE_CHECKER = array("int" => 'checkBuiltin',
220: "long" => 'checkBuiltin',
221: "float" => 'checkBuiltin',
222: "complex"=> 'checkBuiltin',
223: "choice" => 'checkChoice',
224: );
225:
226:
227:
228:
229:
230:
231:
232:
233:
234: public $CHECK_METHODS = array('_checkAction',
235: '_checkType',
236: '_checkChoice',
237: '_checkDest',
238: '_checkConst',
239: '_checkNargs',
240: '_checkCallback',
241: );
242:
243:
244:
245: public $shortOpts = array();
246: public $longOpts = array();
247: public $dest;
248: public $default;
249:
250: 251: 252:
253: public function __construct()
254: {
255:
256:
257: $opts = func_get_args();
258: $num = func_num_args();
259: if ($num == 0 || $num == 1 || !is_array($opts[$num - 1])) {
260: $attrs = array();
261: } else {
262: $attrs = array_pop($opts);
263: }
264:
265:
266:
267: $this->shortOpts = array();
268: $this->longOpts = array();
269: $opts = $this->_checkOptStrings($opts);
270: $this->_setOptStrings($opts);
271:
272:
273: $this->_setAttrs($attrs);
274:
275:
276:
277:
278:
279:
280: foreach ($this->CHECK_METHODS as $checker) {
281: call_user_func(array($this, $checker));
282: }
283: }
284:
285: protected function _checkOptStrings($opts)
286: {
287:
288:
289:
290: $opts = array_filter($opts);
291: if (!$opts) {
292: throw new InvalidArgumentException('at least one option string must be supplied');
293: }
294: return $opts;
295: }
296:
297: protected function _setOptStrings($opts)
298: {
299: foreach ($opts as &$opt) {
300: $opt = (string)$opt;
301:
302: if (strlen($opt) < 2) {
303: throw new Horde_Argv_OptionException(sprintf("invalid option string '%s': must be at least two characters long", $opt), $this);
304: } elseif (strlen($opt) == 2) {
305: if (!($opt[0] == "-" && $opt[1] != "-")) {
306: throw new Horde_Argv_OptionException(sprintf(
307: "invalid short option string '%s': " .
308: "must be of the form -x, (x any non-dash char)", $opt), $this);
309: }
310: $this->shortOpts[] = $opt;
311: } else {
312: if (!(substr($opt, 0, 2) == '--' && $opt[2] != '-')) {
313: throw new Horde_Argv_OptionException(sprintf(
314: "invalid long option string '%s': " .
315: "must start with --, followed by non-dash", $opt), $this);
316: }
317: $this->longOpts[] = $opt;
318: }
319: }
320: }
321:
322: protected function _setAttrs($attrs)
323: {
324: foreach ($this->ATTRS as $attr) {
325: if (array_key_exists($attr, $attrs)) {
326: $this->$attr = $attrs[$attr];
327: unset($attrs[$attr]);
328: } else {
329: if ($attr == 'default') {
330: $this->$attr = self::$NO_DEFAULT;
331: } else {
332: $this->$attr = null;
333: }
334: }
335: }
336:
337: if ($attrs) {
338: $attrs = array_keys($attrs);
339: sort($attrs);
340: throw new Horde_Argv_OptionException(sprintf(
341: "invalid keyword arguments: %s", implode(", ", $attrs)), $this);
342: }
343: }
344:
345:
346:
347:
348: public function _checkAction()
349: {
350: if (is_null($this->action)) {
351: $this->action = "store";
352: } elseif (!in_array($this->action, $this->ACTIONS)) {
353: throw new Horde_Argv_OptionException(sprintf("invalid action: '%s'", $this->action), $this);
354: }
355: }
356:
357: public function _checkType()
358: {
359: if (is_null($this->type)) {
360: if (in_array($this->action, $this->ALWAYS_TYPED_ACTIONS)) {
361: if (!is_null($this->choices)) {
362:
363: $this->type = "choice";
364: } else {
365:
366: $this->type = "string";
367: }
368: }
369: } else {
370: if ($this->type == "str") {
371: $this->type = "string";
372: }
373:
374: if (!in_array($this->type, $this->TYPES)) {
375: throw new Horde_Argv_OptionException(sprintf("invalid option type: '%s'", $this->type), $this);
376: }
377:
378: if (!in_array($this->action, $this->TYPED_ACTIONS)) {
379: throw new Horde_Argv_OptionException(sprintf(
380: "must not supply a type for action '%s'", $this->action), $this);
381: }
382: }
383: }
384:
385: public function _checkChoice()
386: {
387: if ($this->type == "choice") {
388: if (is_null($this->choices)) {
389: throw new Horde_Argv_OptionException(
390: "must supply a list of choices for type 'choice'", $this);
391: } elseif (!(is_array($this->choices) || $this->choices instanceof Iterator)) {
392: throw new Horde_Argv_OptionException(sprintf(
393: "choices must be a list of strings ('%s' supplied)",
394: gettype($this->choices)), $this);
395: }
396: } elseif (!is_null($this->choices)) {
397: throw new Horde_Argv_OptionException(sprintf(
398: "must not supply choices for type '%s'", $this->type), $this);
399: }
400: }
401:
402: public function _checkDest()
403: {
404:
405:
406: $takes_value = (in_array($this->action, $this->STORE_ACTIONS) ||
407: !is_null($this->type));
408: if (is_null($this->dest) && $takes_value) {
409:
410:
411: if ($this->longOpts) {
412:
413: $this->dest = str_replace('-', '_', substr($this->longOpts[0], 2));
414: } else {
415: $this->dest = $this->shortOpts[0][1];
416: }
417: }
418: }
419:
420: public function _checkConst()
421: {
422: if (!in_array($this->action, $this->CONST_ACTIONS) && !is_null($this->const)) {
423: throw new Horde_Argv_OptionException(sprintf(
424: "'const' must not be supplied for action '%s'", $this->action),
425: $this);
426: }
427: }
428:
429: public function _checkNargs()
430: {
431: if (in_array($this->action, $this->TYPED_ACTIONS)) {
432: if (is_null($this->nargs)) {
433: $this->nargs = 1;
434: }
435: } elseif (!is_null($this->nargs)) {
436: throw new Horde_Argv_OptionException(sprintf(
437: "'nargs' must not be supplied for action '%s'", $this->action),
438: $this);
439: }
440: }
441:
442: public function _checkCallback()
443: {
444: if ($this->action == "callback") {
445: if (!is_callable($this->callback)) {
446: $callback_name = is_array($this->callback) ?
447: is_object($this->callback[0]) ? get_class($this->callback[0] . '#' . $this->callback[1]) : implode('#', $this->callback) :
448: $this->callback;
449: throw new Horde_Argv_OptionException(sprintf(
450: "callback not callable: '%s'", $callback_name), $this);
451: }
452: if (!is_null($this->callbackArgs) && !is_array($this->callbackArgs)) {
453: throw new Horde_Argv_OptionException(sprintf(
454: "callbackArgs, if supplied, must be an array: not '%s'",
455: $this->callbackArgs), $this);
456: }
457: } else {
458: if (!is_null($this->callback)) {
459: $callback_name = is_array($this->callback) ?
460: is_object($this->callback[0]) ? get_class($this->callback[0] . '#' . $this->callback[1]) : implode('#', $this->callback) :
461: $this->callback;
462: throw new Horde_Argv_OptionException(sprintf(
463: "callback supplied ('%s') for non-callback option",
464: $callback_name), $this);
465: }
466: if (!is_null($this->callbackArgs)) {
467: throw new Horde_Argv_OptionException(
468: "callbackArgs supplied for non-callback option", $this);
469: }
470: }
471: }
472:
473:
474:
475:
476: public function __toString()
477: {
478: return implode('/', array_merge($this->shortOpts, $this->longOpts));
479: }
480:
481: public function takesValue()
482: {
483: return !is_null($this->type);
484: }
485:
486: public function hasDefault()
487: {
488: return $this->default !== self::$NO_DEFAULT;
489: }
490:
491: public function getOptString()
492: {
493: if ($this->longOpts) {
494: return $this->longOpts[0];
495: } else {
496: return $this->shortOpts[0];
497: }
498: }
499:
500:
501:
502:
503: public function checkValue($opt, $value)
504: {
505: if (!isset($this->TYPE_CHECKER[$this->type])) {
506: return $value;
507: }
508: $checker = $this->TYPE_CHECKER[$this->type];
509: return call_user_func(array($this, $checker), $opt, $value);
510: }
511:
512: public function convertValue($opt, $value)
513: {
514: if (!is_null($value)) {
515: if ($this->nargs == 1) {
516: return $this->checkValue($opt, $value);
517: } else {
518: $return = array();
519: foreach ($value as $v) {
520: $return[] = $this->checkValue($opt, $v);
521: }
522: return $return;
523: }
524: }
525: }
526:
527: public function process($opt, $value, $values, $parser)
528: {
529:
530:
531: $value = $this->convertValue($opt, $value);
532:
533:
534:
535:
536: return $this->takeAction(
537: $this->action, $this->dest, $opt, $value, $values, $parser);
538: }
539:
540: public function takeAction($action, $dest, $opt, $value, $values, $parser)
541: {
542: if ($action == 'store') {
543: $values->$dest = $value;
544: } elseif ($action == 'store_const') {
545: $values->$dest = $this->const;
546: } elseif ($action == 'store_true') {
547: $values->$dest = true;
548: } elseif ($action == 'store_false') {
549: $values->$dest = false;
550: } elseif ($action == 'append') {
551: $values->{$dest}[] = $value;
552: } elseif ($action == 'append_const') {
553: $values->{$dest}[] = $this->const;
554: } elseif ($action == 'count') {
555: $values->ensureValue($dest, 0);
556: $values->$dest++;
557: } elseif ($action == 'callback') {
558: call_user_func($this->callback, $this, $opt, $value, $parser, $this->callbackArgs);
559: } elseif ($action == 'help') {
560: $parser->printHelp();
561: $parser->parserExit();
562: } elseif ($action == 'version') {
563: $parser->printVersion();
564: $parser->parserExit();
565: } else {
566: throw new RuntimeException('unknown action ' . $this->action);
567: }
568:
569: return 1;
570: }
571:
572: }
573: