summaryrefslogtreecommitdiff
path: root/cmd2.py
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2.py')
-rwxr-xr-xcmd2.py148
1 files changed, 104 insertions, 44 deletions
diff --git a/cmd2.py b/cmd2.py
index 5ef4fe8e..c8bbecb0 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -84,6 +84,9 @@ __version__ = '0.7.1a'
# Pyparsing enablePackrat() can greatly speed up parsing, but problems have been seen in Python 3 in the past
pyparsing.ParserElement.enablePackrat()
+# Override the default whitespace chars in Pyparsing so that newlines are not treated as whitespace
+pyparsing.ParserElement.setDefaultWhitespaceChars(' \t')
+
# The next 3 variables and associated setter funtions effect how arguments are parsed for commands using @options.
# The defaults are "sane" and maximize backward compatibility with cmd and previous versions of cmd2.
@@ -127,7 +130,7 @@ def set_use_arg_list(val):
class OptionParser(optparse.OptionParser):
- """Subclase of optparse.OptionParser which stores a reference to the function/method it is parsing options for.
+ """Subclass of optparse.OptionParser which stores a reference to the do_* method it is parsing options for.
Used mostly for getting access to the do_* method's docstring when printing help.
"""
@@ -138,11 +141,19 @@ class OptionParser(optparse.OptionParser):
self._func = None
def exit(self, status=0, msg=None):
+ """Called at the end of showing help when either -h is used to show help or when bad arguments are provided.
+
+ We override exit so it doesn't automatically exit the applicaiton.
+ """
self.values._exit = True
if msg:
print(msg)
def print_help(self, *args, **kwargs):
+ """Called when optparse encounters either -h or --help or bad arguments. It prints help for options.
+
+ We override it so that before the standard optparse help, it prints the do_* method docstring, if available.
+ """
try:
print(self._func.__doc__)
except AttributeError:
@@ -215,7 +226,7 @@ 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,
+ """Used as a decorator and passed a list of optparse-style options,
alters a cmd2 method to populate its ``opts`` argument from its
raw text argument.
@@ -229,13 +240,18 @@ def options(option_list, arg_desc="arg"):
def do_something(self, arg, opts):
if opts.quick:
self.fast_button = True
- '''
+ """
if not isinstance(option_list, list):
option_list = [option_list]
for opt in option_list:
options_defined.append(pyparsing.Literal(opt.get_opt_string()))
def option_setup(func):
+ """Decorator function which modifies on of the do_* methods that use the @options decorator.
+
+ :param func: do_* method which uses the @options decorator
+ :return: modified version of the do_* method
+ """
optionParser = OptionParser()
for opt in option_list:
optionParser.add_option(opt)
@@ -247,6 +263,14 @@ def options(option_list, arg_desc="arg"):
optionParser._func = func
def new_func(instance, arg):
+ """For @options commands this replaces the actual do_* methods in the instance __dict__.
+
+ First it does all of the option/argument parsing. Then it calls the underlying do_* method.
+
+ :param instance: cmd2.Cmd2 derived class application instance
+ :param arg: str - command-line arguments provided to the comman
+ :return: bool - returns whatever the result of calling the underlying do_* method would be
+ """
try:
# Use shlex to split the command line into a list of arguments based on shell rules
opts, newArgList = optionParser.parse_args(shlex.split(arg, posix=POSIX_SHLEX))
@@ -290,23 +314,7 @@ def options(option_list, arg_desc="arg"):
return option_setup
-class PasteBufferError(EnvironmentError):
- if sys.platform[:3] == 'win':
- errmsg = """Redirecting to or from paste buffer requires pywin32
-to be installed on operating system.
-Download from http://sourceforge.net/projects/pywin32/"""
- elif sys.platform[:3] == 'dar':
- # Use built in pbcopy on Mac OSX
- pass
- else:
- 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)
-
-
+# Prefix to use on all OSes when the appropriate library or CLI tool isn't installed for getting access to paste buffer
pastebufferr = """Redirecting to or from paste buffer requires %s
to be installed on operating system.
%s"""
@@ -320,6 +328,10 @@ if sys.platform == "win32":
import win32clipboard
def get_paste_buffer():
+ """Get the contents of the clipboard for Windows OSes.
+
+ :return: str - contents of the clipboard
+ """
win32clipboard.OpenClipboard(0)
try:
result = win32clipboard.GetClipboardData()
@@ -329,6 +341,10 @@ if sys.platform == "win32":
return result
def write_to_paste_buffer(txt):
+ """Paste text to the clipboard for Windows OSes.
+
+ :param txt: str - text to paste to the clipboard
+ """
win32clipboard.OpenClipboard(0)
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText(txt)
@@ -337,6 +353,8 @@ if sys.platform == "win32":
can_clip = True
except ImportError:
def get_paste_buffer(*args):
+ """For Windows OSes without the appropriate libary installed to get text from clipboard, raise an exception.
+ """
raise OSError(pastebufferr % ('pywin32', 'Download from http://sourceforge.net/projects/pywin32/'))
write_to_paste_buffer = get_paste_buffer
@@ -356,18 +374,27 @@ elif sys.platform == 'darwin':
pass
if can_clip:
def get_paste_buffer():
+ """Get the contents of the clipboard for Mac OS X.
+
+ :return: str - contents of the clipboard
+ """
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):
+ """Paste text to the clipboard for Mac OS X.
+
+ :param txt: str - text to paste to the clipboard
+ """
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'))
+ """For Mac OS X without the appropriate tool installed to get text from clipboard, raise an exception."""
+ raise OSError(pastebufferr % ('pbcopy',
+ 'On MacOS X - error should not occur - part of the default installation'))
write_to_paste_buffer = get_paste_buffer
else:
@@ -380,11 +407,19 @@ else:
pass # something went wrong with xclip and we cannot use it
if can_clip:
def get_paste_buffer():
+ """Get the contents of the clipboard for Linux OSes.
+
+ :return: str - contents of the clipboard
+ """
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):
+ """Paste text to the clipboard for Linux OSes.
+
+ :param txt: str - text to paste to the clipboard
+ """
xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
xclipproc.stdin.write(txt.encode())
xclipproc.stdin.close()
@@ -394,12 +429,11 @@ else:
xclipproc.stdin.close()
else:
def get_paste_buffer(*args):
+ """For Linux without the appropriate tool installed to get text from clipboard, raise an exception."""
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):
"""Subclass of str which also stores a pyparsing.ParseResults object containing structured parse results."""
@@ -410,6 +444,7 @@ class ParsedString(str):
parser = None
def full_parsed_statement(self):
+ """Used to reconstruct the full parsed statement when a command isn't recognized."""
new = ParsedString('%s %s' % (self.parsed.command, self.parsed.args))
new.parsed = self.parsed
new.parser = self.parser
@@ -775,11 +810,23 @@ class Cmd(cmd.Cmd):
pyparsing.Optional(fileName) + (pyparsing.stringEnd | '|')
self.inputParser.ignore(self.commentInProgress)
+ # noinspection PyMethodMayBeStatic
def preparse(self, raw):
+ """Hook that runs before parsing the command-line and as the very first hook for a command.
+
+ :param raw: str - raw command line input
+ :return: str - potentially modified raw command line input
+ """
return raw
- def postparse(self, parseResult):
- return parseResult
+ # noinspection PyMethodMayBeStatic
+ def postparse(self, parse_result):
+ """Hook that runs immediately after parsing the command-line but before parsed() returns a ParsedString.
+
+ :param parse_result: pyparsing.ParseResults - parsing results output by the pyparsing parser
+ :return: pyparsing.ParseResults - potentially modified ParseResults object
+ """
+ return parse_result
def parsed(self, raw):
""" This function is where the actual parsing of each line occurs.
@@ -811,6 +858,7 @@ class Cmd(cmd.Cmd):
p.parser = self.parsed
return p
+ # noinspection PyMethodMayBeStatic
def postparsing_precmd(self, statement):
"""This runs after parsing the command-line, but before anything else; even before adding cmd to history.
@@ -828,6 +876,7 @@ class Cmd(cmd.Cmd):
stop = False
return stop, statement
+ # noinspection PyMethodMayBeStatic
def postparsing_postcmd(self, stop):
"""This runs after everything else, including after postcmd().
@@ -952,16 +1001,12 @@ class Cmd(cmd.Cmd):
self._temp_filename = None
def onecmd(self, line):
- """Interpret the argument as though it had been typed in response
- to the prompt.
-
- This may be overridden, but should not normally need to be;
- 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`.
-
+ """ This executes the actual do_* method for a command.
+
+ If the command provided doesn't exist, then it executes _default() instead.
+
+ :param line: ParsedString - subclass of string inclding the pyparsing ParseResults
+ :return: bool - a flag indicating whether the interpretatoin of commands should stop
"""
statement = self.parsed(line)
self.lastcmd = statement.parsed.raw
@@ -976,12 +1021,22 @@ class Cmd(cmd.Cmd):
return stop
def _default(self, statement):
+ """Executed when the command given isn't a recognized command implemented by a do_* method.
+
+ :param statement: ParsedString - subclass of string inclding the pyparsing ParseResults
+ :return:
+ """
arg = statement.full_parsed_statement()
if self.default_to_shell:
result = os.system(arg)
+ # If os.system() succeeded, then don't print warning about unknown command
if not result:
- return self.postparsing_postcmd(None)
- return self.postparsing_postcmd(self.default(arg))
+ return False
+
+ # Print out a message stating this is an unknown command
+ self.default(arg)
+
+ return False
def pseudo_raw_input(self, prompt):
"""copied from cmd's cmdloop; like raw_input, but accounts for changed stdin, stdout"""
@@ -1142,6 +1197,7 @@ class Cmd(cmd.Cmd):
except (ValueError, AttributeError, NotSettableError):
self.do_show(arg)
+ # noinspection PyMethodMayBeStatic
def do_pause(self, text):
"""Displays the specified text then waits for the user to press <Enter>.
@@ -1158,6 +1214,7 @@ class Cmd(cmd.Cmd):
Usage: pause [text]"""
self.stdout.write("{}\n".format(help_str))
+ # noinspection PyMethodMayBeStatic
def do_shell(self, command):
"""Execute a command as if at the OS prompt.
@@ -1234,6 +1291,7 @@ class Cmd(cmd.Cmd):
# Only include the do_ipy() method if IPython is available on the system
if ipython_available:
+ # noinspection PyMethodMayBeStatic
def do_ipy(self, arg):
"""Enters an interactive IPython shell.
@@ -1597,15 +1655,17 @@ class HistoryItem(str):
class History(list):
""" A list of HistoryItems that knows how to respond to user requests. """
- def zero_based_index(self, onebased):
+
+ # noinspection PyMethodMayBeStatic
+ def _zero_based_index(self, onebased):
result = onebased
if result > 0:
result -= 1
return result
- def to_index(self, raw):
+ def _to_index(self, raw):
if raw:
- result = self.zero_based_index(int(raw))
+ result = self._zero_based_index(int(raw))
else:
result = None
return result
@@ -1637,9 +1697,9 @@ class History(list):
if not results:
raise IndexError
if not results.group('separator'):
- return [self[self.to_index(results.group('start'))]]
- start = self.to_index(results.group('start'))
- end = self.to_index(results.group('end'))
+ return [self[self._to_index(results.group('start'))]]
+ start = self._to_index(results.group('start'))
+ end = self._to_index(results.group('end'))
reverse = False
if end is not None:
if end < start: