comparison cmd2.py @ 287:1cd23003e8d5

refactoring, but something went wrong with comments
author catherine@bothari
date Mon, 19 Oct 2009 18:00:26 -0400
parents 3c4ba65cb303
children e743cf74c518
comparison
equal deleted inserted replaced
286:3c4ba65cb303 287:1cd23003e8d5
20 20
21 - Catherine Devlin, Jan 03 2008 - catherinedevlin.blogspot.com 21 - Catherine Devlin, Jan 03 2008 - catherinedevlin.blogspot.com
22 22
23 mercurial repository at http://www.assembla.com/wiki/show/python-cmd2 23 mercurial repository at http://www.assembla.com/wiki/show/python-cmd2
24 """ 24 """
25 import cmd, re, os, sys, optparse, subprocess, tempfile, pyparsing, doctest 25 import cmd
26 import unittest, string, datetime, urllib, glob, traceback 26 import re
27 import os
28 import sys
29 import optparse
30 import subprocess
31 import tempfile
32 import doctest
33 import unittest
34 import datetime
35 import urllib
36 import glob
37 import traceback
27 from code import InteractiveConsole, InteractiveInterpreter, softspace 38 from code import InteractiveConsole, InteractiveInterpreter, softspace
28 from optparse import make_option 39 from optparse import make_option
40
41 import pyparsing
29 __version__ = '0.5.6' 42 __version__ = '0.5.6'
30 43
31 class OptionParser(optparse.OptionParser): 44 class OptionParser(optparse.OptionParser):
32 def exit(self, status=0, msg=None): 45 def exit(self, status=0, msg=None):
33 self.values._exit = True 46 self.values._exit = True
48 If you override this in a subclass, it should not return -- it 61 If you override this in a subclass, it should not return -- it
49 should either exit or raise an exception. 62 should either exit or raise an exception.
50 """ 63 """
51 raise 64 raise
52 65
53 def remainingArgs(oldArgs, newArgList): 66 def remaining_args(oldArgs, newArgList):
54 ''' 67 '''
55 Preserves the spacing originally in the argument after 68 Preserves the spacing originally in the argument after
56 the removal of options. 69 the removal of options.
57 70
58 >>> remainingArgs('-f bar bar cow', ['bar', 'cow']) 71 >>> remaining_args('-f bar bar cow', ['bar', 'cow'])
59 'bar cow' 72 'bar cow'
60 ''' 73 '''
61 pattern = '\s+'.join(re.escape(a) for a in newArgList) + '\s*$' 74 pattern = '\s+'.join(re.escape(a) for a in newArgList) + '\s*$'
62 matchObj = re.search(pattern, oldArgs) 75 matchObj = re.search(pattern, oldArgs)
63 return oldArgs[matchObj.start():] 76 return oldArgs[matchObj.start():]
73 return None 86 return None
74 87
75 optparse.Values.get = _attr_get_ 88 optparse.Values.get = _attr_get_
76 89
77 def options(option_list): 90 def options(option_list):
91 '''Used as a decorator and passed a list of optparse-style options,
92 alters a cmd2 methodo populate its ``opts`` argument from its
93 raw text argument.
94
95 Example: transform
96 def do_something(self, arg):
97
98 into
99 @options([make_option('-q', '--quick', action="store_true",
100 help="Makes things fast")])
101 def do_something(self, arg, opts):
102 if opts.quick:
103 self.fast_button = True
104 '''
78 def option_setup(func): 105 def option_setup(func):
79 optionParser = OptionParser() 106 optionParser = OptionParser()
80 for opt in option_list: 107 for opt in option_list:
81 optionParser.add_option(opt) 108 optionParser.add_option(opt)
82 optionParser.set_usage("%s [options] arg" % func.__name__.strip('do_')) 109 optionParser.set_usage("%s [options] arg" % func.__name__.strip('do_'))
83 optionParser._func = func 110 optionParser._func = func
84 def newFunc(instance, arg): 111 def new_func(instance, arg):
85 try: 112 try:
86 if hasattr(arg, 'parsed'): 113 if hasattr(arg, 'parsed'):
87 args = arg.parsed.raw 114 args = arg.parsed.raw
88 else: 115 else:
89 args = arg 116 args = arg
90 opts, newArgList = optionParser.parse_args(args.split()) 117 opts, newArgList = optionParser.parse_args(args.split())
91 # Must find the remaining args in the original argument list, but 118 # Must find the remaining args in the original argument list, but
92 # mustn't include the command itself 119 # mustn't include the command itself
93 if hasattr(arg, 'parsed') and newArgList[0] == arg.parsed.command: 120 if hasattr(arg, 'parsed') and newArgList[0] == arg.parsed.command:
94 newArgList = newArgList[1:] 121 newArgList = newArgList[1:]
95 newArgs = remainingArgs(args, newArgList) 122 newArgs = remaining_args(args, newArgList)
96 except (optparse.OptionValueError, optparse.BadOptionError, 123 except (optparse.OptionValueError, optparse.BadOptionError,
97 optparse.OptionError, optparse.AmbiguousOptionError, 124 optparse.OptionError, optparse.AmbiguousOptionError,
98 optparse.OptionConflictError), e: 125 optparse.OptionConflictError), e:
99 print e 126 print e
100 optionParser.print_help() 127 optionParser.print_help()
113 else: 140 else:
114 arg = newArgs 141 arg = newArgs
115 result = func(instance, arg, opts) 142 result = func(instance, arg, opts)
116 return result 143 return result
117 newFunc.__doc__ = '%s\n%s' % (func.__doc__, optionParser.format_help()) 144 newFunc.__doc__ = '%s\n%s' % (func.__doc__, optionParser.format_help())
118 return newFunc 145 return new_func
119 return option_setup 146 return option_setup
120 147
121 class PasteBufferError(EnvironmentError): 148 class PasteBufferError(EnvironmentError):
122 if sys.platform[:3] == 'win': 149 if sys.platform[:3] == 'win':
123 errmsg = """Redirecting to or from paste buffer requires pywin32 150 errmsg = """Redirecting to or from paste buffer requires pywin32
135 %s""" 162 %s"""
136 163
137 if subprocess.mswindows: 164 if subprocess.mswindows:
138 try: 165 try:
139 import win32clipboard 166 import win32clipboard
140 def getPasteBuffer(): 167 def get_paste_buffer():
141 win32clipboard.OpenClipboard(0) 168 win32clipboard.OpenClipboard(0)
142 try: 169 try:
143 result = win32clipboard.GetClipboardData() 170 result = win32clipboard.GetClipboardData()
144 except TypeError: 171 except TypeError:
145 result = '' #non-text 172 result = '' #non-text
146 win32clipboard.CloseClipboard() 173 win32clipboard.CloseClipboard()
147 return result 174 return result
148 def writeToPasteBuffer(txt): 175 def write_to_paste_buffer(txt):
149 win32clipboard.OpenClipboard(0) 176 win32clipboard.OpenClipboard(0)
150 win32clipboard.EmptyClipboard() 177 win32clipboard.EmptyClipboard()
151 win32clipboard.SetClipboardText(txt) 178 win32clipboard.SetClipboardText(txt)
152 win32clipboard.CloseClipboard() 179 win32clipboard.CloseClipboard()
153 except ImportError: 180 except ImportError:
154 def getPasteBuffer(*args): 181 def get_paste_buffer(*args):
155 raise OSError, pastebufferr % ('pywin32', 'Download from http://sourceforge.net/projects/pywin32/') 182 raise OSError, pastebufferr % ('pywin32', 'Download from http://sourceforge.net/projects/pywin32/')
156 setPasteBuffer = getPasteBuffer 183 setPasteBuffer = get_paste_buffer
157 else: 184 else:
158 can_clip = False 185 can_clip = False
159 try: 186 try:
160 subprocess.check_call('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 187 subprocess.check_call('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
161 can_clip = True 188 can_clip = True
168 if xclipproc.stdout.read() == teststring: 195 if xclipproc.stdout.read() == teststring:
169 can_clip = True 196 can_clip = True
170 except (subprocess.CalledProcessError, OSError, IOError): 197 except (subprocess.CalledProcessError, OSError, IOError):
171 pass 198 pass
172 if can_clip: 199 if can_clip:
173 def getPasteBuffer(): 200 def get_paste_buffer():
174 xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 201 xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
175 return xclipproc.stdout.read() 202 return xclipproc.stdout.read()
176 def writeToPasteBuffer(txt): 203 def write_to_paste_buffer(txt):
177 xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 204 xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
178 xclipproc.stdin.write(txt) 205 xclipproc.stdin.write(txt)
179 xclipproc.stdin.close() 206 xclipproc.stdin.close()
180 # but we want it in both the "primary" and "mouse" clipboards 207 # but we want it in both the "primary" and "mouse" clipboards
181 xclipproc = subprocess.Popen('xclip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) 208 xclipproc = subprocess.Popen('xclip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
182 xclipproc.stdin.write(txt) 209 xclipproc.stdin.write(txt)
183 xclipproc.stdin.close() 210 xclipproc.stdin.close()
184 else: 211 else:
185 def getPasteBuffer(*args): 212 def get_paste_buffer(*args):
186 raise OSError, pastebufferr % ('xclip', 'On Debian/Ubuntu, install with "sudo apt-get install xclip"') 213 raise OSError, pastebufferr % ('xclip', 'On Debian/Ubuntu, install with "sudo apt-get install xclip"')
187 setPasteBuffer = getPasteBuffer 214 setPasteBuffer = get_paste_buffer
188 writeToPasteBuffer = getPasteBuffer 215 writeToPasteBuffer = get_paste_buffer
189 216
190 pyparsing.ParserElement.setDefaultWhitespaceChars(' \t') 217 pyparsing.ParserElement.setDefaultWhitespaceChars(' \t')
191 218
192 class ParsedString(str): 219 class ParsedString(str):
193 pass 220 pass
237 try: 264 try:
238 result = open(os.path.expanduser(fname[0])).read() 265 result = open(os.path.expanduser(fname[0])).read()
239 except IOError: 266 except IOError:
240 result = '< %s' % fname[0] # wasn't a file after all 267 result = '< %s' % fname[0] # wasn't a file after all
241 else: 268 else:
242 result = getPasteBuffer() 269 result = get_paste_buffer()
243 return result 270 return result
244 271
245 class EmbeddedConsoleExit(Exception): 272 class EmbeddedConsoleExit(Exception):
246 pass 273 pass
247 274
280 shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'} 307 shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'}
281 excludeFromHistory = '''run r list l history hi ed edit li eof'''.split() 308 excludeFromHistory = '''run r list l history hi ed edit li eof'''.split()
282 noSpecialParse = 'set ed edit exit'.split() 309 noSpecialParse = 'set ed edit exit'.split()
283 defaultExtension = 'txt' # For ``save``, ``load``, etc. 310 defaultExtension = 'txt' # For ``save``, ``load``, etc.
284 default_file_name = 'command.txt' # For ``save``, ``load``, etc. 311 default_file_name = 'command.txt' # For ``save``, ``load``, etc.
285 abbrev = True 312 abbrev = True # Abbreviated commands recognized
286 nonpythoncommand = 'cmd'
287 current_script_dir = None 313 current_script_dir = None
288 reserved_words = [] 314 reserved_words = []
289 feedback_to_output = False 315 feedback_to_output = False # Do include nonessentials in >, | output
290 quiet = False 316 quiet = False # Do not suppress nonessential output
291 debug = False 317 debug = False
292 settable = ['prompt', 'continuation_prompt', 'debug', 'default_file_name', 'editor', 318 settable = ['prompt', 'continuation_prompt', 'debug', 'default_file_name', 'editor',
293 'case_insensitive', 'feedback_to_output', 'quiet', 'echo', 'timing', 319 'case_insensitive', 'feedback_to_output', 'quiet', 'echo', 'timing',
294 'abbrev'] 320 'abbrev']
295 settable.sort() 321 settable.sort()
309 if not self.quiet: 335 if not self.quiet:
310 if self.feedback_to_output: 336 if self.feedback_to_output:
311 self.poutput(msg) 337 self.poutput(msg)
312 else: 338 else:
313 print msg 339 print msg
340 _STOP_AND_EXIT = 2
314 editor = os.environ.get('EDITOR') 341 editor = os.environ.get('EDITOR')
315 _STOP_AND_EXIT = 2
316 if not editor: 342 if not editor:
317 if sys.platform[:3] == 'win': 343 if sys.platform[:3] == 'win':
318 editor = 'notepad' 344 editor = 'notepad'
319 else: 345 else:
320 for editor in ['gedit', 'kate', 'vim', 'emacs', 'nano', 'pico']: 346 for editor in ['gedit', 'kate', 'vim', 'emacs', 'nano', 'pico']:
360 386
361 prefixParser = pyparsing.Empty() 387 prefixParser = pyparsing.Empty()
362 commentGrammars = pyparsing.Or([pyparsing.pythonStyleComment, pyparsing.cStyleComment]) 388 commentGrammars = pyparsing.Or([pyparsing.pythonStyleComment, pyparsing.cStyleComment])
363 commentGrammars.addParseAction(lambda x: '') 389 commentGrammars.addParseAction(lambda x: '')
364 commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(pyparsing.stringEnd) 390 commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(pyparsing.stringEnd)
365 commentInProgress = pyparsing.NoMatch()
366 terminators = [';'] 391 terminators = [';']
367 blankLinesAllowed = False 392 blankLinesAllowed = False
368 multilineCommands = [] 393 multilineCommands = []
369 394
370 def _init_parser(self): 395 def _init_parser(self):
672 return self.postparsing_postcmd(stop=0) 697 return self.postparsing_postcmd(stop=0)
673 else: 698 else:
674 statekeeper = Statekeeper(self, ('stdout',)) 699 statekeeper = Statekeeper(self, ('stdout',))
675 self.stdout = tempfile.TemporaryFile() 700 self.stdout = tempfile.TemporaryFile()
676 if statement.parsed.output == '>>': 701 if statement.parsed.output == '>>':
677 self.stdout.write(getPasteBuffer()) 702 self.stdout.write(get_paste_buffer())
678 try: 703 try:
679 try: 704 try:
680 # "heart" of the command, replace's cmd's onecmd() 705 # "heart" of the command, replace's cmd's onecmd()
681 self.lastcmd = statement.parsed.expanded 706 self.lastcmd = statement.parsed.expanded
682 funcname = self.func_named(statement.parsed.command) 707 funcname = self.func_named(statement.parsed.command)
695 finally: 720 finally:
696 if statekeeper: 721 if statekeeper:
697 if statement.parsed.output and not statement.parsed.outputTo: 722 if statement.parsed.output and not statement.parsed.outputTo:
698 self.stdout.seek(0) 723 self.stdout.seek(0)
699 try: 724 try:
700 writeToPasteBuffer(self.stdout.read()) 725 write_to_paste_buffer(self.stdout.read())
701 except Exception, e: 726 except Exception, e:
702 self.perror(e) 727 self.perror(e)
703 elif statement.parsed.pipeTo: 728 elif statement.parsed.pipeTo:
704 for result in redirect.communicate(): 729 for result in redirect.communicate():
705 statekeeper.stdout.write(result or '') 730 statekeeper.stdout.write(result or '')
773 798
774 def do_EOF(self, arg): 799 def do_EOF(self, arg):
775 return True 800 return True
776 do_eof = do_EOF 801 do_eof = do_EOF
777 802
778 def showParam(self, param): 803 def show_param(self, param):
779 any_shown = False 804 any_shown = False
780 param = param.strip().lower() 805 param = param.strip().lower()
781 for p in self.settable: 806 for p in self.settable:
782 if p.startswith(param): 807 if p.startswith(param):
783 val = getattr(self, p)
784 self.stdout.write('%s: %s\n' % (p, str(getattr(self, p)))) 808 self.stdout.write('%s: %s\n' % (p, str(getattr(self, p))))
785 any_shown = True 809 any_shown = True
786 if not any_shown: 810 if not any_shown:
787 self.perror("Parameter '%s' not supported (type 'show' for list of parameters)." % param) 811 self.perror("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
788 812
792 do_q = do_quit 816 do_q = do_quit
793 817
794 def do_show(self, arg): 818 def do_show(self, arg):
795 '''Shows value of a parameter.''' 819 '''Shows value of a parameter.'''
796 if arg.strip(): 820 if arg.strip():
797 self.showParam(arg) 821 self.show_param(arg)
798 else: 822 else:
799 for param in self.settable: 823 for param in self.settable:
800 self.showParam(param) 824 self.show_param(param)
801 825
802 def do_set(self, arg): 826 def do_set(self, arg):
803 '''Sets a cmd2 parameter. Accepts abbreviated parameter names so long as there is no ambiguity. 827 '''Sets a cmd2 parameter. Accepts abbreviated parameter names so long as there is no ambiguity.
804 Call without arguments for a list of settable parameters with their values.''' 828 Call without arguments for a list of settable parameters with their values.'''
805 try: 829 try:
852 raise EmbeddedConsoleExit 876 raise EmbeddedConsoleExit
853 def onecmd(arg): 877 def onecmd(arg):
854 return self.onecmd(arg + '\n') 878 return self.onecmd(arg + '\n')
855 self.pystate['quit'] = quit 879 self.pystate['quit'] = quit
856 self.pystate['exit'] = quit 880 self.pystate['exit'] = quit
857 self.pystate[self.nonpythoncommand] = onecmd
858 try: 881 try:
859 cprt = 'Type "help", "copyright", "credits" or "license" for more information.' 882 cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
860 keepstate = Statekeeper(sys, ('stdin','stdout')) 883 keepstate = Statekeeper(sys, ('stdin','stdout'))
861 sys.stdout = self.stdout 884 sys.stdout = self.stdout
862 sys.stdin = self.stdin 885 sys.stdin = self.stdin
964 except Exception, e: 987 except Exception, e:
965 self.perror('Error saving %s: %s' % (fname, str(e))) 988 self.perror('Error saving %s: %s' % (fname, str(e)))
966 989
967 def read_file_or_url(self, fname): 990 def read_file_or_url(self, fname):
968 if isinstance(fname, file): 991 if isinstance(fname, file):
969 target = open(fname, 'r') 992 result = open(fname, 'r')
970 else: 993 else:
971 match = self.urlre.match(fname) 994 match = self.urlre.match(fname)
972 if match: 995 if match:
973 target = urllib.urlopen(match.group(1)) 996 result = urllib.urlopen(match.group(1))
974 else: 997 else:
975 fname = os.path.expanduser(fname) 998 fname = os.path.expanduser(fname)
976 try: 999 try:
977 result = open(os.path.expanduser(fname), 'r') 1000 result = open(os.path.expanduser(fname), 'r')
978 except IOError, e: 1001 except IOError:
979 result = open('%s.%s' % (os.path.expanduser(fname), 1002 result = open('%s.%s' % (os.path.expanduser(fname),
980 self.defaultExtension), 'r') 1003 self.defaultExtension), 'r')
981 return result 1004 return result
982 1005
983 def do__relative_load(self, arg=None): 1006 def do__relative_load(self, arg=None):
1216 if self.CmdApp: 1239 if self.CmdApp:
1217 self.outputTrap.tearDown() 1240 self.outputTrap.tearDown()
1218 1241
1219 if __name__ == '__main__': 1242 if __name__ == '__main__':
1220 doctest.testmod(optionflags = doctest.NORMALIZE_WHITESPACE) 1243 doctest.testmod(optionflags = doctest.NORMALIZE_WHITESPACE)
1244
1221 1245
1222 ''' 1246 '''
1223 To make your application transcript-testable, add text like this to your .py file 1247 To make your application transcript-testable, add text like this to your .py file
1224 (replacing CmdLineApp with your own application class's name). Then, a cut-and-pasted 1248 (replacing CmdLineApp with your own application class's name). Then, a cut-and-pasted
1225 version of a successful session with your application, saved as a text file, can serve 1249 version of a successful session with your application, saved as a text file, can serve