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()