Repository: python-cmd2/cmd2
Pull request #74 (2017-03-13 01:59:01)

More cleanup
- Added a bunch of comments
- Deleted an unnecessarily override method
- Renamed variables and functions for PEP8 compliance
- Miscellaneous other code cleanup to minimize PyCharm warnings


Patch:
diff --git a/cmd2.py b/cmd2.py
index 9135fa37..987a4bcd 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -178,9 +178,9 @@ def remaining_args(opts_plus_args, arg_list):
     :return: str - non-option arguments as a single string, with original spacing preserved
     """
     pattern = '\s+'.join(re.escape(a) for a in arg_list) + '\s*$'
-    matchObj = re.search(pattern, opts_plus_args)
+    match_obj = re.search(pattern, opts_plus_args)
     try:
-        remaining = opts_plus_args[matchObj.start():]
+        remaining = opts_plus_args[match_obj.start():]
     except AttributeError:
         # Don't preserve spacing, but at least we don't crash and we do preserve args and their order
         remaining = ' '.join(arg_list)
@@ -189,10 +189,10 @@ def remaining_args(opts_plus_args, arg_list):
 
 
 def _attr_get_(obj, attr):
-    '''Returns an attribute's value, or None (no error) if undefined.
+    """Returns an attribute's value, or None (no error) if undefined.
        Analagous to .get() for dictionaries.  Useful when checking for
        value of options that may not have been defined on a given
-       method.'''
+       method."""
     try:
         return getattr(obj, attr)
     except AttributeError:
@@ -252,15 +252,15 @@ def options(option_list, arg_desc="arg"):
         :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)
+        option_parser = OptionParser()
+        for option in option_list:
+            option_parser.add_option(option)
         # Allow reasonable help for commands defined with @options and an empty list of options
         if len(option_list) > 0:
-            optionParser.set_usage("%s [options] %s" % (func.__name__[3:], arg_desc))
+            option_parser.set_usage("%s [options] %s" % (func.__name__[3:], arg_desc))
         else:
-            optionParser.set_usage("%s %s" % (func.__name__[3:], arg_desc))
-        optionParser._func = func
+            option_parser.set_usage("%s %s" % (func.__name__[3:], arg_desc))
+        option_parser._func = func
 
         def new_func(instance, arg):
             """For @options commands this replaces the actual do_* methods in the instance __dict__.
@@ -273,14 +273,14 @@ def options(option_list, arg_desc="arg"):
             """
             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))
+                opts, new_arglist = option_parser.parse_args(shlex.split(arg, posix=POSIX_SHLEX))
 
                 # If not using POSIX shlex, make sure to strip off outer quotes for convenience
                 if not POSIX_SHLEX and STRIP_QUOTES_FOR_NON_POSIX:
-                    new_arg_list = []
-                    for arg in newArgList:
-                        new_arg_list.append(strip_quotes(arg))
-                    newArgList = new_arg_list
+                    temp_arglist = []
+                    for arg in new_arglist:
+                        temp_arglist.append(strip_quotes(arg))
+                    new_arglist = temp_arglist
 
                     # Also strip off outer quotes on string option values
                     for key, val in opts.__dict__.items():
@@ -289,26 +289,26 @@ def options(option_list, arg_desc="arg"):
 
                 # 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:]
+                # if hasattr(arg, 'parsed') and new_arglist[0] == arg.parsed.command:
+                #    new_arglist = new_arglist[1:]
                 if USE_ARG_LIST:
-                    arg = newArgList
+                    arg = new_arglist
                 else:
-                    newArgs = remaining_args(arg, newArgList)
+                    new_args = remaining_args(arg, new_arglist)
                     if isinstance(arg, ParsedString):
-                        arg = arg.with_args_replaced(newArgs)
+                        arg = arg.with_args_replaced(new_args)
                     else:
-                        arg = newArgs
+                        arg = new_args
             except optparse.OptParseError as e:
                 print(e)
-                optionParser.print_help()
+                option_parser.print_help()
                 return
             if hasattr(opts, '_exit'):
                 return None
             result = func(instance, arg, opts)
             return result
 
-        new_func.__doc__ = '%s\n%s' % (func.__doc__, optionParser.format_help())
+        new_func.__doc__ = '%s\n%s' % (func.__doc__, option_parser.format_help())
         return new_func
 
     return option_setup
@@ -352,6 +352,7 @@ if sys.platform == "win32":
 
         can_clip = True
     except ImportError:
+        # noinspection PyUnusedLocal
         def get_paste_buffer(*args):
             """For Windows OSes without the appropriate libary installed to get text from clipboard, raise an exception.
             """
@@ -391,6 +392,7 @@ elif sys.platform == 'darwin':
                                           stderr=subprocess.PIPE)
             pbcopyproc.communicate(txt.encode())
     else:
+        # noinspection PyUnusedLocal
         def get_paste_buffer(*args):
             """For Mac OS X without the appropriate tool installed to get text from clipboard, raise an exception."""
             raise OSError(pastebufferr % ('pbcopy',
@@ -401,9 +403,10 @@ else:
     # Running on Linux
     try:
         with open(os.devnull, 'w') as DEVNULL:
-            subprocess.check_call('xclip -o -sel clip', shell=True, stdin=subprocess.PIPE, stdout=DEVNULL, stderr=DEVNULL)
+            subprocess.check_call('xclip -o -sel clip', shell=True, stdin=subprocess.PIPE, stdout=DEVNULL,
+                                  stderr=DEVNULL)
         can_clip = True
-    except Exception:
+    except (subprocess.CalledProcessError, OSError, IOError):
         pass  # something went wrong with xclip and we cannot use it
     if can_clip:
         def get_paste_buffer():
@@ -428,6 +431,7 @@ else:
             xclipproc.stdin.write(txt.encode())
             xclipproc.stdin.close()
     else:
+        # noinspection PyUnusedLocal
         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"'))
@@ -483,17 +487,17 @@ class StubbornDict(dict):
 
     def __add__(self, arg):
         selfcopy = copy.copy(self)
-        selfcopy.update(stubbornDict(arg))
+        selfcopy.update(stubborn_dict(arg))
         return selfcopy
 
     def __radd__(self, arg):
         selfcopy = copy.copy(self)
-        selfcopy.update(stubbornDict(arg))
+        selfcopy.update(stubborn_dict(arg))
         return selfcopy
 
     @classmethod
     def to_dict(cls, arg):
-        'Generates dictionary from string or list of strings'
+        """Generates dictionary from string or list of strings"""
         if hasattr(arg, 'splitlines'):
             arg = arg.splitlines()
         if hasattr(arg, '__reversed__'):
@@ -513,7 +517,7 @@ class StubbornDict(dict):
         return result
 
 
-def stubbornDict(*arg, **kwarg):
+def stubborn_dict(*arg, **kwarg):
     """ Factory function which creates instances of the StubornDict class.
 
     :param arg: an argument which could be used to construct a built-in dict dictionary
@@ -620,7 +624,7 @@ class Cmd(cmd.Cmd):
     timing = False  # Prints elapsed time for each command
 
     # To make an attribute settable with the "do_set" command, add it to this ...
-    settable = stubbornDict('''
+    settable = stubborn_dict('''
         abbrev                Accept abbreviated commands
         autorun_on_edit       Automatically run files after editing
         case_insensitive      upper- and lower-case both OK
@@ -650,7 +654,7 @@ class Cmd(cmd.Cmd):
         if not use_ipython:
             try:
                 del Cmd.do_ipy
-            except Exception:
+            except AttributeError:
                 pass
 
         # Call super class constructor.  Need to do it in this way for Python 2 and 3 compatibility
@@ -732,6 +736,7 @@ class Cmd(cmd.Cmd):
             return self.colorcodes[color][True] + val + self.colorcodes[color][False]
         return val
 
+    # noinspection PyUnusedLocal
     def do_cmdenvironment(self, args):
         """Summary report of interactive parameters."""
         self.stdout.write("""
@@ -755,6 +760,7 @@ class Cmd(cmd.Cmd):
         else:
             cmd.Cmd.do_help(self, arg)
 
+    # noinspection PyUnusedLocal
     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))
@@ -762,67 +768,67 @@ class Cmd(cmd.Cmd):
 
     def _init_parser(self):
         """ Initializes everything related to pyparsing. """
-        # outputParser = (pyparsing.Literal('>>') | (pyparsing.WordStart() + '>') | pyparsing.Regex('[^=]>'))('output')
-        outputParser = (pyparsing.Literal(self.redirector * 2) |
-                        (pyparsing.WordStart() + self.redirector) |
-                        pyparsing.Regex('[^=]' + self.redirector))('output')
+        # output_parser = (pyparsing.Literal('>>') | (pyparsing.WordStart() + '>') | pyparsing.Regex('[^=]>'))('output')
+        output_parser = (pyparsing.Literal(self.redirector * 2) |
+                         (pyparsing.WordStart() + self.redirector) |
+                         pyparsing.Regex('[^=]' + self.redirector))('output')
 
-        terminatorParser = pyparsing.Or(
+        terminator_parser = pyparsing.Or(
             [(hasattr(t, 'parseString') and t) or pyparsing.Literal(t) for t in self.terminators])('terminator')
-        stringEnd = pyparsing.stringEnd ^ '\nEOF'
+        string_end = pyparsing.stringEnd ^ '\nEOF'
         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')
+        oneline_command = (~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')) + \
+        do_not_parse = self.commentGrammars | self.commentInProgress | pyparsing.quotedString
+        after_elements = \
+            pyparsing.Optional(pipe + pyparsing.SkipTo(output_parser ^ string_end, ignore=do_not_parse)('pipeTo')) + \
             pyparsing.Optional(
-                outputParser + pyparsing.SkipTo(stringEnd, ignore=doNotParse).setParseAction(lambda x: x[0].strip())(
+                output_parser + pyparsing.SkipTo(string_end, ignore=do_not_parse).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())
+            oneline_command.setParseAction(lambda x: x[0].lower())
         if self.blankLinesAllowed:
             self.blankLineTerminationParser = pyparsing.NoMatch
         else:
             self.blankLineTerminator = (pyparsing.lineEnd + pyparsing.lineEnd)('terminator')
             self.blankLineTerminator.setResultsName('terminator')
-            self.blankLineTerminationParser = ((self.multilineCommand ^ oneLineCommand) +
-                                               pyparsing.SkipTo(self.blankLineTerminator, ignore=doNotParse).setParseAction(
+            self.blankLineTerminationParser = ((self.multilineCommand ^ oneline_command) +
+                                               pyparsing.SkipTo(self.blankLineTerminator, ignore=do_not_parse).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 = (((self.multilineCommand ^ oneline_command) + pyparsing.SkipTo(terminator_parser,
+                                                                                              ignore=do_not_parse).setParseAction(
+            lambda x: x[0].strip())('args') + terminator_parser)('statement') +
+                                pyparsing.SkipTo(output_parser ^ pipe ^ string_end, ignore=do_not_parse).setParseAction(
+                                    lambda x: x[0].strip())('suffix') + after_elements)
         self.multilineParser.ignore(self.commentInProgress)
-        self.singleLineParser = ((oneLineCommand + pyparsing.SkipTo(terminatorParser ^ stringEnd ^ pipe ^ outputParser,
-                                                                    ignore=doNotParse).setParseAction(
+        self.singleLineParser = ((oneline_command + pyparsing.SkipTo(terminator_parser ^ string_end ^ pipe ^ output_parser,
+                                                                     ignore=do_not_parse).setParseAction(
             lambda x: x[0].strip())('args'))('statement') +
-                                 pyparsing.Optional(terminatorParser) + afterElements)
+                                 pyparsing.Optional(terminator_parser) + after_elements)
         # self.multilineParser = self.multilineParser.setResultsName('multilineParser')
         # self.singleLineParser = self.singleLineParser.setResultsName('singleLineParser')
         self.blankLineTerminationParser = self.blankLineTerminationParser.setResultsName('statement')
         self.parser = self.prefixParser + (
-            stringEnd |
+            string_end |
             self.multilineParser |
             self.singleLineParser |
             self.blankLineTerminationParser |
-            self.multilineCommand + pyparsing.SkipTo(stringEnd, ignore=doNotParse)
+            self.multilineCommand + pyparsing.SkipTo(string_end, ignore=do_not_parse)
         )
         self.parser.ignore(self.commentGrammars)
 
-        inputMark = pyparsing.Literal('<')
-        inputMark.setParseAction(lambda x: '')
-        fileName = pyparsing.Word(self.legalChars + '/\\')
-        inputFrom = fileName('inputFrom')
-        inputFrom.setParseAction(replace_with_file_contents)
+        input_mark = pyparsing.Literal('<')
+        input_mark.setParseAction(lambda x: '')
+        file_name = pyparsing.Word(self.legalChars + '/\\')
+        input_from = file_name('inputFrom')
+        input_from.setParseAction(replace_with_file_contents)
         # a not-entirely-satisfactory way of distinguishing < as in "import from" from <
         # as in "lesser than"
-        self.inputParser = inputMark + pyparsing.Optional(inputFrom) + pyparsing.Optional('>') + \
-            pyparsing.Optional(fileName) + (pyparsing.stringEnd | '|')
+        self.inputParser = input_mark + pyparsing.Optional(input_from) + pyparsing.Optional('>') + \
+            pyparsing.Optional(file_name) + (pyparsing.stringEnd | '|')
         self.inputParser.ignore(self.commentInProgress)
 
     # noinspection PyMethodMayBeStatic
@@ -929,37 +935,33 @@ class Cmd(cmd.Cmd):
         :param line: str - line of text read from inp
         :return: bool - True if cmdloop() should exit, False otherwise
         """
-        # The outermost level of try/finally nesting can be condensed once
-        # Python 2.4 support can be dropped.
         stop = 0
         try:
-            statement = ''
+            statement = self.complete_statement(line)
+            (stop, statement) = self.postparsing_precmd(statement)
+            if stop:
+                return self.postparsing_postcmd(stop)
+            if statement.parsed.command not in self.excludeFromHistory:
+                self.history.append(statement.parsed.raw)
             try:
-                statement = self.complete_statement(line)
-                (stop, statement) = self.postparsing_precmd(statement)
-                if stop:
-                    return self.postparsing_postcmd(stop)
-                if statement.parsed.command not in self.excludeFromHistory:
-                    self.history.append(statement.parsed.raw)
-                try:
-                    if self.allow_redirection:
-                        self.redirect_output(statement)
-                    timestart = datetime.datetime.now()
-                    statement = self.precmd(statement)
-                    stop = self.onecmd(statement)
-                    stop = self.postcmd(stop, statement)
-                    if self.timing:
-                        self.pfeedback('Elapsed: %s' % str(datetime.datetime.now() - timestart))
-                finally:
-                    if self.allow_redirection:
-                        self.restore_output(statement)
-            except EmptyStatement:
-                pass
-            except ValueError as ex:
-                # If shlex.split failed on syntax, let user know whats going on
-                self.perror("Invalid syntax: {}".format(ex), traceback_war=False)
-            except Exception as ex:
-                self.perror(ex, type(ex).__name__)
+                if self.allow_redirection:
+                    self.redirect_output(statement)
+                timestart = datetime.datetime.now()
+                statement = self.precmd(statement)
+                stop = self.onecmd(statement)
+                stop = self.postcmd(stop, statement)
+                if self.timing:
+                    self.pfeedback('Elapsed: %s' % str(datetime.datetime.now() - timestart))
+            finally:
+                if self.allow_redirection:
+                    self.restore_output(statement)
+        except EmptyStatement:
+            pass
+        except ValueError as ex:
+            # If shlex.split failed on syntax, let user know whats going on
+            self.perror("Invalid syntax: {}".format(ex), traceback_war=False)
+        except Exception as ex:
+            self.perror(ex, type(ex).__name__)
         finally:
             return self.postparsing_postcmd(stop)
 
@@ -1098,6 +1100,10 @@ class Cmd(cmd.Cmd):
         """Repeatedly issue a prompt, accept input, parse an initial prefix
         off the received input, and dispatch to action methods, passing them
         the remainder of the line as argument.
+        
+        This serves the same role as cmd.cmdloop().
+        
+        :return: bool - True implies the entire application should exit.
         """
         # An almost perfect copy from Cmd; however, the pseudo_raw_input portion
         # has been split out so that it can be called separately
@@ -1113,6 +1119,7 @@ class Cmd(cmd.Cmd):
         stop = None
         try:
             while not stop:
+                # NOTE: cmdqueue appears completely unused, but it is defined in cmd.Cmd, so we are leaving it here
                 if self.cmdqueue:
                     line = self.cmdqueue.pop(0)
                 else:
@@ -1130,29 +1137,31 @@ class Cmd(cmd.Cmd):
                     pass
             return stop
 
+    # noinspection PyUnusedLocal
     def do_eof(self, arg):
-        """Automatically called at end of loading a script."""
-        return self._STOP_SCRIPT_NO_EXIT  # End of script; should not exit app
+        """Automatically called at end of loading a script or when <Ctrl>-D is pressed."""
+        # End of script should not exit app, but <Ctrl>-D should.
+        return self._STOP_SCRIPT_NO_EXIT
 
     def do_quit(self, arg):
         """Exits this application."""
         self._should_quit = True
         return self._STOP_AND_EXIT
 
-    def select(self, options, prompt='Your choice? '):
+    def select(self, opts, 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:
+           Argument ``opts`` 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
                                    that the return value can differ from
                                    the text advertised to the user """
-        local_opts = options
-        if isinstance(options, string_types):
-            local_opts = list(zip(options.split(), options.split()))
+        local_opts = opts
+        if isinstance(opts, string_types):
+            local_opts = list(zip(opts.split(), opts.split()))
         fulloptions = []
         for opt in local_opts:
             if isinstance(opt, string_types):
@@ -1209,29 +1218,29 @@ class Cmd(cmd.Cmd):
         Call without arguments for a list of settable parameters with their values.
         """
         try:
-            statement, paramName, val = arg.parsed.raw.split(None, 2)
+            statement, param_name, val = arg.parsed.raw.split(None, 2)
             val = val.strip()
-            paramName = paramName.strip().lower()
-            if paramName not in self.settable:
-                hits = [p for p in self.settable if p.startswith(paramName)]
+            param_name = param_name.strip().lower()
+            if param_name not in self.settable:
+                hits = [p for p in self.settable if p.startswith(param_name)]
                 if len(hits) == 1:
-                    paramName = hits[0]
+                    param_name = hits[0]
                 else:
-                    return self.do_show(paramName)
-            currentVal = getattr(self, paramName)
+                    return self.do_show(param_name)
+            current_val = getattr(self, param_name)
             if (val[0] == val[-1]) and val[0] in ("'", '"'):
                 val = val[1:-1]
             else:
-                val = cast(currentVal, val)
-            setattr(self, paramName, val)
-            self.stdout.write('%s - was: %s\nnow: %s\n' % (paramName, currentVal, val))
-            if currentVal != val:
+                val = cast(current_val, val)
+            setattr(self, param_name, val)
+            self.stdout.write('%s - was: %s\nnow: %s\n' % (param_name, current_val, val))
+            if current_val != val:
                 try:
-                    onchange_hook = getattr(self, '_onchange_%s' % paramName)
-                    onchange_hook(old=currentVal, new=val)
+                    onchange_hook = getattr(self, '_onchange_%s' % param_name)
+                    onchange_hook(old=current_val, new=val)
                 except AttributeError:
                     pass
-        except (ValueError, AttributeError, NotSettableError):
+        except (ValueError, AttributeError):
             self.do_show(arg)
 
     # noinspection PyMethodMayBeStatic
@@ -1285,24 +1294,24 @@ class Cmd(cmd.Cmd):
         arg = arg.parsed.raw[2:].strip()
 
         # Support the run command even if called prior to invoking an interactive interpreter
-        def run(arg):
+        def run(filename):
             """Run a Python script file in the interactive console.
             
-            :param arg: str - filename of *.py script file to run
+            :param filename: str - filename of *.py script file to run
             """
             try:
-                with open(arg) as f:
+                with open(filename) as f:
                     interp.runcode(f.read())
             except IOError as e:
                 self.perror(e)
 
-        def onecmd_plus_hooks(arg):
+        def onecmd_plus_hooks(cmd_plus_args):
             """Run a cmd2.Cmd command from a Python script or the interactive Python console.
             
-            :param arg: str - command line including command and arguments to run
+            :param cmd_plus_args: str - command line including command and arguments to run
             :return: bool - True if cmdloop() should exit once leaving the interactive Python console, False otherwise.
             """
-            return self.onecmd_plus_hooks(arg + '\n')
+            return self.onecmd_plus_hooks(cmd_plus_args + '\n')
 
         self.pystate['run'] = run
         self.pystate['cmd'] = onecmd_plus_hooks
@@ -1314,6 +1323,7 @@ class Cmd(cmd.Cmd):
         if arg.strip():
             interp.runcode(arg)
         else:
+            # noinspection PyShadowingBuiltins
             def quit():
                 """Function callable from the interactive Python console to exit that environment and return to cmd2."""
                 raise EmbeddedConsoleExit
@@ -1338,7 +1348,7 @@ class Cmd(cmd.Cmd):
 
     # Only include the do_ipy() method if IPython is available on the system
     if ipython_available:
-        # noinspection PyMethodMayBeStatic
+        # noinspection PyMethodMayBeStatic,PyUnusedLocal
         def do_ipy(self, arg):
             """Enters an interactive IPython shell.
 
@@ -1641,7 +1651,7 @@ Script should contain one command per line, just like command would be typed in
         runme = self.last_matching(arg)
         self.pfeedback(runme)
         if runme:
-            stop = self.onecmd_plus_hooks(runme)
+            return self.onecmd_plus_hooks(runme)
 
     def help_run(self):
         """Print help for do_run()."""
@@ -1653,7 +1663,14 @@ Script should contain one command per line, just like command would be typed in
     arg is /enclosed in forward-slashes/ -> run most recent by regex"""
         self.stdout.write("{}\n".format(help_str))
 
-    def runTranscriptTests(self, callargs):
+    def run_transcript_tests(self, callargs):
+        """Runs transcript tests for provided file(s).
+        
+        This is called when either -t is provided on the command line or the transcript_files argument is provided 
+        during construction of the cmd2.Cmd instance.
+        
+        :param callargs: List[str] - list of transcript test file names
+        """
         class TestMyAppCase(Cmd2TestCase):
             CmdApp = self.__class__
 
@@ -1665,11 +1682,26 @@ Script should contain one command per line, just like command would be typed in
         result.printErrors()
 
     def run_commands_at_invocation(self, callargs):
+        """Runs commands provided as arguments on the command line when the application is started.
+        
+        :param callargs: List[str] - list of strings where each string is a command plus its arguments
+        :return: bool - True implies the entire application should exit
+        """
         for initial_command in callargs:
             if self.onecmd_plus_hooks(initial_command + '\n'):
                 return self._STOP_AND_EXIT
 
     def cmdloop(self, intro=None):
+        """This is an outer wrapper around _cmdloop() which deals with extra features provided by cmd2.
+        
+        _cmdloop() provides the main loop equivalent to cmd.cmdloop().  This is a wrapper around that which deals with
+        the following extra feactures provided by cmd2:
+        - commands at invocation
+        - transcript testing
+        - intro banner
+        
+        :param intro: str - if provided this overrides self.intro and serves as the intro banner printed once at start
+        """
         callargs = None
         if self.allow_cli_args:
             parser = optparse.OptionParser()
@@ -1681,7 +1713,7 @@ Script should contain one command per line, just like command would be typed in
                 self._transcript_files = callargs
 
         if self._transcript_files is not None:
-            self.runTranscriptTests(self._transcript_files)
+            self.run_transcript_tests(self._transcript_files)
         else:
             # Always run the preloop first
             self.preloop()
@@ -1708,14 +1740,24 @@ Script should contain one command per line, just like command would be typed in
 
 
 class HistoryItem(str):
+    """Class used to represent an item in the History list.
+    
+    Thing wrapper around str class which adds a custom format for printing.  It also keeps track of its index in the 
+    list as well as a lowercase representation of itself for convenience/efficiency.
+    """
     listformat = '-------------------------[%d]\n%s\n'
 
+    # noinspection PyUnusedLocal
     def __init__(self, instr):
         str.__init__(self)
         self.lowercase = self.lower()
         self.idx = None
 
     def pr(self):
+        """Represent a HistoryItem in a pretty fashion suitable for printing.
+        
+        :return: str - pretty print string version of a HistoryItem
+        """
         return self.listformat % (self.idx, str(self))
 
 
@@ -1757,6 +1799,11 @@ class History(list):
     spanpattern = re.compile(r'^\s*(?P<start>-?\d+)?\s*(?P<separator>:|(\.{2,}))?\s*(?P<end>-?\d+)?\s*$')
 
     def span(self, raw):
+        """Parses the input string search for a span pattern and if if found, returns a slice from the History list.
+        
+        :param raw: str - string potentially containing a span of the forms a..b, a:b, a:, ..b
+        :return: List[HistoryItem] - slice from the History list
+        """
         if raw.lower() in ('*', '-', 'all'):
             raw = ':'
         results = self.spanpattern.search(raw)
@@ -1780,15 +1827,20 @@ class History(list):
     rangePattern = re.compile(r'^\s*(?P<start>[\d]+)?\s*-\s*(?P<end>[\d]+)?\s*$')
 
     def append(self, new):
+        """Append a HistoryItem to end of the History list
+        
+        :param new: str - command line to convert to HistoryItem and add to the end of the History list
+        """
         new = HistoryItem(new)
         list.append(self, new)
         new.idx = len(self)
 
-    def extend(self, new):
-        for n in new:
-            self.append(n)
-
-    def get(self, getme=None, fromEnd=False):
+    def get(self, getme=None):
+        """Get an item or items from the History list using 1-based indexing.
+        
+        :param getme: int or str - item(s) to get - either an integer index or string to search for
+        :return: List[str] - list of HistoryItems matching the retrieval criteria
+        """
         if not getme:
             return self
         try:
@@ -1800,10 +1852,10 @@ class History(list):
         except IndexError:
             return []
         except ValueError:
-            rangeResult = self.rangePattern.search(getme)
-            if rangeResult:
-                start = rangeResult.group('start') or None
-                end = rangeResult.group('start') or None
+            range_result = self.rangePattern.search(getme)
+            if range_result:
+                start = range_result.group('start') or None
+                end = range_result.group('start') or None
                 if start:
                     start = int(start) - 1
                 if end:
@@ -1817,19 +1869,30 @@ class History(list):
                 finder = re.compile(getme[1:-1], re.DOTALL | re.MULTILINE | re.IGNORECASE)
 
                 def isin(hi):
+                    """Listcomp filter function for doing a regular expression search of History.
+                    
+                    :param hi: HistoryItem
+                    :return: bool - True if search matches
+                    """
                     return finder.search(hi)
             else:
                 def isin(hi):
+                    """Listcomp filter function for doing a case-insensitive string search of History.
+                    
+                    :param hi: HistoryItem
+                    :return: bool - True if search matches
+                    """
                     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."""
+    """Tries to force a new value into the same type as the current when trying to set the value for a parameter.
+    
+    :param current: current value for the parameter, type varies
+    :param new: str - new value
+    :return: new value with same type as current, or the current value if there was an error casting
+    """
     typ = type(current)
     if typ == bool:
         try:
@@ -1838,7 +1901,7 @@ def cast(current, new):
             pass
         try:
             new = new.lower()
-        except:
+        except AttributeError:
             pass
         if (new == 'on') or (new[0] in ('y', 't')):
             return True
@@ -1847,24 +1910,32 @@ def cast(current, new):
     else:
         try:
             return typ(new)
-        except:
+        except (ValueError, TypeError):
             pass
     print("Problem setting parameter (now %s) to %s; incorrect type?" % (current, new))
     return current
 
 
 class Statekeeper(object):
+    """Class used to save and restore state during load and py commands as well as when redirecting output or pipes."""
     def __init__(self, obj, attribs):
+        """Use the instance attributes as a generic key-value store to copy instance attributes from outer object.
+        
+        :param obj: instance of cmd2.Cmd derived class (your application instance)
+        :param attribs: Tuple[str] - tuple of strings listing attributes of obj to save a copy of
+        """
         self.obj = obj
         self.attribs = attribs
         if self.obj:
-            self.save()
+            self._save()
 
-    def save(self):
+    def _save(self):
+        """Create copies of attributes from self.obj inside this Statekeeper instance."""
         for attrib in self.attribs:
             setattr(self, attrib, getattr(self.obj, attrib))
 
     def restore(self):
+        """Overwrite attributes in self.obj with the saved values stored in this Statekeeper instance."""
         if self.obj:
             for attrib in self.attribs:
                 setattr(self.obj, attrib, getattr(self, attrib))
@@ -1891,22 +1962,31 @@ class OutputTrap(Borg):
         sys.stdout = self
 
     def write(self, txt):
+        """Add text to the internal contents.
+        
+        :param txt: str
+        """
         self.contents += txt
 
     def read(self):
+        """Read from the internal contents and then clear them out.
+        
+        :return: str - text from the internal contents
+        """
         result = self.contents
         self.contents = ''
         return result
 
-    def tearDown(self):
+    def tear_down(self):
+        """Restores normal output."""
         sys.stdout = self.old_stdout
         self.contents = ''
 
 
 class Cmd2TestCase(unittest.TestCase):
-    '''Subclass this, setting CmdApp, to make a unittest.TestCase class
+    """Subclass this, setting CmdApp, to make a unittest.TestCase class
        that will execute the commands in a transcript file and expect the results shown.
-       See example.py'''
+       See example.py"""
     CmdApp = None
 
     def fetchTranscripts(self):
@@ -1939,10 +2019,10 @@ class Cmd2TestCase(unittest.TestCase):
     anyWhitespace = re.compile(r'\s', re.DOTALL | re.MULTILINE)
 
     def _test_transcript(self, fname, transcript):
-        lineNum = 0
+        line_num = 0
         finished = False
         line = next(transcript)
-        lineNum += 1
+        line_num += 1
         while not finished:
             # Scroll forward to where actual commands begin
             while not line.startswith(self.cmdapp.prompt):
@@ -1951,7 +2031,7 @@ class Cmd2TestCase(unittest.TestCase):
                 except StopIteration:
                     finished = True
                     break
-                lineNum += 1
+                line_num += 1
             command = [line[len(self.cmdapp.prompt):]]
             line = next(transcript)
             # Read the entirety of a multi-line command
@@ -1961,10 +2041,10 @@ class Cmd2TestCase(unittest.TestCase):
                     line = next(transcript)
                 except StopIteration:
                     raise (StopIteration,
-                           'Transcript broke off while reading command beginning at line {} with\n{}'.format(lineNum,
+                           'Transcript broke off while reading command beginning at line {} with\n{}'.format(line_num,
                                                                                                              command[0])
                            )
-                lineNum += 1
+                line_num += 1
             command = ''.join(command)
             # Send the command into the application and capture the resulting output
             stop = self.cmdapp.onecmd_plus_hooks(command)
@@ -1973,7 +2053,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, line_num, command, result)
                 self.assert_(not (result.strip()), message)
                 continue
             expected = []
@@ -1984,11 +2064,11 @@ class Cmd2TestCase(unittest.TestCase):
                 except StopIteration:
                     finished = True
                     break
-                lineNum += 1
+                line_num += 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, line_num, command, expected, result)
             expected = self.expectationParser.transformString(expected)
             # checking whitespace is a pain - let's skip it
             expected = self.anyWhitespace.sub('', expected)
@@ -1997,7 +2077,7 @@ class Cmd2TestCase(unittest.TestCase):
 
     def tearDown(self):
         if self.CmdApp:
-            self.outputTrap.tearDown()
+            self.outputTrap.tear_down()
 
 
 #noinspection PyClassHasNoInit


Test patch:
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
index f073bc01..d6b64093 100644
--- a/tests/test_parsing.py
+++ b/tests/test_parsing.py
@@ -51,13 +51,13 @@ def test_stubborn_dict_class():
                                  ('shoe', 'schuh'), ('small', 'klein')]
 
 def test_stubborn_dict_factory():
-    assert sorted(cmd2.stubbornDict('cow a bovine\nhorse an equine').items()) == [('cow', 'a bovine'),
-                                                                                  ('horse', 'an equine')]
-    assert sorted(cmd2.stubbornDict(['badger', 'porcupine a poky creature']).items()) == [('badger', ''),
-                                                                                          ('porcupine',
+    assert sorted(cmd2.stubborn_dict('cow a bovine\nhorse an equine').items()) == [('cow', 'a bovine'),
+                                                                                   ('horse', 'an equine')]
+    assert sorted(cmd2.stubborn_dict(['badger', 'porcupine a poky creature']).items()) == [('badger', ''),
+                                                                                           ('porcupine',
                                                                                            'a poky creature')]
-    assert sorted(cmd2.stubbornDict(turtle='has shell', frog='jumpy').items()) == [('frog', 'jumpy'),
-                                                                                   ('turtle', 'has shell')]
+    assert sorted(cmd2.stubborn_dict(turtle='has shell', frog='jumpy').items()) == [('frog', 'jumpy'),
+                                                                                    ('turtle', 'has shell')]
 
 
 def test_history_span(hist):