Mercurial > python-cmd2
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 |