Mercurial > python-cmd2
comparison cmd2.py @ 79:f583663c610f
switch to pyparsing worked
author | catherine@Elli.myhome.westell.com |
---|---|
date | Fri, 04 Jul 2008 07:08:21 -0400 |
parents | f844b6c78192 |
children | 1e4ba836539e |
comparison
equal
deleted
inserted
replaced
57:86bb50447e99 | 79:f583663c610f |
---|---|
16 | 16 |
17 CHANGES: | 17 CHANGES: |
18 As of 0.3.0, options should be specified as `optparse` options. See README.txt. | 18 As of 0.3.0, options should be specified as `optparse` options. See README.txt. |
19 flagReader.py options are still supported for backward compatibility | 19 flagReader.py options are still supported for backward compatibility |
20 """ | 20 """ |
21 import cmd, re, os, sys, optparse, subprocess, tempfile, pyparsing | 21 import cmd, re, os, sys, optparse, subprocess, tempfile, pyparsing, doctest |
22 from optparse import make_option | 22 from optparse import make_option |
23 | 23 |
24 class OptionParser(optparse.OptionParser): | 24 class OptionParser(optparse.OptionParser): |
25 def exit(self, status=0, msg=None): | 25 def exit(self, status=0, msg=None): |
26 self.values._exit = True | 26 self.values._exit = True |
121 xclipproc.stdin.close() | 121 xclipproc.stdin.close() |
122 else: | 122 else: |
123 def getPasteBuffer(): | 123 def getPasteBuffer(): |
124 raise OSError, pastebufferr % ('xclip', 'On Debian/Ubuntu, install with "sudo apt-get install xclip"') | 124 raise OSError, pastebufferr % ('xclip', 'On Debian/Ubuntu, install with "sudo apt-get install xclip"') |
125 setPasteBuffer = getPasteBuffer | 125 setPasteBuffer = getPasteBuffer |
126 | 126 |
127 pyparsing.ParserElement.setDefaultWhitespaceChars(' \t') | |
128 def parseSearchResults(pattern, s): | |
129 try: | |
130 return pattern.searchString(s)[0] | |
131 except IndexError: | |
132 return pyparsing.ParseResults('') | |
133 | |
127 class Cmd(cmd.Cmd): | 134 class Cmd(cmd.Cmd): |
128 caseInsensitive = True | 135 caseInsensitive = True |
129 multilineCommands = [] | 136 multilineCommands = [] |
130 continuationPrompt = '> ' | 137 continuationPrompt = '> ' |
131 shortcuts = {'?': 'help', '!': 'shell', '@': 'load'} | 138 shortcuts = {'?': 'help', '!': 'shell', '@': 'load'} |
166 pass | 173 pass |
167 | 174 |
168 def __init__(self, *args, **kwargs): | 175 def __init__(self, *args, **kwargs): |
169 cmd.Cmd.__init__(self, *args, **kwargs) | 176 cmd.Cmd.__init__(self, *args, **kwargs) |
170 self.history = History() | 177 self.history = History() |
178 self.punctuationPattern = self.terminators ^ self.pipePattern ^ \ | |
179 self.redirectInPattern ^ \ | |
180 self.redirectOutPattern | |
181 self.punctuationPattern.ignore(pyparsing.sglQuotedString) | |
182 self.punctuationPattern.ignore(pyparsing.dblQuotedString) | |
171 | 183 |
172 def do_shortcuts(self, args): | 184 def do_shortcuts(self, args): |
173 """Lists single-key shortcuts available.""" | 185 """Lists single-key shortcuts available.""" |
174 result = "\n".join('%s: %s' % (sc[0], sc[1]) for sc in self.shortcuts.items()) | 186 result = "\n".join('%s: %s' % (sc[0], sc[1]) for sc in self.shortcuts.items()) |
175 self.stdout.write("Single-key shortcuts for other commands:\n%s\n" % (result)) | 187 self.stdout.write("Single-key shortcuts for other commands:\n%s\n" % (result)) |
176 | 188 |
177 notAPipe = pyparsing.SkipTo('|') | 189 terminators = (pyparsing.Literal(';') ^ pyparsing.Literal('\n\n')) \ |
178 notAPipe.ignore(pyparsing.sglQuotedString) | 190 ('terminator') |
179 notAPipe.ignore(pyparsing.dblQuotedString) | 191 argSeparatorPattern = pyparsing.Word(pyparsing.printables)('command') \ |
180 pipeFinder = notAPipe + '|' + pyparsing.SkipTo(pyparsing.StringEnd()) | 192 + pyparsing.SkipTo(pyparsing.StringEnd())('args') |
181 def parsePipe(self, statement, mustBeTerminated): | 193 filenamePattern = pyparsing.Word(pyparsing.alphanums + '#$-_~{},.!') |
182 try: | 194 integerPattern = pyparsing.Word(pyparsing.nums).setParseAction( lambda s,l,t: [ int(t[0]) ] ) |
183 statement, pipe, destination = self.pipeFinder.parseString(statement) | 195 pipePattern = pyparsing.Literal('|')('pipe') + pyparsing.restOfLine('pipeTo') |
184 redirect = subprocess.Popen(destination, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) | 196 redirectOutPattern = (pyparsing.Literal('>>') ^ '>')('output') \ |
185 return statement, redirect | 197 + pyparsing.Optional(filenamePattern)('outputTo') |
198 redirectInPattern = pyparsing.Literal('<')('input') \ | |
199 + pyparsing.Optional(filenamePattern)('inputFrom') | |
200 for p in (terminators, pipePattern, redirectInPattern, redirectOutPattern): | |
201 p.ignore(pyparsing.sglQuotedString) | |
202 p.ignore(pyparsing.dblQuotedString) | |
203 | |
204 def parsed(self, s): | |
205 ''' | |
206 >>> c = Cmd() | |
207 >>> c.parsed('quotes "are > ignored" < inp.txt').asDict() | |
208 {'args': ' "are > ignored"', 'inputFrom': 'inp.txt', 'command': 'quotes', 'statement': 'quotes "are > ignored"', 'input': '<', 'fullStatement': 'quotes "are > ignored" < inp.txt'} | |
209 >>> c.parsed('very complex; < from.txt >> to.txt etc.').asDict() | |
210 {'args': ' complex', 'inputFrom': 'from.txt', 'command': 'very', 'terminator': ';', 'statement': 'very complex', 'input': '<', 'output': '>>', 'outputTo': 'to.txt', 'fullStatement': 'very complex; < from.txt >> to.txt etc.'} | |
211 >>> c.parsed('nothing to parse').asDict() | |
212 {'args': ' to parse', 'command': 'nothing', 'statement': 'nothing to parse', 'fullStatement': 'nothing to parse'} | |
213 >>> c.parsed('send it to | sort | wc').asDict() | |
214 {'args': ' it to', 'pipe': '|', 'pipeTo': ' sort | wc', 'command': 'send', 'statement': 'send it to', 'fullStatement': 'send it to | sort | wc'} | |
215 >>> r = c.parsed('got from < thisfile.txt plus blah blah') | |
216 >>> r.asDict() | |
217 {'args': ' from', 'inputFrom': 'thisfile.txt', 'command': 'got', 'statement': 'got from', 'input': '<', 'fullStatement': 'got from < thisfile.txt plus blah blah'} | |
218 >>> c.parsed(r).asDict() | |
219 {'args': ' from', 'inputFrom': 'thisfile.txt', 'command': 'got', 'statement': 'got from', 'input': '<', 'fullStatement': 'got from < thisfile.txt plus blah blah'} | |
220 ''' | |
221 if isinstance(s, pyparsing.ParseResults): | |
222 return s | |
223 result = (pyparsing.SkipTo(pyparsing.StringEnd()))('fullStatement').parseString(s) | |
224 result['statement'] = result.fullStatement | |
225 try: | |
226 result += (pyparsing.SkipTo(self.punctuationPattern)('statement') \ | |
227 + self.punctuationPattern).parseString(s) | |
228 result += parseSearchResults(self.pipePattern, s) | |
229 result += parseSearchResults(self.redirectInPattern, s) | |
230 result += parseSearchResults(self.redirectOutPattern, s) | |
186 except pyparsing.ParseException: | 231 except pyparsing.ParseException: |
187 return statement, None | 232 pass |
188 | 233 finally: |
189 legalFileName = re.compile(r'''^[^"'\s]+$''') | 234 try: |
190 def parseRedirector(self, statement, symbol, mustBeTerminated=False): | 235 result += self.argSeparatorPattern.parseString(result.statement) |
191 # pipeFinder.scanString(statement) | 236 except pyparsing.ParseException: |
192 parts = statement.split(symbol) | 237 return result |
193 if (len(parts) < 2): | 238 if self.caseInsensitive: |
194 return statement, None | 239 result['command'] = result.command.lower() |
195 if mustBeTerminated and (not self.statementEndPattern.search(parts[-2])): | 240 result['statement'] = '%s %s' % (result.command, result.args) |
196 return statement, None | 241 return result |
197 (newStatement, redirect) = (symbol.join(parts[:-1]), parts[-1].strip()) | 242 |
198 if redirect: | |
199 if not self.legalFileName.search(redirect): | |
200 return statement, None | |
201 else: | |
202 redirect = self._TO_PASTE_BUFFER | |
203 return newStatement, redirect | |
204 | |
205 def extractCommand(self, statement): | 243 def extractCommand(self, statement): |
206 try: | 244 try: |
207 (command, args) = statement.split(None,1) | 245 (command, args) = statement.split(None,1) |
208 except ValueError: | 246 except ValueError: |
209 (command, args) = statement, '' | 247 (command, args) = statement, '' |
210 if self.caseInsensitive: | 248 if self.caseInsensitive: |
211 command = command.lower() | 249 command = command.lower() |
212 return command, args | 250 return command, args |
213 | 251 |
214 def parseRedirectors(self, statement): | |
215 mustBeTerminated = self.extractCommand(statement)[0] in self.multilineCommands | |
216 newStatement, redirect = self.parsePipe(statement, mustBeTerminated) | |
217 if redirect: | |
218 return newStatement, redirect, 'pipe' | |
219 newStatement, redirect = self.parseRedirector(statement, '>>', mustBeTerminated) | |
220 if redirect: | |
221 return newStatement, redirect, 'a' | |
222 newStatement, redirect = self.parseRedirector(statement, '>', mustBeTerminated) | |
223 if redirect: | |
224 return newStatement, redirect, 'w' | |
225 newStatement, redirect = self.parseRedirector(statement, '<', mustBeTerminated) | |
226 if redirect: | |
227 return newStatement, redirect, 'r' | |
228 return statement, '', '' | |
229 | |
230 def onecmd(self, line, assumeComplete=False): | 252 def onecmd(self, line, assumeComplete=False): |
231 """Interpret the argument as though it had been typed in response | 253 """Interpret the argument as though it had been typed in response |
232 to the prompt. | 254 to the prompt. |
233 | 255 |
234 This may be overridden, but should not normally need to be; | 256 This may be overridden, but should not normally need to be; |
235 see the precmd() and postcmd() methods for useful execution hooks. | 257 see the precmd() and postcmd() methods for useful execution hooks. |
236 The return value is a flag indicating whether interpretation of | 258 The return value is a flag indicating whether interpretation of |
237 commands by the interpreter should stop. | 259 commands by the interpreter should stop. |
238 | 260 |
239 """ | 261 """ |
240 command, args = self.extractCommand(line) | 262 statement = self.parsed(line) |
241 statement = originalStatement = ' '.join([command, args]) | 263 while (statement.command in self.multilineCommands) and not \ |
242 if (not assumeComplete) and (command in self.multilineCommands): | 264 (statement.terminator or assumeComplete): |
243 statement = self.finishStatement(statement) | 265 statement = self.parsed('%s\n%s' % (statement.fullStatement, |
266 self.pseudo_raw_input(self.continuationPrompt))) | |
267 | |
244 statekeeper = None | 268 statekeeper = None |
245 stop = 0 | 269 stop = 0 |
246 statement, redirect, mode = self.parseRedirectors(statement) | 270 if statement.input: |
247 if isinstance(redirect, subprocess.Popen): | 271 if statement.inputFrom: |
272 try: | |
273 newinput = open(statement.inputFrom, 'r').read() | |
274 except OSError, e: | |
275 print e | |
276 return 0 | |
277 else: | |
278 newinput = getPasteBuffer() | |
279 start, end = self.redirectInPattern.scanString(statement.fullStatement).next()[1:] | |
280 return self.onecmd('%s%s%s' % (statement.fullStatement[:start], | |
281 newinput, statement.fullStatement[end:])) | |
282 if statement.pipe and statement.pipeTo: | |
283 redirect = subprocess.Popen(statement.pipeTo, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) | |
248 statekeeper = Statekeeper(self, ('stdout',)) | 284 statekeeper = Statekeeper(self, ('stdout',)) |
249 self.stdout = redirect.stdin | 285 self.stdout = redirect.stdin |
250 elif redirect == self._TO_PASTE_BUFFER: | 286 elif statement.output: |
251 try: | 287 statekeeper = Statekeeper(self, ('stdout',)) |
252 clipcontents = getPasteBuffer() | 288 if statement.outputTo: |
253 if mode in ('w', 'a'): | 289 mode = 'w' |
254 statekeeper = Statekeeper(self, ('stdout',)) | 290 if statement.output == '>>': |
255 self.stdout = tempfile.TemporaryFile() | 291 mode = 'a' |
256 if mode == 'a': | 292 try: |
257 self.stdout.write(clipcontents) | 293 self.stdout = open(statement.outputTo, mode) |
258 else: | 294 except OSError, e: |
259 statement = '%s %s' % (statement, clipcontents) | 295 print e |
260 except OSError, e: | 296 return 0 |
261 print e | 297 else: |
262 return 0 | |
263 elif redirect: | |
264 if mode in ('w','a'): | |
265 statekeeper = Statekeeper(self, ('stdout',)) | 298 statekeeper = Statekeeper(self, ('stdout',)) |
266 self.stdout = open(redirect, mode) | 299 self.stdout = tempfile.TemporaryFile() |
267 else: | 300 if statement.output == '>>': |
268 statement = '%s %s' % (statement, self.fileimport(statement=statement, source=redirect)) | 301 self.stdout.write(getPasteBuffer()) |
269 if isinstance(redirect, subprocess.Popen): | 302 stop = cmd.Cmd.onecmd(self, statement.statement) |
270 stop = self.onecmd(statement) | 303 try: |
271 else: | 304 if statement.command not in self.excludeFromHistory: |
272 stop = cmd.Cmd.onecmd(self, statement) | 305 self.history.append(statement.fullStatement) |
273 try: | |
274 if command not in self.excludeFromHistory: | |
275 self.history.append(originalStatement) | |
276 finally: | 306 finally: |
277 if statekeeper: | 307 if statekeeper: |
278 if redirect == self._TO_PASTE_BUFFER: | 308 if statement.output and not statement.outputTo: |
279 self.stdout.seek(0) | 309 self.stdout.seek(0) |
280 writeToPasteBuffer(self.stdout.read()) | 310 writeToPasteBuffer(self.stdout.read()) |
281 elif isinstance(redirect, subprocess.Popen): | 311 elif statement.pipe: |
282 for result in redirect.communicate(): | 312 for result in redirect.communicate(): |
283 statekeeper.stdout.write(result or '') | 313 statekeeper.stdout.write(result or '') |
284 self.stdout.close() | 314 self.stdout.close() |
285 statekeeper.restore() | 315 statekeeper.restore() |
286 | 316 |
287 return stop | 317 return stop |
288 | |
289 statementEndPattern = re.compile(r'[%s]\s*$' % terminators) | |
290 def statementHasEnded(self, lines): | |
291 return bool(self.statementEndPattern.search(lines)) \ | |
292 or lines[-3:] == 'EOF' \ | |
293 or self.parseRedirectors(lines)[1] | |
294 | |
295 def finishStatement(self, firstline): | |
296 statement = firstline | |
297 while not self.statementHasEnded(statement): | |
298 inp = self.pseudo_raw_input(self.continuationPrompt) | |
299 statement = '%s\n%s' % (statement, inp) | |
300 return statement | |
301 # assembling a list of lines and joining them at the end would be faster, | |
302 # but statementHasEnded needs a string arg; anyway, we're getting | |
303 # user input and users are slow. | |
304 | 318 |
305 def pseudo_raw_input(self, prompt): | 319 def pseudo_raw_input(self, prompt): |
306 """copied from cmd's cmdloop; like raw_input, but accounts for changed stdin, stdout""" | 320 """copied from cmd's cmdloop; like raw_input, but accounts for changed stdin, stdout""" |
307 | 321 |
308 if self.use_rawinput: | 322 if self.use_rawinput: |
370 def clean(self, s): | 384 def clean(self, s): |
371 """cleans up a string""" | 385 """cleans up a string""" |
372 if self.caseInsensitive: | 386 if self.caseInsensitive: |
373 return s.strip().lower() | 387 return s.strip().lower() |
374 return s.strip() | 388 return s.strip() |
375 | |
376 def parseline(self, line): | |
377 """Parse the line into a command name and a string containing | |
378 the arguments. Returns a tuple containing (command, args, line). | |
379 'command' and 'args' may be None if the line couldn't be parsed. | |
380 """ | |
381 line = line.strip() | |
382 if not line: | |
383 return None, None, line | |
384 shortcut = self.shortcuts.get(line[0]) | |
385 if shortcut and hasattr(self, 'do_%s' % shortcut): | |
386 line = '%s %s' % (shortcut, line[1:]) | |
387 i, n = 0, len(line) | |
388 while i < n and line[i] in self.identchars: i = i+1 | |
389 cmd, arg = line[:i], line[i:].strip().strip(self.terminators) | |
390 return cmd, arg, line | |
391 | 389 |
392 def showParam(self, param): | 390 def showParam(self, param): |
393 param = self.clean(param) | 391 param = self.clean(param) |
394 if param in self.settable: | 392 if param in self.settable: |
395 val = getattr(self, param) | 393 val = getattr(self, param) |
637 for attrib in self.attribs: | 635 for attrib in self.attribs: |
638 setattr(self, attrib, getattr(self.obj, attrib)) | 636 setattr(self, attrib, getattr(self.obj, attrib)) |
639 def restore(self): | 637 def restore(self): |
640 for attrib in self.attribs: | 638 for attrib in self.attribs: |
641 setattr(self.obj, attrib, getattr(self, attrib)) | 639 setattr(self.obj, attrib, getattr(self, attrib)) |
640 | |
641 if __name__ == '__main__': | |
642 doctest.testmod() |