From 1fe2d681507dcb226f0950ae089315dc2398ab44 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 12 Mar 2017 11:37:11 -0400 Subject: [PATCH 1/3] Code cleanup - Removed some redundant escape charaters in regular expressions - Removed some some unused arguments from functions and associated dead code --- cmd2.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/cmd2.py b/cmd2.py index 5ad6bf405..01270d120 100755 --- a/cmd2.py +++ b/cmd2.py @@ -524,7 +524,7 @@ class Cmd(cmd.Cmd): reserved_words = [] shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'} terminators = [';'] - urlre = re.compile('(https?://[-\\w\\./]+)') + urlre = re.compile('(https?://[-\\w./]+)') # Attributes which ARE dynamicaly settable at runtime abbrev = True # Abbreviated commands recognized @@ -748,18 +748,23 @@ def _init_parser(self): pyparsing.Optional(fileName) + (pyparsing.stringEnd | '|') self.inputParser.ignore(self.commentInProgress) - def preparse(self, raw, **kwargs): + def preparse(self, raw): return raw def postparse(self, parseResult): return parseResult - def parsed(self, raw, **kwargs): + def parsed(self, raw): + """ This function is where the actual parsing of each line occurs. + + :param raw: str - the line of text as it was entered + :return: ParsedString - custom subclass of str with extra attributes + """ if isinstance(raw, ParsedString): p = raw else: # preparse is an overridable hook; default makes no changes - s = self.preparse(raw, **kwargs) + s = self.preparse(raw) s = self.inputParser.transformString(s.lstrip()) s = self.commentGrammars.transformString(s) for (shortcut, expansion) in self.shortcuts: @@ -777,8 +782,6 @@ def parsed(self, raw, **kwargs): p = ParsedString(result.args) p.parsed = result p.parser = self.parsed - for (key, val) in kwargs.items(): - p.parsed[key] = val return p def postparsing_precmd(self, statement): @@ -1586,7 +1589,7 @@ def search(self, target): pattern = re.compile(target, re.IGNORECASE) return [s for s in self if pattern.search(s)] - spanpattern = re.compile(r'^\s*(?P\-?\d+)?\s*(?P:|(\.{2,}))?\s*(?P\-?\d+)?\s*$') + spanpattern = re.compile(r'^\s*(?P-?\d+)?\s*(?P:|(\.{2,}))?\s*(?P-?\d+)?\s*$') def span(self, raw): if raw.lower() in ('*', '-', 'all'): @@ -1609,7 +1612,7 @@ def span(self, raw): result.reverse() return result - rangePattern = re.compile(r'^\s*(?P[\d]+)?\s*\-\s*(?P[\d]+)?\s*$') + rangePattern = re.compile(r'^\s*(?P[\d]+)?\s*-\s*(?P[\d]+)?\s*$') def append(self, new): new = HistoryItem(new) From ea4d9cb99ba2390344c36a88c4d17c8769912867 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 12 Mar 2017 11:47:32 -0400 Subject: [PATCH 2/3] Fixed a bug where an exception would occur if the list command was run with an empty history. --- cmd2.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmd2.py b/cmd2.py index 01270d120..564c1d3f8 100755 --- a/cmd2.py +++ b/cmd2.py @@ -1581,6 +1581,15 @@ def to_index(self, raw): return result def search(self, target): + """ Search the history for a particular term. + + :param target: str - term to search for + :return: List[str] - list of matches + """ + # If there is no history yet, don't try to search through a non-existent list, just return an empty list + if len(self) < 1: + return [] + target = target.strip() if target[0] == target[-1] == '/' and len(target) > 1: target = target[1:-1] @@ -1858,4 +1867,5 @@ def __bool__(self): if __name__ == '__main__': # If run as the main application, simply start a bare-bones cmd2 application with only built-in functionality. app = Cmd() + app.debug = True app.cmdloop() From 82180e405f758061107f5c6e5e09c3e626177b3d Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Sun, 12 Mar 2017 12:57:29 -0400 Subject: [PATCH 3/3] Code cleanup - Added some lines to suppress innocuous PyCharm warnings - Added some comments to explain the purpose of some of the custom classes and functions - Added declaration of some attributes within __init__ for clarity --- cmd2.py | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/cmd2.py b/cmd2.py index 564c1d3f8..5ef4fe8e3 100755 --- a/cmd2.py +++ b/cmd2.py @@ -59,11 +59,13 @@ from six.moves import zip # Python 2 urllib2.urlopen() or Python3 urllib.request.urlopen() +# noinspection PyUnresolvedReferences from six.moves.urllib.request import urlopen # Python 3 compatability hack due to no built-in file keyword in Python 3 # Due to one occurence of isinstance(, file) checking to see if something is of file type try: + # noinspection PyUnboundLocalVariable,PyUnresolvedReferences file except NameError: import io @@ -72,6 +74,7 @@ # Detect whether IPython is installed to determine if the built-in "ipy" command should be included ipython_available = True try: + # noinspection PyUnresolvedReferences from IPython import embed except ImportError: ipython_available = False @@ -124,6 +127,16 @@ 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. + + Used mostly for getting access to the do_* method's docstring when printing help. + """ + def __init__(self): + # Call super class constructor. Need to do it in this way for Python 2 and 3 compatibility + optparse.OptionParser.__init__(self) + # The do_* method this class is parsing options for. Used for accessing docstring help. + self._func = None + def exit(self, status=0, msg=None): self.values._exit = True if msg: @@ -303,6 +316,7 @@ def __init__(self): if sys.platform == "win32": # Running on Windows try: + # noinspection PyUnresolvedReferences import win32clipboard def get_paste_buffer(): @@ -388,6 +402,13 @@ def get_paste_buffer(*args): class ParsedString(str): + """Subclass of str which also stores a pyparsing.ParseResults object containing structured parse results.""" + # pyarsing.ParseResults - structured parse results, to provide multiple means of access to the parsed data + parsed = None + + # Function which did the parsing + parser = None + def full_parsed_statement(self): new = ParsedString('%s %s' % (self.parsed.command, self.parsed.args)) new.parsed = self.parsed @@ -514,7 +535,6 @@ class Cmd(cmd.Cmd): default_to_shell = False defaultExtension = 'txt' # For ``save``, ``load``, etc. excludeFromHistory = '''run r list l history hi ed edit li eof'''.split() - kept_state = None # make sure your terminators are not in legalChars! legalChars = u'!#$%.:?@_-' + pyparsing.alphanums + pyparsing.alphas8bit multilineCommands = [] @@ -589,11 +609,11 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, use_ipython=False self.initial_stdout = sys.stdout self.history = History() self.pystate = {} + # noinspection PyUnresolvedReferences 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._init_parser() - self._temp_filename = None self._transcript_files = transcript_files # Used to enable the ability for a Python script to quit the application @@ -606,6 +626,13 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, use_ipython=False # Built-in commands don't make use of this. It is purely there for user-defined commands and convenience. self._last_result = None + # Used to save state during a redirection + self.kept_state = None + self.kept_sys = None + + # Used for a temp file during a pipe (needed tempfile instead of real pipe for Python 3.x prior to 3.5) + self._temp_filename = None + def poutput(self, msg): """Convenient shortcut for self.stdout.write(); adds newline if necessary.""" if msg: @@ -984,6 +1011,7 @@ def _cmdloop(self): # has been split out so that it can be called separately if self.use_rawinput and self.completekey: try: + # noinspection PyUnresolvedReferences import readline self.old_completer = readline.get_completer() readline.set_completer(self.complete) @@ -1003,6 +1031,7 @@ def _cmdloop(self): finally: if self.use_rawinput and self.completekey: try: + # noinspection PyUnresolvedReferences import readline readline.set_completer(self.old_completer) except ImportError: @@ -1517,6 +1546,7 @@ def run_commands_at_invocation(self, callargs): return self._STOP_AND_EXIT def cmdloop(self, intro=None): + callargs = None if self.allow_cli_args: parser = optparse.OptionParser() parser.add_option('-t', '--test', dest='test', @@ -1654,6 +1684,7 @@ def get(self, getme=None, fromEnd=False): end = int(end) return self[start:end] + # noinspection PyUnresolvedReferences getme = getme.strip() if getme.startswith(r'/') and getme.endswith(r'/'): @@ -1714,19 +1745,19 @@ def restore(self): class Borg(object): - '''All instances of any Borg subclass will share state. - from Python Cookbook, 2nd Ed., recipe 6.16''' + """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 = object.__new__(cls) 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.''' + """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 = ''