blob: 3df2d1da65766ed2e920ff930566b653f5db9380 [file] [log] [blame]
Greg Ward2689e3d1999-03-22 14:52:19 +00001"""distutils.fancy_getopt
2
3Wrapper around the standard getopt module that provides the following
4additional features:
5 * short and long options are tied together
6 * options have help strings, so fancy_getopt could potentially
7 create a complete usage summary
8 * options set attributes of a passed-in object
9"""
10
11# created 1999/03/03, Greg Ward
12
13__rcsid__ = "$Id$"
14
15import string, re
16from types import *
17import getopt
18from distutils.errors import *
19
20# Much like command_re in distutils.core, this is close to but not quite
21# the same as a Python NAME -- except, in the spirit of most GNU
22# utilities, we use '-' in place of '_'. (The spirit of LISP lives on!)
23# The similarities to NAME are again not a coincidence...
Greg Warda564cc31999-10-03 20:48:53 +000024longopt_pat = r'[a-zA-Z](?:[a-zA-Z0-9-]*)'
25longopt_re = re.compile (r'^%s$' % longopt_pat)
26
27# For recognizing "negative alias" options, eg. "quiet=!verbose"
28neg_alias_re = re.compile ("^(%s)=!(%s)$" % (longopt_pat, longopt_pat))
29
Greg Ward2689e3d1999-03-22 14:52:19 +000030
31# This is used to translate long options to legitimate Python identifiers
32# (for use as attributes of some object).
33longopt_xlate = string.maketrans ('-', '_')
34
35
36def fancy_getopt (options, object, args):
37
38 # The 'options' table is a list of 3-tuples:
39 # (long_option, short_option, help_string)
40 # if an option takes an argument, its long_option should have '='
41 # appended; short_option should just be a single character, no ':' in
42 # any case. If a long_option doesn't have a corresponding
43 # short_option, short_option should be None. All option tuples must
44 # have long options.
45
46 # Build the short_opts string and long_opts list, remembering how
47 # the two are tied together
48
49 short_opts = [] # we'll join 'em when done
50 long_opts = []
51 short2long = {}
52 attr_name = {}
53 takes_arg = {}
Greg Warda564cc31999-10-03 20:48:53 +000054 neg_alias = {}
Greg Ward2689e3d1999-03-22 14:52:19 +000055
Greg Ward0081cc51999-08-14 23:44:37 +000056 for option in options:
57 try:
58 (long, short, help) = option
59 except ValueError:
60 raise DistutilsGetoptError, \
61 "invalid option tuple " + str (option)
62
Greg Ward2689e3d1999-03-22 14:52:19 +000063 # Type-check the option names
64 if type (long) is not StringType or len (long) < 2:
65 raise DistutilsGetoptError, \
Greg Ward0081cc51999-08-14 23:44:37 +000066 "long option '%s' must be a string of length >= 2" % \
67 long
Greg Ward2689e3d1999-03-22 14:52:19 +000068
69 if (not ((short is None) or
70 (type (short) is StringType and len (short) == 1))):
71 raise DistutilsGetoptError, \
Greg Ward0081cc51999-08-14 23:44:37 +000072 "short option '%s' must be None or string of length 1" % \
73 short
Greg Ward2689e3d1999-03-22 14:52:19 +000074
75 long_opts.append (long)
76
77 if long[-1] == '=': # option takes an argument?
78 if short: short = short + ':'
79 long = long[0:-1]
80 takes_arg[long] = 1
81 else:
Greg Warda564cc31999-10-03 20:48:53 +000082
83 # Is option is a "negative alias" for some other option (eg.
84 # "quiet=!verbose")?
85 match = neg_alias_re.match (long)
86 if match:
87 (alias_from, alias_to) = match.group (1,2)
88 if not takes_arg.has_key(alias_to) or takes_arg[alias_to]:
89 raise DistutilsGetoptError, \
90 ("option '%s' is a negative alias for '%s', " +
91 "which either hasn't been defined yet " +
92 "or takes an argument") % (alias_from, alias_to)
93
94 long = alias_from
95 neg_alias[long] = alias_to
96 long_opts[-1] = long
97 takes_arg[long] = 0
98
99 else:
100 takes_arg[long] = 0
101
Greg Ward2689e3d1999-03-22 14:52:19 +0000102
103 # Now enforce some bondage on the long option name, so we can later
104 # translate it to an attribute name in 'object'. Have to do this a
105 # bit late to make sure we've removed any trailing '='.
106 if not longopt_re.match (long):
107 raise DistutilsGetoptError, \
108 ("invalid long option name '%s' " +
109 "(must be letters, numbers, hyphens only") % long
110
111 attr_name[long] = string.translate (long, longopt_xlate)
112 if short:
113 short_opts.append (short)
114 short2long[short[0]] = long
115
116 # end loop over 'options'
117
118 short_opts = string.join (short_opts)
119 try:
120 (opts, args) = getopt.getopt (args, short_opts, long_opts)
121 except getopt.error, msg:
122 raise DistutilsArgError, msg
123
124 for (opt, val) in opts:
125 if len (opt) == 2 and opt[0] == '-': # it's a short option
126 opt = short2long[opt[1]]
127
128 elif len (opt) > 2 and opt[0:2] == '--':
129 opt = opt[2:]
130
131 else:
132 raise RuntimeError, "getopt lies! (bad option string '%s')" % \
133 opt
134
135 attr = attr_name[opt]
136 if takes_arg[opt]:
137 setattr (object, attr, val)
138 else:
139 if val == '':
Greg Warda564cc31999-10-03 20:48:53 +0000140 alias = neg_alias.get (opt)
141 if alias:
142 setattr (object, attr_name[alias], 0)
143 else:
144 setattr (object, attr, 1)
Greg Ward2689e3d1999-03-22 14:52:19 +0000145 else:
146 raise RuntimeError, "getopt lies! (bad value '%s')" % value
147
148 # end loop over options found in 'args'
149
150 return args
151
152# end fancy_getopt()