summaryrefslogtreecommitdiffstats
path: root/Lib/distutils/fancy_getopt.py
blob: 3df2d1da65766ed2e920ff930566b653f5db9380 (plain)
1
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"""distutils.fancy_getopt

Wrapper around the standard getopt module that provides the following
additional features:
  * short and long options are tied together
  * options have help strings, so fancy_getopt could potentially
    create a complete usage summary
  * options set attributes of a passed-in object
"""

# created 1999/03/03, Greg Ward

__rcsid__ = "$Id$"

import string, re
from types import *
import getopt
from distutils.errors import *

# Much like command_re in distutils.core, this is close to but not quite
# the same as a Python NAME -- except, in the spirit of most GNU
# utilities, we use '-' in place of '_'.  (The spirit of LISP lives on!)
# The similarities to NAME are again not a coincidence...
longopt_pat = r'[a-zA-Z](?:[a-zA-Z0-9-]*)'
longopt_re = re.compile (r'^%s$' % longopt_pat)

# For recognizing "negative alias" options, eg. "quiet=!verbose"
neg_alias_re = re.compile ("^(%s)=!(%s)$" % (longopt_pat, longopt_pat))


# This is used to translate long options to legitimate Python identifiers
# (for use as attributes of some object).
longopt_xlate = string.maketrans ('-', '_')


def fancy_getopt (options, object, args):

    # The 'options' table is a list of 3-tuples:
    #   (long_option, short_option, help_string)
    # if an option takes an argument, its long_option should have '='
    # appended; short_option should just be a single character, no ':' in
    # any case.  If a long_option doesn't have a corresponding
    # short_option, short_option should be None.  All option tuples must
    # have long options.

    # Build the short_opts string and long_opts list, remembering how
    # the two are tied together

    short_opts = []                     # we'll join 'em when done
    long_opts = []
    short2long = {}
    attr_name = {}
    takes_arg = {}
    neg_alias = {}

    for option in options:
        try:
            (long, short, help) = option
        except ValueError:
            raise DistutilsGetoptError, \
                  "invalid option tuple " + str (option)

        # Type-check the option names
        if type (long) is not StringType or len (long) < 2:
            raise DistutilsGetoptError, \
                  "long option '%s' must be a string of length >= 2" % \
                  long

        if (not ((short is None) or
                 (type (short) is StringType and len (short) == 1))):
            raise DistutilsGetoptError, \
                  "short option '%s' must be None or string of length 1" % \
                  short

        long_opts.append (long)

        if long[-1] == '=':             # option takes an argument?
            if short: short = short + ':'
            long = long[0:-1]
            takes_arg[long] = 1
        else:

            # Is option is a "negative alias" for some other option (eg.
            # "quiet=!verbose")?
            match = neg_alias_re.match (long)
            if match:
                (alias_from, alias_to) = match.group (1,2)
                if not takes_arg.has_key(alias_to) or takes_arg[alias_to]:
                    raise DistutilsGetoptError, \
                          ("option '%s' is a negative alias for '%s', " +
                           "which either hasn't been defined yet " +
                           "or takes an argument") % (alias_from, alias_to)

                long = alias_from
                neg_alias[long] = alias_to
                long_opts[-1] = long
                takes_arg[long] = 0

            else:
                takes_arg[long] = 0
                

        # Now enforce some bondage on the long option name, so we can later
        # translate it to an attribute name in 'object'.  Have to do this a
        # bit late to make sure we've removed any trailing '='.
        if not longopt_re.match (long):
            raise DistutilsGetoptError, \
                  ("invalid long option name '%s' " +
                   "(must be letters, numbers, hyphens only") % long

        attr_name[long] = string.translate (long, longopt_xlate)
        if short:
            short_opts.append (short)
            short2long[short[0]] = long

    # end loop over 'options'

    short_opts = string.join (short_opts)
    try:
        (opts, args) = getopt.getopt (args, short_opts, long_opts)
    except getopt.error, msg:
        raise DistutilsArgError, msg

    for (opt, val) in opts:
        if len (opt) == 2 and opt[0] == '-': # it's a short option
            opt = short2long[opt[1]]

        elif len (opt) > 2 and opt[0:2] == '--':
            opt = opt[2:]

        else:
            raise RuntimeError, "getopt lies! (bad option string '%s')" % \
                  opt

        attr = attr_name[opt]
        if takes_arg[opt]:
            setattr (object, attr, val)
        else:
            if val == '':
                alias = neg_alias.get (opt)
                if alias:
                    setattr (object, attr_name[alias], 0)
                else:
                    setattr (object, attr, 1)
            else:
                raise RuntimeError, "getopt lies! (bad value '%s')" % value

    # end loop over options found in 'args'

    return args

# end fancy_getopt()