xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/cmd.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker"""A generic class to build line-oriented command interpreters.
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard WorkerInterpreters constructed with this class obey the following conventions:
4*cda5da8dSAndroid Build Coastguard Worker
5*cda5da8dSAndroid Build Coastguard Worker1. End of file on input is processed as the command 'EOF'.
6*cda5da8dSAndroid Build Coastguard Worker2. A command is parsed out of each line by collecting the prefix composed
7*cda5da8dSAndroid Build Coastguard Worker   of characters in the identchars member.
8*cda5da8dSAndroid Build Coastguard Worker3. A command `foo' is dispatched to a method 'do_foo()'; the do_ method
9*cda5da8dSAndroid Build Coastguard Worker   is passed a single argument consisting of the remainder of the line.
10*cda5da8dSAndroid Build Coastguard Worker4. Typing an empty line repeats the last command.  (Actually, it calls the
11*cda5da8dSAndroid Build Coastguard Worker   method `emptyline', which may be overridden in a subclass.)
12*cda5da8dSAndroid Build Coastguard Worker5. There is a predefined `help' method.  Given an argument `topic', it
13*cda5da8dSAndroid Build Coastguard Worker   calls the command `help_topic'.  With no arguments, it lists all topics
14*cda5da8dSAndroid Build Coastguard Worker   with defined help_ functions, broken into up to three topics; documented
15*cda5da8dSAndroid Build Coastguard Worker   commands, miscellaneous help topics, and undocumented commands.
16*cda5da8dSAndroid Build Coastguard Worker6. The command '?' is a synonym for `help'.  The command '!' is a synonym
17*cda5da8dSAndroid Build Coastguard Worker   for `shell', if a do_shell method exists.
18*cda5da8dSAndroid Build Coastguard Worker7. If completion is enabled, completing commands will be done automatically,
19*cda5da8dSAndroid Build Coastguard Worker   and completing of commands args is done by calling complete_foo() with
20*cda5da8dSAndroid Build Coastguard Worker   arguments text, line, begidx, endidx.  text is string we are matching
21*cda5da8dSAndroid Build Coastguard Worker   against, all returned matches must begin with it.  line is the current
22*cda5da8dSAndroid Build Coastguard Worker   input line (lstripped), begidx and endidx are the beginning and end
23*cda5da8dSAndroid Build Coastguard Worker   indexes of the text being matched, which could be used to provide
24*cda5da8dSAndroid Build Coastguard Worker   different completion depending upon which position the argument is in.
25*cda5da8dSAndroid Build Coastguard Worker
26*cda5da8dSAndroid Build Coastguard WorkerThe `default' method may be overridden to intercept commands for which there
27*cda5da8dSAndroid Build Coastguard Workeris no do_ method.
28*cda5da8dSAndroid Build Coastguard Worker
29*cda5da8dSAndroid Build Coastguard WorkerThe `completedefault' method may be overridden to intercept completions for
30*cda5da8dSAndroid Build Coastguard Workercommands that have no complete_ method.
31*cda5da8dSAndroid Build Coastguard Worker
32*cda5da8dSAndroid Build Coastguard WorkerThe data member `self.ruler' sets the character used to draw separator lines
33*cda5da8dSAndroid Build Coastguard Workerin the help messages.  If empty, no ruler line is drawn.  It defaults to "=".
34*cda5da8dSAndroid Build Coastguard Worker
35*cda5da8dSAndroid Build Coastguard WorkerIf the value of `self.intro' is nonempty when the cmdloop method is called,
36*cda5da8dSAndroid Build Coastguard Workerit is printed out on interpreter startup.  This value may be overridden
37*cda5da8dSAndroid Build Coastguard Workervia an optional argument to the cmdloop() method.
38*cda5da8dSAndroid Build Coastguard Worker
39*cda5da8dSAndroid Build Coastguard WorkerThe data members `self.doc_header', `self.misc_header', and
40*cda5da8dSAndroid Build Coastguard Worker`self.undoc_header' set the headers used for the help function's
41*cda5da8dSAndroid Build Coastguard Workerlistings of documented functions, miscellaneous topics, and undocumented
42*cda5da8dSAndroid Build Coastguard Workerfunctions respectively.
43*cda5da8dSAndroid Build Coastguard Worker"""
44*cda5da8dSAndroid Build Coastguard Worker
45*cda5da8dSAndroid Build Coastguard Workerimport string, sys
46*cda5da8dSAndroid Build Coastguard Worker
47*cda5da8dSAndroid Build Coastguard Worker__all__ = ["Cmd"]
48*cda5da8dSAndroid Build Coastguard Worker
49*cda5da8dSAndroid Build Coastguard WorkerPROMPT = '(Cmd) '
50*cda5da8dSAndroid Build Coastguard WorkerIDENTCHARS = string.ascii_letters + string.digits + '_'
51*cda5da8dSAndroid Build Coastguard Worker
52*cda5da8dSAndroid Build Coastguard Workerclass Cmd:
53*cda5da8dSAndroid Build Coastguard Worker    """A simple framework for writing line-oriented command interpreters.
54*cda5da8dSAndroid Build Coastguard Worker
55*cda5da8dSAndroid Build Coastguard Worker    These are often useful for test harnesses, administrative tools, and
56*cda5da8dSAndroid Build Coastguard Worker    prototypes that will later be wrapped in a more sophisticated interface.
57*cda5da8dSAndroid Build Coastguard Worker
58*cda5da8dSAndroid Build Coastguard Worker    A Cmd instance or subclass instance is a line-oriented interpreter
59*cda5da8dSAndroid Build Coastguard Worker    framework.  There is no good reason to instantiate Cmd itself; rather,
60*cda5da8dSAndroid Build Coastguard Worker    it's useful as a superclass of an interpreter class you define yourself
61*cda5da8dSAndroid Build Coastguard Worker    in order to inherit Cmd's methods and encapsulate action methods.
62*cda5da8dSAndroid Build Coastguard Worker
63*cda5da8dSAndroid Build Coastguard Worker    """
64*cda5da8dSAndroid Build Coastguard Worker    prompt = PROMPT
65*cda5da8dSAndroid Build Coastguard Worker    identchars = IDENTCHARS
66*cda5da8dSAndroid Build Coastguard Worker    ruler = '='
67*cda5da8dSAndroid Build Coastguard Worker    lastcmd = ''
68*cda5da8dSAndroid Build Coastguard Worker    intro = None
69*cda5da8dSAndroid Build Coastguard Worker    doc_leader = ""
70*cda5da8dSAndroid Build Coastguard Worker    doc_header = "Documented commands (type help <topic>):"
71*cda5da8dSAndroid Build Coastguard Worker    misc_header = "Miscellaneous help topics:"
72*cda5da8dSAndroid Build Coastguard Worker    undoc_header = "Undocumented commands:"
73*cda5da8dSAndroid Build Coastguard Worker    nohelp = "*** No help on %s"
74*cda5da8dSAndroid Build Coastguard Worker    use_rawinput = 1
75*cda5da8dSAndroid Build Coastguard Worker
76*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, completekey='tab', stdin=None, stdout=None):
77*cda5da8dSAndroid Build Coastguard Worker        """Instantiate a line-oriented interpreter framework.
78*cda5da8dSAndroid Build Coastguard Worker
79*cda5da8dSAndroid Build Coastguard Worker        The optional argument 'completekey' is the readline name of a
80*cda5da8dSAndroid Build Coastguard Worker        completion key; it defaults to the Tab key. If completekey is
81*cda5da8dSAndroid Build Coastguard Worker        not None and the readline module is available, command completion
82*cda5da8dSAndroid Build Coastguard Worker        is done automatically. The optional arguments stdin and stdout
83*cda5da8dSAndroid Build Coastguard Worker        specify alternate input and output file objects; if not specified,
84*cda5da8dSAndroid Build Coastguard Worker        sys.stdin and sys.stdout are used.
85*cda5da8dSAndroid Build Coastguard Worker
86*cda5da8dSAndroid Build Coastguard Worker        """
87*cda5da8dSAndroid Build Coastguard Worker        if stdin is not None:
88*cda5da8dSAndroid Build Coastguard Worker            self.stdin = stdin
89*cda5da8dSAndroid Build Coastguard Worker        else:
90*cda5da8dSAndroid Build Coastguard Worker            self.stdin = sys.stdin
91*cda5da8dSAndroid Build Coastguard Worker        if stdout is not None:
92*cda5da8dSAndroid Build Coastguard Worker            self.stdout = stdout
93*cda5da8dSAndroid Build Coastguard Worker        else:
94*cda5da8dSAndroid Build Coastguard Worker            self.stdout = sys.stdout
95*cda5da8dSAndroid Build Coastguard Worker        self.cmdqueue = []
96*cda5da8dSAndroid Build Coastguard Worker        self.completekey = completekey
97*cda5da8dSAndroid Build Coastguard Worker
98*cda5da8dSAndroid Build Coastguard Worker    def cmdloop(self, intro=None):
99*cda5da8dSAndroid Build Coastguard Worker        """Repeatedly issue a prompt, accept input, parse an initial prefix
100*cda5da8dSAndroid Build Coastguard Worker        off the received input, and dispatch to action methods, passing them
101*cda5da8dSAndroid Build Coastguard Worker        the remainder of the line as argument.
102*cda5da8dSAndroid Build Coastguard Worker
103*cda5da8dSAndroid Build Coastguard Worker        """
104*cda5da8dSAndroid Build Coastguard Worker
105*cda5da8dSAndroid Build Coastguard Worker        self.preloop()
106*cda5da8dSAndroid Build Coastguard Worker        if self.use_rawinput and self.completekey:
107*cda5da8dSAndroid Build Coastguard Worker            try:
108*cda5da8dSAndroid Build Coastguard Worker                import readline
109*cda5da8dSAndroid Build Coastguard Worker                self.old_completer = readline.get_completer()
110*cda5da8dSAndroid Build Coastguard Worker                readline.set_completer(self.complete)
111*cda5da8dSAndroid Build Coastguard Worker                readline.parse_and_bind(self.completekey+": complete")
112*cda5da8dSAndroid Build Coastguard Worker            except ImportError:
113*cda5da8dSAndroid Build Coastguard Worker                pass
114*cda5da8dSAndroid Build Coastguard Worker        try:
115*cda5da8dSAndroid Build Coastguard Worker            if intro is not None:
116*cda5da8dSAndroid Build Coastguard Worker                self.intro = intro
117*cda5da8dSAndroid Build Coastguard Worker            if self.intro:
118*cda5da8dSAndroid Build Coastguard Worker                self.stdout.write(str(self.intro)+"\n")
119*cda5da8dSAndroid Build Coastguard Worker            stop = None
120*cda5da8dSAndroid Build Coastguard Worker            while not stop:
121*cda5da8dSAndroid Build Coastguard Worker                if self.cmdqueue:
122*cda5da8dSAndroid Build Coastguard Worker                    line = self.cmdqueue.pop(0)
123*cda5da8dSAndroid Build Coastguard Worker                else:
124*cda5da8dSAndroid Build Coastguard Worker                    if self.use_rawinput:
125*cda5da8dSAndroid Build Coastguard Worker                        try:
126*cda5da8dSAndroid Build Coastguard Worker                            line = input(self.prompt)
127*cda5da8dSAndroid Build Coastguard Worker                        except EOFError:
128*cda5da8dSAndroid Build Coastguard Worker                            line = 'EOF'
129*cda5da8dSAndroid Build Coastguard Worker                    else:
130*cda5da8dSAndroid Build Coastguard Worker                        self.stdout.write(self.prompt)
131*cda5da8dSAndroid Build Coastguard Worker                        self.stdout.flush()
132*cda5da8dSAndroid Build Coastguard Worker                        line = self.stdin.readline()
133*cda5da8dSAndroid Build Coastguard Worker                        if not len(line):
134*cda5da8dSAndroid Build Coastguard Worker                            line = 'EOF'
135*cda5da8dSAndroid Build Coastguard Worker                        else:
136*cda5da8dSAndroid Build Coastguard Worker                            line = line.rstrip('\r\n')
137*cda5da8dSAndroid Build Coastguard Worker                line = self.precmd(line)
138*cda5da8dSAndroid Build Coastguard Worker                stop = self.onecmd(line)
139*cda5da8dSAndroid Build Coastguard Worker                stop = self.postcmd(stop, line)
140*cda5da8dSAndroid Build Coastguard Worker            self.postloop()
141*cda5da8dSAndroid Build Coastguard Worker        finally:
142*cda5da8dSAndroid Build Coastguard Worker            if self.use_rawinput and self.completekey:
143*cda5da8dSAndroid Build Coastguard Worker                try:
144*cda5da8dSAndroid Build Coastguard Worker                    import readline
145*cda5da8dSAndroid Build Coastguard Worker                    readline.set_completer(self.old_completer)
146*cda5da8dSAndroid Build Coastguard Worker                except ImportError:
147*cda5da8dSAndroid Build Coastguard Worker                    pass
148*cda5da8dSAndroid Build Coastguard Worker
149*cda5da8dSAndroid Build Coastguard Worker
150*cda5da8dSAndroid Build Coastguard Worker    def precmd(self, line):
151*cda5da8dSAndroid Build Coastguard Worker        """Hook method executed just before the command line is
152*cda5da8dSAndroid Build Coastguard Worker        interpreted, but after the input prompt is generated and issued.
153*cda5da8dSAndroid Build Coastguard Worker
154*cda5da8dSAndroid Build Coastguard Worker        """
155*cda5da8dSAndroid Build Coastguard Worker        return line
156*cda5da8dSAndroid Build Coastguard Worker
157*cda5da8dSAndroid Build Coastguard Worker    def postcmd(self, stop, line):
158*cda5da8dSAndroid Build Coastguard Worker        """Hook method executed just after a command dispatch is finished."""
159*cda5da8dSAndroid Build Coastguard Worker        return stop
160*cda5da8dSAndroid Build Coastguard Worker
161*cda5da8dSAndroid Build Coastguard Worker    def preloop(self):
162*cda5da8dSAndroid Build Coastguard Worker        """Hook method executed once when the cmdloop() method is called."""
163*cda5da8dSAndroid Build Coastguard Worker        pass
164*cda5da8dSAndroid Build Coastguard Worker
165*cda5da8dSAndroid Build Coastguard Worker    def postloop(self):
166*cda5da8dSAndroid Build Coastguard Worker        """Hook method executed once when the cmdloop() method is about to
167*cda5da8dSAndroid Build Coastguard Worker        return.
168*cda5da8dSAndroid Build Coastguard Worker
169*cda5da8dSAndroid Build Coastguard Worker        """
170*cda5da8dSAndroid Build Coastguard Worker        pass
171*cda5da8dSAndroid Build Coastguard Worker
172*cda5da8dSAndroid Build Coastguard Worker    def parseline(self, line):
173*cda5da8dSAndroid Build Coastguard Worker        """Parse the line into a command name and a string containing
174*cda5da8dSAndroid Build Coastguard Worker        the arguments.  Returns a tuple containing (command, args, line).
175*cda5da8dSAndroid Build Coastguard Worker        'command' and 'args' may be None if the line couldn't be parsed.
176*cda5da8dSAndroid Build Coastguard Worker        """
177*cda5da8dSAndroid Build Coastguard Worker        line = line.strip()
178*cda5da8dSAndroid Build Coastguard Worker        if not line:
179*cda5da8dSAndroid Build Coastguard Worker            return None, None, line
180*cda5da8dSAndroid Build Coastguard Worker        elif line[0] == '?':
181*cda5da8dSAndroid Build Coastguard Worker            line = 'help ' + line[1:]
182*cda5da8dSAndroid Build Coastguard Worker        elif line[0] == '!':
183*cda5da8dSAndroid Build Coastguard Worker            if hasattr(self, 'do_shell'):
184*cda5da8dSAndroid Build Coastguard Worker                line = 'shell ' + line[1:]
185*cda5da8dSAndroid Build Coastguard Worker            else:
186*cda5da8dSAndroid Build Coastguard Worker                return None, None, line
187*cda5da8dSAndroid Build Coastguard Worker        i, n = 0, len(line)
188*cda5da8dSAndroid Build Coastguard Worker        while i < n and line[i] in self.identchars: i = i+1
189*cda5da8dSAndroid Build Coastguard Worker        cmd, arg = line[:i], line[i:].strip()
190*cda5da8dSAndroid Build Coastguard Worker        return cmd, arg, line
191*cda5da8dSAndroid Build Coastguard Worker
192*cda5da8dSAndroid Build Coastguard Worker    def onecmd(self, line):
193*cda5da8dSAndroid Build Coastguard Worker        """Interpret the argument as though it had been typed in response
194*cda5da8dSAndroid Build Coastguard Worker        to the prompt.
195*cda5da8dSAndroid Build Coastguard Worker
196*cda5da8dSAndroid Build Coastguard Worker        This may be overridden, but should not normally need to be;
197*cda5da8dSAndroid Build Coastguard Worker        see the precmd() and postcmd() methods for useful execution hooks.
198*cda5da8dSAndroid Build Coastguard Worker        The return value is a flag indicating whether interpretation of
199*cda5da8dSAndroid Build Coastguard Worker        commands by the interpreter should stop.
200*cda5da8dSAndroid Build Coastguard Worker
201*cda5da8dSAndroid Build Coastguard Worker        """
202*cda5da8dSAndroid Build Coastguard Worker        cmd, arg, line = self.parseline(line)
203*cda5da8dSAndroid Build Coastguard Worker        if not line:
204*cda5da8dSAndroid Build Coastguard Worker            return self.emptyline()
205*cda5da8dSAndroid Build Coastguard Worker        if cmd is None:
206*cda5da8dSAndroid Build Coastguard Worker            return self.default(line)
207*cda5da8dSAndroid Build Coastguard Worker        self.lastcmd = line
208*cda5da8dSAndroid Build Coastguard Worker        if line == 'EOF' :
209*cda5da8dSAndroid Build Coastguard Worker            self.lastcmd = ''
210*cda5da8dSAndroid Build Coastguard Worker        if cmd == '':
211*cda5da8dSAndroid Build Coastguard Worker            return self.default(line)
212*cda5da8dSAndroid Build Coastguard Worker        else:
213*cda5da8dSAndroid Build Coastguard Worker            try:
214*cda5da8dSAndroid Build Coastguard Worker                func = getattr(self, 'do_' + cmd)
215*cda5da8dSAndroid Build Coastguard Worker            except AttributeError:
216*cda5da8dSAndroid Build Coastguard Worker                return self.default(line)
217*cda5da8dSAndroid Build Coastguard Worker            return func(arg)
218*cda5da8dSAndroid Build Coastguard Worker
219*cda5da8dSAndroid Build Coastguard Worker    def emptyline(self):
220*cda5da8dSAndroid Build Coastguard Worker        """Called when an empty line is entered in response to the prompt.
221*cda5da8dSAndroid Build Coastguard Worker
222*cda5da8dSAndroid Build Coastguard Worker        If this method is not overridden, it repeats the last nonempty
223*cda5da8dSAndroid Build Coastguard Worker        command entered.
224*cda5da8dSAndroid Build Coastguard Worker
225*cda5da8dSAndroid Build Coastguard Worker        """
226*cda5da8dSAndroid Build Coastguard Worker        if self.lastcmd:
227*cda5da8dSAndroid Build Coastguard Worker            return self.onecmd(self.lastcmd)
228*cda5da8dSAndroid Build Coastguard Worker
229*cda5da8dSAndroid Build Coastguard Worker    def default(self, line):
230*cda5da8dSAndroid Build Coastguard Worker        """Called on an input line when the command prefix is not recognized.
231*cda5da8dSAndroid Build Coastguard Worker
232*cda5da8dSAndroid Build Coastguard Worker        If this method is not overridden, it prints an error message and
233*cda5da8dSAndroid Build Coastguard Worker        returns.
234*cda5da8dSAndroid Build Coastguard Worker
235*cda5da8dSAndroid Build Coastguard Worker        """
236*cda5da8dSAndroid Build Coastguard Worker        self.stdout.write('*** Unknown syntax: %s\n'%line)
237*cda5da8dSAndroid Build Coastguard Worker
238*cda5da8dSAndroid Build Coastguard Worker    def completedefault(self, *ignored):
239*cda5da8dSAndroid Build Coastguard Worker        """Method called to complete an input line when no command-specific
240*cda5da8dSAndroid Build Coastguard Worker        complete_*() method is available.
241*cda5da8dSAndroid Build Coastguard Worker
242*cda5da8dSAndroid Build Coastguard Worker        By default, it returns an empty list.
243*cda5da8dSAndroid Build Coastguard Worker
244*cda5da8dSAndroid Build Coastguard Worker        """
245*cda5da8dSAndroid Build Coastguard Worker        return []
246*cda5da8dSAndroid Build Coastguard Worker
247*cda5da8dSAndroid Build Coastguard Worker    def completenames(self, text, *ignored):
248*cda5da8dSAndroid Build Coastguard Worker        dotext = 'do_'+text
249*cda5da8dSAndroid Build Coastguard Worker        return [a[3:] for a in self.get_names() if a.startswith(dotext)]
250*cda5da8dSAndroid Build Coastguard Worker
251*cda5da8dSAndroid Build Coastguard Worker    def complete(self, text, state):
252*cda5da8dSAndroid Build Coastguard Worker        """Return the next possible completion for 'text'.
253*cda5da8dSAndroid Build Coastguard Worker
254*cda5da8dSAndroid Build Coastguard Worker        If a command has not been entered, then complete against command list.
255*cda5da8dSAndroid Build Coastguard Worker        Otherwise try to call complete_<command> to get list of completions.
256*cda5da8dSAndroid Build Coastguard Worker        """
257*cda5da8dSAndroid Build Coastguard Worker        if state == 0:
258*cda5da8dSAndroid Build Coastguard Worker            import readline
259*cda5da8dSAndroid Build Coastguard Worker            origline = readline.get_line_buffer()
260*cda5da8dSAndroid Build Coastguard Worker            line = origline.lstrip()
261*cda5da8dSAndroid Build Coastguard Worker            stripped = len(origline) - len(line)
262*cda5da8dSAndroid Build Coastguard Worker            begidx = readline.get_begidx() - stripped
263*cda5da8dSAndroid Build Coastguard Worker            endidx = readline.get_endidx() - stripped
264*cda5da8dSAndroid Build Coastguard Worker            if begidx>0:
265*cda5da8dSAndroid Build Coastguard Worker                cmd, args, foo = self.parseline(line)
266*cda5da8dSAndroid Build Coastguard Worker                if cmd == '':
267*cda5da8dSAndroid Build Coastguard Worker                    compfunc = self.completedefault
268*cda5da8dSAndroid Build Coastguard Worker                else:
269*cda5da8dSAndroid Build Coastguard Worker                    try:
270*cda5da8dSAndroid Build Coastguard Worker                        compfunc = getattr(self, 'complete_' + cmd)
271*cda5da8dSAndroid Build Coastguard Worker                    except AttributeError:
272*cda5da8dSAndroid Build Coastguard Worker                        compfunc = self.completedefault
273*cda5da8dSAndroid Build Coastguard Worker            else:
274*cda5da8dSAndroid Build Coastguard Worker                compfunc = self.completenames
275*cda5da8dSAndroid Build Coastguard Worker            self.completion_matches = compfunc(text, line, begidx, endidx)
276*cda5da8dSAndroid Build Coastguard Worker        try:
277*cda5da8dSAndroid Build Coastguard Worker            return self.completion_matches[state]
278*cda5da8dSAndroid Build Coastguard Worker        except IndexError:
279*cda5da8dSAndroid Build Coastguard Worker            return None
280*cda5da8dSAndroid Build Coastguard Worker
281*cda5da8dSAndroid Build Coastguard Worker    def get_names(self):
282*cda5da8dSAndroid Build Coastguard Worker        # This method used to pull in base class attributes
283*cda5da8dSAndroid Build Coastguard Worker        # at a time dir() didn't do it yet.
284*cda5da8dSAndroid Build Coastguard Worker        return dir(self.__class__)
285*cda5da8dSAndroid Build Coastguard Worker
286*cda5da8dSAndroid Build Coastguard Worker    def complete_help(self, *args):
287*cda5da8dSAndroid Build Coastguard Worker        commands = set(self.completenames(*args))
288*cda5da8dSAndroid Build Coastguard Worker        topics = set(a[5:] for a in self.get_names()
289*cda5da8dSAndroid Build Coastguard Worker                     if a.startswith('help_' + args[0]))
290*cda5da8dSAndroid Build Coastguard Worker        return list(commands | topics)
291*cda5da8dSAndroid Build Coastguard Worker
292*cda5da8dSAndroid Build Coastguard Worker    def do_help(self, arg):
293*cda5da8dSAndroid Build Coastguard Worker        'List available commands with "help" or detailed help with "help cmd".'
294*cda5da8dSAndroid Build Coastguard Worker        if arg:
295*cda5da8dSAndroid Build Coastguard Worker            # XXX check arg syntax
296*cda5da8dSAndroid Build Coastguard Worker            try:
297*cda5da8dSAndroid Build Coastguard Worker                func = getattr(self, 'help_' + arg)
298*cda5da8dSAndroid Build Coastguard Worker            except AttributeError:
299*cda5da8dSAndroid Build Coastguard Worker                try:
300*cda5da8dSAndroid Build Coastguard Worker                    doc=getattr(self, 'do_' + arg).__doc__
301*cda5da8dSAndroid Build Coastguard Worker                    if doc:
302*cda5da8dSAndroid Build Coastguard Worker                        self.stdout.write("%s\n"%str(doc))
303*cda5da8dSAndroid Build Coastguard Worker                        return
304*cda5da8dSAndroid Build Coastguard Worker                except AttributeError:
305*cda5da8dSAndroid Build Coastguard Worker                    pass
306*cda5da8dSAndroid Build Coastguard Worker                self.stdout.write("%s\n"%str(self.nohelp % (arg,)))
307*cda5da8dSAndroid Build Coastguard Worker                return
308*cda5da8dSAndroid Build Coastguard Worker            func()
309*cda5da8dSAndroid Build Coastguard Worker        else:
310*cda5da8dSAndroid Build Coastguard Worker            names = self.get_names()
311*cda5da8dSAndroid Build Coastguard Worker            cmds_doc = []
312*cda5da8dSAndroid Build Coastguard Worker            cmds_undoc = []
313*cda5da8dSAndroid Build Coastguard Worker            topics = set()
314*cda5da8dSAndroid Build Coastguard Worker            for name in names:
315*cda5da8dSAndroid Build Coastguard Worker                if name[:5] == 'help_':
316*cda5da8dSAndroid Build Coastguard Worker                    topics.add(name[5:])
317*cda5da8dSAndroid Build Coastguard Worker            names.sort()
318*cda5da8dSAndroid Build Coastguard Worker            # There can be duplicates if routines overridden
319*cda5da8dSAndroid Build Coastguard Worker            prevname = ''
320*cda5da8dSAndroid Build Coastguard Worker            for name in names:
321*cda5da8dSAndroid Build Coastguard Worker                if name[:3] == 'do_':
322*cda5da8dSAndroid Build Coastguard Worker                    if name == prevname:
323*cda5da8dSAndroid Build Coastguard Worker                        continue
324*cda5da8dSAndroid Build Coastguard Worker                    prevname = name
325*cda5da8dSAndroid Build Coastguard Worker                    cmd=name[3:]
326*cda5da8dSAndroid Build Coastguard Worker                    if cmd in topics:
327*cda5da8dSAndroid Build Coastguard Worker                        cmds_doc.append(cmd)
328*cda5da8dSAndroid Build Coastguard Worker                        topics.remove(cmd)
329*cda5da8dSAndroid Build Coastguard Worker                    elif getattr(self, name).__doc__:
330*cda5da8dSAndroid Build Coastguard Worker                        cmds_doc.append(cmd)
331*cda5da8dSAndroid Build Coastguard Worker                    else:
332*cda5da8dSAndroid Build Coastguard Worker                        cmds_undoc.append(cmd)
333*cda5da8dSAndroid Build Coastguard Worker            self.stdout.write("%s\n"%str(self.doc_leader))
334*cda5da8dSAndroid Build Coastguard Worker            self.print_topics(self.doc_header,   cmds_doc,   15,80)
335*cda5da8dSAndroid Build Coastguard Worker            self.print_topics(self.misc_header,  sorted(topics),15,80)
336*cda5da8dSAndroid Build Coastguard Worker            self.print_topics(self.undoc_header, cmds_undoc, 15,80)
337*cda5da8dSAndroid Build Coastguard Worker
338*cda5da8dSAndroid Build Coastguard Worker    def print_topics(self, header, cmds, cmdlen, maxcol):
339*cda5da8dSAndroid Build Coastguard Worker        if cmds:
340*cda5da8dSAndroid Build Coastguard Worker            self.stdout.write("%s\n"%str(header))
341*cda5da8dSAndroid Build Coastguard Worker            if self.ruler:
342*cda5da8dSAndroid Build Coastguard Worker                self.stdout.write("%s\n"%str(self.ruler * len(header)))
343*cda5da8dSAndroid Build Coastguard Worker            self.columnize(cmds, maxcol-1)
344*cda5da8dSAndroid Build Coastguard Worker            self.stdout.write("\n")
345*cda5da8dSAndroid Build Coastguard Worker
346*cda5da8dSAndroid Build Coastguard Worker    def columnize(self, list, displaywidth=80):
347*cda5da8dSAndroid Build Coastguard Worker        """Display a list of strings as a compact set of columns.
348*cda5da8dSAndroid Build Coastguard Worker
349*cda5da8dSAndroid Build Coastguard Worker        Each column is only as wide as necessary.
350*cda5da8dSAndroid Build Coastguard Worker        Columns are separated by two spaces (one was not legible enough).
351*cda5da8dSAndroid Build Coastguard Worker        """
352*cda5da8dSAndroid Build Coastguard Worker        if not list:
353*cda5da8dSAndroid Build Coastguard Worker            self.stdout.write("<empty>\n")
354*cda5da8dSAndroid Build Coastguard Worker            return
355*cda5da8dSAndroid Build Coastguard Worker
356*cda5da8dSAndroid Build Coastguard Worker        nonstrings = [i for i in range(len(list))
357*cda5da8dSAndroid Build Coastguard Worker                        if not isinstance(list[i], str)]
358*cda5da8dSAndroid Build Coastguard Worker        if nonstrings:
359*cda5da8dSAndroid Build Coastguard Worker            raise TypeError("list[i] not a string for i in %s"
360*cda5da8dSAndroid Build Coastguard Worker                            % ", ".join(map(str, nonstrings)))
361*cda5da8dSAndroid Build Coastguard Worker        size = len(list)
362*cda5da8dSAndroid Build Coastguard Worker        if size == 1:
363*cda5da8dSAndroid Build Coastguard Worker            self.stdout.write('%s\n'%str(list[0]))
364*cda5da8dSAndroid Build Coastguard Worker            return
365*cda5da8dSAndroid Build Coastguard Worker        # Try every row count from 1 upwards
366*cda5da8dSAndroid Build Coastguard Worker        for nrows in range(1, len(list)):
367*cda5da8dSAndroid Build Coastguard Worker            ncols = (size+nrows-1) // nrows
368*cda5da8dSAndroid Build Coastguard Worker            colwidths = []
369*cda5da8dSAndroid Build Coastguard Worker            totwidth = -2
370*cda5da8dSAndroid Build Coastguard Worker            for col in range(ncols):
371*cda5da8dSAndroid Build Coastguard Worker                colwidth = 0
372*cda5da8dSAndroid Build Coastguard Worker                for row in range(nrows):
373*cda5da8dSAndroid Build Coastguard Worker                    i = row + nrows*col
374*cda5da8dSAndroid Build Coastguard Worker                    if i >= size:
375*cda5da8dSAndroid Build Coastguard Worker                        break
376*cda5da8dSAndroid Build Coastguard Worker                    x = list[i]
377*cda5da8dSAndroid Build Coastguard Worker                    colwidth = max(colwidth, len(x))
378*cda5da8dSAndroid Build Coastguard Worker                colwidths.append(colwidth)
379*cda5da8dSAndroid Build Coastguard Worker                totwidth += colwidth + 2
380*cda5da8dSAndroid Build Coastguard Worker                if totwidth > displaywidth:
381*cda5da8dSAndroid Build Coastguard Worker                    break
382*cda5da8dSAndroid Build Coastguard Worker            if totwidth <= displaywidth:
383*cda5da8dSAndroid Build Coastguard Worker                break
384*cda5da8dSAndroid Build Coastguard Worker        else:
385*cda5da8dSAndroid Build Coastguard Worker            nrows = len(list)
386*cda5da8dSAndroid Build Coastguard Worker            ncols = 1
387*cda5da8dSAndroid Build Coastguard Worker            colwidths = [0]
388*cda5da8dSAndroid Build Coastguard Worker        for row in range(nrows):
389*cda5da8dSAndroid Build Coastguard Worker            texts = []
390*cda5da8dSAndroid Build Coastguard Worker            for col in range(ncols):
391*cda5da8dSAndroid Build Coastguard Worker                i = row + nrows*col
392*cda5da8dSAndroid Build Coastguard Worker                if i >= size:
393*cda5da8dSAndroid Build Coastguard Worker                    x = ""
394*cda5da8dSAndroid Build Coastguard Worker                else:
395*cda5da8dSAndroid Build Coastguard Worker                    x = list[i]
396*cda5da8dSAndroid Build Coastguard Worker                texts.append(x)
397*cda5da8dSAndroid Build Coastguard Worker            while texts and not texts[-1]:
398*cda5da8dSAndroid Build Coastguard Worker                del texts[-1]
399*cda5da8dSAndroid Build Coastguard Worker            for col in range(len(texts)):
400*cda5da8dSAndroid Build Coastguard Worker                texts[col] = texts[col].ljust(colwidths[col])
401*cda5da8dSAndroid Build Coastguard Worker            self.stdout.write("%s\n"%str("  ".join(texts)))
402