xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/distutils/fancy_getopt.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker"""distutils.fancy_getopt
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard WorkerWrapper around the standard getopt module that provides the following
4*cda5da8dSAndroid Build Coastguard Workeradditional features:
5*cda5da8dSAndroid Build Coastguard Worker  * short and long options are tied together
6*cda5da8dSAndroid Build Coastguard Worker  * options have help strings, so fancy_getopt could potentially
7*cda5da8dSAndroid Build Coastguard Worker    create a complete usage summary
8*cda5da8dSAndroid Build Coastguard Worker  * options set attributes of a passed-in object
9*cda5da8dSAndroid Build Coastguard Worker"""
10*cda5da8dSAndroid Build Coastguard Worker
11*cda5da8dSAndroid Build Coastguard Workerimport sys, string, re
12*cda5da8dSAndroid Build Coastguard Workerimport getopt
13*cda5da8dSAndroid Build Coastguard Workerfrom distutils.errors import *
14*cda5da8dSAndroid Build Coastguard Worker
15*cda5da8dSAndroid Build Coastguard Worker# Much like command_re in distutils.core, this is close to but not quite
16*cda5da8dSAndroid Build Coastguard Worker# the same as a Python NAME -- except, in the spirit of most GNU
17*cda5da8dSAndroid Build Coastguard Worker# utilities, we use '-' in place of '_'.  (The spirit of LISP lives on!)
18*cda5da8dSAndroid Build Coastguard Worker# The similarities to NAME are again not a coincidence...
19*cda5da8dSAndroid Build Coastguard Workerlongopt_pat = r'[a-zA-Z](?:[a-zA-Z0-9-]*)'
20*cda5da8dSAndroid Build Coastguard Workerlongopt_re = re.compile(r'^%s$' % longopt_pat)
21*cda5da8dSAndroid Build Coastguard Worker
22*cda5da8dSAndroid Build Coastguard Worker# For recognizing "negative alias" options, eg. "quiet=!verbose"
23*cda5da8dSAndroid Build Coastguard Workerneg_alias_re = re.compile("^(%s)=!(%s)$" % (longopt_pat, longopt_pat))
24*cda5da8dSAndroid Build Coastguard Worker
25*cda5da8dSAndroid Build Coastguard Worker# This is used to translate long options to legitimate Python identifiers
26*cda5da8dSAndroid Build Coastguard Worker# (for use as attributes of some object).
27*cda5da8dSAndroid Build Coastguard Workerlongopt_xlate = str.maketrans('-', '_')
28*cda5da8dSAndroid Build Coastguard Worker
29*cda5da8dSAndroid Build Coastguard Workerclass FancyGetopt:
30*cda5da8dSAndroid Build Coastguard Worker    """Wrapper around the standard 'getopt()' module that provides some
31*cda5da8dSAndroid Build Coastguard Worker    handy extra functionality:
32*cda5da8dSAndroid Build Coastguard Worker      * short and long options are tied together
33*cda5da8dSAndroid Build Coastguard Worker      * options have help strings, and help text can be assembled
34*cda5da8dSAndroid Build Coastguard Worker        from them
35*cda5da8dSAndroid Build Coastguard Worker      * options set attributes of a passed-in object
36*cda5da8dSAndroid Build Coastguard Worker      * boolean options can have "negative aliases" -- eg. if
37*cda5da8dSAndroid Build Coastguard Worker        --quiet is the "negative alias" of --verbose, then "--quiet"
38*cda5da8dSAndroid Build Coastguard Worker        on the command line sets 'verbose' to false
39*cda5da8dSAndroid Build Coastguard Worker    """
40*cda5da8dSAndroid Build Coastguard Worker
41*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, option_table=None):
42*cda5da8dSAndroid Build Coastguard Worker        # The option table is (currently) a list of tuples.  The
43*cda5da8dSAndroid Build Coastguard Worker        # tuples may have 3 or four values:
44*cda5da8dSAndroid Build Coastguard Worker        #   (long_option, short_option, help_string [, repeatable])
45*cda5da8dSAndroid Build Coastguard Worker        # if an option takes an argument, its long_option should have '='
46*cda5da8dSAndroid Build Coastguard Worker        # appended; short_option should just be a single character, no ':'
47*cda5da8dSAndroid Build Coastguard Worker        # in any case.  If a long_option doesn't have a corresponding
48*cda5da8dSAndroid Build Coastguard Worker        # short_option, short_option should be None.  All option tuples
49*cda5da8dSAndroid Build Coastguard Worker        # must have long options.
50*cda5da8dSAndroid Build Coastguard Worker        self.option_table = option_table
51*cda5da8dSAndroid Build Coastguard Worker
52*cda5da8dSAndroid Build Coastguard Worker        # 'option_index' maps long option names to entries in the option
53*cda5da8dSAndroid Build Coastguard Worker        # table (ie. those 3-tuples).
54*cda5da8dSAndroid Build Coastguard Worker        self.option_index = {}
55*cda5da8dSAndroid Build Coastguard Worker        if self.option_table:
56*cda5da8dSAndroid Build Coastguard Worker            self._build_index()
57*cda5da8dSAndroid Build Coastguard Worker
58*cda5da8dSAndroid Build Coastguard Worker        # 'alias' records (duh) alias options; {'foo': 'bar'} means
59*cda5da8dSAndroid Build Coastguard Worker        # --foo is an alias for --bar
60*cda5da8dSAndroid Build Coastguard Worker        self.alias = {}
61*cda5da8dSAndroid Build Coastguard Worker
62*cda5da8dSAndroid Build Coastguard Worker        # 'negative_alias' keeps track of options that are the boolean
63*cda5da8dSAndroid Build Coastguard Worker        # opposite of some other option
64*cda5da8dSAndroid Build Coastguard Worker        self.negative_alias = {}
65*cda5da8dSAndroid Build Coastguard Worker
66*cda5da8dSAndroid Build Coastguard Worker        # These keep track of the information in the option table.  We
67*cda5da8dSAndroid Build Coastguard Worker        # don't actually populate these structures until we're ready to
68*cda5da8dSAndroid Build Coastguard Worker        # parse the command-line, since the 'option_table' passed in here
69*cda5da8dSAndroid Build Coastguard Worker        # isn't necessarily the final word.
70*cda5da8dSAndroid Build Coastguard Worker        self.short_opts = []
71*cda5da8dSAndroid Build Coastguard Worker        self.long_opts = []
72*cda5da8dSAndroid Build Coastguard Worker        self.short2long = {}
73*cda5da8dSAndroid Build Coastguard Worker        self.attr_name = {}
74*cda5da8dSAndroid Build Coastguard Worker        self.takes_arg = {}
75*cda5da8dSAndroid Build Coastguard Worker
76*cda5da8dSAndroid Build Coastguard Worker        # And 'option_order' is filled up in 'getopt()'; it records the
77*cda5da8dSAndroid Build Coastguard Worker        # original order of options (and their values) on the command-line,
78*cda5da8dSAndroid Build Coastguard Worker        # but expands short options, converts aliases, etc.
79*cda5da8dSAndroid Build Coastguard Worker        self.option_order = []
80*cda5da8dSAndroid Build Coastguard Worker
81*cda5da8dSAndroid Build Coastguard Worker    def _build_index(self):
82*cda5da8dSAndroid Build Coastguard Worker        self.option_index.clear()
83*cda5da8dSAndroid Build Coastguard Worker        for option in self.option_table:
84*cda5da8dSAndroid Build Coastguard Worker            self.option_index[option[0]] = option
85*cda5da8dSAndroid Build Coastguard Worker
86*cda5da8dSAndroid Build Coastguard Worker    def set_option_table(self, option_table):
87*cda5da8dSAndroid Build Coastguard Worker        self.option_table = option_table
88*cda5da8dSAndroid Build Coastguard Worker        self._build_index()
89*cda5da8dSAndroid Build Coastguard Worker
90*cda5da8dSAndroid Build Coastguard Worker    def add_option(self, long_option, short_option=None, help_string=None):
91*cda5da8dSAndroid Build Coastguard Worker        if long_option in self.option_index:
92*cda5da8dSAndroid Build Coastguard Worker            raise DistutilsGetoptError(
93*cda5da8dSAndroid Build Coastguard Worker                  "option conflict: already an option '%s'" % long_option)
94*cda5da8dSAndroid Build Coastguard Worker        else:
95*cda5da8dSAndroid Build Coastguard Worker            option = (long_option, short_option, help_string)
96*cda5da8dSAndroid Build Coastguard Worker            self.option_table.append(option)
97*cda5da8dSAndroid Build Coastguard Worker            self.option_index[long_option] = option
98*cda5da8dSAndroid Build Coastguard Worker
99*cda5da8dSAndroid Build Coastguard Worker    def has_option(self, long_option):
100*cda5da8dSAndroid Build Coastguard Worker        """Return true if the option table for this parser has an
101*cda5da8dSAndroid Build Coastguard Worker        option with long name 'long_option'."""
102*cda5da8dSAndroid Build Coastguard Worker        return long_option in self.option_index
103*cda5da8dSAndroid Build Coastguard Worker
104*cda5da8dSAndroid Build Coastguard Worker    def get_attr_name(self, long_option):
105*cda5da8dSAndroid Build Coastguard Worker        """Translate long option name 'long_option' to the form it
106*cda5da8dSAndroid Build Coastguard Worker        has as an attribute of some object: ie., translate hyphens
107*cda5da8dSAndroid Build Coastguard Worker        to underscores."""
108*cda5da8dSAndroid Build Coastguard Worker        return long_option.translate(longopt_xlate)
109*cda5da8dSAndroid Build Coastguard Worker
110*cda5da8dSAndroid Build Coastguard Worker    def _check_alias_dict(self, aliases, what):
111*cda5da8dSAndroid Build Coastguard Worker        assert isinstance(aliases, dict)
112*cda5da8dSAndroid Build Coastguard Worker        for (alias, opt) in aliases.items():
113*cda5da8dSAndroid Build Coastguard Worker            if alias not in self.option_index:
114*cda5da8dSAndroid Build Coastguard Worker                raise DistutilsGetoptError(("invalid %s '%s': "
115*cda5da8dSAndroid Build Coastguard Worker                       "option '%s' not defined") % (what, alias, alias))
116*cda5da8dSAndroid Build Coastguard Worker            if opt not in self.option_index:
117*cda5da8dSAndroid Build Coastguard Worker                raise DistutilsGetoptError(("invalid %s '%s': "
118*cda5da8dSAndroid Build Coastguard Worker                       "aliased option '%s' not defined") % (what, alias, opt))
119*cda5da8dSAndroid Build Coastguard Worker
120*cda5da8dSAndroid Build Coastguard Worker    def set_aliases(self, alias):
121*cda5da8dSAndroid Build Coastguard Worker        """Set the aliases for this option parser."""
122*cda5da8dSAndroid Build Coastguard Worker        self._check_alias_dict(alias, "alias")
123*cda5da8dSAndroid Build Coastguard Worker        self.alias = alias
124*cda5da8dSAndroid Build Coastguard Worker
125*cda5da8dSAndroid Build Coastguard Worker    def set_negative_aliases(self, negative_alias):
126*cda5da8dSAndroid Build Coastguard Worker        """Set the negative aliases for this option parser.
127*cda5da8dSAndroid Build Coastguard Worker        'negative_alias' should be a dictionary mapping option names to
128*cda5da8dSAndroid Build Coastguard Worker        option names, both the key and value must already be defined
129*cda5da8dSAndroid Build Coastguard Worker        in the option table."""
130*cda5da8dSAndroid Build Coastguard Worker        self._check_alias_dict(negative_alias, "negative alias")
131*cda5da8dSAndroid Build Coastguard Worker        self.negative_alias = negative_alias
132*cda5da8dSAndroid Build Coastguard Worker
133*cda5da8dSAndroid Build Coastguard Worker    def _grok_option_table(self):
134*cda5da8dSAndroid Build Coastguard Worker        """Populate the various data structures that keep tabs on the
135*cda5da8dSAndroid Build Coastguard Worker        option table.  Called by 'getopt()' before it can do anything
136*cda5da8dSAndroid Build Coastguard Worker        worthwhile.
137*cda5da8dSAndroid Build Coastguard Worker        """
138*cda5da8dSAndroid Build Coastguard Worker        self.long_opts = []
139*cda5da8dSAndroid Build Coastguard Worker        self.short_opts = []
140*cda5da8dSAndroid Build Coastguard Worker        self.short2long.clear()
141*cda5da8dSAndroid Build Coastguard Worker        self.repeat = {}
142*cda5da8dSAndroid Build Coastguard Worker
143*cda5da8dSAndroid Build Coastguard Worker        for option in self.option_table:
144*cda5da8dSAndroid Build Coastguard Worker            if len(option) == 3:
145*cda5da8dSAndroid Build Coastguard Worker                long, short, help = option
146*cda5da8dSAndroid Build Coastguard Worker                repeat = 0
147*cda5da8dSAndroid Build Coastguard Worker            elif len(option) == 4:
148*cda5da8dSAndroid Build Coastguard Worker                long, short, help, repeat = option
149*cda5da8dSAndroid Build Coastguard Worker            else:
150*cda5da8dSAndroid Build Coastguard Worker                # the option table is part of the code, so simply
151*cda5da8dSAndroid Build Coastguard Worker                # assert that it is correct
152*cda5da8dSAndroid Build Coastguard Worker                raise ValueError("invalid option tuple: %r" % (option,))
153*cda5da8dSAndroid Build Coastguard Worker
154*cda5da8dSAndroid Build Coastguard Worker            # Type- and value-check the option names
155*cda5da8dSAndroid Build Coastguard Worker            if not isinstance(long, str) or len(long) < 2:
156*cda5da8dSAndroid Build Coastguard Worker                raise DistutilsGetoptError(("invalid long option '%s': "
157*cda5da8dSAndroid Build Coastguard Worker                       "must be a string of length >= 2") % long)
158*cda5da8dSAndroid Build Coastguard Worker
159*cda5da8dSAndroid Build Coastguard Worker            if (not ((short is None) or
160*cda5da8dSAndroid Build Coastguard Worker                     (isinstance(short, str) and len(short) == 1))):
161*cda5da8dSAndroid Build Coastguard Worker                raise DistutilsGetoptError("invalid short option '%s': "
162*cda5da8dSAndroid Build Coastguard Worker                       "must a single character or None" % short)
163*cda5da8dSAndroid Build Coastguard Worker
164*cda5da8dSAndroid Build Coastguard Worker            self.repeat[long] = repeat
165*cda5da8dSAndroid Build Coastguard Worker            self.long_opts.append(long)
166*cda5da8dSAndroid Build Coastguard Worker
167*cda5da8dSAndroid Build Coastguard Worker            if long[-1] == '=':             # option takes an argument?
168*cda5da8dSAndroid Build Coastguard Worker                if short: short = short + ':'
169*cda5da8dSAndroid Build Coastguard Worker                long = long[0:-1]
170*cda5da8dSAndroid Build Coastguard Worker                self.takes_arg[long] = 1
171*cda5da8dSAndroid Build Coastguard Worker            else:
172*cda5da8dSAndroid Build Coastguard Worker                # Is option is a "negative alias" for some other option (eg.
173*cda5da8dSAndroid Build Coastguard Worker                # "quiet" == "!verbose")?
174*cda5da8dSAndroid Build Coastguard Worker                alias_to = self.negative_alias.get(long)
175*cda5da8dSAndroid Build Coastguard Worker                if alias_to is not None:
176*cda5da8dSAndroid Build Coastguard Worker                    if self.takes_arg[alias_to]:
177*cda5da8dSAndroid Build Coastguard Worker                        raise DistutilsGetoptError(
178*cda5da8dSAndroid Build Coastguard Worker                              "invalid negative alias '%s': "
179*cda5da8dSAndroid Build Coastguard Worker                              "aliased option '%s' takes a value"
180*cda5da8dSAndroid Build Coastguard Worker                              % (long, alias_to))
181*cda5da8dSAndroid Build Coastguard Worker
182*cda5da8dSAndroid Build Coastguard Worker                    self.long_opts[-1] = long # XXX redundant?!
183*cda5da8dSAndroid Build Coastguard Worker                self.takes_arg[long] = 0
184*cda5da8dSAndroid Build Coastguard Worker
185*cda5da8dSAndroid Build Coastguard Worker            # If this is an alias option, make sure its "takes arg" flag is
186*cda5da8dSAndroid Build Coastguard Worker            # the same as the option it's aliased to.
187*cda5da8dSAndroid Build Coastguard Worker            alias_to = self.alias.get(long)
188*cda5da8dSAndroid Build Coastguard Worker            if alias_to is not None:
189*cda5da8dSAndroid Build Coastguard Worker                if self.takes_arg[long] != self.takes_arg[alias_to]:
190*cda5da8dSAndroid Build Coastguard Worker                    raise DistutilsGetoptError(
191*cda5da8dSAndroid Build Coastguard Worker                          "invalid alias '%s': inconsistent with "
192*cda5da8dSAndroid Build Coastguard Worker                          "aliased option '%s' (one of them takes a value, "
193*cda5da8dSAndroid Build Coastguard Worker                          "the other doesn't"
194*cda5da8dSAndroid Build Coastguard Worker                          % (long, alias_to))
195*cda5da8dSAndroid Build Coastguard Worker
196*cda5da8dSAndroid Build Coastguard Worker            # Now enforce some bondage on the long option name, so we can
197*cda5da8dSAndroid Build Coastguard Worker            # later translate it to an attribute name on some object.  Have
198*cda5da8dSAndroid Build Coastguard Worker            # to do this a bit late to make sure we've removed any trailing
199*cda5da8dSAndroid Build Coastguard Worker            # '='.
200*cda5da8dSAndroid Build Coastguard Worker            if not longopt_re.match(long):
201*cda5da8dSAndroid Build Coastguard Worker                raise DistutilsGetoptError(
202*cda5da8dSAndroid Build Coastguard Worker                       "invalid long option name '%s' "
203*cda5da8dSAndroid Build Coastguard Worker                       "(must be letters, numbers, hyphens only" % long)
204*cda5da8dSAndroid Build Coastguard Worker
205*cda5da8dSAndroid Build Coastguard Worker            self.attr_name[long] = self.get_attr_name(long)
206*cda5da8dSAndroid Build Coastguard Worker            if short:
207*cda5da8dSAndroid Build Coastguard Worker                self.short_opts.append(short)
208*cda5da8dSAndroid Build Coastguard Worker                self.short2long[short[0]] = long
209*cda5da8dSAndroid Build Coastguard Worker
210*cda5da8dSAndroid Build Coastguard Worker    def getopt(self, args=None, object=None):
211*cda5da8dSAndroid Build Coastguard Worker        """Parse command-line options in args. Store as attributes on object.
212*cda5da8dSAndroid Build Coastguard Worker
213*cda5da8dSAndroid Build Coastguard Worker        If 'args' is None or not supplied, uses 'sys.argv[1:]'.  If
214*cda5da8dSAndroid Build Coastguard Worker        'object' is None or not supplied, creates a new OptionDummy
215*cda5da8dSAndroid Build Coastguard Worker        object, stores option values there, and returns a tuple (args,
216*cda5da8dSAndroid Build Coastguard Worker        object).  If 'object' is supplied, it is modified in place and
217*cda5da8dSAndroid Build Coastguard Worker        'getopt()' just returns 'args'; in both cases, the returned
218*cda5da8dSAndroid Build Coastguard Worker        'args' is a modified copy of the passed-in 'args' list, which
219*cda5da8dSAndroid Build Coastguard Worker        is left untouched.
220*cda5da8dSAndroid Build Coastguard Worker        """
221*cda5da8dSAndroid Build Coastguard Worker        if args is None:
222*cda5da8dSAndroid Build Coastguard Worker            args = sys.argv[1:]
223*cda5da8dSAndroid Build Coastguard Worker        if object is None:
224*cda5da8dSAndroid Build Coastguard Worker            object = OptionDummy()
225*cda5da8dSAndroid Build Coastguard Worker            created_object = True
226*cda5da8dSAndroid Build Coastguard Worker        else:
227*cda5da8dSAndroid Build Coastguard Worker            created_object = False
228*cda5da8dSAndroid Build Coastguard Worker
229*cda5da8dSAndroid Build Coastguard Worker        self._grok_option_table()
230*cda5da8dSAndroid Build Coastguard Worker
231*cda5da8dSAndroid Build Coastguard Worker        short_opts = ' '.join(self.short_opts)
232*cda5da8dSAndroid Build Coastguard Worker        try:
233*cda5da8dSAndroid Build Coastguard Worker            opts, args = getopt.getopt(args, short_opts, self.long_opts)
234*cda5da8dSAndroid Build Coastguard Worker        except getopt.error as msg:
235*cda5da8dSAndroid Build Coastguard Worker            raise DistutilsArgError(msg)
236*cda5da8dSAndroid Build Coastguard Worker
237*cda5da8dSAndroid Build Coastguard Worker        for opt, val in opts:
238*cda5da8dSAndroid Build Coastguard Worker            if len(opt) == 2 and opt[0] == '-': # it's a short option
239*cda5da8dSAndroid Build Coastguard Worker                opt = self.short2long[opt[1]]
240*cda5da8dSAndroid Build Coastguard Worker            else:
241*cda5da8dSAndroid Build Coastguard Worker                assert len(opt) > 2 and opt[:2] == '--'
242*cda5da8dSAndroid Build Coastguard Worker                opt = opt[2:]
243*cda5da8dSAndroid Build Coastguard Worker
244*cda5da8dSAndroid Build Coastguard Worker            alias = self.alias.get(opt)
245*cda5da8dSAndroid Build Coastguard Worker            if alias:
246*cda5da8dSAndroid Build Coastguard Worker                opt = alias
247*cda5da8dSAndroid Build Coastguard Worker
248*cda5da8dSAndroid Build Coastguard Worker            if not self.takes_arg[opt]:     # boolean option?
249*cda5da8dSAndroid Build Coastguard Worker                assert val == '', "boolean option can't have value"
250*cda5da8dSAndroid Build Coastguard Worker                alias = self.negative_alias.get(opt)
251*cda5da8dSAndroid Build Coastguard Worker                if alias:
252*cda5da8dSAndroid Build Coastguard Worker                    opt = alias
253*cda5da8dSAndroid Build Coastguard Worker                    val = 0
254*cda5da8dSAndroid Build Coastguard Worker                else:
255*cda5da8dSAndroid Build Coastguard Worker                    val = 1
256*cda5da8dSAndroid Build Coastguard Worker
257*cda5da8dSAndroid Build Coastguard Worker            attr = self.attr_name[opt]
258*cda5da8dSAndroid Build Coastguard Worker            # The only repeating option at the moment is 'verbose'.
259*cda5da8dSAndroid Build Coastguard Worker            # It has a negative option -q quiet, which should set verbose = 0.
260*cda5da8dSAndroid Build Coastguard Worker            if val and self.repeat.get(attr) is not None:
261*cda5da8dSAndroid Build Coastguard Worker                val = getattr(object, attr, 0) + 1
262*cda5da8dSAndroid Build Coastguard Worker            setattr(object, attr, val)
263*cda5da8dSAndroid Build Coastguard Worker            self.option_order.append((opt, val))
264*cda5da8dSAndroid Build Coastguard Worker
265*cda5da8dSAndroid Build Coastguard Worker        # for opts
266*cda5da8dSAndroid Build Coastguard Worker        if created_object:
267*cda5da8dSAndroid Build Coastguard Worker            return args, object
268*cda5da8dSAndroid Build Coastguard Worker        else:
269*cda5da8dSAndroid Build Coastguard Worker            return args
270*cda5da8dSAndroid Build Coastguard Worker
271*cda5da8dSAndroid Build Coastguard Worker    def get_option_order(self):
272*cda5da8dSAndroid Build Coastguard Worker        """Returns the list of (option, value) tuples processed by the
273*cda5da8dSAndroid Build Coastguard Worker        previous run of 'getopt()'.  Raises RuntimeError if
274*cda5da8dSAndroid Build Coastguard Worker        'getopt()' hasn't been called yet.
275*cda5da8dSAndroid Build Coastguard Worker        """
276*cda5da8dSAndroid Build Coastguard Worker        if self.option_order is None:
277*cda5da8dSAndroid Build Coastguard Worker            raise RuntimeError("'getopt()' hasn't been called yet")
278*cda5da8dSAndroid Build Coastguard Worker        else:
279*cda5da8dSAndroid Build Coastguard Worker            return self.option_order
280*cda5da8dSAndroid Build Coastguard Worker
281*cda5da8dSAndroid Build Coastguard Worker    def generate_help(self, header=None):
282*cda5da8dSAndroid Build Coastguard Worker        """Generate help text (a list of strings, one per suggested line of
283*cda5da8dSAndroid Build Coastguard Worker        output) from the option table for this FancyGetopt object.
284*cda5da8dSAndroid Build Coastguard Worker        """
285*cda5da8dSAndroid Build Coastguard Worker        # Blithely assume the option table is good: probably wouldn't call
286*cda5da8dSAndroid Build Coastguard Worker        # 'generate_help()' unless you've already called 'getopt()'.
287*cda5da8dSAndroid Build Coastguard Worker
288*cda5da8dSAndroid Build Coastguard Worker        # First pass: determine maximum length of long option names
289*cda5da8dSAndroid Build Coastguard Worker        max_opt = 0
290*cda5da8dSAndroid Build Coastguard Worker        for option in self.option_table:
291*cda5da8dSAndroid Build Coastguard Worker            long = option[0]
292*cda5da8dSAndroid Build Coastguard Worker            short = option[1]
293*cda5da8dSAndroid Build Coastguard Worker            l = len(long)
294*cda5da8dSAndroid Build Coastguard Worker            if long[-1] == '=':
295*cda5da8dSAndroid Build Coastguard Worker                l = l - 1
296*cda5da8dSAndroid Build Coastguard Worker            if short is not None:
297*cda5da8dSAndroid Build Coastguard Worker                l = l + 5                   # " (-x)" where short == 'x'
298*cda5da8dSAndroid Build Coastguard Worker            if l > max_opt:
299*cda5da8dSAndroid Build Coastguard Worker                max_opt = l
300*cda5da8dSAndroid Build Coastguard Worker
301*cda5da8dSAndroid Build Coastguard Worker        opt_width = max_opt + 2 + 2 + 2     # room for indent + dashes + gutter
302*cda5da8dSAndroid Build Coastguard Worker
303*cda5da8dSAndroid Build Coastguard Worker        # Typical help block looks like this:
304*cda5da8dSAndroid Build Coastguard Worker        #   --foo       controls foonabulation
305*cda5da8dSAndroid Build Coastguard Worker        # Help block for longest option looks like this:
306*cda5da8dSAndroid Build Coastguard Worker        #   --flimflam  set the flim-flam level
307*cda5da8dSAndroid Build Coastguard Worker        # and with wrapped text:
308*cda5da8dSAndroid Build Coastguard Worker        #   --flimflam  set the flim-flam level (must be between
309*cda5da8dSAndroid Build Coastguard Worker        #               0 and 100, except on Tuesdays)
310*cda5da8dSAndroid Build Coastguard Worker        # Options with short names will have the short name shown (but
311*cda5da8dSAndroid Build Coastguard Worker        # it doesn't contribute to max_opt):
312*cda5da8dSAndroid Build Coastguard Worker        #   --foo (-f)  controls foonabulation
313*cda5da8dSAndroid Build Coastguard Worker        # If adding the short option would make the left column too wide,
314*cda5da8dSAndroid Build Coastguard Worker        # we push the explanation off to the next line
315*cda5da8dSAndroid Build Coastguard Worker        #   --flimflam (-l)
316*cda5da8dSAndroid Build Coastguard Worker        #               set the flim-flam level
317*cda5da8dSAndroid Build Coastguard Worker        # Important parameters:
318*cda5da8dSAndroid Build Coastguard Worker        #   - 2 spaces before option block start lines
319*cda5da8dSAndroid Build Coastguard Worker        #   - 2 dashes for each long option name
320*cda5da8dSAndroid Build Coastguard Worker        #   - min. 2 spaces between option and explanation (gutter)
321*cda5da8dSAndroid Build Coastguard Worker        #   - 5 characters (incl. space) for short option name
322*cda5da8dSAndroid Build Coastguard Worker
323*cda5da8dSAndroid Build Coastguard Worker        # Now generate lines of help text.  (If 80 columns were good enough
324*cda5da8dSAndroid Build Coastguard Worker        # for Jesus, then 78 columns are good enough for me!)
325*cda5da8dSAndroid Build Coastguard Worker        line_width = 78
326*cda5da8dSAndroid Build Coastguard Worker        text_width = line_width - opt_width
327*cda5da8dSAndroid Build Coastguard Worker        big_indent = ' ' * opt_width
328*cda5da8dSAndroid Build Coastguard Worker        if header:
329*cda5da8dSAndroid Build Coastguard Worker            lines = [header]
330*cda5da8dSAndroid Build Coastguard Worker        else:
331*cda5da8dSAndroid Build Coastguard Worker            lines = ['Option summary:']
332*cda5da8dSAndroid Build Coastguard Worker
333*cda5da8dSAndroid Build Coastguard Worker        for option in self.option_table:
334*cda5da8dSAndroid Build Coastguard Worker            long, short, help = option[:3]
335*cda5da8dSAndroid Build Coastguard Worker            text = wrap_text(help, text_width)
336*cda5da8dSAndroid Build Coastguard Worker            if long[-1] == '=':
337*cda5da8dSAndroid Build Coastguard Worker                long = long[0:-1]
338*cda5da8dSAndroid Build Coastguard Worker
339*cda5da8dSAndroid Build Coastguard Worker            # Case 1: no short option at all (makes life easy)
340*cda5da8dSAndroid Build Coastguard Worker            if short is None:
341*cda5da8dSAndroid Build Coastguard Worker                if text:
342*cda5da8dSAndroid Build Coastguard Worker                    lines.append("  --%-*s  %s" % (max_opt, long, text[0]))
343*cda5da8dSAndroid Build Coastguard Worker                else:
344*cda5da8dSAndroid Build Coastguard Worker                    lines.append("  --%-*s  " % (max_opt, long))
345*cda5da8dSAndroid Build Coastguard Worker
346*cda5da8dSAndroid Build Coastguard Worker            # Case 2: we have a short option, so we have to include it
347*cda5da8dSAndroid Build Coastguard Worker            # just after the long option
348*cda5da8dSAndroid Build Coastguard Worker            else:
349*cda5da8dSAndroid Build Coastguard Worker                opt_names = "%s (-%s)" % (long, short)
350*cda5da8dSAndroid Build Coastguard Worker                if text:
351*cda5da8dSAndroid Build Coastguard Worker                    lines.append("  --%-*s  %s" %
352*cda5da8dSAndroid Build Coastguard Worker                                 (max_opt, opt_names, text[0]))
353*cda5da8dSAndroid Build Coastguard Worker                else:
354*cda5da8dSAndroid Build Coastguard Worker                    lines.append("  --%-*s" % opt_names)
355*cda5da8dSAndroid Build Coastguard Worker
356*cda5da8dSAndroid Build Coastguard Worker            for l in text[1:]:
357*cda5da8dSAndroid Build Coastguard Worker                lines.append(big_indent + l)
358*cda5da8dSAndroid Build Coastguard Worker        return lines
359*cda5da8dSAndroid Build Coastguard Worker
360*cda5da8dSAndroid Build Coastguard Worker    def print_help(self, header=None, file=None):
361*cda5da8dSAndroid Build Coastguard Worker        if file is None:
362*cda5da8dSAndroid Build Coastguard Worker            file = sys.stdout
363*cda5da8dSAndroid Build Coastguard Worker        for line in self.generate_help(header):
364*cda5da8dSAndroid Build Coastguard Worker            file.write(line + "\n")
365*cda5da8dSAndroid Build Coastguard Worker
366*cda5da8dSAndroid Build Coastguard Worker
367*cda5da8dSAndroid Build Coastguard Workerdef fancy_getopt(options, negative_opt, object, args):
368*cda5da8dSAndroid Build Coastguard Worker    parser = FancyGetopt(options)
369*cda5da8dSAndroid Build Coastguard Worker    parser.set_negative_aliases(negative_opt)
370*cda5da8dSAndroid Build Coastguard Worker    return parser.getopt(args, object)
371*cda5da8dSAndroid Build Coastguard Worker
372*cda5da8dSAndroid Build Coastguard Worker
373*cda5da8dSAndroid Build Coastguard WorkerWS_TRANS = {ord(_wschar) : ' ' for _wschar in string.whitespace}
374*cda5da8dSAndroid Build Coastguard Worker
375*cda5da8dSAndroid Build Coastguard Workerdef wrap_text(text, width):
376*cda5da8dSAndroid Build Coastguard Worker    """wrap_text(text : string, width : int) -> [string]
377*cda5da8dSAndroid Build Coastguard Worker
378*cda5da8dSAndroid Build Coastguard Worker    Split 'text' into multiple lines of no more than 'width' characters
379*cda5da8dSAndroid Build Coastguard Worker    each, and return the list of strings that results.
380*cda5da8dSAndroid Build Coastguard Worker    """
381*cda5da8dSAndroid Build Coastguard Worker    if text is None:
382*cda5da8dSAndroid Build Coastguard Worker        return []
383*cda5da8dSAndroid Build Coastguard Worker    if len(text) <= width:
384*cda5da8dSAndroid Build Coastguard Worker        return [text]
385*cda5da8dSAndroid Build Coastguard Worker
386*cda5da8dSAndroid Build Coastguard Worker    text = text.expandtabs()
387*cda5da8dSAndroid Build Coastguard Worker    text = text.translate(WS_TRANS)
388*cda5da8dSAndroid Build Coastguard Worker    chunks = re.split(r'( +|-+)', text)
389*cda5da8dSAndroid Build Coastguard Worker    chunks = [ch for ch in chunks if ch] # ' - ' results in empty strings
390*cda5da8dSAndroid Build Coastguard Worker    lines = []
391*cda5da8dSAndroid Build Coastguard Worker
392*cda5da8dSAndroid Build Coastguard Worker    while chunks:
393*cda5da8dSAndroid Build Coastguard Worker        cur_line = []                   # list of chunks (to-be-joined)
394*cda5da8dSAndroid Build Coastguard Worker        cur_len = 0                     # length of current line
395*cda5da8dSAndroid Build Coastguard Worker
396*cda5da8dSAndroid Build Coastguard Worker        while chunks:
397*cda5da8dSAndroid Build Coastguard Worker            l = len(chunks[0])
398*cda5da8dSAndroid Build Coastguard Worker            if cur_len + l <= width:    # can squeeze (at least) this chunk in
399*cda5da8dSAndroid Build Coastguard Worker                cur_line.append(chunks[0])
400*cda5da8dSAndroid Build Coastguard Worker                del chunks[0]
401*cda5da8dSAndroid Build Coastguard Worker                cur_len = cur_len + l
402*cda5da8dSAndroid Build Coastguard Worker            else:                       # this line is full
403*cda5da8dSAndroid Build Coastguard Worker                # drop last chunk if all space
404*cda5da8dSAndroid Build Coastguard Worker                if cur_line and cur_line[-1][0] == ' ':
405*cda5da8dSAndroid Build Coastguard Worker                    del cur_line[-1]
406*cda5da8dSAndroid Build Coastguard Worker                break
407*cda5da8dSAndroid Build Coastguard Worker
408*cda5da8dSAndroid Build Coastguard Worker        if chunks:                      # any chunks left to process?
409*cda5da8dSAndroid Build Coastguard Worker            # if the current line is still empty, then we had a single
410*cda5da8dSAndroid Build Coastguard Worker            # chunk that's too big too fit on a line -- so we break
411*cda5da8dSAndroid Build Coastguard Worker            # down and break it up at the line width
412*cda5da8dSAndroid Build Coastguard Worker            if cur_len == 0:
413*cda5da8dSAndroid Build Coastguard Worker                cur_line.append(chunks[0][0:width])
414*cda5da8dSAndroid Build Coastguard Worker                chunks[0] = chunks[0][width:]
415*cda5da8dSAndroid Build Coastguard Worker
416*cda5da8dSAndroid Build Coastguard Worker            # all-whitespace chunks at the end of a line can be discarded
417*cda5da8dSAndroid Build Coastguard Worker            # (and we know from the re.split above that if a chunk has
418*cda5da8dSAndroid Build Coastguard Worker            # *any* whitespace, it is *all* whitespace)
419*cda5da8dSAndroid Build Coastguard Worker            if chunks[0][0] == ' ':
420*cda5da8dSAndroid Build Coastguard Worker                del chunks[0]
421*cda5da8dSAndroid Build Coastguard Worker
422*cda5da8dSAndroid Build Coastguard Worker        # and store this line in the list-of-all-lines -- as a single
423*cda5da8dSAndroid Build Coastguard Worker        # string, of course!
424*cda5da8dSAndroid Build Coastguard Worker        lines.append(''.join(cur_line))
425*cda5da8dSAndroid Build Coastguard Worker
426*cda5da8dSAndroid Build Coastguard Worker    return lines
427*cda5da8dSAndroid Build Coastguard Worker
428*cda5da8dSAndroid Build Coastguard Worker
429*cda5da8dSAndroid Build Coastguard Workerdef translate_longopt(opt):
430*cda5da8dSAndroid Build Coastguard Worker    """Convert a long option name to a valid Python identifier by
431*cda5da8dSAndroid Build Coastguard Worker    changing "-" to "_".
432*cda5da8dSAndroid Build Coastguard Worker    """
433*cda5da8dSAndroid Build Coastguard Worker    return opt.translate(longopt_xlate)
434*cda5da8dSAndroid Build Coastguard Worker
435*cda5da8dSAndroid Build Coastguard Worker
436*cda5da8dSAndroid Build Coastguard Workerclass OptionDummy:
437*cda5da8dSAndroid Build Coastguard Worker    """Dummy class just used as a place to hold command-line option
438*cda5da8dSAndroid Build Coastguard Worker    values as instance attributes."""
439*cda5da8dSAndroid Build Coastguard Worker
440*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, options=[]):
441*cda5da8dSAndroid Build Coastguard Worker        """Create a new OptionDummy instance.  The attributes listed in
442*cda5da8dSAndroid Build Coastguard Worker        'options' will be initialized to None."""
443*cda5da8dSAndroid Build Coastguard Worker        for opt in options:
444*cda5da8dSAndroid Build Coastguard Worker            setattr(self, opt, None)
445*cda5da8dSAndroid Build Coastguard Worker
446*cda5da8dSAndroid Build Coastguard Worker
447*cda5da8dSAndroid Build Coastguard Workerif __name__ == "__main__":
448*cda5da8dSAndroid Build Coastguard Worker    text = """\
449*cda5da8dSAndroid Build Coastguard WorkerTra-la-la, supercalifragilisticexpialidocious.
450*cda5da8dSAndroid Build Coastguard WorkerHow *do* you spell that odd word, anyways?
451*cda5da8dSAndroid Build Coastguard Worker(Someone ask Mary -- she'll know [or she'll
452*cda5da8dSAndroid Build Coastguard Workersay, "How should I know?"].)"""
453*cda5da8dSAndroid Build Coastguard Worker
454*cda5da8dSAndroid Build Coastguard Worker    for w in (10, 20, 30, 40):
455*cda5da8dSAndroid Build Coastguard Worker        print("width: %d" % w)
456*cda5da8dSAndroid Build Coastguard Worker        print("\n".join(wrap_text(text, w)))
457*cda5da8dSAndroid Build Coastguard Worker        print()
458