summaryrefslogtreecommitdiff
path: root/cmd2.py
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2.py')
-rwxr-xr-xcmd2.py266
1 files changed, 177 insertions, 89 deletions
diff --git a/cmd2.py b/cmd2.py
index c16cba9b..606eed8c 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -63,6 +63,7 @@ from six.moves.urllib.request import urlopen
# Due to one occurence of isinstance(<foo>, file) checking to see if something is of file type
try:
import io
+
file = io.TextIOWrapper
except ImportError:
pass # Python2
@@ -87,6 +88,7 @@ NameError: global name 'exc' is not defined
Bug report filed: https://sourceforge.net/tracker/?func=detail&atid=617311&aid=3381439&group_id=97203
"""
+
class OptionParser(optparse.OptionParser):
def exit(self, status=0, msg=None):
self.values._exit = True
@@ -140,9 +142,10 @@ def _which(editor):
except OSError:
return None
+
optparse.Values.get = _attr_get_
-options_defined = [] # used to distinguish --options from SQL-style --comments
+options_defined = [] # used to distinguish --options from SQL-style --comments
def options(option_list, arg_desc="arg"):
@@ -165,18 +168,20 @@ def options(option_list, arg_desc="arg"):
option_list = [option_list]
for opt in option_list:
options_defined.append(pyparsing.Literal(opt.get_opt_string()))
+
def option_setup(func):
optionParser = OptionParser()
for opt in option_list:
optionParser.add_option(opt)
optionParser.set_usage("%s [options] %s" % (func.__name__[3:], arg_desc))
optionParser._func = func
+
def new_func(instance, arg):
try:
opts, newArgList = optionParser.parse_args(arg.split())
# Must find the remaining args in the original argument list, but
# mustn't include the command itself
- #if hasattr(arg, 'parsed') and newArgList[0] == arg.parsed.command:
+ # if hasattr(arg, 'parsed') and newArgList[0] == arg.parsed.command:
# newArgList = newArgList[1:]
newArgs = remaining_args(arg, newArgList)
if isinstance(arg, ParsedString):
@@ -194,6 +199,7 @@ def options(option_list, arg_desc="arg"):
new_func.__doc__ = '%s\n%s' % (func.__doc__, optionParser.format_help())
return new_func
+
return option_setup
@@ -209,9 +215,11 @@ Download from http://sourceforge.net/projects/pywin32/"""
errmsg = """Redirecting to or from paste buffer requires xclip
to be installed on operating system.
On Debian/Ubuntu, 'sudo apt-get install xclip' will install it."""
+
def __init__(self):
Exception.__init__(self, self.errmsg)
+
pastebufferr = """Redirecting to or from paste buffer requires %s
to be installed on operating system.
%s"""
@@ -219,14 +227,18 @@ to be installed on operating system.
if sys.platform == "win32":
try:
import win32clipboard
+
+
def get_paste_buffer():
win32clipboard.OpenClipboard(0)
try:
result = win32clipboard.GetClipboardData()
except TypeError:
- result = '' #non-text
+ result = '' # non-text
win32clipboard.CloseClipboard()
return result
+
+
def write_to_paste_buffer(txt):
win32clipboard.OpenClipboard(0)
win32clipboard.EmptyClipboard()
@@ -235,30 +247,41 @@ if sys.platform == "win32":
except ImportError:
def get_paste_buffer(*args):
raise OSError(pastebufferr % ('pywin32', 'Download from http://sourceforge.net/projects/pywin32/'))
+
+
write_to_paste_buffer = get_paste_buffer
elif sys.platform == 'darwin':
can_clip = False
try:
# test for pbcopy - AFAIK, should always be installed on MacOS
- subprocess.check_call('pbcopy -help', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
+ subprocess.check_call('pbcopy -help', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE)
can_clip = True
except (subprocess.CalledProcessError, OSError, IOError):
pass
if can_clip:
def get_paste_buffer():
- pbcopyproc = subprocess.Popen('pbcopy -help', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
+ pbcopyproc = subprocess.Popen('pbcopy -help', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE)
return pbcopyproc.stdout.read()
+
+
def write_to_paste_buffer(txt):
- pbcopyproc = subprocess.Popen('pbcopy', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
+ pbcopyproc = subprocess.Popen('pbcopy', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE)
pbcopyproc.communicate(txt.encode())
else:
def get_paste_buffer(*args):
- raise OSError(pastebufferr % ('pbcopy', 'On MacOS X - error should not occur - part of the default installation'))
+ raise OSError(
+ pastebufferr % ('pbcopy', 'On MacOS X - error should not occur - part of the default installation'))
+
+
write_to_paste_buffer = get_paste_buffer
else:
can_clip = False
try:
- subprocess.check_call('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
+ subprocess.check_call('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE)
can_clip = True
except AttributeError: # check_call not defined, Python < 2.5
try:
@@ -266,17 +289,21 @@ else:
xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
xclipproc.stdin.write(teststring)
xclipproc.stdin.close()
- xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+ xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE)
if xclipproc.stdout.read() == teststring:
can_clip = True
- except Exception: # hate a bare Exception call, but exception classes vary too much b/t stdlib versions
+ except Exception: # hate a bare Exception call, but exception classes vary too much b/t stdlib versions
pass
except Exception:
- pass # something went wrong with xclip and we cannot use it
+ pass # something went wrong with xclip and we cannot use it
if can_clip:
def get_paste_buffer():
- xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+ xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE)
return xclipproc.stdout.read()
+
+
def write_to_paste_buffer(txt):
xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
xclipproc.stdin.write(txt.encode())
@@ -288,6 +315,8 @@ else:
else:
def get_paste_buffer(*args):
raise OSError(pastebufferr % ('xclip', 'On Debian/Ubuntu, install with "sudo apt-get install xclip"'))
+
+
write_to_paste_buffer = get_paste_buffer
pyparsing.ParserElement.setDefaultWhitespaceChars(' \t')
@@ -308,6 +337,7 @@ class ParsedString(str):
new.parsed.statement['args'] = newargs
return new
+
class StubbornDict(dict):
'''Dictionary that tolerates many input formats.
Create it with stubbornDict(arg) factory function.
@@ -322,8 +352,10 @@ class StubbornDict(dict):
>>> sorted(d.items())
[('girl', 'Frauelein, Maedchen'), ('large', 'gross'), ('plaid', ''), ('plain', ''), ('shoe', 'schuh'), ('small', 'klein')]
'''
+
def update(self, arg):
dict.update(self, StubbornDict.to_dict(arg))
+
append = update
def __iadd__(self, arg):
@@ -406,28 +438,29 @@ def ljust(x, width, fillchar=' '):
x = (x + [fillchar] * width)[:width]
return x
+
class Cmd(cmd.Cmd):
echo = False
- case_insensitive = True # Commands recognized regardless of case
+ case_insensitive = True # Commands recognized regardless of case
continuation_prompt = '> '
- timing = False # Prints elapsed time for each command
+ timing = False # Prints elapsed time for each command
# make sure your terminators are not in legalChars!
legalChars = u'!#$%.:?@_' + pyparsing.alphanums + pyparsing.alphas8bit
shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'}
excludeFromHistory = '''run r list l history hi ed edit li eof'''.split()
default_to_shell = False
noSpecialParse = 'set ed edit exit'.split()
- defaultExtension = 'txt' # For ``save``, ``load``, etc.
- default_file_name = 'command.txt' # For ``save``, ``load``, etc.
- abbrev = True # Abbreviated commands recognized
+ defaultExtension = 'txt' # For ``save``, ``load``, etc.
+ default_file_name = 'command.txt' # For ``save``, ``load``, etc.
+ abbrev = True # Abbreviated commands recognized
current_script_dir = None
reserved_words = []
- feedback_to_output = False # Do include nonessentials in >, | output
- quiet = False # Do not suppress nonessential output
+ feedback_to_output = False # Do include nonessentials in >, | output
+ quiet = False # Do not suppress nonessential output
debug = False
locals_in_py = True
kept_state = None
- redirector = '>' # for sending output to file
+ redirector = '>' # for sending output to file
settable = stubbornDict('''
prompt
colors Colorized output (*nix only)
@@ -463,6 +496,7 @@ class Cmd(cmd.Cmd):
self.poutput(msg)
else:
print(msg)
+
_STOP_AND_EXIT = True # distinguish end of script file from actual exit
_STOP_SCRIPT_NO_EXIT = -999
editor = os.environ.get('EDITOR')
@@ -474,14 +508,15 @@ class Cmd(cmd.Cmd):
if _which(editor):
break
- colorcodes = {'bold':{True:'\x1b[1m',False:'\x1b[22m'},
- 'cyan':{True:'\x1b[36m',False:'\x1b[39m'},
- 'blue':{True:'\x1b[34m',False:'\x1b[39m'},
- 'red':{True:'\x1b[31m',False:'\x1b[39m'},
- 'magenta':{True:'\x1b[35m',False:'\x1b[39m'},
- 'green':{True:'\x1b[32m',False:'\x1b[39m'},
- 'underline':{True:'\x1b[4m',False:'\x1b[24m'}}
+ colorcodes = {'bold': {True: '\x1b[1m', False: '\x1b[22m'},
+ 'cyan': {True: '\x1b[36m', False: '\x1b[39m'},
+ 'blue': {True: '\x1b[34m', False: '\x1b[39m'},
+ 'red': {True: '\x1b[31m', False: '\x1b[39m'},
+ 'magenta': {True: '\x1b[35m', False: '\x1b[39m'},
+ 'green': {True: '\x1b[32m', False: '\x1b[39m'},
+ 'underline': {True: '\x1b[4m', False: '\x1b[24m'}}
colors = (platform.system() != 'Windows')
+
def colorize(self, val, color):
'''Given a string (``val``), returns that string wrapped in UNIX-style
special characters that turn on (and then off) text color and style.
@@ -498,11 +533,11 @@ class Cmd(cmd.Cmd):
self.stdout.write("""
Commands are %(casesensitive)scase-sensitive.
Commands may be terminated with: %(terminators)s
- Settable parameters: %(settable)s\n""" % \
- { 'casesensitive': (self.case_insensitive and 'not ') or '',
- 'terminators': str(self.terminators),
- 'settable': ' '.join(self.settable)
- })
+ Settable parameters: %(settable)s\n""" %
+ {'casesensitive': (self.case_insensitive and 'not ') or '',
+ 'terminators': str(self.terminators),
+ 'settable': ' '.join(self.settable)
+ })
def do_help(self, arg):
if arg:
@@ -529,13 +564,12 @@ class Cmd(cmd.Cmd):
def do_shortcuts(self, args):
"""Lists single-key shortcuts available."""
result = "\n".join('%s: %s' % (sc[0], sc[1]) for sc in sorted(self.shortcuts))
- self.stdout.write("Single-key shortcuts for other commands:\n%s\n" % (result))
+ self.stdout.write("Single-key shortcuts for other commands:\n{}\n".format(result))
prefixParser = pyparsing.Empty()
commentGrammars = pyparsing.Or([pyparsing.pythonStyleComment, pyparsing.cStyleComment])
commentGrammars.addParseAction(lambda x: '')
- commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(
- pyparsing.stringEnd ^ '*/')
+ commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(pyparsing.stringEnd ^ '*/')
terminators = [';']
blankLinesAllowed = False
multilineCommands = []
@@ -716,21 +750,25 @@ class Cmd(cmd.Cmd):
- args: if "quoted strings /* seem to " start comments?
- command: what
'''
- #outputParser = (pyparsing.Literal('>>') | (pyparsing.WordStart() + '>') | pyparsing.Regex('[^=]>'))('output')
- outputParser = (pyparsing.Literal(self.redirector *2) |
- (pyparsing.WordStart() + self.redirector) |
+ # outputParser = (pyparsing.Literal('>>') | (pyparsing.WordStart() + '>') | pyparsing.Regex('[^=]>'))('output')
+ outputParser = (pyparsing.Literal(self.redirector * 2) |
+ (pyparsing.WordStart() + self.redirector) |
pyparsing.Regex('[^=]' + self.redirector))('output')
- terminatorParser = pyparsing.Or([(hasattr(t, 'parseString') and t) or pyparsing.Literal(t) for t in self.terminators])('terminator')
+ terminatorParser = pyparsing.Or(
+ [(hasattr(t, 'parseString') and t) or pyparsing.Literal(t) for t in self.terminators])('terminator')
stringEnd = pyparsing.stringEnd ^ '\nEOF'
- self.multilineCommand = pyparsing.Or([pyparsing.Keyword(c, caseless=self.case_insensitive) for c in self.multilineCommands])('multilineCommand')
+ self.multilineCommand = pyparsing.Or(
+ [pyparsing.Keyword(c, caseless=self.case_insensitive) for c in self.multilineCommands])('multilineCommand')
oneLineCommand = (~self.multilineCommand + pyparsing.Word(self.legalChars))('command')
pipe = pyparsing.Keyword('|', identChars='|')
self.commentGrammars.ignore(pyparsing.quotedString).setParseAction(lambda x: '')
doNotParse = self.commentGrammars | self.commentInProgress | pyparsing.quotedString
afterElements = \
pyparsing.Optional(pipe + pyparsing.SkipTo(outputParser ^ stringEnd, ignore=doNotParse)('pipeTo')) + \
- pyparsing.Optional(outputParser + pyparsing.SkipTo(stringEnd, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('outputTo'))
+ pyparsing.Optional(
+ outputParser + pyparsing.SkipTo(stringEnd, ignore=doNotParse).setParseAction(lambda x: x[0].strip())(
+ 'outputTo'))
if self.case_insensitive:
self.multilineCommand.setParseAction(lambda x: x[0].lower())
oneLineCommand.setParseAction(lambda x: x[0].lower())
@@ -739,14 +777,21 @@ class Cmd(cmd.Cmd):
else:
self.blankLineTerminator = (pyparsing.lineEnd + pyparsing.lineEnd)('terminator')
self.blankLineTerminator.setResultsName('terminator')
- self.blankLineTerminationParser = ((self.multilineCommand ^ oneLineCommand) + pyparsing.SkipTo(self.blankLineTerminator, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('args') + self.blankLineTerminator)('statement')
- self.multilineParser = (((self.multilineCommand ^ oneLineCommand) + pyparsing.SkipTo(terminatorParser, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('args') + terminatorParser)('statement') +
- pyparsing.SkipTo(outputParser ^ pipe ^ stringEnd, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('suffix') + afterElements)
+ self.blankLineTerminationParser = ((self.multilineCommand ^ oneLineCommand) +
+ pyparsing.SkipTo(self.blankLineTerminator, ignore=doNotParse).setParseAction(
+ lambda x: x[0].strip())('args') + self.blankLineTerminator)('statement')
+ self.multilineParser = (((self.multilineCommand ^ oneLineCommand) + pyparsing.SkipTo(terminatorParser,
+ ignore=doNotParse).setParseAction(
+ lambda x: x[0].strip())('args') + terminatorParser)('statement') +
+ pyparsing.SkipTo(outputParser ^ pipe ^ stringEnd, ignore=doNotParse).setParseAction(
+ lambda x: x[0].strip())('suffix') + afterElements)
self.multilineParser.ignore(self.commentInProgress)
- self.singleLineParser = ((oneLineCommand + pyparsing.SkipTo(terminatorParser ^ stringEnd ^ pipe ^ outputParser, ignore=doNotParse).setParseAction(lambda x:x[0].strip())('args'))('statement') +
+ self.singleLineParser = ((oneLineCommand + pyparsing.SkipTo(terminatorParser ^ stringEnd ^ pipe ^ outputParser,
+ ignore=doNotParse).setParseAction(
+ lambda x: x[0].strip())('args'))('statement') +
pyparsing.Optional(terminatorParser) + afterElements)
- #self.multilineParser = self.multilineParser.setResultsName('multilineParser')
- #self.singleLineParser = self.singleLineParser.setResultsName('singleLineParser')
+ # self.multilineParser = self.multilineParser.setResultsName('multilineParser')
+ # self.singleLineParser = self.singleLineParser.setResultsName('singleLineParser')
self.blankLineTerminationParser = self.blankLineTerminationParser.setResultsName('statement')
self.parser = self.prefixParser + (
stringEnd |
@@ -754,7 +799,7 @@ class Cmd(cmd.Cmd):
self.singleLineParser |
self.blankLineTerminationParser |
self.multilineCommand + pyparsing.SkipTo(stringEnd, ignore=doNotParse)
- )
+ )
self.parser.ignore(self.commentGrammars)
inputMark = pyparsing.Literal('<')
@@ -770,6 +815,7 @@ class Cmd(cmd.Cmd):
def preparse(self, raw, **kwargs):
return raw
+
def postparse(self, parseResult):
return parseResult
@@ -799,6 +845,7 @@ class Cmd(cmd.Cmd):
def postparsing_precmd(self, statement):
stop = 0
return stop, statement
+
def postparsing_postcmd(self, stop):
return stop
@@ -808,11 +855,12 @@ class Cmd(cmd.Cmd):
if target in dir(self):
result = target
else:
- if self.abbrev: # accept shortened versions of commands
+ if self.abbrev: # accept shortened versions of commands
funcs = [fname for fname in self.keywords if fname.startswith(arg)]
if len(funcs) == 1:
result = 'do_' + funcs[0]
return result
+
def onecmd_plus_hooks(self, line):
# The outermost level of try/finally nesting can be condensed once
# Python 2.4 support can be dropped.
@@ -842,11 +890,10 @@ class Cmd(cmd.Cmd):
self.perror(str(e), statement)
finally:
return self.postparsing_postcmd(stop)
+
def complete_statement(self, line):
"""Keep accepting lines of input until the command is complete."""
- if (not line) or (
- not pyparsing.Or(self.commentGrammars).
- setParseAction(lambda x: '').transformString(line)):
+ if not line or (not pyparsing.Or(self.commentGrammars).setParseAction(lambda x: '').transformString(line)):
raise EmptyStatement()
statement = self.parsed(line)
while statement.parsed.multilineCommand and (statement.parsed.terminator == ''):
@@ -861,7 +908,8 @@ class Cmd(cmd.Cmd):
if statement.parsed.pipeTo:
self.kept_state = Statekeeper(self, ('stdout',))
self.kept_sys = Statekeeper(sys, ('stdout',))
- self.redirect = subprocess.Popen(statement.parsed.pipeTo, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+ self.redirect = subprocess.Popen(statement.parsed.pipeTo, shell=True, stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE)
sys.stdout = self.stdout = self.redirect.stdin
elif statement.parsed.output:
if (not statement.parsed.outputTo) and (not can_clip):
@@ -939,7 +987,7 @@ class Cmd(cmd.Cmd):
if not len(line):
line = 'EOF'
else:
- if line[-1] == '\n': # this was always true in Cmd
+ if line[-1] == '\n': # this was always true in Cmd
line = line[:-1]
return line
@@ -958,7 +1006,7 @@ class Cmd(cmd.Cmd):
import readline
self.old_completer = readline.get_completer()
readline.set_completer(self.complete)
- readline.parse_and_bind(self.completekey+": complete")
+ readline.parse_and_bind(self.completekey + ": complete")
except ImportError:
pass
stop = None
@@ -966,7 +1014,7 @@ class Cmd(cmd.Cmd):
if intro is not None:
self.intro = intro
if self.intro:
- self.stdout.write(str(self.intro)+"\n")
+ self.stdout.write(str(self.intro) + "\n")
while not stop:
if self.cmdqueue:
line = self.cmdqueue.pop(0)
@@ -986,11 +1034,13 @@ class Cmd(cmd.Cmd):
return stop
def do_EOF(self, arg):
- return self._STOP_SCRIPT_NO_EXIT # End of script; should not exit app
+ return self._STOP_SCRIPT_NO_EXIT # End of script; should not exit app
+
do_eof = do_EOF
def do_quit(self, arg):
return self._STOP_AND_EXIT
+
do_exit = do_quit
do_q = do_quit
@@ -1018,7 +1068,7 @@ class Cmd(cmd.Cmd):
except IndexError:
fulloptions.append((opt[0], opt[0]))
for (idx, (value, text)) in enumerate(fulloptions):
- self.poutput(' %2d. %s\n' % (idx+1, text))
+ self.poutput(' %2d. %s\n' % (idx + 1, text))
while True:
response = input(prompt)
try:
@@ -1026,11 +1076,10 @@ class Cmd(cmd.Cmd):
result = fulloptions[response - 1][0]
break
except ValueError:
- pass # loop and ask again
+ pass # loop and ask again
return result
- @options([make_option('-l', '--long', action="store_true",
- help="describe function of parameter")])
+ @options([make_option('-l', '--long', action="store_true", help="describe function of parameter")])
def do_show(self, arg, opts):
'''Shows value of a parameter.'''
param = arg.strip().lower()
@@ -1047,7 +1096,7 @@ class Cmd(cmd.Cmd):
else:
self.poutput(result[p])
else:
- raise NotImplementedError("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
+ raise LookupError("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
def do_set(self, arg):
'''
@@ -1106,8 +1155,10 @@ class Cmd(cmd.Cmd):
else:
def quit():
raise EmbeddedConsoleExit
+
def onecmd_plus_hooks(arg):
return self.onecmd_plus_hooks(arg + '\n')
+
def run(arg):
try:
file = open(arg)
@@ -1115,6 +1166,7 @@ class Cmd(cmd.Cmd):
file.close()
except IOError as e:
self.perror(e)
+
self.pystate['quit'] = quit
self.pystate['exit'] = quit
self.pystate['cmd'] = onecmd_plus_hooks
@@ -1122,18 +1174,18 @@ class Cmd(cmd.Cmd):
keepstate = None
try:
cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
- keepstate = Statekeeper(sys, ('stdin','stdout'))
+ keepstate = Statekeeper(sys, ('stdin', 'stdout'))
sys.stdout = self.stdout
sys.stdin = self.stdin
- interp.interact(banner= "Python %s on %s\n%s\n(%s)\n%s" %
- (sys.version, sys.platform, cprt, self.__class__.__name__, self.do_py.__doc__))
+ interp.interact(banner="Python %s on %s\n%s\n(%s)\n%s" %
+ (sys.version, sys.platform, cprt, self.__class__.__name__, self.do_py.__doc__))
except EmbeddedConsoleExit:
pass
if keepstate is not None:
keepstate.restore()
@options([make_option('-s', '--script', action="store_true", help="Script format; no separation lines"),
- ], arg_desc = '(limit on which commands to include)')
+ ], arg_desc='(limit on which commands to include)')
def do_history(self, arg, opts):
"""history [arg]: lists past commands issued
@@ -1151,6 +1203,7 @@ class Cmd(cmd.Cmd):
self.poutput(hi)
else:
self.stdout.write(hi.pr())
+
def last_matching(self, arg):
try:
if arg:
@@ -1159,6 +1212,7 @@ class Cmd(cmd.Cmd):
return self.history[-1]
except IndexError:
return None
+
def do_list(self, arg):
"""list [arg]: lists last command issued
@@ -1206,11 +1260,13 @@ class Cmd(cmd.Cmd):
os.system('%s %s' % (self.editor, filename))
self.do__load(filename)
+
do_edit = do_ed
- saveparser = (pyparsing.Optional(pyparsing.Word(pyparsing.nums)^'*')("idx") +
+ saveparser = (pyparsing.Optional(pyparsing.Word(pyparsing.nums) ^ '*')("idx") +
pyparsing.Optional(pyparsing.Word(legalChars + '/\\'))("fname") +
pyparsing.stringEnd)
+
def do_save(self, arg):
"""`save [N] [filename.ext]`
@@ -1228,16 +1284,16 @@ class Cmd(cmd.Cmd):
if args.idx == '*':
saveme = '\n\n'.join(self.history[:])
elif args.idx:
- saveme = self.history[int(args.idx)-1]
+ saveme = self.history[int(args.idx) - 1]
else:
saveme = self.history[-1]
try:
f = open(os.path.expanduser(fname), 'w')
f.write(saveme)
f.close()
- self.pfeedback('Saved to %s' % (fname))
+ self.pfeedback('Saved to {}'.format(fname))
except Exception as e:
- self.perror('Error saving %s' % (fname))
+ self.perror('Error saving {}'.format(fname))
raise
def read_file_or_url(self, fname):
@@ -1269,6 +1325,7 @@ class Cmd(cmd.Cmd):
self.do__load('%s %s' % (targetname, args))
urlre = re.compile('(https?://[-\\w\\./]+)')
+
def do_load(self, arg=None):
"""Runs script of command(s) from a file or URL."""
if arg is None:
@@ -1281,8 +1338,8 @@ class Cmd(cmd.Cmd):
except IOError as e:
self.perror('Problem accessing script from %s: \n%s' % (targetname, e))
return
- keepstate = Statekeeper(self, ('stdin','use_rawinput','prompt',
- 'continuation_prompt','current_script_dir'))
+ keepstate = Statekeeper(self, ('stdin', 'use_rawinput', 'prompt',
+ 'continuation_prompt', 'current_script_dir'))
self.stdin = target
self.use_rawinput = False
self.prompt = self.continuation_prompt = ''
@@ -1292,6 +1349,7 @@ class Cmd(cmd.Cmd):
keepstate.restore()
self.lastcmd = ''
return stop and (stop != self._STOP_SCRIPT_NO_EXIT)
+
do__load = do_load # avoid an unfortunate legacy use of do_load from sqlpython
def do_run(self, arg):
@@ -1307,6 +1365,7 @@ class Cmd(cmd.Cmd):
self.pfeedback(runme)
if runme:
stop = self.onecmd_plus_hooks(runme)
+
do_r = do_run
def fileimport(self, statement, source):
@@ -1322,8 +1381,9 @@ class Cmd(cmd.Cmd):
def runTranscriptTests(self, callargs):
class TestMyAppCase(Cmd2TestCase):
CmdApp = self.__class__
+
self.__class__.testfiles = callargs
- sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main()
+ sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main()
testcase = TestMyAppCase()
runner = unittest.TextTestRunner()
result = runner.run(testcase)
@@ -1337,8 +1397,8 @@ class Cmd(cmd.Cmd):
def cmdloop(self, intro=None):
parser = optparse.OptionParser()
parser.add_option('-t', '--test', dest='test',
- action="store_true",
- help='Test against transcript(s) in FILE (wildcards OK)')
+ action="store_true",
+ help='Test against transcript(s) in FILE (wildcards OK)')
(callopts, callargs) = parser.parse_args()
if callopts.test:
self.runTranscriptTests(callargs)
@@ -1346,15 +1406,19 @@ class Cmd(cmd.Cmd):
if not self.run_commands_at_invocation(callargs):
self._cmdloop()
+
class HistoryItem(str):
listformat = '-------------------------[%d]\n%s\n'
+
def __init__(self, instr):
str.__init__(self)
self.lowercase = self.lower()
self.idx = None
+
def pr(self):
return self.listformat % (self.idx, str(self))
+
class History(list):
'''A list of HistoryItems that knows how to respond to user requests.
>>> h = History([HistoryItem('first'), HistoryItem('second'), HistoryItem('third'), HistoryItem('fourth')])
@@ -1377,17 +1441,20 @@ class History(list):
>>> h.search('/IR/')
['first', 'third']
'''
+
def zero_based_index(self, onebased):
result = onebased
if result > 0:
result -= 1
return result
+
def to_index(self, raw):
if raw:
result = self.zero_based_index(int(raw))
else:
result = None
return result
+
def search(self, target):
target = target.strip()
if target[0] == target[-1] == '/' and len(target) > 1:
@@ -1396,7 +1463,9 @@ class History(list):
target = re.escape(target)
pattern = re.compile(target, re.IGNORECASE)
return [s for s in self if pattern.search(s)]
+
spanpattern = re.compile(r'^\s*(?P<start>\-?\d+)?\s*(?P<separator>:|(\.{2,}))?\s*(?P<end>\-?\d+)?\s*$')
+
def span(self, raw):
if raw.lower() in ('*', '-', 'all'):
raw = ':'
@@ -1419,10 +1488,12 @@ class History(list):
return result
rangePattern = re.compile(r'^\s*(?P<start>[\d]+)?\s*\-\s*(?P<end>[\d]+)?\s*$')
+
def append(self, new):
new = HistoryItem(new)
list.append(self, new)
new.idx = len(self)
+
def extend(self, new):
for n in new:
self.append(n)
@@ -1435,7 +1506,7 @@ class History(list):
if getme < 0:
return self[:(-1 * getme)]
else:
- return [self[getme-1]]
+ return [self[getme - 1]]
except IndexError:
return []
except ValueError:
@@ -1453,16 +1524,19 @@ class History(list):
if getme.startswith(r'/') and getme.endswith(r'/'):
finder = re.compile(getme[1:-1], re.DOTALL | re.MULTILINE | re.IGNORECASE)
+
def isin(hi):
return finder.search(hi)
else:
def isin(hi):
- return (getme.lower() in hi.lowercase)
+ return getme.lower() in hi.lowercase
return [itm for itm in self if isin(itm)]
+
class NotSettableError(Exception):
pass
+
def cast(current, new):
"""Tries to force a new value into the same type as the current."""
typ = type(current)
@@ -1475,9 +1549,9 @@ def cast(current, new):
new = new.lower()
except:
pass
- if (new=='on') or (new[0] in ('y','t')):
+ if (new == 'on') or (new[0] in ('y', 't')):
return True
- if (new=='off') or (new[0] in ('n','f')):
+ if (new == 'off') or (new[0] in ('n', 'f')):
return False
else:
try:
@@ -1487,42 +1561,52 @@ def cast(current, new):
print("Problem setting parameter (now %s) to %s; incorrect type?" % (current, new))
return current
+
class Statekeeper(object):
def __init__(self, obj, attribs):
self.obj = obj
self.attribs = attribs
if self.obj:
self.save()
+
def save(self):
for attrib in self.attribs:
setattr(self, attrib, getattr(self.obj, attrib))
+
def restore(self):
if self.obj:
for attrib in self.attribs:
setattr(self.obj, attrib, getattr(self, attrib))
+
class Borg(object):
'''All instances of any Borg subclass will share state.
from Python Cookbook, 2nd Ed., recipe 6.16'''
_shared_state = {}
+
def __new__(cls, *a, **k):
obj = object.__new__(cls, *a, **k)
obj.__dict__ = cls._shared_state
return obj
+
class OutputTrap(Borg):
'''Instantiate an OutputTrap to divert/capture ALL stdout output. For use in unit testing.
Call `tearDown()` to return to normal output.'''
+
def __init__(self):
self.contents = ''
self.old_stdout = sys.stdout
sys.stdout = self
+
def write(self, txt):
self.contents += txt
+
def read(self):
result = self.contents
self.contents = ''
return result
+
def tearDown(self):
sys.stdout = self.old_stdout
self.contents = ''
@@ -1533,6 +1617,7 @@ class Cmd2TestCase(unittest.TestCase):
that will execute the commands in a transcript file and expect the results shown.
See example.py'''
CmdApp = None
+
def fetchTranscripts(self):
self.transcripts = {}
for fileset in self.CmdApp.testfiles:
@@ -1542,22 +1627,26 @@ class Cmd2TestCase(unittest.TestCase):
tfile.close()
if not len(self.transcripts):
raise Exception("No test files found - nothing to test.")
+
def setUp(self):
if self.CmdApp:
self.outputTrap = OutputTrap()
self.cmdapp = self.CmdApp()
self.fetchTranscripts()
- def runTest(self): # was testall
+
+ def runTest(self): # was testall
if self.CmdApp:
its = sorted(self.transcripts.items())
for (fname, transcript) in its:
self._test_transcript(fname, transcript)
+
regexPattern = pyparsing.QuotedString(quoteChar=r'/', escChar='\\', multiline=True, unquoteResults=True)
regexPattern.ignore(pyparsing.cStyleComment)
notRegexPattern = pyparsing.Word(pyparsing.printables)
notRegexPattern.setParseAction(lambda t: re.escape(t[0]))
expectationParser = regexPattern | notRegexPattern
anyWhitespace = re.compile(r'\s', re.DOTALL | re.MULTILINE)
+
def _test_transcript(self, fname, transcript):
lineNum = 0
finished = False
@@ -1589,13 +1678,13 @@ class Cmd2TestCase(unittest.TestCase):
command = ''.join(command)
# Send the command into the application and capture the resulting output
stop = self.cmdapp.onecmd_plus_hooks(command)
- #TODO: should act on ``stop``
+ # TODO: should act on ``stop``
result = self.outputTrap.read()
# Read the expected result from transcript
if line.startswith(self.cmdapp.prompt):
- message = '\nFile %s, line %d\nCommand was:\n%r\nExpected: (nothing)\nGot:\n%r\n'%\
- (fname, lineNum, command, result)
- self.assert_(not(result.strip()), message)
+ message = '\nFile %s, line %d\nCommand was:\n%r\nExpected: (nothing)\nGot:\n%r\n' % \
+ (fname, lineNum, command, result)
+ self.assert_(not (result.strip()), message)
continue
expected = []
while not line.startswith(self.cmdapp.prompt):
@@ -1608,8 +1697,8 @@ class Cmd2TestCase(unittest.TestCase):
lineNum += 1
expected = ''.join(expected)
# Compare actual result to expected
- message = '\nFile %s, line %d\nCommand was:\n%s\nExpected:\n%s\nGot:\n%s\n'%\
- (fname, lineNum, command, expected, result)
+ message = '\nFile %s, line %d\nCommand was:\n%s\nExpected:\n%s\nGot:\n%s\n' % \
+ (fname, lineNum, command, expected, result)
expected = self.expectationParser.transformString(expected)
# checking whitespace is a pain - let's skip it
expected = self.anyWhitespace.sub('', expected)
@@ -1620,8 +1709,9 @@ class Cmd2TestCase(unittest.TestCase):
if self.CmdApp:
self.outputTrap.tearDown()
+
if __name__ == '__main__':
- doctest.testmod(optionflags = doctest.NORMALIZE_WHITESPACE)
+ doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
'''
To make your application transcript-testable, replace
@@ -1645,5 +1735,3 @@ into a file, ``transcript.test``, and invoke the test like::
Wildcards can be used to test against multiple transcript files.
'''
-
-