comparison cmd2.py @ 87:683ed678b636

restoring IOError to xclip trap; backing out weird hg problem
author catherine@Elli.myhome.westell.com
date Mon, 30 Jun 2008 22:05:01 -0400
parents f844b6c78192
children
comparison
equal deleted inserted replaced
57:86bb50447e99 87:683ed678b636
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
117 return xclipproc.stdout.read() 117 return xclipproc.stdout.read()
118 def writeToPasteBuffer(txt): 118 def writeToPasteBuffer(txt):
119 xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 119 xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
120 xclipproc.stdin.write(txt) 120 xclipproc.stdin.write(txt)
121 xclipproc.stdin.close() 121 xclipproc.stdin.close()
122 # but we want it in both the "primary" and "mouse" clipboards
123 xclipproc = subprocess.Popen('xclip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
124 xclipproc.stdin.write(txt)
125 xclipproc.stdin.close()
122 else: 126 else:
123 def getPasteBuffer(): 127 def getPasteBuffer():
124 raise OSError, pastebufferr % ('xclip', 'On Debian/Ubuntu, install with "sudo apt-get install xclip"') 128 raise OSError, pastebufferr % ('xclip', 'On Debian/Ubuntu, install with "sudo apt-get install xclip"')
125 setPasteBuffer = getPasteBuffer 129 setPasteBuffer = getPasteBuffer
126 130
131 pyparsing.ParserElement.setDefaultWhitespaceChars(' \t') # see http://pyparsing.wikispaces.com/message/view/home/1352689
132
133 class Arguments(str):
134 def __new__(cls, s, parent):
135 result = str.__new__(cls, s)
136 result.parent = parent
137 return result
138
139 class UserCommand(str):
140 def __new__(cls, s, app):
141 return str.__new__(cls, s)
142 def __init__(self, s, app):
143 self.terminator = None
144 self.terminator_suffix = None
145 s = s.strip()
146 shortcut = app.shortcuts.get(s[0])
147 if shortcut and hasattr(app, 'do_%s' % shortcut):
148 s = '%s %s' % (shortcut, s[1:])
149 self.searchable = self.asEntered = s
150 self.app = app
151 self.terminator_pattern = self.punctuationPattern(self.app.terminators)
152 self.output_destination_pattern = self.punctuationPattern(['>>', '>'])
153 self.input_source_pattern = self.punctuationPattern(['<'])
154 self.pipe_destination_pattern = self.punctuationPattern(['|'])
155 def punctuationPattern(self, punctuators):
156 processed = punctuators[:]
157 if not hasattr(processed[0], 'parseString'):
158 processed[0] = pyparsing.Literal(processed[0])
159 processed = reduce(lambda x, y: x ^ y, processed)
160 processed.ignore(pyparsing.sglQuotedString)
161 processed.ignore(pyparsing.dblQuotedString)
162 pattern = pyparsing.SkipTo(processed) + processed + pyparsing.restOfLine
163 return pattern
164 def parse(self):
165 termination = self.terminator_pattern.searchString(self.asEntered)
166 if termination:
167 self.terminator = termination[0][1]
168 if len(termination[0]) > 3:
169 self.terminator_suffix = termination[0][2]
170 punctuators = ['|','>','>>','<']
171 punctuators.extend(self.app.terminators)
172 punctuated = self.punctuationPattern(punctuators).searchString(self.asEntered)
173 if punctuated:
174 self.executable, self.searchable = punctuated[0][0], self.asEntered[len(punctuated[0][0]):]
175 else:
176 self.executable, self.searchable = self.asEntered, ''
177 self.executable = self.executable.strip()
178 try:
179 self.cmd, self.arg = (pyparsing.Word(self.app.identchars)+pyparsing.restOfLine).parseString(self.executable)
180 self.arg = Arguments(self.arg, self)
181 except pyparsing.ParseException:
182 self.cmd, self.arg = None, None
183
184 def complete(self):
185 while not self.terminator_pattern.searchString(self.asEntered):
186 inp = self.app.pseudo_raw_input(self.app.continuationPrompt)
187 self.asEntered = '%s\n%s' % (self.asEntered, inp)
188 def redirectedInput(self):
189 inputFrom = self.input_source_pattern.searchString(self.searchable)
190 if inputFrom:
191 if inputFrom[0][-1].strip():
192 input = self.app.fileimport(source=inputFrom[0][-1].strip())
193 else:
194 input = getPasteBuffer()
195 if self.terminator:
196 self.executable = '%s %s' % (self.executable, input)
197 else:
198 self.executable = '%s %s %s' % (self.executable, inputFrom[0][0], input)
199 def pipeDestination(self):
200 pipeTo = self.pipe_destination_pattern.searchString(self.searchable)
201 return (pipeTo and pipeTo[0][-1]) or None
202 def redirectedOutput(self):
203 outputTo = self.output_destination_pattern.searchString(self.searchable)
204 if outputTo:
205 dest = outputTo[0][-1].strip()
206 if outputTo[0][1] == '>>':
207 mode = 'a'
208 else:
209 mode = 'w'
210 return dest, mode
211 return None, None
212
127 class Cmd(cmd.Cmd): 213 class Cmd(cmd.Cmd):
128 caseInsensitive = True 214 caseInsensitive = True
129 multilineCommands = [] 215 multilineCommands = [] # commands that need a terminator to be finished
216 terminators = [';', pyparsing.LineEnd() + pyparsing.LineEnd()]
217 terminatorKeepingCommands = [] # commands that expect to process their own terminators (else it will be stripped during parse)
130 continuationPrompt = '> ' 218 continuationPrompt = '> '
131 shortcuts = {'?': 'help', '!': 'shell', '@': 'load'} 219 shortcuts = {'?': 'help', '!': 'shell', '@': 'load'}
132 excludeFromHistory = '''run r list l history hi ed edit li eof'''.split() 220 excludeFromHistory = '''run r list l history hi ed edit li eof'''.split()
133 defaultExtension = 'txt' 221 defaultExtension = 'txt'
134 defaultFileName = 'command.txt' 222 defaultFileName = 'command.txt'
141 for editor in ['gedit', 'kate', 'vim', 'emacs', 'nano', 'pico']: 229 for editor in ['gedit', 'kate', 'vim', 'emacs', 'nano', 'pico']:
142 if not os.system('which %s' % (editor)): 230 if not os.system('which %s' % (editor)):
143 break 231 break
144 232
145 settable = ['prompt', 'continuationPrompt', 'defaultFileName', 'editor', 'caseInsensitive'] 233 settable = ['prompt', 'continuationPrompt', 'defaultFileName', 'editor', 'caseInsensitive']
146 terminators = ';\n'
147 _TO_PASTE_BUFFER = 1 234 _TO_PASTE_BUFFER = 1
148 def do_cmdenvironment(self, args): 235 def do_cmdenvironment(self, args):
149 self.stdout.write(""" 236 self.stdout.write("""
150 Commands are %(casesensitive)scase-sensitive. 237 Commands are %(casesensitive)scase-sensitive.
151 Commands may be terminated with: %(terminators)s 238 Commands may be terminated with: %(terminators)s
152 Settable parameters: %(settable)s 239 Settable parameters: %(settable)s
153 """ % 240 """ %
154 { 'casesensitive': ('not ' and self.caseInsensitive) or '', 241 { 'casesensitive': (self.caseInsensitive or '') and 'not ',
155 'terminators': ' '.join(self.terminators), 242 'terminators': str(self.terminators),
156 'settable': ' '.join(self.settable) 243 'settable': ' '.join(self.settable)
157 }) 244 })
158 245
159 def do_help(self, arg): 246 def do_help(self, arg):
160 cmd.Cmd.do_help(self, arg) 247 cmd.Cmd.do_help(self, arg)
172 def do_shortcuts(self, args): 259 def do_shortcuts(self, args):
173 """Lists single-key shortcuts available.""" 260 """Lists single-key shortcuts available."""
174 result = "\n".join('%s: %s' % (sc[0], sc[1]) for sc in self.shortcuts.items()) 261 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)) 262 self.stdout.write("Single-key shortcuts for other commands:\n%s\n" % (result))
176 263
177 notAPipe = pyparsing.SkipTo('|') 264 def strip_terminators(self, txt):
178 notAPipe.ignore(pyparsing.sglQuotedString) 265 termination = self.commmand_terminator_finder(txt)
179 notAPipe.ignore(pyparsing.dblQuotedString) 266 if termination:
180 pipeFinder = notAPipe + '|' + pyparsing.SkipTo(pyparsing.StringEnd()) 267 txt = termination[0]
181 def parsePipe(self, statement, mustBeTerminated): 268 return txt
182 try:
183 statement, pipe, destination = self.pipeFinder.parseString(statement)
184 redirect = subprocess.Popen(destination, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
185 return statement, redirect
186 except pyparsing.ParseException:
187 return statement, None
188
189 legalFileName = re.compile(r'''^[^"'\s]+$''')
190 def parseRedirector(self, statement, symbol, mustBeTerminated=False):
191 # pipeFinder.scanString(statement)
192 parts = statement.split(symbol)
193 if (len(parts) < 2):
194 return statement, None
195 if mustBeTerminated and (not self.statementEndPattern.search(parts[-2])):
196 return statement, None
197 (newStatement, redirect) = (symbol.join(parts[:-1]), parts[-1].strip())
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 269
205 def extractCommand(self, statement): 270 def extractCommand(self, statement):
206 try: 271 try:
207 (command, args) = statement.split(None,1) 272 (command, args) = statement.split(None,1)
208 except ValueError: 273 except ValueError:
209 (command, args) = statement, '' 274 (command, args) = statement, ''
210 if self.caseInsensitive: 275 if self.caseInsensitive:
211 command = command.lower() 276 command = command.lower()
212 return command, args 277 return command, args
213 278
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): 279 def onecmd(self, line, assumeComplete=False):
231 """Interpret the argument as though it had been typed in response 280 """Interpret the argument as though it had been typed in response
232 to the prompt. 281 to the prompt.
233 282
234 This may be overridden, but should not normally need to be; 283 This may be overridden, but should not normally need to be;
235 see the precmd() and postcmd() methods for useful execution hooks. 284 see the precmd() and postcmd() methods for useful execution hooks.
236 The return value is a flag indicating whether interpretation of 285 The return value is a flag indicating whether interpretation of
237 commands by the interpreter should stop. 286 commands by the interpreter should stop.
238 287
239 """ 288 """
289 statekeeper = None
290 stop = 0
240 command, args = self.extractCommand(line) 291 command, args = self.extractCommand(line)
241 statement = originalStatement = ' '.join([command, args]) 292 originalStatement = ' '.join([command, args])
293 statement = UserCommand(originalStatement, self)
242 if (not assumeComplete) and (command in self.multilineCommands): 294 if (not assumeComplete) and (command in self.multilineCommands):
243 statement = self.finishStatement(statement) 295 statement.complete()
244 statekeeper = None 296 statement.parse()
245 stop = 0 297 statement.redirectedInput()
246 statement, redirect, mode = self.parseRedirectors(statement) 298 pipeTo = statement.pipeDestination()
247 if isinstance(redirect, subprocess.Popen): 299 if pipeTo:
248 statekeeper = Statekeeper(self, ('stdout',)) 300 statekeeper = Statekeeper(self, ('stdout',))
249 self.stdout = redirect.stdin 301 dest = subprocess.Popen(pipeTo, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
250 elif redirect == self._TO_PASTE_BUFFER: 302 self.stdout = dest.stdin
251 try: 303 else: # can't pipe output AND send it to a file
252 clipcontents = getPasteBuffer() 304 outputTo, outputMode = statement.redirectedOutput()
253 if mode in ('w', 'a'): 305 if outputMode:
254 statekeeper = Statekeeper(self, ('stdout',)) 306 statekeeper = Statekeeper(self, ('stdout',))
307 if outputTo:
308 self.stdout = open(outputTo, outputMode)
309 else:
255 self.stdout = tempfile.TemporaryFile() 310 self.stdout = tempfile.TemporaryFile()
256 if mode == 'a': 311 if outputMode == 'a':
257 self.stdout.write(clipcontents) 312 self.stdout.write(getPasteBuffer())
258 else: 313
259 statement = '%s %s' % (statement, clipcontents) 314 stop = cmd.Cmd.onecmd(self, statement)
260 except OSError, e:
261 print e
262 return 0
263 elif redirect:
264 if mode in ('w','a'):
265 statekeeper = Statekeeper(self, ('stdout',))
266 self.stdout = open(redirect, mode)
267 else:
268 statement = '%s %s' % (statement, self.fileimport(statement=statement, source=redirect))
269 if isinstance(redirect, subprocess.Popen):
270 stop = self.onecmd(statement)
271 else:
272 stop = cmd.Cmd.onecmd(self, statement)
273 try: 315 try:
274 if command not in self.excludeFromHistory: 316 if command not in self.excludeFromHistory:
275 self.history.append(originalStatement) 317 self.history.append(originalStatement)
276 finally: 318 finally:
277 if statekeeper: 319 if statekeeper:
278 if redirect == self._TO_PASTE_BUFFER: 320 if pipeTo:
321 for result in dest.communicate():
322 statekeeper.stdout.write(result or '')
323 elif outputMode and not outputTo:
279 self.stdout.seek(0) 324 self.stdout.seek(0)
280 writeToPasteBuffer(self.stdout.read()) 325 writeToPasteBuffer(self.stdout.read())
281 elif isinstance(redirect, subprocess.Popen):
282 for result in redirect.communicate():
283 statekeeper.stdout.write(result or '')
284 self.stdout.close() 326 self.stdout.close()
285 statekeeper.restore() 327 statekeeper.restore()
286 328
287 return stop 329 return stop
288 330
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
305 def pseudo_raw_input(self, prompt): 331 def pseudo_raw_input(self, prompt):
306 """copied from cmd's cmdloop; like raw_input, but accounts for changed stdin, stdout""" 332 """copied from cmd's cmdloop; like raw_input, but accounts for changed stdin, stdout"""
307 333
308 if self.use_rawinput: 334 if self.use_rawinput:
309 try: 335 try:
376 def parseline(self, line): 402 def parseline(self, line):
377 """Parse the line into a command name and a string containing 403 """Parse the line into a command name and a string containing
378 the arguments. Returns a tuple containing (command, args, line). 404 the arguments. Returns a tuple containing (command, args, line).
379 'command' and 'args' may be None if the line couldn't be parsed. 405 'command' and 'args' may be None if the line couldn't be parsed.
380 """ 406 """
381 line = line.strip() 407 if not line.executable:
382 if not line:
383 return None, None, line 408 return None, None, line
384 shortcut = self.shortcuts.get(line[0]) 409 return line.cmd, line.arg, line.executable
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
392 def showParam(self, param): 410 def showParam(self, param):
393 param = self.clean(param) 411 param = self.clean(param)
394 if param in self.settable: 412 if param in self.settable:
395 val = getattr(self, param) 413 val = getattr(self, param)
396 self.stdout.write('%s: %s\n' % (param, str(getattr(self, param)))) 414 self.stdout.write('%s: %s\n' % (param, str(getattr(self, param))))
414 paramName, val = arg.split(None, 1) 432 paramName, val = arg.split(None, 1)
415 paramName = self.clean(paramName) 433 paramName = self.clean(paramName)
416 if paramName not in self.settable: 434 if paramName not in self.settable:
417 raise NotSettableError 435 raise NotSettableError
418 currentVal = getattr(self, paramName) 436 currentVal = getattr(self, paramName)
419 val = cast(currentVal, val.strip(self.terminators)) 437 val = cast(currentVal, self.strip_terminators(val))
420 setattr(self, paramName, val) 438 setattr(self, paramName, val)
421 self.stdout.write('%s - was: %s\nnow: %s\n' % (paramName, currentVal, val)) 439 self.stdout.write('%s - was: %s\nnow: %s\n' % (paramName, currentVal, val))
422 except (ValueError, AttributeError, NotSettableError), e: 440 except (ValueError, AttributeError, NotSettableError), e:
423 self.do_show(arg) 441 self.do_show(arg)
424 442
549 runme = self.precmd(runme) 567 runme = self.precmd(runme)
550 stop = self.onecmd(runme) 568 stop = self.onecmd(runme)
551 stop = self.postcmd(stop, runme) 569 stop = self.postcmd(stop, runme)
552 do_r = do_run 570 do_r = do_run
553 571
554 def fileimport(self, statement, source): 572 def fileimport(self, source):
555 try: 573 try:
556 f = open(source) 574 f = open(source)
557 except IOError: 575 except IOError:
558 self.stdout.write("Couldn't read from file %s\n" % source) 576 self.stdout.write("Couldn't read from file %s\n" % source)
559 return '' 577 return ''
637 for attrib in self.attribs: 655 for attrib in self.attribs:
638 setattr(self, attrib, getattr(self.obj, attrib)) 656 setattr(self, attrib, getattr(self.obj, attrib))
639 def restore(self): 657 def restore(self):
640 for attrib in self.attribs: 658 for attrib in self.attribs:
641 setattr(self.obj, attrib, getattr(self, attrib)) 659 setattr(self.obj, attrib, getattr(self, attrib))
660
661 if __name__ == '__main__':
662 doctest.testmod()
663