Ditched '_find_SET()', since it was a no-value-added wrapper around
[python/dscho.git] / Lib / cmd.py
blobd0c749831e5a665eda037245fe90c3edace16182
1 """A generic class to build line-oriented command interpreters.
3 Interpreters constructed with this class obey the following conventions:
5 1. End of file on input is processed as the command 'EOF'.
6 2. A command is parsed out of each line by collecting the prefix composed
7 of characters in the identchars member.
8 3. A command `foo' is dispatched to a method 'do_foo()'; the do_ method
9 is passed a single argument consisting of the remainder of the line.
10 4. Typing an empty line repeats the last command. (Actually, it calls the
11 method `emptyline', which may be overridden in a subclass.)
12 5. There is a predefined `help' method. Given an argument `topic', it
13 calls the command `help_topic'. With no arguments, it lists all topics
14 with defined help_ functions, broken into up to three topics; documented
15 commands, miscellaneous help topics, and undocumented commands.
16 6. The command '?' is a synonym for `help'. The command '!' is a synonym
17 for `shell', if a do_shell method exists.
19 The `default' method may be overridden to intercept commands for which there
20 is no do_ method.
22 The data member `self.ruler' sets the character used to draw separator lines
23 in the help messages. If empty, no ruler line is drawn. It defaults to "=".
25 If the value of `self.intro' is nonempty when the cmdloop method is called,
26 it is printed out on interpreter startup. This value may be overridden
27 via an optional argument to the cmdloop() method.
29 The data members `self.doc_header', `self.misc_header', and
30 `self.undoc_header' set the headers used for the help function's
31 listings of documented functions, miscellaneous topics, and undocumented
32 functions respectively.
34 These interpreters use raw_input; thus, if the readline module is loaded,
35 they automatically support Emacs-like command history and editing features.
36 """
38 import string
40 PROMPT = '(Cmd) '
41 IDENTCHARS = string.letters + string.digits + '_'
43 class Cmd:
44 prompt = PROMPT
45 identchars = IDENTCHARS
46 ruler = '='
47 lastcmd = ''
48 cmdqueue = []
49 intro = None
50 doc_leader = ""
51 doc_header = "Documented commands (type help <topic>):"
52 misc_header = "Miscellaneous help topics:"
53 undoc_header = "Undocumented commands:"
54 nohelp = "*** No help on %s"
56 def __init__(self): pass
58 def cmdloop(self, intro=None):
59 self.preloop()
60 if intro != None:
61 self.intro = intro
62 if self.intro:
63 print self.intro
64 stop = None
65 while not stop:
66 if self.cmdqueue:
67 line = self.cmdqueue[0]
68 del self.cmdqueue[0]
69 else:
70 try:
71 line = raw_input(self.prompt)
72 except EOFError:
73 line = 'EOF'
74 line = self.precmd(line)
75 stop = self.onecmd(line)
76 stop = self.postcmd(stop, line)
77 self.postloop()
79 def precmd(self, line):
80 return line
82 def postcmd(self, stop, line):
83 return stop
85 def preloop(self):
86 pass
88 def postloop(self):
89 pass
91 def onecmd(self, line):
92 line = string.strip(line)
93 if line == '?':
94 line = 'help'
95 elif line == '!':
96 if hasattr(self, 'do_shell'):
97 line = 'shell'
98 else:
99 return self.default(line)
100 elif not line:
101 return self.emptyline()
102 self.lastcmd = line
103 i, n = 0, len(line)
104 while i < n and line[i] in self.identchars: i = i+1
105 cmd, arg = line[:i], string.strip(line[i:])
106 if cmd == '':
107 return self.default(line)
108 else:
109 try:
110 func = getattr(self, 'do_' + cmd)
111 except AttributeError:
112 return self.default(line)
113 return func(arg)
115 def emptyline(self):
116 if self.lastcmd:
117 return self.onecmd(self.lastcmd)
119 def default(self, line):
120 print '*** Unknown syntax:', line
122 def do_help(self, arg):
123 if arg:
124 # XXX check arg syntax
125 try:
126 func = getattr(self, 'help_' + arg)
127 except:
128 try:
129 doc=getattr(self, 'do_' + arg).__doc__
130 if doc:
131 print doc
132 return
133 except:
134 pass
135 print self.nohelp % (arg,)
136 return
137 func()
138 else:
139 # Inheritance says we have to look in class and
140 # base classes; order is not important.
141 names = []
142 classes = [self.__class__]
143 while classes:
144 aclass = classes[0]
145 if aclass.__bases__:
146 classes = classes + list(aclass.__bases__)
147 names = names + dir(aclass)
148 del classes[0]
149 cmds_doc = []
150 cmds_undoc = []
151 help = {}
152 for name in names:
153 if name[:5] == 'help_':
154 help[name[5:]]=1
155 names.sort()
156 # There can be duplicates if routines overridden
157 prevname = ''
158 for name in names:
159 if name[:3] == 'do_':
160 if name == prevname:
161 continue
162 prevname = name
163 cmd=name[3:]
164 if help.has_key(cmd):
165 cmds_doc.append(cmd)
166 del help[cmd]
167 elif getattr(self, name).__doc__:
168 cmds_doc.append(cmd)
169 else:
170 cmds_undoc.append(cmd)
171 print self.doc_leader
172 self.print_topics(self.doc_header, cmds_doc, 15,80)
173 self.print_topics(self.misc_header, help.keys(),15,80)
174 self.print_topics(self.undoc_header, cmds_undoc, 15,80)
176 def print_topics(self, header, cmds, cmdlen, maxcol):
177 if cmds:
178 print header;
179 if self.ruler:
180 print self.ruler * len(header)
181 (cmds_per_line,junk)=divmod(maxcol,cmdlen)
182 col=cmds_per_line
183 for cmd in cmds:
184 if col==0: print
185 print (("%-"+`cmdlen`+"s") % cmd),
186 col = (col+1) % cmds_per_line
187 print "\n"