summaryrefslogtreecommitdiff
path: root/cmd2.py
diff options
context:
space:
mode:
authorFederico Ceratto <federico.ceratto@gmail.com>2016-02-20 15:46:53 +0000
committerFederico Ceratto <federico.ceratto@gmail.com>2016-02-20 15:46:53 +0000
commita4af014d66d213e9817e60c76c5582b6ea25a29c (patch)
tree0f1c2d0fd811867891d98675ed9af7c137672193 /cmd2.py
parent5b863b50752684350872c66ce7a6ead60cd13f87 (diff)
downloadcmd2-git-a4af014d66d213e9817e60c76c5582b6ea25a29c.tar.gz
Minor cleanup
Diffstat (limited to 'cmd2.py')
-rwxr-xr-xcmd2.py321
1 files changed, 170 insertions, 151 deletions
diff --git a/cmd2.py b/cmd2.py
index f9740af7..63e422f3 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -16,8 +16,8 @@ Easy transcript-based testing of applications (see example/example.py)
Bash-style ``select`` available
Note that redirection with > and | will only work if `self.stdout.write()`
-is used in place of `print`. The standard library's `cmd` module is
-written to use `self.stdout.write()`,
+is used in place of `print`. The standard library's `cmd` module is
+written to use `self.stdout.write()`,
- Catherine Devlin, Jan 03 2008 - catherinedevlin.blogspot.com
@@ -66,7 +66,7 @@ class OptionParser(optparse.OptionParser):
self.values._exit = True
if msg:
print (msg)
-
+
def print_help(self, *args, **kwargs):
try:
print (self._func.__doc__)
@@ -82,19 +82,21 @@ class OptionParser(optparse.OptionParser):
should either exit or raise an exception.
"""
raise optparse.OptParseError(msg)
-
+
+
def remaining_args(oldArgs, newArgList):
'''
Preserves the spacing originally in the argument after
the removal of options.
-
+
>>> remaining_args('-f bar bar cow', ['bar', 'cow'])
'bar cow'
'''
pattern = '\s+'.join(re.escape(a) for a in newArgList) + '\s*$'
matchObj = re.search(pattern, oldArgs)
return oldArgs[matchObj.start():]
-
+
+
def _attr_get_(obj, attr):
'''Returns an attribute's value, or None (no error) if undefined.
Analagous to .get() for dictionaries. Useful when checking for
@@ -105,16 +107,18 @@ def _attr_get_(obj, attr):
except AttributeError:
return None
+
def _which(editor):
try:
return subprocess.Popen(['which', editor], stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0]
except OSError:
return None
-
+
optparse.Values.get = _attr_get_
-
+
options_defined = [] # used to distinguish --options from SQL-style --comments
+
def options(option_list, arg_desc="arg"):
'''Used as a decorator and passed a list of optparse-style options,
alters a cmd2 method to populate its ``opts`` argument from its
@@ -144,7 +148,7 @@ def options(option_list, arg_desc="arg"):
def new_func(instance, arg):
try:
opts, newArgList = optionParser.parse_args(arg.split())
- # Must find the remaining args in the original argument list, but
+ # 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:
# newArgList = newArgList[1:]
@@ -159,12 +163,14 @@ def options(option_list, arg_desc="arg"):
return
if hasattr(opts, '_exit'):
return None
- result = func(instance, arg, opts)
- return result
+ result = func(instance, arg, opts)
+ return result
+
new_func.__doc__ = '%s\n%s' % (func.__doc__, optionParser.format_help())
return new_func
return option_setup
+
class PasteBufferError(EnvironmentError):
if sys.platform[:3] == 'win':
errmsg = """Redirecting to or from paste buffer requires pywin32
@@ -174,9 +180,9 @@ Download from http://sourceforge.net/projects/pywin32/"""
# Use built in pbcopy on Mac OSX
pass
else:
- errmsg = """Redirecting to or from paste buffer requires xclip
+ 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."""
+On Debian/Ubuntu, 'sudo apt-get install xclip' will install it."""
def __init__(self):
Exception.__init__(self, self.errmsg)
@@ -194,12 +200,12 @@ if subprocess.mswindows:
except TypeError:
result = '' #non-text
win32clipboard.CloseClipboard()
- return result
+ return result
def write_to_paste_buffer(txt):
win32clipboard.OpenClipboard(0)
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText(txt)
- win32clipboard.CloseClipboard()
+ win32clipboard.CloseClipboard()
except ImportError:
def get_paste_buffer(*args):
raise OSError(pastebufferr % ('pywin32', 'Download from http://sourceforge.net/projects/pywin32/'))
@@ -234,14 +240,14 @@ 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
pass
except Exception:
pass # something went wrong with xclip and we cannot use it
- if can_clip:
+ if can_clip:
def get_paste_buffer():
xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
return xclipproc.stdout.read()
@@ -257,15 +263,17 @@ 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')
+
class ParsedString(str):
def full_parsed_statement(self):
new = ParsedString('%s %s' % (self.parsed.command, self.parsed.args))
new.parsed = self.parsed
new.parser = self.parser
- return new
+ return new
+
def with_args_replaced(self, newargs):
new = ParsedString(newargs)
new.parsed = self.parsed
@@ -273,11 +281,11 @@ class ParsedString(str):
new.parsed['args'] = newargs
new.parsed.statement['args'] = newargs
return new
-
+
class StubbornDict(dict):
'''Dictionary that tolerates many input formats.
Create it with stubbornDict(arg) factory function.
-
+
>>> d = StubbornDict(large='gross', small='klein')
>>> sorted(d.items())
[('large', 'gross'), ('small', 'klein')]
@@ -287,29 +295,32 @@ class StubbornDict(dict):
>>> d += ' girl Frauelein, Maedchen\\n\\n shoe schuh'
>>> 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):
self.update(arg)
return self
+
def __add__(self, arg):
selfcopy = copy.copy(self)
selfcopy.update(stubbornDict(arg))
return selfcopy
+
def __radd__(self, arg):
selfcopy = copy.copy(self)
selfcopy.update(stubbornDict(arg))
- return selfcopy
-
+ return selfcopy
+
@classmethod
def to_dict(cls, arg):
'Generates dictionary from string or list of strings'
if hasattr(arg, 'splitlines'):
arg = arg.splitlines()
if hasattr(arg, '__reversed__'):
- result = {}
+ result = {}
for a in arg:
a = a.strip()
if a:
@@ -324,6 +335,7 @@ class StubbornDict(dict):
result = arg
return result
+
def stubbornDict(*arg, **kwarg):
'''
>>> sorted(stubbornDict('cow a bovine\\nhorse an equine').items())
@@ -336,9 +348,10 @@ def stubbornDict(*arg, **kwarg):
result = {}
for a in arg:
result.update(StubbornDict.to_dict(a))
- result.update(kwarg)
+ result.update(kwarg)
return StubbornDict(result)
-
+
+
def replace_with_file_contents(fname):
if fname:
try:
@@ -347,14 +360,17 @@ def replace_with_file_contents(fname):
result = '< %s' % fname[0] # wasn't a file after all
else:
result = get_paste_buffer()
- return result
+ return result
+
class EmbeddedConsoleExit(SystemExit):
pass
+
class EmptyStatement(Exception):
pass
+
def ljust(x, width, fillchar=' '):
'analogous to str.ljust, but works for lists'
if hasattr(x, 'ljust'):
@@ -363,11 +379,11 @@ def ljust(x, width, fillchar=' '):
if len(x) < width:
x = (x + [fillchar] * width)[:width]
return x
-
+
class Cmd(cmd.Cmd):
echo = False
case_insensitive = True # Commands recognized regardless of case
- continuation_prompt = '> '
+ continuation_prompt = '> '
timing = False # Prints elapsed time for each command
# make sure your terminators are not in legalChars!
legalChars = u'!#$%.:?@_' + pyparsing.alphanums + pyparsing.alphas8bit
@@ -392,25 +408,27 @@ class Cmd(cmd.Cmd):
continuation_prompt On 2nd+ line of input
debug Show full error stack on error
default_file_name for ``save``, ``load``, etc.
- editor Program used by ``edit``
+ editor Program used by ``edit``
case_insensitive upper- and lower-case both OK
- feedback_to_output include nonessentials in `|`, `>` results
+ feedback_to_output include nonessentials in `|`, `>` results
quiet Don't print nonessential feedback
echo Echo command issued into output
timing Report execution times
abbrev Accept abbreviated commands
''')
-
+
def poutput(self, msg):
'''Convenient shortcut for self.stdout.write(); adds newline if necessary.'''
if msg:
self.stdout.write(msg)
if msg[-1] != '\n':
self.stdout.write('\n')
+
def perror(self, errmsg, statement=None):
if self.debug:
traceback.print_exc()
print (str(errmsg))
+
def pfeedback(self, msg):
"""For printing nonessential feedback. Can be silenced with `quiet`.
Inclusion in redirected output is controlled by `feedback_to_output`."""
@@ -439,7 +457,7 @@ class Cmd(cmd.Cmd):
'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
+ '''Given a string (``val``), returns that string wrapped in UNIX-style
special characters that turn on (and then off) text color and style.
If the ``colors`` environment paramter is ``False``, or the application
is running on Windows, will return ``val`` unchanged.
@@ -459,7 +477,7 @@ class Cmd(cmd.Cmd):
'terminators': str(self.terminators),
'settable': ' '.join(self.settable)
})
-
+
def do_help(self, arg):
if arg:
funcname = self.func_named(arg)
@@ -471,17 +489,17 @@ class Cmd(cmd.Cmd):
cmd.Cmd.do_help(self, funcname[3:])
else:
cmd.Cmd.do_help(self, arg)
-
- def __init__(self, *args, **kwargs):
+
+ def __init__(self, *args, **kwargs):
cmd.Cmd.__init__(self, *args, **kwargs)
self.initial_stdout = sys.stdout
self.history = History()
self.pystate = {}
self.shortcuts = sorted(self.shortcuts.items(), reverse=True)
- self.keywords = self.reserved_words + [fname[3:] for fname in dir(self)
- if fname.startswith('do_')]
+ self.keywords = self.reserved_words + [fname[3:] for fname in dir(self)
+ if fname.startswith('do_')]
self._init_parser()
-
+
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))
@@ -495,7 +513,7 @@ class Cmd(cmd.Cmd):
terminators = [';']
blankLinesAllowed = False
multilineCommands = []
-
+
def _init_parser(self):
r'''
>>> c = Cmd()
@@ -505,21 +523,21 @@ class Cmd(cmd.Cmd):
>>> print (c.parser.parseString('').dump())
[]
>>> print (c.parser.parseString('').dump())
- []
+ []
>>> print (c.parser.parseString('/* empty command */').dump())
- []
+ []
>>> print (c.parser.parseString('plainword').dump())
['plainword', '']
- command: plainword
- statement: ['plainword', '']
- - command: plainword
+ - command: plainword
>>> print (c.parser.parseString('termbare;').dump())
['termbare', '', ';', '']
- command: termbare
- statement: ['termbare', '', ';']
- command: termbare
- terminator: ;
- - terminator: ;
+ - terminator: ;
>>> print (c.parser.parseString('termbare; suffx').dump())
['termbare', '', ';', 'suffx']
- command: termbare
@@ -527,7 +545,7 @@ class Cmd(cmd.Cmd):
- command: termbare
- terminator: ;
- suffix: suffx
- - terminator: ;
+ - terminator: ;
>>> print (c.parser.parseString('barecommand').dump())
['barecommand', '']
- command: barecommand
@@ -582,7 +600,7 @@ class Cmd(cmd.Cmd):
- outputTo: afile.txt
- statement: ['output', 'into']
- args: into
- - command: output
+ - command: output
>>> print (c.parser.parseString('output into;sufx | pipethrume plz > afile.txt').dump())
['output', 'into', ';', 'sufx', '|', ' pipethrume plz', '>', 'afile.txt']
- args: into
@@ -621,10 +639,10 @@ class Cmd(cmd.Cmd):
- args: > inside
- command: has
- terminator: ;
- - terminator: ;
+ - terminator: ;
>>> print (c.parser.parseString('multiline has > inside an unfinished command').dump())
['multiline', ' has > inside an unfinished command']
- - multilineCommand: multiline
+ - multilineCommand: multiline
>>> print (c.parser.parseString('multiline has > inside;').dump())
['multiline', 'has > inside', ';', '']
- args: has > inside
@@ -633,7 +651,7 @@ class Cmd(cmd.Cmd):
- args: has > inside
- multilineCommand: multiline
- terminator: ;
- - terminator: ;
+ - terminator: ;
>>> print (c.parser.parseString('multiline command /* with comment in progress;').dump())
['multiline', ' command /* with comment in progress;']
- multilineCommand: multiline
@@ -676,7 +694,7 @@ class Cmd(cmd.Cmd):
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')
stringEnd = pyparsing.stringEnd ^ '\nEOF'
self.multilineCommand = pyparsing.Or([pyparsing.Keyword(c, caseless=self.case_insensitive) for c in self.multilineCommands])('multilineCommand')
@@ -708,11 +726,11 @@ class Cmd(cmd.Cmd):
stringEnd |
self.multilineParser |
self.singleLineParser |
- self.blankLineTerminationParser |
+ self.blankLineTerminationParser |
self.multilineCommand + pyparsing.SkipTo(stringEnd, ignore=doNotParse)
)
self.parser.ignore(self.commentGrammars)
-
+
inputMark = pyparsing.Literal('<')
inputMark.setParseAction(lambda x: '')
fileName = pyparsing.Word(self.legalChars + '/\\')
@@ -722,13 +740,13 @@ class Cmd(cmd.Cmd):
# as in "lesser than"
self.inputParser = inputMark + pyparsing.Optional(inputFrom) + pyparsing.Optional('>') + \
pyparsing.Optional(fileName) + (pyparsing.stringEnd | '|')
- self.inputParser.ignore(self.commentInProgress)
-
+ self.inputParser.ignore(self.commentInProgress)
+
def preparse(self, raw, **kwargs):
return raw
def postparse(self, parseResult):
return parseResult
-
+
def parsed(self, raw, **kwargs):
if isinstance(raw, ParsedString):
p = raw
@@ -742,8 +760,8 @@ class Cmd(cmd.Cmd):
s = s.replace(shortcut, expansion + ' ', 1)
break
result = self.parser.parseString(s)
- result['raw'] = raw
- result['command'] = result.multilineCommand or result.command
+ result['raw'] = raw
+ result['command'] = result.multilineCommand or result.command
result = self.postparse(result)
p = ParsedString(result.args)
p.parsed = result
@@ -751,13 +769,13 @@ class Cmd(cmd.Cmd):
for (key, val) in kwargs.items():
p.parsed[key] = val
return p
-
+
def postparsing_precmd(self, statement):
stop = 0
return stop, statement
def postparsing_postcmd(self, stop):
return stop
-
+
def func_named(self, arg):
result = None
target = 'do_' + arg
@@ -780,7 +798,7 @@ class Cmd(cmd.Cmd):
if stop:
return self.postparsing_postcmd(stop)
if statement.parsed.command not in self.excludeFromHistory:
- self.history.append(statement.parsed.raw)
+ self.history.append(statement.parsed.raw)
try:
self.redirect_output(statement)
timestart = datetime.datetime.now()
@@ -794,9 +812,9 @@ class Cmd(cmd.Cmd):
except EmptyStatement:
return 0
except Exception as e:
- self.perror(str(e), statement)
+ self.perror(str(e), statement)
finally:
- return self.postparsing_postcmd(stop)
+ return self.postparsing_postcmd(stop)
def complete_statement(self, line):
"""Keep accepting lines of input until the command is complete."""
if (not line) or (
@@ -805,13 +823,13 @@ class Cmd(cmd.Cmd):
raise EmptyStatement()
statement = self.parsed(line)
while statement.parsed.multilineCommand and (statement.parsed.terminator == ''):
- statement = '%s\n%s' % (statement.parsed.raw,
- self.pseudo_raw_input(self.continuation_prompt))
+ statement = '%s\n%s' % (statement.parsed.raw,
+ self.pseudo_raw_input(self.continuation_prompt))
statement = self.parsed(statement)
if not statement.parsed.command:
raise EmptyStatement()
return statement
-
+
def redirect_output(self, statement):
if statement.parsed.pipeTo:
self.kept_state = Statekeeper(self, ('stdout',))
@@ -821,18 +839,18 @@ class Cmd(cmd.Cmd):
elif statement.parsed.output:
if (not statement.parsed.outputTo) and (not can_clip):
raise EnvironmentError('Cannot redirect to paste buffer; install ``xclip`` and re-run to enable')
- self.kept_state = Statekeeper(self, ('stdout',))
+ self.kept_state = Statekeeper(self, ('stdout',))
self.kept_sys = Statekeeper(sys, ('stdout',))
if statement.parsed.outputTo:
mode = 'w'
if statement.parsed.output == 2 * self.redirector:
mode = 'a'
- sys.stdout = self.stdout = open(os.path.expanduser(statement.parsed.outputTo), mode)
+ sys.stdout = self.stdout = open(os.path.expanduser(statement.parsed.outputTo), mode)
else:
sys.stdout = self.stdout = tempfile.TemporaryFile(mode="w+")
if statement.parsed.output == '>>':
self.stdout.write(get_paste_buffer())
-
+
def restore_output(self, statement):
if self.kept_state:
if statement.parsed.output:
@@ -840,13 +858,13 @@ class Cmd(cmd.Cmd):
self.stdout.seek(0)
write_to_paste_buffer(self.stdout.read())
elif statement.parsed.pipeTo:
- for result in self.redirect.communicate():
- self.kept_state.stdout.write(result or '')
+ for result in self.redirect.communicate():
+ self.kept_state.stdout.write(result or '')
self.stdout.close()
- self.kept_state.restore()
+ self.kept_state.restore()
self.kept_sys.restore()
- self.kept_state = None
-
+ self.kept_state = None
+
def onecmd(self, line):
"""Interpret the argument as though it had been typed in response
to the prompt.
@@ -855,12 +873,12 @@ class Cmd(cmd.Cmd):
see the precmd() and postcmd() methods for useful execution hooks.
The return value is a flag indicating whether interpretation of
commands by the interpreter should stop.
-
+
This (`cmd2`) version of `onecmd` already override's `cmd`'s `onecmd`.
"""
statement = self.parsed(line)
- self.lastcmd = statement.parsed.raw
+ self.lastcmd = statement.parsed.raw
funcname = self.func_named(statement.parsed.command)
if not funcname:
return self._default(statement)
@@ -868,9 +886,9 @@ class Cmd(cmd.Cmd):
func = getattr(self, funcname)
except AttributeError:
return self._default(statement)
- stop = func(statement)
- return stop
-
+ stop = func(statement)
+ return stop
+
def _default(self, statement):
arg = statement.full_parsed_statement()
if self.default_to_shell:
@@ -881,7 +899,7 @@ class Cmd(cmd.Cmd):
def pseudo_raw_input(self, prompt):
"""copied from cmd's cmdloop; like raw_input, but accounts for changed stdin, stdout"""
-
+
if self.use_rawinput:
try:
line = raw_input(prompt)
@@ -895,9 +913,9 @@ class Cmd(cmd.Cmd):
line = 'EOF'
else:
if line[-1] == '\n': # this was always true in Cmd
- line = line[:-1]
+ line = line[:-1]
return line
-
+
def _cmdloop(self, intro=None):
"""Repeatedly issue a prompt, accept input, parse an initial prefix
off the received input, and dispatch to action methods, passing them
@@ -906,7 +924,7 @@ class Cmd(cmd.Cmd):
# An almost perfect copy from Cmd; however, the pseudo_raw_input portion
# has been split out so that it can be called separately
-
+
self.preloop()
if self.use_rawinput and self.completekey:
try:
@@ -937,27 +955,27 @@ class Cmd(cmd.Cmd):
import readline
readline.set_completer(self.old_completer)
except ImportError:
- pass
+ pass
return stop
def do_EOF(self, arg):
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
-
+
def select(self, options, prompt='Your choice? '):
'''Presents a numbered menu to the user. Modelled after
the bash shell's SELECT. Returns the item chosen.
-
+
Argument ``options`` can be:
| a single string -> will be split into one-word options
| a list of strings -> will be offered as options
- | a list of tuples -> interpreted as (value, text), so
+ | a list of tuples -> interpreted as (value, text), so
that the return value can differ from
the text advertised to the user '''
if isinstance(options, basestring):
@@ -982,9 +1000,9 @@ class Cmd(cmd.Cmd):
except ValueError:
pass # loop and ask again
return result
-
+
@options([make_option('-l', '--long', action="store_true",
- help="describe function of parameter")])
+ help="describe function of parameter")])
def do_show(self, arg, opts):
'''Shows value of a parameter.'''
param = arg.strip().lower()
@@ -1002,11 +1020,11 @@ class Cmd(cmd.Cmd):
self.poutput(result[p])
else:
raise NotImplementedError("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
-
+
def do_set(self, arg):
'''
Sets a cmd2 parameter. Accepts abbreviated parameter names so long
- as there is no ambiguity. Call without arguments for a list of
+ as there is no ambiguity. Call without arguments for a list of
settable parameters with their values.'''
try:
statement, paramName, val = arg.parsed.raw.split(None, 2)
@@ -1021,7 +1039,7 @@ class Cmd(cmd.Cmd):
currentVal = getattr(self, paramName)
if (val[0] == val[-1]) and val[0] in ("'", '"'):
val = val[1:-1]
- else:
+ else:
val = cast(currentVal, val)
setattr(self, paramName, val)
self.stdout.write('%s - was: %s\nnow: %s\n' % (paramName, currentVal, val))
@@ -1033,16 +1051,16 @@ class Cmd(cmd.Cmd):
pass
except (ValueError, AttributeError, NotSettableError) as e:
self.do_show(arg)
-
+
def do_pause(self, arg):
'Displays the specified text then waits for the user to press RETURN.'
raw_input(arg + '\n')
-
+
def do_shell(self, arg):
'execute a command as if at the OS prompt.'
os.system(arg)
-
- def do_py(self, arg):
+
+ def do_py(self, arg):
'''
py <command>: Executes a Python command.
py: Enters interactive Python mode.
@@ -1074,7 +1092,7 @@ class Cmd(cmd.Cmd):
self.pystate['cmd'] = onecmd_plus_hooks
self.pystate['run'] = run
try:
- cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
+ cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
keepstate = Statekeeper(sys, ('stdin','stdout'))
sys.stdout = self.stdout
sys.stdin = self.stdin
@@ -1083,12 +1101,12 @@ class Cmd(cmd.Cmd):
except EmbeddedConsoleExit:
pass
keepstate.restore()
-
+
@options([make_option('-s', '--script', action="store_true", help="Script format; no separation lines"),
], arg_desc = '(limit on which commands to include)')
def do_history(self, arg, opts):
"""history [arg]: lists past commands issued
-
+
| no arg: list all
| arg is integer: list one history item, by index
| arg is string: string search
@@ -1110,10 +1128,10 @@ class Cmd(cmd.Cmd):
else:
return self.history[-1]
except IndexError:
- return None
+ return None
def do_list(self, arg):
"""list [arg]: lists last command issued
-
+
no arg -> list most recent command
arg is integer -> list one history item, by index
a..b, a:b, a:, ..b -> list spans from a (or start) to b (or end)
@@ -1130,12 +1148,12 @@ class Cmd(cmd.Cmd):
do_hi = do_history
do_l = do_list
do_li = do_list
-
+
def do_ed(self, arg):
"""ed: edit most recent command in text editor
ed [N]: edit numbered command from history
ed [filename]: edit specified file name
-
+
commands are run after editor is closed.
"set edit (program-name)" or set EDITOR environment variable
to control which editing program is used."""
@@ -1154,21 +1172,21 @@ class Cmd(cmd.Cmd):
if buffer:
f = open(os.path.expanduser(filename), 'w')
f.write(buffer or '')
- f.close()
-
+ f.close()
+
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)
+ pyparsing.stringEnd)
def do_save(self, arg):
"""`save [N] [filename.ext]`
Saves command from history to file.
- | N => Number of command (from history), or `*`;
+ | N => Number of command (from history), or `*`;
| most recent command if omitted"""
try:
@@ -1191,7 +1209,7 @@ class Cmd(cmd.Cmd):
except Exception as e:
self.perror('Error saving %s' % (fname))
raise
-
+
def read_file_or_url(self, fname):
# TODO: not working on localhost
if isinstance(fname, file):
@@ -1204,24 +1222,24 @@ class Cmd(cmd.Cmd):
fname = os.path.expanduser(fname)
try:
result = open(os.path.expanduser(fname), 'r')
- except IOError:
- result = open('%s.%s' % (os.path.expanduser(fname),
+ except IOError:
+ result = open('%s.%s' % (os.path.expanduser(fname),
self.defaultExtension), 'r')
return result
-
+
def do__relative_load(self, arg=None):
'''
Runs commands in script at file or URL; if this is called from within an
- already-running script, the filename will be interpreted relative to the
+ already-running script, the filename will be interpreted relative to the
already-running script's directory.'''
if arg:
arg = arg.split(None, 1)
targetname, args = arg[0], (arg[1:] or [''])[0]
targetname = os.path.join(self.current_script_dir or '', targetname)
self.do__load('%s %s' % (targetname, args))
-
+
urlre = re.compile('(https?://[-\\w\\./]+)')
- def do_load(self, arg=None):
+ def do_load(self, arg=None):
"""Runs script of command(s) from a file or URL."""
if arg is None:
targetname = self.default_file_name
@@ -1235,7 +1253,7 @@ class Cmd(cmd.Cmd):
return
keepstate = Statekeeper(self, ('stdin','use_rawinput','prompt',
'continuation_prompt','current_script_dir'))
- self.stdin = target
+ self.stdin = target
self.use_rawinput = False
self.prompt = self.continuation_prompt = ''
self.current_script_dir = os.path.split(targetname)[0]
@@ -1243,24 +1261,24 @@ class Cmd(cmd.Cmd):
self.stdin.close()
keepstate.restore()
self.lastcmd = ''
- return stop and (stop != self._STOP_SCRIPT_NO_EXIT)
+ 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):
"""run [arg]: re-runs an earlier command
-
+
no arg -> run most recent command
arg is integer -> run one history item, by index
arg is string -> run most recent command by string search
arg is /enclosed in forward-slashes/ -> run most recent by regex
- """
+ """
'run [N]: runs the SQL that was run N commands ago'
runme = self.last_matching(arg)
self.pfeedback(runme)
if runme:
stop = self.onecmd_plus_hooks(runme)
- do_r = do_run
-
+ do_r = do_run
+
def fileimport(self, statement, source):
try:
f = open(os.path.expanduser(source))
@@ -1273,7 +1291,7 @@ class Cmd(cmd.Cmd):
def runTranscriptTests(self, callargs):
class TestMyAppCase(Cmd2TestCase):
- CmdApp = self.__class__
+ CmdApp = self.__class__
self.__class__.testfiles = callargs
sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main()
testcase = TestMyAppCase()
@@ -1289,15 +1307,15 @@ class Cmd(cmd.Cmd):
def cmdloop(self):
parser = optparse.OptionParser()
parser.add_option('-t', '--test', dest='test',
- action="store_true",
+ action="store_true",
help='Test against transcript(s) in FILE (wildcards OK)')
(callopts, callargs) = parser.parse_args()
if callopts.test:
self.runTranscriptTests(callargs)
else:
if not self.run_commands_at_invocation(callargs):
- self._cmdloop()
-
+ self._cmdloop()
+
class HistoryItem(str):
listformat = '-------------------------[%d]\n%s\n'
def __init__(self, instr):
@@ -1306,7 +1324,7 @@ class HistoryItem(str):
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')])
@@ -1315,15 +1333,15 @@ class History(list):
>>> h.span('2..3')
['second', 'third']
>>> h.span('3')
- ['third']
+ ['third']
>>> h.span(':')
['first', 'second', 'third', 'fourth']
>>> h.span('2..')
['second', 'third', 'fourth']
>>> h.span('-1')
- ['fourth']
+ ['fourth']
>>> h.span('-2..-3')
- ['third', 'second']
+ ['third', 'second']
>>> h.search('o')
['second', 'fourth']
>>> h.search('/IR/')
@@ -1369,7 +1387,7 @@ class History(list):
if reverse:
result.reverse()
return result
-
+
rangePattern = re.compile(r'^\s*(?P<start>[\d]+)?\s*\-\s*(?P<end>[\d]+)?\s*$')
def append(self, new):
new = HistoryItem(new)
@@ -1378,7 +1396,7 @@ class History(list):
def extend(self, new):
for n in new:
self.append(n)
-
+
def get(self, getme=None, fromEnd=False):
if not getme:
return self
@@ -1400,7 +1418,7 @@ class History(list):
if end:
end = int(end)
return self[start:end]
-
+
getme = getme.strip()
if getme.startswith(r'/') and getme.endswith(r'/'):
@@ -1414,7 +1432,7 @@ class History(list):
class NotSettableError(Exception):
pass
-
+
def cast(current, new):
"""Tries to force a new value into the same type as the current."""
typ = type(current)
@@ -1424,7 +1442,7 @@ def cast(current, new):
except (ValueError, TypeError):
pass
try:
- new = new.lower()
+ new = new.lower()
except:
pass
if (new=='on') or (new[0] in ('y','t')):
@@ -1438,7 +1456,7 @@ def cast(current, new):
pass
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
@@ -1451,7 +1469,7 @@ class Statekeeper(object):
def restore(self):
if self.obj:
for attrib in self.attribs:
- setattr(self.obj, attrib, getattr(self, attrib))
+ setattr(self.obj, attrib, getattr(self, attrib))
class Borg(object):
'''All instances of any Borg subclass will share state.
@@ -1461,7 +1479,7 @@ class Borg(object):
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.'''
@@ -1478,7 +1496,8 @@ class OutputTrap(Borg):
def tearDown(self):
sys.stdout = self.old_stdout
self.contents = ''
-
+
+
class Cmd2TestCase(unittest.TestCase):
'''Subclass this, setting CmdApp, to make a unittest.TestCase class
that will execute the commands in a transcript file and expect the results shown.
@@ -1532,11 +1551,11 @@ class Cmd2TestCase(unittest.TestCase):
try:
line = transcript.next()
except StopIteration:
- raise (StopIteration,
- 'Transcript broke off while reading command beginning at line %d with\n%s'
+ raise (StopIteration,
+ 'Transcript broke off while reading command beginning at line %d with\n%s'
% (command[0]))
lineNum += 1
- command = ''.join(command)
+ 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``
@@ -1544,7 +1563,7 @@ class Cmd2TestCase(unittest.TestCase):
# 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)
+ (fname, lineNum, command, result)
self.assert_(not(result.strip()), message)
continue
expected = []
@@ -1553,13 +1572,13 @@ class Cmd2TestCase(unittest.TestCase):
try:
line = transcript.next()
except StopIteration:
- finished = True
+ finished = True
break
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)
+ (fname, lineNum, command, expected, result)
expected = self.expectationParser.transformString(expected)
# checking whitespace is a pain - let's skip it
expected = self.anyWhitespace.sub('', expected)
@@ -1572,22 +1591,22 @@ class Cmd2TestCase(unittest.TestCase):
if __name__ == '__main__':
doctest.testmod(optionflags = doctest.NORMALIZE_WHITESPACE)
-
+
'''
-To make your application transcript-testable, replace
+To make your application transcript-testable, replace
::
app = MyApp()
app.cmdloop()
-
+
with
::
app = MyApp()
cmd2.run(app)
-
+
Then run a session of your application and paste the entire screen contents
into a file, ``transcript.test``, and invoke the test like::