diff options
author | Steven Knight <knight@baldmt.com> | 2002-11-06 12:25:25 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2002-11-06 12:25:25 (GMT) |
commit | 8dbe61168174137944a5bc55ea5a9ff389336376 (patch) | |
tree | b507cef9d3ffa1248dde67c7f26368841c148b42 /src/engine/SCons/Optik/option.py | |
parent | d3a159a23fdeaa76c7931c7f8b1b7dafd43b4b99 (diff) | |
download | SCons-8dbe61168174137944a5bc55ea5a9ff389336376.zip SCons-8dbe61168174137944a5bc55ea5a9ff389336376.tar.gz SCons-8dbe61168174137944a5bc55ea5a9ff389336376.tar.bz2 |
Refactor command-line parsing. (Steve Leblanc)
Diffstat (limited to 'src/engine/SCons/Optik/option.py')
-rw-r--r-- | src/engine/SCons/Optik/option.py | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/src/engine/SCons/Optik/option.py b/src/engine/SCons/Optik/option.py new file mode 100644 index 0000000..2e1ec33 --- /dev/null +++ b/src/engine/SCons/Optik/option.py @@ -0,0 +1,388 @@ +"""optik.option + +Defines the Option class and some standard value-checking functions. +""" + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +# Original Optik revision this is based on: +__Optik_revision__ = "option.py,v 1.19.2.1 2002/07/23 01:51:14 gward Exp" + +# Copyright (c) 2001 Gregory P. Ward. All rights reserved. +# See the README.txt distributed with Optik for licensing terms. + +# created 2001/10/17, GPW (from optik.py) + +import sys +import string +from types import TupleType, ListType, DictType +from SCons.Optik.errors import OptionError, OptionValueError + +_builtin_cvt = { "int" : (int, "integer"), + "long" : (long, "long integer"), + "float" : (float, "floating-point"), + "complex" : (complex, "complex") } + +def check_builtin (option, opt, value): + (cvt, what) = _builtin_cvt[option.type] + try: + return cvt(value) + except ValueError: + raise OptionValueError( + #"%s: invalid %s argument %s" % (opt, what, repr(value))) + "option %s: invalid %s value: %s" % (opt, what, repr(value))) + +def check_choice(option, opt, value): + if value in option.choices: + return value + else: + choices = string.join(map(repr, option.choices),", ") + raise OptionValueError( + "option %s: invalid choice: %s (choose from %s)" + % (opt, repr(value), choices)) + +# Not supplying a default is different from a default of None, +# so we need an explicit "not supplied" value. +NO_DEFAULT = "NO"+"DEFAULT" + + +class Option: + """ + Instance attributes: + _short_opts : [string] + _long_opts : [string] + + action : string + type : string + dest : string + default : any + nargs : int + const : any + choices : [string] + callback : function + callback_args : (any*) + callback_kwargs : { string : any } + help : string + metavar : string + """ + + # The list of instance attributes that may be set through + # keyword args to the constructor. + ATTRS = ['action', + 'type', + 'dest', + 'default', + 'nargs', + 'const', + 'choices', + 'callback', + 'callback_args', + 'callback_kwargs', + 'help', + 'metavar'] + + # The set of actions allowed by option parsers. Explicitly listed + # here so the constructor can validate its arguments. + ACTIONS = ("store", + "store_const", + "store_true", + "store_false", + "append", + "count", + "callback", + "help", + "version") + + # The set of actions that involve storing a value somewhere; + # also listed just for constructor argument validation. (If + # the action is one of these, there must be a destination.) + STORE_ACTIONS = ("store", + "store_const", + "store_true", + "store_false", + "append", + "count") + + # The set of actions for which it makes sense to supply a value + # type, ie. where we expect an argument to this option. + TYPED_ACTIONS = ("store", + "append", + "callback") + + # The set of known types for option parsers. Again, listed here for + # constructor argument validation. + TYPES = ("string", "int", "long", "float", "complex", "choice") + + # Dictionary of argument checking functions, which convert and + # validate option arguments according to the option type. + # + # Signature of checking functions is: + # check(option : Option, opt : string, value : string) -> any + # where + # option is the Option instance calling the checker + # opt is the actual option seen on the command-line + # (eg. "-a", "--file") + # value is the option argument seen on the command-line + # + # The return value should be in the appropriate Python type + # for option.type -- eg. an integer if option.type == "int". + # + # If no checker is defined for a type, arguments will be + # unchecked and remain strings. + TYPE_CHECKER = { "int" : check_builtin, + "long" : check_builtin, + "float" : check_builtin, + "complex" : check_builtin, + "choice" : check_choice, + } + + + # CHECK_METHODS is a list of unbound method objects; they are called + # by the constructor, in order, after all attributes are + # initialized. The list is created and filled in later, after all + # the methods are actually defined. (I just put it here because I + # like to define and document all class attributes in the same + # place.) Subclasses that add another _check_*() method should + # define their own CHECK_METHODS list that adds their check method + # to those from this class. + CHECK_METHODS = None + + + # -- Constructor/initialization methods ---------------------------- + + def __init__ (self, *opts, **attrs): + # Set _short_opts, _long_opts attrs from 'opts' tuple + opts = self._check_opt_strings(opts) + self._set_opt_strings(opts) + + # Set all other attrs (action, type, etc.) from 'attrs' dict + self._set_attrs(attrs) + + # Check all the attributes we just set. There are lots of + # complicated interdependencies, but luckily they can be farmed + # out to the _check_*() methods listed in CHECK_METHODS -- which + # could be handy for subclasses! The one thing these all share + # is that they raise OptionError if they discover a problem. + for checker in self.CHECK_METHODS: + checker(self) + + def _check_opt_strings (self, opts): + # Filter out None because early versions of Optik had exactly + # one short option and one long option, either of which + # could be None. + opts = filter(None, opts) + if not opts: + raise OptionError("at least one option string must be supplied", + self) + return opts + + def _set_opt_strings (self, opts): + self._short_opts = [] + self._long_opts = [] + for opt in opts: + if len(opt) < 2: + raise OptionError( + "invalid option string %s: " + "must be at least two characters long" % (`opt`,), self) + elif len(opt) == 2: + if not (opt[0] == "-" and opt[1] != "-"): + raise OptionError( + "invalid short option string %s: " + "must be of the form -x, (x any non-dash char)" % (`opt`,), + self) + self._short_opts.append(opt) + else: + if not (opt[0:2] == "--" and opt[2] != "-"): + raise OptionError( + "invalid long option string %s: " + "must start with --, followed by non-dash" % (`opt`,), + self) + self._long_opts.append(opt) + + def _set_attrs (self, attrs): + for attr in self.ATTRS: + if attrs.has_key(attr): + setattr(self, attr, attrs[attr]) + del attrs[attr] + else: + if attr == 'default': + setattr(self, attr, NO_DEFAULT) + else: + setattr(self, attr, None) + if attrs: + raise OptionError( + "invalid keyword arguments: %s" % string.join(attrs.keys(),", "), + self) + + + # -- Constructor validation methods -------------------------------- + + def _check_action (self): + if self.action is None: + self.action = "store" + elif self.action not in self.ACTIONS: + raise OptionError("invalid action: %s" % (`self.action`,), self) + + def _check_type (self): + if self.type is None: + # XXX should factor out another class attr here: list of + # actions that *require* a type + if self.action in ("store", "append"): + if self.choices is not None: + # The "choices" attribute implies "choice" type. + self.type = "choice" + else: + # No type given? "string" is the most sensible default. + self.type = "string" + else: + if self.type not in self.TYPES: + raise OptionError("invalid option type: %s" % (`self.type`,), self) + if self.action not in self.TYPED_ACTIONS: + raise OptionError( + "must not supply a type for action %s" % (`self.action`,), self) + + def _check_choice(self): + if self.type == "choice": + if self.choices is None: + raise OptionError( + "must supply a list of choices for type 'choice'", self) + elif type(self.choices) not in (TupleType, ListType): + raise OptionError( + "choices must be a list of strings ('%s' supplied)" + % string.split(str(type(self.choices)),"'")[1], self) + elif self.choices is not None: + raise OptionError( + "must not supply choices for type %s" % (repr(self.type),), self) + + def _check_dest (self): + if self.action in self.STORE_ACTIONS and self.dest is None: + # No destination given, and we need one for this action. + # Glean a destination from the first long option string, + # or from the first short option string if no long options. + if self._long_opts: + # eg. "--foo-bar" -> "foo_bar" + self.dest = string.replace(self._long_opts[0][2:],'-', '_') + else: + self.dest = self._short_opts[0][1] + + def _check_const (self): + if self.action != "store_const" and self.const is not None: + raise OptionError( + "'const' must not be supplied for action %s" % (repr(self.action),), + self) + + def _check_nargs (self): + if self.action in self.TYPED_ACTIONS: + if self.nargs is None: + self.nargs = 1 + elif self.nargs is not None: + raise OptionError( + "'nargs' must not be supplied for action %s" % (repr(self.action),), + self) + + def _check_callback (self): + if self.action == "callback": + if not callable(self.callback): + raise OptionError( + "callback not callable: %s" % (repr(self.callback),), self) + if (self.callback_args is not None and + type(self.callback_args) is not TupleType): + raise OptionError( + "callback_args, if supplied, must be a tuple: not %s" + % (repr(self.callback_args),), self) + if (self.callback_kwargs is not None and + type(self.callback_kwargs) is not DictType): + raise OptionError( + "callback_kwargs, if supplied, must be a dict: not %s" + % (repr(self.callback_kwargs),), self) + else: + if self.callback is not None: + raise OptionError( + "callback supplied (%s) for non-callback option" + % (repr(self.callback),), self) + if self.callback_args is not None: + raise OptionError( + "callback_args supplied for non-callback option", self) + if self.callback_kwargs is not None: + raise OptionError( + "callback_kwargs supplied for non-callback option", self) + + + CHECK_METHODS = [_check_action, + _check_type, + _check_choice, + _check_dest, + _check_const, + _check_nargs, + _check_callback] + + + # -- Miscellaneous methods ----------------------------------------- + + def __str__ (self): + if self._short_opts or self._long_opts: + return string.join(self._short_opts + self._long_opts,"/") + else: + raise RuntimeError, "short_opts and long_opts both empty!" + + def takes_value (self): + return self.type is not None + + + # -- Processing methods -------------------------------------------- + + def check_value (self, opt, value): + checker = self.TYPE_CHECKER.get(self.type) + if checker is None: + return value + else: + return checker(self, opt, value) + + def process (self, opt, value, values, parser): + + # First, convert the value(s) to the right type. Howl if any + # value(s) are bogus. + if value is not None: + if self.nargs == 1: + value = self.check_value(opt, value) + else: + def cv(v,check=self.check_value,o=opt): + return check(o,v) + + value = tuple(map(cv,value)) + + # And then take whatever action is expected of us. + # This is a separate method to make life easier for + # subclasses to add new actions. + return self.take_action( + self.action, self.dest, opt, value, values, parser) + + def take_action (self, action, dest, opt, value, values, parser): + if action == "store": + setattr(values, dest, value) + elif action == "store_const": + setattr(values, dest, self.const) + elif action == "store_true": + setattr(values, dest, 1) + elif action == "store_false": + setattr(values, dest, 0) + elif action == "append": + values.ensure_value(dest, []).append(value) + elif action == "count": + setattr(values, dest, values.ensure_value(dest, 0) + 1) + elif action == "callback": + args = self.callback_args or () + kwargs = self.callback_kwargs or {} + apply( self.callback, (self, opt, value, parser,)+ args, kwargs) + elif action == "help": + parser.print_help() + sys.exit(0) + elif action == "version": + parser.print_version() + sys.exit(0) + else: + raise RuntimeError, "unknown action %s" % (repr(self.action),) + + return 1 + +# class Option |