Overview

Packages

  • Argv

Classes

  • Horde_Argv_AmbiguousOptionException
  • Horde_Argv_BadOptionException
  • Horde_Argv_Exception
  • Horde_Argv_HelpFormatter
  • Horde_Argv_IndentedHelpFormatter
  • Horde_Argv_Option
  • Horde_Argv_OptionConflictException
  • Horde_Argv_OptionContainer
  • Horde_Argv_OptionException
  • Horde_Argv_OptionGroup
  • Horde_Argv_OptionValueException
  • Horde_Argv_Parser
  • Horde_Argv_TitledHelpFormatter
  • Horde_Argv_Translation
  • Horde_Argv_Values
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * @author   Chuck Hagenbuch <chuck@horde.org>
  4:  * @author   Mike Naberezny <mike@maintainable.com>
  5:  * @license  http://www.horde.org/licenses/bsd BSD
  6:  * @category Horde
  7:  * @package  Argv
  8:  */
  9: 
 10: /**
 11:  * Defines the Option class and some standard value-checking functions.
 12:  *
 13:  * Instance attributes:
 14:  *    shortOpts : [string]
 15:  *    longOpts : [string]
 16:  *
 17:  *    action : string
 18:  *    type : string
 19:  *    dest : string
 20:  *    default : any
 21:  *    nargs : int
 22:  *    const : any
 23:  *    choices : [string]
 24:  *    callback : function
 25:  *    callbackArgs : (any*)
 26:  *    help : string
 27:  *    metavar : string
 28:  *
 29:  * @category Horde
 30:  * @package  Argv
 31:  */
 32: class Horde_Argv_Option
 33: {
 34:     const SUPPRESS_HELP = 'SUPPRESS HELP';
 35:     const SUPPRESS_USAGE = 'SUPPRESS USAGE';
 36: 
 37:     /**
 38:      * Not supplying a default is different from a default of None,
 39:      * so we need an explicit "not supplied" value.
 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:         // Values to check against or compute with.
 50:         $first = substr($value, 0, 1);
 51:         $prefix = substr($value, 0, 2);
 52:         $suffix = substr($value, 2);
 53: 
 54:         // Hex
 55:         if ($prefix == '0x' || $prefix == '0X') {
 56:             if (strspn($suffix, '0123456789abcdefABCDEF') != strlen($suffix)) {
 57:                 return false;
 58:             }
 59:             return hexdec($value);
 60:         }
 61: 
 62:         // Binary
 63:         if ($prefix == '0b' || $prefix == '0B') {
 64:             if (strspn($suffix, '01') != strlen($suffix)) {
 65:                 return false;
 66:             }
 67:             return bindec($value);
 68:         }
 69: 
 70:         // Octal
 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:         // Base 10
 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:      * The list of instance attributes that may be set through keyword args to
130:      * the constructor.
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:      * The set of actions allowed by option parsers.  Explicitly listed here so
148:      * the constructor can validate its arguments.
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:      * The set of actions that involve storing a value somewhere;
165:      * also listed just for constructor argument validation.  (If
166:      * the action is one of these, there must be a destination.)
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:      * The set of actions for which it makes sense to supply a value type,
180:      * ie. which may consume an argument from the command line.
181:      */
182:     public $TYPED_ACTIONS = array(
183:         'store',
184:         'append',
185:         'callback',
186:     );
187: 
188:     /**
189:      * The set of actions which *require* a value type, ie. that always consume
190:      * an argument from the command line.
191:      */
192:     public $ALWAYS_TYPED_ACTIONS = array('store', 'append');
193: 
194:     /**
195:      * The set of actions which take a 'const' attribute.
196:      */
197:     public $CONST_ACTIONS = array('store_const', 'append_const');
198: 
199:     # The set of known types for option parsers.  Again, listed here for
200:     # constructor argument validation.
201:     public $TYPES = array('string', 'int', 'long', 'float', 'complex', 'choice');
202: 
203:     # Dictionary of argument checking functions, which convert and
204:     # validate option arguments according to the option type.
205:     #
206:     # Signature of checking functions is:
207:     #   check(option : Option, opt : string, value : string) -> any
208:     # where
209:     #   option is the Option instance calling the checker
210:     #   opt is the actual option seen on the command-line
211:     #     (eg. "-a", "--file")
212:     #   value is the option argument seen on the command-line
213:     #
214:     # The return value should be in the appropriate Python type
215:     # for option.type -- eg. an integer if option.type == "int".
216:     #
217:     # If no checker is defined for a type, arguments will be
218:     # unchecked and remain strings.
219:     public $TYPE_CHECKER = array("int"    => 'checkBuiltin',
220:                                  "long"   => 'checkBuiltin',
221:                                  "float"  => 'checkBuiltin',
222:                                  "complex"=> 'checkBuiltin',
223:                                  "choice" => 'checkChoice',
224:     );
225: 
226:     # CHECK_METHODS is a list of unbound method objects; they are called
227:     # by the constructor, in order, after all attributes are
228:     # initialized.  The list is created and filled in later, after all
229:     # the methods are actually defined.  (I just put it here because I
230:     # like to define and document all class attributes in the same
231:     # place.)  Subclasses that add another _check_*() method should
232:     # define their own CHECK_METHODS list that adds their check method
233:     # to those from this class.
234:     public $CHECK_METHODS = array('_checkAction',
235:                                   '_checkType',
236:                                   '_checkChoice',
237:                                   '_checkDest',
238:                                   '_checkConst',
239:                                   '_checkNargs',
240:                                   '_checkCallback',
241:                                   );
242: 
243:     // -- Constructor/initialization methods ----------------------------
244: 
245:     public $shortOpts = array();
246:     public $longOpts = array();
247:     public $dest;
248:     public $default;
249: 
250:     /**
251:      * Constructor.
252:      */
253:     public function __construct()
254:     {
255:         // The last argument to this function is an $attrs hash, if it
256:         // is present and an array. All other arguments are $opts.
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:         // Set shortOpts, longOpts attrs from 'opts' tuple.
266:         // Have to be set now, in case no option strings are supplied.
267:         $this->shortOpts = array();
268:         $this->longOpts = array();
269:         $opts = $this->_checkOptStrings($opts);
270:         $this->_setOptStrings($opts);
271: 
272:         // Set all other attrs (action, type, etc.) from 'attrs' dict
273:         $this->_setAttrs($attrs);
274: 
275:         // Check all the attributes we just set.  There are lots of
276:         // complicated interdependencies, but luckily they can be farmed
277:         // out to the _check*() methods listed in CHECK_METHODS -- which
278:         // could be handy for subclasses!  The one thing these all share
279:         // is that they raise OptionError if they discover a problem.
280:         foreach ($this->CHECK_METHODS as $checker) {
281:             call_user_func(array($this, $checker));
282:         }
283:     }
284: 
285:     protected function _checkOptStrings($opts)
286:     {
287:         // Filter out None because early versions of Optik had exactly
288:         // one short option and one long option, either of which
289:         // could be None.
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:     // -- Constructor validation methods --------------------------------
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:                     // The "choices" attribute implies "choice" type.
363:                     $this->type = "choice";
364:                 } else {
365:                     // No type given?  "string" is the most sensible default.
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:         // No destination given, and we need one for this action.  The
405:         // $this->type check is for callbacks that take a value.
406:         $takes_value = (in_array($this->action, $this->STORE_ACTIONS) ||
407:                         !is_null($this->type));
408:         if (is_null($this->dest) && $takes_value) {
409:             // Glean a destination from the first long option string,
410:             // or from the first short option string if no long options.
411:             if ($this->longOpts) {
412:                 // eg. "--foo-bar" -> "foo_bar"
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:     // -- Miscellaneous methods -----------------------------------------
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:     // -- Processing methods --------------------------------------------
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:         // First, convert the value(s) to the right type.  Howl if any
530:         // value(s) are bogus.
531:         $value = $this->convertValue($opt, $value);
532: 
533:         // And then take whatever action is expected of us.
534:         // This is a separate method to make life easier for
535:         // subclasses to add new actions.
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: 
API documentation generated by ApiGen