comparison cmd2.py @ 157:10e917acf787

wow, parsing is broken
author catherine@dellzilla
date Mon, 24 Nov 2008 18:53:25 -0500
parents 7e5a57df88aa
children 87d3f3203b96
comparison
equal deleted inserted replaced
156:7e5a57df88aa 157:10e917acf787
49 for opt in option_list: 49 for opt in option_list:
50 optionParser.add_option(opt) 50 optionParser.add_option(opt)
51 optionParser.set_usage("%s [options] arg" % func.__name__.strip('do_')) 51 optionParser.set_usage("%s [options] arg" % func.__name__.strip('do_'))
52 def newFunc(instance, arg): 52 def newFunc(instance, arg):
53 try: 53 try:
54 opts, arg = optionParser.parse_args(arg.split()) 54 opts, newArgs = optionParser.parse_args(arg.split())
55 arg = ' '.join(arg) 55 arg.parsed['args'] = arg[arg.find(newArgs[0]):]
56 except (optparse.OptionValueError, optparse.BadOptionError, 56 except (optparse.OptionValueError, optparse.BadOptionError,
57 optparse.OptionError, optparse.AmbiguousOptionError, 57 optparse.OptionError, optparse.AmbiguousOptionError,
58 optparse.OptionConflictError), e: 58 optparse.OptionConflictError), e:
59 print e 59 print e
60 optionParser.print_help() 60 optionParser.print_help()
165 return 0 165 return 0
166 start, end = self.redirectInPattern.scanString(statement.fullStatement).next()[1:] 166 start, end = self.redirectInPattern.scanString(statement.fullStatement).next()[1:]
167 return self.onecmd('%s%s%s' % (statement.fullStatement[:start], 167 return self.onecmd('%s%s%s' % (statement.fullStatement[:start],
168 newinput, statement.fullStatement[end:])) 168 newinput, statement.fullStatement[end:]))
169 169
170 class ParsedString(str):
171 pass
172
170 class Cmd(cmd.Cmd): 173 class Cmd(cmd.Cmd):
171 echo = False 174 echo = False
172 caseInsensitive = True 175 caseInsensitive = True
173 continuationPrompt = '> ' 176 continuationPrompt = '> '
177 legalChars = '!#$%.:?@_' # make sure your terminators are not in here!
174 shortcuts = {'?': 'help', '!': 'shell', '@': 'load' } 178 shortcuts = {'?': 'help', '!': 'shell', '@': 'load' }
175 excludeFromHistory = '''run r list l history hi ed edit li eof'''.split() 179 excludeFromHistory = '''run r list l history hi ed edit li eof'''.split()
176 noSpecialParse = 'set ed edit exit'.split() 180 noSpecialParse = 'set ed edit exit'.split()
177 defaultExtension = 'txt' 181 defaultExtension = 'txt'
178 defaultFileName = 'command.txt' 182 defaultFileName = 'command.txt'
227 ''' 231 '''
228 >>> c = Cmd() 232 >>> c = Cmd()
229 >>> c.multilineCommands = ['multiline'] 233 >>> c.multilineCommands = ['multiline']
230 >>> c.caseInsensitive = True 234 >>> c.caseInsensitive = True
231 >>> c._init_parser() 235 >>> c._init_parser()
236 >>> print c.parser.parseString('termbare;').dump()
237 >>> print c.parser.parseString('termbare; suffx').dump()
232 >>> print c.parser.parseString('barecommand').dump() 238 >>> print c.parser.parseString('barecommand').dump()
233 ['barecommand', ''] 239 ['barecommand', '']
234 - args:
235 - command: barecommand 240 - command: barecommand
236 - statement: ['barecommand', ''] 241 - statement: ['barecommand', '']
237 - args:
238 - command: barecommand 242 - command: barecommand
239 >>> print c.parser.parseString('COMmand with args').dump() 243 >>> print c.parser.parseString('COMmand with args').dump()
240 ['command', ' with args'] 244 ['command', 'with args']
241 - args: with args 245 - args: with args
242 - command: command 246 - command: command
243 - statement: ['command', ' with args'] 247 - statement: ['command', 'with args']
244 - args: with args 248 - args: with args
245 - command: command 249 - command: command
246 >>> print c.parser.parseString('command with args and terminator; and suffix').dump() 250 >>> print c.parser.parseString('command with args and terminator; and suffix').dump()
247 ['command', 'with args and terminator', ';', ' and suffix'] 251 ['command', 'with args and terminator', ';', ' and suffix']
248 - args: with args and terminator 252 - args: with args and terminator
249 - command: command 253 - command: command
252 - command: command 256 - command: command
253 - terminator: ; 257 - terminator: ;
254 - suffix: and suffix 258 - suffix: and suffix
255 - terminator: ; 259 - terminator: ;
256 >>> print c.parser.parseString('simple | piped').dump() 260 >>> print c.parser.parseString('simple | piped').dump()
257 ['simple', '', '|', 'piped'] 261 ['simple', '', '|', ' piped']
258 - args:
259 - command: simple 262 - command: simple
260 - pipeTo: piped 263 - pipeTo: piped
261 - statement: ['simple', ''] 264 - statement: ['simple', '']
262 - args:
263 - command: simple 265 - command: simple
264 >>> print c.parser.parseString('command with args, terminator;sufx | piped').dump() 266 >>> print c.parser.parseString('command with args, terminator;sufx | piped').dump()
265 ['command', 'with args, terminator', ';', 'sufx', '|', 'piped'] 267 ['command', 'with args, terminator', ';', 'sufx', '|', ' piped']
266 - args: with args, terminator 268 - args: with args, terminator
267 - command: command 269 - command: command
268 - pipeTo: piped 270 - pipeTo: piped
269 - statement: ['command', 'with args, terminator', ';'] 271 - statement: ['command', 'with args, terminator', ';']
270 - args: with args, terminator 272 - args: with args, terminator
271 - command: command 273 - command: command
272 - terminator: ; 274 - terminator: ;
273 - suffix: sufx 275 - suffix: sufx
274 - terminator: ; 276 - terminator: ;
275 >>> print c.parser.parseString('output into > afile.txt').dump() 277 >>> print c.parser.parseString('output into > afile.txt').dump()
276 ['output', ' into', '>', 'afile.txt'] 278 ['output', 'into', '>', 'afile.txt']
277 - args: into
278 - command: output
279 - output: >
280 - outputTo: afile.txt
281 - statement: ['output', ' into']
282 - args: into
283 - command: output
284 >>> print c.parser.parseString('output into;sufx | pipethrume plz > afile.txt').dump()
285 ['output', 'into', ';', 'sufx', '|', 'pipethrume plz', '>', 'afile.txt']
286 - args: into 279 - args: into
287 - command: output 280 - command: output
288 - output: > 281 - output: >
289 - outputTo: afile.txt 282 - outputTo: afile.txt
290 - pipeTo: pipethrume plz 283 - statement: ['output', 'into']
284 - args: into
285 - command: output
286 >>> print c.parser.parseString('output into;sufx | pipethrume plz > afile.txt').dump()
287 ['output', 'into', ';', 'sufx', '|', ' pipethrume plz', '>', 'afile.txt']
288 - args: into
289 - command: output
290 - output: >
291 - outputTo: afile.txt
292 - pipeTo: pipethrume plz
291 - statement: ['output', 'into', ';'] 293 - statement: ['output', 'into', ';']
292 - args: into 294 - args: into
293 - command: output 295 - command: output
294 - terminator: ; 296 - terminator: ;
295 - suffix: sufx 297 - suffix: sufx
296 - terminator: ; 298 - terminator: ;
297 >>> print c.parser.parseString('output to paste buffer >> ').dump() 299 >>> print c.parser.parseString('output to paste buffer >> ').dump()
298 ['output', ' to paste buffer', '>>', ''] 300 ['output', 'to paste buffer', '>>', '']
299 - args: to paste buffer 301 - args: to paste buffer
300 - command: output 302 - command: output
301 - output: >> 303 - output: >>
302 - statement: ['output', ' to paste buffer'] 304 - statement: ['output', 'to paste buffer']
303 - args: to paste buffer 305 - args: to paste buffer
304 - command: output 306 - command: output
305 >>> print c.parser.parseString('ignore the /* commented | > */ stuff;').dump() 307 >>> print c.parser.parseString('ignore the /* commented | > */ stuff;').dump()
306 ['ignore', 'the /* commented | > */ stuff', ';', ''] 308 ['ignore', 'the /* commented | > */ stuff', ';', '']
307 - args: the /* commented | > */ stuff 309 - args: the /* commented | > */ stuff
308 - command: ignore 310 - command: ignore
351 ''' 353 '''
352 outputParser = pyparsing.oneOf(['>>','>'])('output') 354 outputParser = pyparsing.oneOf(['>>','>'])('output')
353 terminatorParser = pyparsing.oneOf(self.terminators)('terminator') 355 terminatorParser = pyparsing.oneOf(self.terminators)('terminator')
354 stringEnd = pyparsing.stringEnd ^ '\nEOF' 356 stringEnd = pyparsing.stringEnd ^ '\nEOF'
355 multilineCommand = pyparsing.Or([pyparsing.Keyword(c, caseless=self.caseInsensitive) for c in self.multilineCommands])('multilineCommand') 357 multilineCommand = pyparsing.Or([pyparsing.Keyword(c, caseless=self.caseInsensitive) for c in self.multilineCommands])('multilineCommand')
356 oneLineCommand = pyparsing.Word(pyparsing.printables)('command') 358 oneLineCommand = pyparsing.Word(self.legalChars)('command')
357 afterElements = \ 359 afterElements = \
358 pyparsing.Optional('|' + pyparsing.SkipTo(outputParser ^ stringEnd)('pipeTo')) + \ 360 pyparsing.Optional('|' + pyparsing.SkipTo(outputParser ^ stringEnd)('pipeTo')) + \
359 pyparsing.Optional(outputParser + pyparsing.SkipTo(stringEnd).setParseAction(lambda x: x[0].strip())('outputTo')) 361 pyparsing.Optional(outputParser + pyparsing.SkipTo(stringEnd).setParseAction(lambda x: x[0].strip())('outputTo'))
360 if self.caseInsensitive: 362 if self.caseInsensitive:
361 multilineCommand.setParseAction(lambda x: x[0].lower()) 363 multilineCommand.setParseAction(lambda x: x[0].lower())
362 oneLineCommand.setParseAction(lambda x: x[0].lower()) 364 oneLineCommand.setParseAction(lambda x: x[0].lower())
363 self.parser = ( 365 self.parser = (
364 (((multilineCommand ^ oneLineCommand) + pyparsing.SkipTo(terminatorParser)('args') + terminatorParser)('statement') + 366 (((multilineCommand ^ oneLineCommand) + pyparsing.SkipTo(terminatorParser).setParseAction(lambda x: x[0].strip())('args') + terminatorParser)('statement') +
365 pyparsing.SkipTo(outputParser ^ '|' ^ stringEnd)('suffix') + afterElements) 367 pyparsing.SkipTo(outputParser ^ '|' ^ stringEnd)('suffix') + afterElements)
366 ^ 368 ^
367 multilineCommand + pyparsing.SkipTo(pyparsing.stringEnd) 369 multilineCommand + pyparsing.SkipTo(pyparsing.stringEnd)
368 ^ 370 ^
369 ((oneLineCommand + pyparsing.SkipTo(terminatorParser ^ stringEnd ^ '|' ^ outputParser)('args'))('statement') + 371 ((oneLineCommand + pyparsing.SkipTo(terminatorParser ^ stringEnd ^ '|' ^ outputParser).setParseAction(lambda x:x[0].strip())('args'))('statement') +
370 afterElements) 372 pyparsing.Optional(terminatorParser) + afterElements)
371 ) 373 )
372 self.commentGrammars.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).setParseAction(lambda x: '') 374 self.commentGrammars.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).setParseAction(lambda x: '')
373 self.commentInProgress.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).ignore(pyparsing.cStyleComment) 375 self.commentInProgress.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).ignore(pyparsing.cStyleComment)
374 self.parser.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).ignore(self.commentGrammars).ignore(self.commentInProgress) 376 self.parser.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).ignore(self.commentGrammars).ignore(self.commentInProgress)
375 377
376 inputMark = pyparsing.Literal('<') 378 inputMark = pyparsing.Literal('<')
377 inputMark.setParseAction(lambda x: '') 379 inputMark.setParseAction(lambda x: '')
378 inputFrom = pyparsing.Word(pyparsing.printables)('inputFrom') 380 inputFrom = pyparsing.Word(self.legalChars)('inputFrom')
379 inputFrom.setParseAction(lambda x: (x and open(x[0]).read()) or getPasteBuffer()) 381 inputFrom.setParseAction(lambda x: (x and open(x[0]).read()) or getPasteBuffer())
380 self.inputParser = inputMark + pyparsing.Optional(inputFrom) 382 self.inputParser = inputMark + pyparsing.Optional(inputFrom)
381 self.inputParser.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).ignore(self.commentGrammars).ignore(self.commentInProgress) 383 self.inputParser.ignore(pyparsing.sglQuotedString).ignore(pyparsing.dblQuotedString).ignore(self.commentGrammars).ignore(self.commentInProgress)
382 384
383 def parsed(self, raw): 385 def parsed(self, raw, useTerminatorFrom=None):
384 s = self.inputParser.transformString(raw) 386 s = self.inputParser.transformString(raw)
385 for (shortcut, expansion) in self.shortcuts.items(): 387 for (shortcut, expansion) in self.shortcuts.items():
386 if s.startswith(shortcut): 388 if s.startswith(shortcut):
387 s = s.replace(shortcut, expansion + ' ', 1) 389 s = s.replace(shortcut, expansion + ' ', 1)
388 break 390 break
389 result = self.parser.parseString(s) 391 result = self.parser.parseString(s)
390 result['command'] = result.multilineCommand or result.command 392 result['command'] = result.multilineCommand or result.command
391 result['statement'] = ' '.join(result.statement) 393 #result['statement'] = ' '.join(result.statement)
392 result['raw'] = raw 394 result['raw'] = raw
393 result['clean'] = self.commentGrammars.transformString(result.statement) 395 #result['clean'] = self.commentGrammars.transformString(result.statement)
394 result['cleanArgs'] = self.commentGrammars.transformString(result.args) 396 result['clean'] = self.commentGrammars.transformString(result.args)
395 return result 397 result['expanded'] = s
396 398 if useTerminatorFrom:
397 def extractCommand(self, statement): 399 result['terminator'] = useTerminatorFrom.parsed.terminator
398 try: 400 result['suffix'] = useTerminatorFrom.parsed.suffix
399 (command, args) = statement.split(None,1) 401 p = ParsedString(result.args)
400 except ValueError: 402 p.parsed = result
401 (command, args) = statement, '' 403 return p
402 if self.caseInsensitive: 404
403 command = command.lower()
404 return command, args
405
406 def onecmd(self, line, assumeComplete=False): 405 def onecmd(self, line, assumeComplete=False):
407 """Interpret the argument as though it had been typed in response 406 """Interpret the argument as though it had been typed in response
408 to the prompt. 407 to the prompt.
409 408
410 This may be overridden, but should not normally need to be; 409 This may be overridden, but should not normally need to be;
411 see the precmd() and postcmd() methods for useful execution hooks. 410 see the precmd() and postcmd() methods for useful execution hooks.
412 The return value is a flag indicating whether interpretation of 411 The return value is a flag indicating whether interpretation of
413 commands by the interpreter should stop. 412 commands by the interpreter should stop.
413
414 This (`cmd2`) version of `onecmd` already override's `cmd`'s `onecmd`.
414 415
415 """ 416 """
416 line = line.strip() 417 line = line.strip()
417 if not line: 418 if not line:
418 return 419 return self.emptyline()
419 if not pyparsing.Or(self.commentGrammars).setParseAction(lambda x: '').transformString(line): 420 if not pyparsing.Or(self.commentGrammars).setParseAction(lambda x: '').transformString(line):
420 return 421 return 0
421 try: 422 try:
422 statement = self.parsed(line) 423 statement = self.parsed(line)
423 if assumeComplete: 424 if assumeComplete:
424 if statement.multilineCommand and not statement.terminator: 425 if statement.parsed.multilineCommand and not statement.parsed.terminator:
425 statement = self.parsed(statement.raw + self.terminators[0]) 426 statement.parsed.terminator = self.terminators[0]
426 else: 427 else:
427 while statement.multilineCommand and not statement.terminator: 428 while statement.parsed.multilineCommand and not statement.parsed.terminator:
428 statement = self.parsed('%s\n%s' % (statement.raw, 429 statement = self.parsed('%s\n%s' % (statement.parsed.raw,
429 self.pseudo_raw_input(self.continuationPrompt))) 430 self.pseudo_raw_input(self.continuationPrompt)))
430 except Exception, e: 431 except Exception, e:
431 print e 432 print e
432 return 0 433 return 0
433 434
434 statekeeper = None 435 statekeeper = None
435 stop = 0 436 stop = 0
436 437
437 if statement.pipeTo: 438 if statement.parsed.pipeTo:
438 redirect = subprocess.Popen(statement.pipeTo, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 439 redirect = subprocess.Popen(statement.parsed.pipeTo, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
439 statekeeper = Statekeeper(self, ('stdout',)) 440 statekeeper = Statekeeper(self, ('stdout',))
440 self.stdout = redirect.stdin 441 self.stdout = redirect.stdin
441 elif statement.output: 442 elif statement.parsed.output:
442 statekeeper = Statekeeper(self, ('stdout',)) 443 statekeeper = Statekeeper(self, ('stdout',))
443 if statement.outputTo: 444 if statement.parsed.outputTo:
444 mode = 'w' 445 mode = 'w'
445 if statement.output == '>>': 446 if statement.parsed.output == '>>':
446 mode = 'a' 447 mode = 'a'
447 try: 448 try:
448 self.stdout = open(statement.outputTo, mode) 449 self.stdout = open(statement.parsed.outputTo, mode)
449 except OSError, e: 450 except OSError, e:
450 print e 451 print e
451 return 0 452 return 0
452 else: 453 else:
453 statekeeper = Statekeeper(self, ('stdout',)) 454 statekeeper = Statekeeper(self, ('stdout',))
454 self.stdout = tempfile.TemporaryFile() 455 self.stdout = tempfile.TemporaryFile()
455 if statement.output == '>>': 456 if statement.parsed.output == '>>':
456 self.stdout.write(getPasteBuffer()) 457 self.stdout.write(getPasteBuffer())
457 try: 458 try:
458 stop = cmd.Cmd.onecmd(self, statement.clean) 459 # "heart" of the command, replace's cmd's onecmd()
460 self.lastcmd = statement.parsed.expanded
461 try:
462 func = getattr(self, 'do_' + statement.parsed.command)
463 except AttributeError:
464 return self.default(statement)
465 stop = func(statement)
459 except Exception, e: 466 except Exception, e:
460 print e 467 print e
461 try: 468 try:
462 if statement.command not in self.excludeFromHistory: 469 if statement.parsed.command not in self.excludeFromHistory:
463 self.history.append(statement.raw) 470 self.history.append(statement.parsed.raw)
464 finally: 471 finally:
465 if statekeeper: 472 if statekeeper:
466 if statement.output and not statement.outputTo: 473 if statement.parsed.output and not statement.parsed.outputTo:
467 self.stdout.seek(0) 474 self.stdout.seek(0)
468 try: 475 try:
469 writeToPasteBuffer(self.stdout.read()) 476 writeToPasteBuffer(self.stdout.read())
470 except Exception, e: 477 except Exception, e:
471 print str(e) 478 print str(e)
472 elif statement.pipeTo: 479 elif statement.parsed.pipeTo:
473 for result in redirect.communicate(): 480 for result in redirect.communicate():
474 statekeeper.stdout.write(result or '') 481 statekeeper.stdout.write(result or '')
475 self.stdout.close() 482 self.stdout.close()
476 statekeeper.restore() 483 statekeeper.restore()
477 484
652 os.system('%s %s' % (self.editor, filename)) 659 os.system('%s %s' % (self.editor, filename))
653 self.do__load(filename) 660 self.do__load(filename)
654 do_edit = do_ed 661 do_edit = do_ed
655 662
656 saveparser = (pyparsing.Optional(pyparsing.Word(pyparsing.nums)^'*')("idx") + 663 saveparser = (pyparsing.Optional(pyparsing.Word(pyparsing.nums)^'*')("idx") +
657 pyparsing.Optional(pyparsing.Word(pyparsing.printables))("fname") + 664 pyparsing.Optional(pyparsing.Word(legalChars + '/\\'))("fname") +
658 pyparsing.stringEnd) 665 pyparsing.stringEnd)
659 def do_save(self, arg): 666 def do_save(self, arg):
660 """`save [N] [filename.ext]` 667 """`save [N] [filename.ext]`
661 Saves command from history to file. 668 Saves command from history to file.
662 N => Number of command (from history), or `*`; 669 N => Number of command (from history), or `*`;
903 def tearDown(self): 910 def tearDown(self):
904 if self.CmdApp: 911 if self.CmdApp:
905 self.outputTrap.tearDown() 912 self.outputTrap.tearDown()
906 913
907 if __name__ == '__main__': 914 if __name__ == '__main__':
908 doctest.testmod(optionflags = doctest.NORMALIZE_WHITESPACE) 915 #doctest.testmod(optionflags = doctest.NORMALIZE_WHITESPACE)
916 c = Cmd()