1"""optik.option
2
3Defines the Option class and some standard value-checking functions.
4"""
5
6# Copyright (c) 2001-2006 Gregory P. Ward.  All rights reserved.
7# See the README.txt distributed with Optik for licensing terms.
8
9import sys
10import types
11from optik.errors import OptionError, OptionValueError, gettext as _
12
13__revision__ = "$Id: option.py 522 2006-06-11 16:22:03Z gward $"
14
15__all__ = ['Option']
16
17# Do the right thing with boolean values for all known Python versions.
18try:
19    True, False
20except NameError:
21    (True, False) = (1, 0)
22
23_idmax = 2L * sys.maxint + 1
24
25def _repr(self):
26    return "<%s at 0x%x: %s>" % (self.__class__.__name__,
27                                 id(self) & _idmax,
28                                 self)
29
30def _parse_num(val, type):
31    if val[:2].lower() == "0x":         # hexadecimal
32        radix = 16
33    elif val[:2].lower() == "0b":       # binary
34        radix = 2
35        val = val[2:] or "0"            # have to remove "0b" prefix
36    elif val[:1] == "0":                # octal
37        radix = 8
38    else:                               # decimal
39        radix = 10
40
41    return type(val, radix)
42
43def _parse_int(val):
44    return _parse_num(val, int)
45
46def _parse_long(val):
47    return _parse_num(val, long)
48
49_builtin_cvt = { "int" : (_parse_int, _("integer")),
50                 "long" : (_parse_long, _("long integer")),
51                 "float" : (float, _("floating-point")),
52                 "complex" : (complex, _("complex")) }
53
54def check_builtin(option, opt, value):
55    (cvt, what) = _builtin_cvt[option.type]
56    try:
57        return cvt(value)
58    except ValueError:
59        raise OptionValueError(
60            _("option %s: invalid %s value: %r") % (opt, what, value))
61
62def check_choice(option, opt, value):
63    if value in option.choices:
64        return value
65    else:
66        choices = ", ".join(map(repr, option.choices))
67        raise OptionValueError(
68            _("option %s: invalid choice: %r (choose from %s)")
69            % (opt, value, choices))
70
71# Not supplying a default is different from a default of None,
72# so we need an explicit "not supplied" value.
73NO_DEFAULT = ("NO", "DEFAULT")
74
75
76class Option:
77    """
78    Instance attributes:
79      _short_opts : [string]
80      _long_opts : [string]
81
82      action : string
83      type : string
84      dest : string
85      default : any
86      nargs : int
87      const : any
88      choices : [string]
89      callback : function
90      callback_args : (any*)
91      callback_kwargs : { string : any }
92      help : string
93      metavar : string
94    """
95
96    # The list of instance attributes that may be set through
97    # keyword args to the constructor.
98    ATTRS = ['action',
99             'type',
100             'dest',
101             'default',
102             'nargs',
103             'const',
104             'choices',
105             'callback',
106             'callback_args',
107             'callback_kwargs',
108             'help',
109             'metavar']
110
111    # The set of actions allowed by option parsers.  Explicitly listed
112    # here so the constructor can validate its arguments.
113    ACTIONS = ("store",
114               "store_const",
115               "store_true",
116               "store_false",
117               "append",
118               "append_const",
119               "count",
120               "callback",
121               "help",
122               "version")
123
124    # The set of actions that involve storing a value somewhere;
125    # also listed just for constructor argument validation.  (If
126    # the action is one of these, there must be a destination.)
127    STORE_ACTIONS = ("store",
128                     "store_const",
129                     "store_true",
130                     "store_false",
131                     "append",
132                     "append_const",
133                     "count")
134
135    # The set of actions for which it makes sense to supply a value
136    # type, ie. which may consume an argument from the command line.
137    TYPED_ACTIONS = ("store",
138                     "append",
139                     "callback")
140
141    # The set of actions which *require* a value type, ie. that
142    # always consume an argument from the command line.
143    ALWAYS_TYPED_ACTIONS = ("store",
144                            "append")
145
146    # The set of actions which take a 'const' attribute.
147    CONST_ACTIONS = ("store_const",
148                     "append_const")
149
150    # The set of known types for option parsers.  Again, listed here for
151    # constructor argument validation.
152    TYPES = ("string", "int", "long", "float", "complex", "choice")
153
154    # Dictionary of argument checking functions, which convert and
155    # validate option arguments according to the option type.
156    #
157    # Signature of checking functions is:
158    #   check(option : Option, opt : string, value : string) -> any
159    # where
160    #   option is the Option instance calling the checker
161    #   opt is the actual option seen on the command-line
162    #     (eg. "-a", "--file")
163    #   value is the option argument seen on the command-line
164    #
165    # The return value should be in the appropriate Python type
166    # for option.type -- eg. an integer if option.type == "int".
167    #
168    # If no checker is defined for a type, arguments will be
169    # unchecked and remain strings.
170    TYPE_CHECKER = { "int"    : check_builtin,
171                     "long"   : check_builtin,
172                     "float"  : check_builtin,
173                     "complex": check_builtin,
174                     "choice" : check_choice,
175                   }
176
177
178    # CHECK_METHODS is a list of unbound method objects; they are called
179    # by the constructor, in order, after all attributes are
180    # initialized.  The list is created and filled in later, after all
181    # the methods are actually defined.  (I just put it here because I
182    # like to define and document all class attributes in the same
183    # place.)  Subclasses that add another _check_*() method should
184    # define their own CHECK_METHODS list that adds their check method
185    # to those from this class.
186    CHECK_METHODS = None
187
188
189    # -- Constructor/initialization methods ----------------------------
190
191    def __init__(self, *opts, **attrs):
192        # Set _short_opts, _long_opts attrs from 'opts' tuple.
193        # Have to be set now, in case no option strings are supplied.
194        self._short_opts = []
195        self._long_opts = []
196        opts = self._check_opt_strings(opts)
197        self._set_opt_strings(opts)
198
199        # Set all other attrs (action, type, etc.) from 'attrs' dict
200        self._set_attrs(attrs)
201
202        # Check all the attributes we just set.  There are lots of
203        # complicated interdependencies, but luckily they can be farmed
204        # out to the _check_*() methods listed in CHECK_METHODS -- which
205        # could be handy for subclasses!  The one thing these all share
206        # is that they raise OptionError if they discover a problem.
207        for checker in self.CHECK_METHODS:
208            checker(self)
209
210    def _check_opt_strings(self, opts):
211        # Filter out None because early versions of Optik had exactly
212        # one short option and one long option, either of which
213        # could be None.
214        opts = filter(None, opts)
215        if not opts:
216            raise TypeError("at least one option string must be supplied")
217        return opts
218
219    def _set_opt_strings(self, opts):
220        for opt in opts:
221            if len(opt) < 2:
222                raise OptionError(
223                    "invalid option string %r: "
224                    "must be at least two characters long" % opt, self)
225            elif len(opt) == 2:
226                if not (opt[0] == "-" and opt[1] != "-"):
227                    raise OptionError(
228                        "invalid short option string %r: "
229                        "must be of the form -x, (x any non-dash char)" % opt,
230                        self)
231                self._short_opts.append(opt)
232            else:
233                if not (opt[0:2] == "--" and opt[2] != "-"):
234                    raise OptionError(
235                        "invalid long option string %r: "
236                        "must start with --, followed by non-dash" % opt,
237                        self)
238                self._long_opts.append(opt)
239
240    def _set_attrs(self, attrs):
241        for attr in self.ATTRS:
242            if attrs.has_key(attr):
243                setattr(self, attr, attrs[attr])
244                del attrs[attr]
245            else:
246                if attr == 'default':
247                    setattr(self, attr, NO_DEFAULT)
248                else:
249                    setattr(self, attr, None)
250        if attrs:
251            attrs = attrs.keys()
252            attrs.sort()
253            raise OptionError(
254                "invalid keyword arguments: %s" % ", ".join(attrs),
255                self)
256
257
258    # -- Constructor validation methods --------------------------------
259
260    def _check_action(self):
261        if self.action is None:
262            self.action = "store"
263        elif self.action not in self.ACTIONS:
264            raise OptionError("invalid action: %r" % self.action, self)
265
266    def _check_type(self):
267        if self.type is None:
268            if self.action in self.ALWAYS_TYPED_ACTIONS:
269                if self.choices is not None:
270                    # The "choices" attribute implies "choice" type.
271                    self.type = "choice"
272                else:
273                    # No type given?  "string" is the most sensible default.
274                    self.type = "string"
275        else:
276            # Allow type objects or builtin type conversion functions
277            # (int, str, etc.) as an alternative to their names.  (The
278            # complicated check of __builtin__ is only necessary for
279            # Python 2.1 and earlier, and is short-circuited by the
280            # first check on modern Pythons.)
281            import __builtin__
282            if ( type(self.type) is types.TypeType or
283                 (hasattr(self.type, "__name__") and
284                  getattr(__builtin__, self.type.__name__, None) is self.type) ):
285                self.type = self.type.__name__
286
287            if self.type == "str":
288                self.type = "string"
289
290            if self.type not in self.TYPES:
291                raise OptionError("invalid option type: %r" % self.type, self)
292            if self.action not in self.TYPED_ACTIONS:
293                raise OptionError(
294                    "must not supply a type for action %r" % self.action, self)
295
296    def _check_choice(self):
297        if self.type == "choice":
298            if self.choices is None:
299                raise OptionError(
300                    "must supply a list of choices for type 'choice'", self)
301            elif type(self.choices) not in (types.TupleType, types.ListType):
302                raise OptionError(
303                    "choices must be a list of strings ('%s' supplied)"
304                    % str(type(self.choices)).split("'")[1], self)
305        elif self.choices is not None:
306            raise OptionError(
307                "must not supply choices for type %r" % self.type, self)
308
309    def _check_dest(self):
310        # No destination given, and we need one for this action.  The
311        # self.type check is for callbacks that take a value.
312        takes_value = (self.action in self.STORE_ACTIONS or
313                       self.type is not None)
314        if self.dest is None and takes_value:
315
316            # Glean a destination from the first long option string,
317            # or from the first short option string if no long options.
318            if self._long_opts:
319                # eg. "--foo-bar" -> "foo_bar"
320                self.dest = self._long_opts[0][2:].replace('-', '_')
321            else:
322                self.dest = self._short_opts[0][1]
323
324    def _check_const(self):
325        if self.action not in self.CONST_ACTIONS and self.const is not None:
326            raise OptionError(
327                "'const' must not be supplied for action %r" % self.action,
328                self)
329
330    def _check_nargs(self):
331        if self.action in self.TYPED_ACTIONS:
332            if self.nargs is None:
333                self.nargs = 1
334        elif self.nargs is not None:
335            raise OptionError(
336                "'nargs' must not be supplied for action %r" % self.action,
337                self)
338
339    def _check_callback(self):
340        if self.action == "callback":
341            if not callable(self.callback):
342                raise OptionError(
343                    "callback not callable: %r" % self.callback, self)
344            if (self.callback_args is not None and
345                type(self.callback_args) is not types.TupleType):
346                raise OptionError(
347                    "callback_args, if supplied, must be a tuple: not %r"
348                    % self.callback_args, self)
349            if (self.callback_kwargs is not None and
350                type(self.callback_kwargs) is not types.DictType):
351                raise OptionError(
352                    "callback_kwargs, if supplied, must be a dict: not %r"
353                    % self.callback_kwargs, self)
354        else:
355            if self.callback is not None:
356                raise OptionError(
357                    "callback supplied (%r) for non-callback option"
358                    % self.callback, self)
359            if self.callback_args is not None:
360                raise OptionError(
361                    "callback_args supplied for non-callback option", self)
362            if self.callback_kwargs is not None:
363                raise OptionError(
364                    "callback_kwargs supplied for non-callback option", self)
365
366
367    CHECK_METHODS = [_check_action,
368                     _check_type,
369                     _check_choice,
370                     _check_dest,
371                     _check_const,
372                     _check_nargs,
373                     _check_callback]
374
375
376    # -- Miscellaneous methods -----------------------------------------
377
378    def __str__(self):
379        return "/".join(self._short_opts + self._long_opts)
380
381    __repr__ = _repr
382
383    def takes_value(self):
384        return self.type is not None
385
386    def get_opt_string(self):
387        if self._long_opts:
388            return self._long_opts[0]
389        else:
390            return self._short_opts[0]
391
392
393    # -- Processing methods --------------------------------------------
394
395    def check_value(self, opt, value):
396        checker = self.TYPE_CHECKER.get(self.type)
397        if checker is None:
398            return value
399        else:
400            return checker(self, opt, value)
401
402    def convert_value(self, opt, value):
403        if value is not None:
404            if self.nargs == 1:
405                return self.check_value(opt, value)
406            else:
407                return tuple([self.check_value(opt, v) for v in value])
408
409    def process(self, opt, value, values, parser):
410
411        # First, convert the value(s) to the right type.  Howl if any
412        # value(s) are bogus.
413        value = self.convert_value(opt, value)
414
415        # And then take whatever action is expected of us.
416        # This is a separate method to make life easier for
417        # subclasses to add new actions.
418        return self.take_action(
419            self.action, self.dest, opt, value, values, parser)
420
421    def take_action(self, action, dest, opt, value, values, parser):
422        if action == "store":
423            setattr(values, dest, value)
424        elif action == "store_const":
425            setattr(values, dest, self.const)
426        elif action == "store_true":
427            setattr(values, dest, True)
428        elif action == "store_false":
429            setattr(values, dest, False)
430        elif action == "append":
431            values.ensure_value(dest, []).append(value)
432        elif action == "append_const":
433            values.ensure_value(dest, []).append(self.const)
434        elif action == "count":
435            setattr(values, dest, values.ensure_value(dest, 0) + 1)
436        elif action == "callback":
437            args = self.callback_args or ()
438            kwargs = self.callback_kwargs or {}
439            self.callback(self, opt, value, parser, *args, **kwargs)
440        elif action == "help":
441            parser.print_help()
442            parser.exit()
443        elif action == "version":
444            parser.print_version()
445            parser.exit()
446        else:
447            raise RuntimeError, "unknown action %r" % self.action
448
449        return 1
450
451# class Option
452