Repository: python-cmd2/cmd2

Issue:
Add type hinting
Now that ``cmd2`` no longer needs to support Python 2.7 from release 0.9.0 onward, we can begin to add [type hinting](https://docs.python.org/3/library/typing.html) to function/method arguments and return values.

Everyone is encouraged to begin doing this for all new and modified code.  In the long run it should make the code easier to read and maintain.

Note: If you use type hinting for function/method arguments and/or return values, then **just** use type hinting.  Don't put the type in a docstring comment if you are using type hinting.  With tools like the [mypy](http://mypy-lang.org) static analysis type checker and [PyCharm](https://www.jetbrains.com/help/pycharm/type-hinting-in-product.html), it is much easier to ensure that Python 3 optional type hints stay in sync with actual code changes.  It is much more likely that the documentation comments could get out of sync.  

*If you modify an existing function/method to add type hinting and it previously had type info in the docstring, then please remove the type info from the docstring.*

Please do NOT start adding type hinting to local variables as support for this syntax wasn't added until Python 3.6.  

Also, for now please be careful to use types from the ``typing`` module which exist in Python 3.5 and the ``typing`` module backport for Python 3.4 - a lot of additional types were added to that module in Python 3.6.

Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index ad7f428b..46298801 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,6 @@ htmlcov
 
 # Visual Studio Code
 .vscode
+
+# mypy optional static type checker
+.mypy_cache
diff --git a/cmd2/__init__.py b/cmd2/__init__.py
index 617d643b..e9a82acb 100644
--- a/cmd2/__init__.py
+++ b/cmd2/__init__.py
@@ -1,4 +1,5 @@
 #
 # -*- coding: utf-8 -*-
+"""This simply imports certain things for backwards compatibility."""
 from .cmd2 import __version__, Cmd, CmdResult, Statement, EmptyStatement, categorize
 from .cmd2 import with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index 904fb7b3..1bc7f342 100755
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -80,16 +80,16 @@ ACTION_SUPPRESS_HINT = 'suppress_hint'
 
 
 class CompletionItem(str):
-    def __new__(cls, o, desc='', *args, **kwargs):
+    def __new__(cls, o, desc='', *args, **kwargs) -> str:
         return str.__new__(cls, o, *args, **kwargs)
 
     # noinspection PyMissingConstructor,PyUnusedLocal
-    def __init__(self, o, desc='', *args, **kwargs):
+    def __init__(self, o, desc='', *args, **kwargs) -> None:
         self.description = desc
 
 
 class _RangeAction(object):
-    def __init__(self, nargs: Union[int, str, Tuple[int, int], None]):
+    def __init__(self, nargs: Union[int, str, Tuple[int, int], None]) -> None:
         self.nargs_min = None
         self.nargs_max = None
 
@@ -128,7 +128,7 @@ class _StoreRangeAction(argparse._StoreAction, _RangeAction):
                  choices=None,
                  required=False,
                  help=None,
-                 metavar=None):
+                 metavar=None) -> None:
 
         _RangeAction.__init__(self, nargs)
 
@@ -157,7 +157,7 @@ class _AppendRangeAction(argparse._AppendAction, _RangeAction):
                  choices=None,
                  required=False,
                  help=None,
-                 metavar=None):
+                 metavar=None) -> None:
 
         _RangeAction.__init__(self, nargs)
 
@@ -174,7 +174,7 @@ class _AppendRangeAction(argparse._AppendAction, _RangeAction):
                                         metavar=metavar)
 
 
-def register_custom_actions(parser: argparse.ArgumentParser):
+def register_custom_actions(parser: argparse.ArgumentParser) -> None:
     """Register custom argument action types"""
     parser.register('action', None, _StoreRangeAction)
     parser.register('action', 'store', _StoreRangeAction)
@@ -185,14 +185,14 @@ class AutoCompleter(object):
     """Automatically command line tab completion based on argparse parameters"""
 
     class _ArgumentState(object):
-        def __init__(self):
+        def __init__(self) -> None:
             self.min = None
             self.max = None
             self.count = 0
             self.needed = False
             self.variable = False
 
-        def reset(self):
+        def reset(self) -> None:
             """reset tracking values"""
             self.min = None
             self.max = None
@@ -206,7 +206,7 @@ class AutoCompleter(object):
                  arg_choices: Dict[str, Union[List, Tuple, Callable]] = None,
                  subcmd_args_lookup: dict = None,
                  tab_for_arg_help: bool = True,
-                 cmd2_app=None):
+                 cmd2_app=None) -> None:
         """
         Create an AutoCompleter
 
@@ -439,7 +439,7 @@ class AutoCompleter(object):
 
         return completion_results
 
-    def _format_completions(self, action, completions: List[Union[str, CompletionItem]]):
+    def _format_completions(self, action, completions: List[Union[str, CompletionItem]]) -> List[str]:
         if completions and len(completions) > 1 and isinstance(completions[0], CompletionItem):
             token_width = len(action.dest)
             completions_with_desc = []
@@ -665,7 +665,7 @@ class AutoCompleter(object):
 class ACHelpFormatter(argparse.HelpFormatter):
     """Custom help formatter to configure ordering of help text"""
 
-    def _format_usage(self, usage, actions, groups, prefix):
+    def _format_usage(self, usage, actions, groups, prefix) -> str:
         if prefix is None:
             prefix = _('Usage: ')
 
@@ -778,7 +778,7 @@ class ACHelpFormatter(argparse.HelpFormatter):
         # prefix with 'usage:'
         return '%s%s\n\n' % (prefix, usage)
 
-    def _format_action_invocation(self, action):
+    def _format_action_invocation(self, action) -> str:
         if not action.option_strings:
             default = self._get_default_metavar_for_positional(action)
             metavar, = self._metavar_formatter(action, default)(1)
@@ -803,7 +803,7 @@ class ACHelpFormatter(argparse.HelpFormatter):
                 return ', '.join(action.option_strings) + ' ' + args_string
             # End cmd2 customization
 
-    def _metavar_formatter(self, action, default_metavar):
+    def _metavar_formatter(self, action, default_metavar) -> Callable:
         if action.metavar is not None:
             result = action.metavar
         elif action.choices is not None:
@@ -822,7 +822,7 @@ class ACHelpFormatter(argparse.HelpFormatter):
                 return (result, ) * tuple_size
         return format
 
-    def _format_args(self, action, default_metavar):
+    def _format_args(self, action, default_metavar) -> str:
         get_metavar = self._metavar_formatter(action, default_metavar)
         # Begin cmd2 customization (less verbose)
         if isinstance(action, _RangeAction) and \
@@ -837,7 +837,7 @@ class ACHelpFormatter(argparse.HelpFormatter):
             result = super()._format_args(action, default_metavar)
         return result
 
-    def _split_lines(self, text, width):
+    def _split_lines(self, text: str, width) -> List[str]:
         return text.splitlines()
 
 
@@ -845,7 +845,7 @@ class ACHelpFormatter(argparse.HelpFormatter):
 class ACArgumentParser(argparse.ArgumentParser):
     """Custom argparse class to override error method to change default help text."""
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, *args, **kwargs) -> None:
         if 'formatter_class' not in kwargs:
             kwargs['formatter_class'] = ACHelpFormatter
 
@@ -855,7 +855,7 @@ class ACArgumentParser(argparse.ArgumentParser):
         self._custom_error_message = ''
 
     # Begin cmd2 customization
-    def set_custom_message(self, custom_message=''):
+    def set_custom_message(self, custom_message: str='') -> None:
         """
         Allows an error message override to the error() function, useful when forcing a
         re-parse of arguments with newly required parameters
@@ -863,7 +863,7 @@ class ACArgumentParser(argparse.ArgumentParser):
         self._custom_error_message = custom_message
     # End cmd2 customization
 
-    def error(self, message):
+    def error(self, message: str) -> None:
         """Custom error override. Allows application to control the error being displayed by argparse"""
         if len(self._custom_error_message) > 0:
             message = self._custom_error_message
@@ -884,7 +884,7 @@ class ACArgumentParser(argparse.ArgumentParser):
         self.print_help()
         sys.exit(1)
 
-    def format_help(self):
+    def format_help(self) -> str:
         """Copy of format_help() from argparse.ArgumentParser with tweaks to separately display required parameters"""
         formatter = self._get_formatter()
 
@@ -934,7 +934,7 @@ class ACArgumentParser(argparse.ArgumentParser):
         # determine help from format above
         return formatter.format_help()
 
-    def _get_nargs_pattern(self, action):
+    def _get_nargs_pattern(self, action) -> str:
         # Override _get_nargs_pattern behavior to use the nargs ranges provided by AutoCompleter
         if isinstance(action, _RangeAction) and \
                 action.nargs_min is not None and action.nargs_max is not None:
@@ -947,7 +947,7 @@ class ACArgumentParser(argparse.ArgumentParser):
             return nargs_pattern
         return super(ACArgumentParser, self)._get_nargs_pattern(action)
 
-    def _match_argument(self, action, arg_strings_pattern):
+    def _match_argument(self, action, arg_strings_pattern) -> int:
         # match the pattern for this action to the arg strings
         nargs_pattern = self._get_nargs_pattern(action)
         match = _re.match(nargs_pattern, arg_strings_pattern)
@@ -963,7 +963,7 @@ class ACArgumentParser(argparse.ArgumentParser):
 
     # This is the official python implementation with a 5 year old patch applied
     # See the comment below describing the patch
-    def _parse_known_args(self, arg_strings, namespace):  # pragma: no cover
+    def _parse_known_args(self, arg_strings, namespace) -> Tuple[argparse.Namespace, List[str]]:  # pragma: no cover
         # replace arg strings that are file references
         if self.fromfile_prefix_chars is not None:
             arg_strings = self._read_args_from_files(arg_strings)
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index d0d8352c..3d2a8afe 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -39,7 +39,7 @@ import platform
 import re
 import shlex
 import sys
-from typing import Callable, List, Union, Tuple
+from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Union
 
 import pyperclip
 
@@ -146,6 +146,7 @@ def categorize(func: Union[Callable, Iterable], category: str) -> None:
     else:
         setattr(func, HELP_CATEGORY, category)
 
+
 def parse_quoted_string(cmdline: str) -> List[str]:
     """Parse a quoted string into a list of arguments."""
     if isinstance(cmdline, list):
@@ -322,6 +323,30 @@ class EmptyStatement(Exception):
     pass
 
 
+class HistoryItem(str):
+    """Class used to represent an item in the History list.
+
+    Thin 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 = '-------------------------[{}]\n{}\n'
+
+    # noinspection PyUnusedLocal
+    def __init__(self, instr: str) -> None:
+        str.__init__(self)
+        self.lowercase = self.lower()
+        self.idx = None
+
+    def pr(self) -> str:
+        """Represent a HistoryItem in a pretty fashion suitable for printing.
+
+        :return: pretty print string version of a HistoryItem
+        """
+        return self.listformat.format(self.idx, str(self).rstrip())
+
+
 class Cmd(cmd.Cmd):
     """An easy but powerful framework for writing line-oriented command interpreters.
 
@@ -376,18 +401,19 @@ class Cmd(cmd.Cmd):
                 'quiet': "Don't print nonessential feedback",
                 'timing': 'Report execution times'}
 
-    def __init__(self, completekey='tab', stdin=None, stdout=None, persistent_history_file='',
-                 persistent_history_length=1000, startup_script=None, use_ipython=False, transcript_files=None):
+    def __init__(self, completekey: str='tab', stdin=None, stdout=None, persistent_history_file: str='',
+                 persistent_history_length: int=1000, startup_script: Optional[str]=None, use_ipython: bool=False,
+                 transcript_files: Optional[List[str]]=None) -> None:
         """An easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package.
 
-        :param completekey: str - (optional) readline name of a completion key, default to Tab
+        :param completekey: (optional) readline name of a completion key, default to Tab
         :param stdin: (optional) alternate input file object, if not specified, sys.stdin is used
         :param stdout: (optional) alternate output file object, if not specified, sys.stdout is used
-        :param persistent_history_file: str - (optional) file path to load a persistent readline history from
-        :param persistent_history_length: int - (optional) max number of lines which will be written to the history file
-        :param startup_script: str - (optional) file path to a a script to load and execute at startup
+        :param persistent_history_file: (optional) file path to load a persistent readline history from
+        :param persistent_history_length: (optional) max number of lines which will be written to the history file
+        :param startup_script: (optional) file path to a a script to load and execute at startup
         :param use_ipython: (optional) should the "ipy" command be included for an embedded IPython shell
-        :param transcript_files: str - (optional) allows running transcript tests when allow_cli_args is False
+        :param transcript_files: (optional) allows running transcript tests when allow_cli_args is False
         """
         # If use_ipython is False, make sure the do_ipy() method doesn't exit
         if not use_ipython:
@@ -511,31 +537,32 @@ class Cmd(cmd.Cmd):
     # -----  Methods related to presenting output to the user -----
 
     @property
-    def visible_prompt(self):
+    def visible_prompt(self) -> str:
         """Read-only property to get the visible prompt with any ANSI escape codes stripped.
 
         Used by transcript testing to make it easier and more reliable when users are doing things like coloring the
         prompt using ANSI color codes.
 
-        :return: str - prompt stripped of any ANSI escape codes
+        :return: prompt stripped of any ANSI escape codes
         """
         return utils.strip_ansi(self.prompt)
 
-    def _finalize_app_parameters(self):
+    def _finalize_app_parameters(self) -> None:
+        """Finalize the shortcuts and settable parameters."""
         # noinspection PyUnresolvedReferences
         self.shortcuts = sorted(self.shortcuts.items(), reverse=True)
 
         # Make sure settable parameters are sorted alphabetically by key
         self.settable = collections.OrderedDict(sorted(self.settable.items(), key=lambda t: t[0]))
 
-    def poutput(self, msg, end='\n'):
+    def poutput(self, msg: str, end: str='\n') -> None:
         """Convenient shortcut for self.stdout.write(); by default adds newline to end if not already present.
 
         Also handles BrokenPipeError exceptions for when a commands's output has been piped to another process and
         that process terminates before the cmd2 command is finished executing.
 
-        :param msg: str - message to print to current stdout - anything convertible to a str with '{}'.format() is OK
-        :param end: str - string appended after the end of the message if not already present, default a newline
+        :param msg: message to print to current stdout - anything convertible to a str with '{}'.format() is OK
+        :param end: string appended after the end of the message if not already present, default a newline
         """
         if msg is not None and msg != '':
             try:
@@ -550,30 +577,29 @@ class Cmd(cmd.Cmd):
                 if self.broken_pipe_warning:
                     sys.stderr.write(self.broken_pipe_warning)
 
-    def perror(self, errmsg, exception_type=None, traceback_war=True):
+    def perror(self, err: Union[str, Exception], traceback_war: bool=True) -> None:
         """ Print error message to sys.stderr and if debug is true, print an exception Traceback if one exists.
 
-        :param errmsg: str - error message to print out
-        :param exception_type: str - (optional) type of exception which precipitated this error message
-        :param traceback_war: bool - (optional) if True, print a message to let user know they can enable debug
+        :param err: an Exception or error message to print out
+        :param traceback_war: (optional) if True, print a message to let user know they can enable debug
         :return:
         """
         if self.debug:
             import traceback
             traceback.print_exc()
 
-        if exception_type is None:
-            err = self.colorize("ERROR: {}\n".format(errmsg), 'red')
-            sys.stderr.write(err)
+        if isinstance(err, Exception):
+            err_msg = "EXCEPTION of type '{}' occurred with message: '{}'\n".format(type(err).__name__, err)
+            sys.stderr.write(self.colorize(err_msg, 'red'))
         else:
-            err = "EXCEPTION of type '{}' occurred with message: '{}'\n".format(exception_type, errmsg)
-            sys.stderr.write(self.colorize(err, 'red'))
+            err_msg = self.colorize("ERROR: {}\n".format(err), 'red')
+            sys.stderr.write(err_msg)
 
         if traceback_war:
             war = "To enable full traceback, run the following command:  'set debug true'\n"
             sys.stderr.write(self.colorize(war, 'yellow'))
 
-    def pfeedback(self, msg):
+    def pfeedback(self, msg: str) -> None:
         """For printing nonessential feedback.  Can be silenced with `quiet`.
            Inclusion in redirected output is controlled by `feedback_to_output`."""
         if not self.quiet:
@@ -582,7 +608,7 @@ class Cmd(cmd.Cmd):
             else:
                 sys.stderr.write("{}\n".format(msg))
 
-    def ppaged(self, msg, end='\n'):
+    def ppaged(self, msg: str, end: str='\n') -> None:
         """Print output using a pager if it would go off screen and stdout isn't currently being redirected.
 
         Never uses a pager inside of a script (Python or text) or when output is being redirected or piped or when
@@ -644,7 +670,7 @@ class Cmd(cmd.Cmd):
                 if self.broken_pipe_warning:
                     sys.stderr.write(self.broken_pipe_warning)
 
-    def colorize(self, val, color):
+    def colorize(self, val: str, color: str) -> str:
         """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 parameter is ``False``, or the application
@@ -657,7 +683,7 @@ class Cmd(cmd.Cmd):
 
     # -----  Methods related to tab completion -----
 
-    def reset_completion_defaults(self):
+    def reset_completion_defaults(self) -> None:
         """
         Resets tab completion settings
         Needs to be called each time readline runs tab completion
@@ -673,12 +699,13 @@ class Cmd(cmd.Cmd):
         elif rl_type == RlType.PYREADLINE:
             readline.rl.mode._display_completions = self._display_matches_pyreadline
 
-    def tokens_for_completion(self, line, begidx, endidx):
+    def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> Tuple[Optional[List[str]],
+                                                                                  Optional[List[str]]]:
         """
         Used by tab completion functions to get all tokens through the one being completed
-        :param line: str - the current input line with leading whitespace removed
-        :param begidx: int - the beginning index of the prefix text
-        :param endidx: int - the ending index of the prefix text
+        :param line: the current input line with leading whitespace removed
+        :param begidx: the beginning index of the prefix text
+        :param endidx: the ending index of the prefix text
         :return: A 2 item tuple where the items are
                  On Success
                      tokens: list of unquoted tokens
@@ -795,20 +822,21 @@ class Cmd(cmd.Cmd):
 
     # noinspection PyUnusedLocal
     @staticmethod
-    def basic_complete(text, line, begidx, endidx, match_against):
+    def basic_complete(text: str, line: str, begidx: int, endidx: int, match_against: Iterable) -> List[str]:
         """
         Performs tab completion against a list
 
-        :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
-        :param line: str - the current input line with leading whitespace removed
-        :param begidx: int - the beginning index of the prefix text
-        :param endidx: int - the ending index of the prefix text
-        :param match_against: Collection - the list being matched against
-        :return: List[str] - a list of possible tab completions
+        :param text: the string prefix we are attempting to match (all returned matches must begin with it)
+        :param line: the current input line with leading whitespace removed
+        :param begidx: the beginning index of the prefix text
+        :param endidx: the ending index of the prefix text
+        :param match_against: the list being matched against
+        :return: a list of possible tab completions
         """
         return [cur_match for cur_match in match_against if cur_match.startswith(text)]
 
-    def delimiter_complete(self, text, line, begidx, endidx, match_against, delimiter):
+    def delimiter_complete(self, text: str, line: str, begidx: int, endidx: int, match_against: Iterable,
+                           delimiter: str) -> List[str]:
         """
         Performs tab completion against a list but each match is split on a delimiter and only
         the portion of the match being tab completed is shown as the completion suggestions.
@@ -833,13 +861,13 @@ class Cmd(cmd.Cmd):
         In this case the delimiter would be :: and the user could easily narrow down what they are looking
         for if they were only shown suggestions in the category they are at in the string.
 
-        :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
-        :param line: str - the current input line with leading whitespace removed
-        :param begidx: int - the beginning index of the prefix text
-        :param endidx: int - the ending index of the prefix text
-        :param match_against: Collection - the list being matched against
-        :param delimiter: str - what delimits each portion of the matches (ex: paths are delimited by a slash)
-        :return: List[str] - a list of possible tab completions
+        :param text: the string prefix we are attempting to match (all returned matches must begin with it)
+        :param line: the current input line with leading whitespace removed
+        :param begidx: the beginning index of the prefix text
+        :param endidx: the ending index of the prefix text
+        :param match_against: the list being matched against
+        :param delimiter: what delimits each portion of the matches (ex: paths are delimited by a slash)
+        :return: a list of possible tab completions
         """
         matches = self.basic_complete(text, line, begidx, endidx, match_against)
 
@@ -868,13 +896,15 @@ class Cmd(cmd.Cmd):
 
         return matches
 
-    def flag_based_complete(self, text, line, begidx, endidx, flag_dict, all_else=None):
+    def flag_based_complete(self, text: str, line: str, begidx: int, endidx: int,
+                            flag_dict: Dict[str, Union[Iterable, Callable]],
+                            all_else: Union[None, Iterable, Callable]=None) -> List[str]:
         """
         Tab completes based on a particular flag preceding the token being completed
-        :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
-        :param line: str - the current input line with leading whitespace removed
-        :param begidx: int - the beginning index of the prefix text
-        :param endidx: int - the ending index of the prefix text
+        :param text: the string prefix we are attempting to match (all returned matches must begin with it)
+        :param line: the current input line with leading whitespace removed
+        :param begidx: the beginning index of the prefix text
+        :param endidx: the ending index of the prefix text
         :param flag_dict: dict - dictionary whose structure is the following:
                                  keys - flags (ex: -c, --create) that result in tab completion for the next
                                         argument in the command line
@@ -883,7 +913,7 @@ class Cmd(cmd.Cmd):
                                     2. function that performs tab completion (ex: path_complete)
         :param all_else: Collection or function - an optional parameter for tab completing any token that isn't preceded
                                                   by a flag in flag_dict
-        :return: List[str] - a list of possible tab completions
+        :return: a list of possible tab completions
         """
         # Get all tokens through the one being completed
         tokens, _ = self.tokens_for_completion(line, begidx, endidx)
@@ -909,13 +939,15 @@ class Cmd(cmd.Cmd):
 
         return completions_matches
 
-    def index_based_complete(self, text, line, begidx, endidx, index_dict, all_else=None):
+    def index_based_complete(self, text: str, line: str, begidx: int, endidx: int,
+                             index_dict: Mapping[int, Union[Iterable, Callable]],
+                             all_else: Union[None, Iterable, Callable] = None) -> List[str]:
         """
         Tab completes based on a fixed position in the input string
-        :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
-        :param line: str - the current input line with leading whitespace removed
-        :param begidx: int - the beginning index of the prefix text
-        :param endidx: int - the ending index of the prefix text
+        :param text: the string prefix we are attempting to match (all returned matches must begin with it)
+        :param line: the current input line with leading whitespace removed
+        :param begidx: the beginning index of the prefix text
+        :param endidx: the ending index of the prefix text
         :param index_dict: dict - dictionary whose structure is the following:
                                  keys - 0-based token indexes into command line that determine which tokens
                                         perform tab completion
@@ -924,7 +956,7 @@ class Cmd(cmd.Cmd):
                                     2. function that performs tab completion (ex: path_complete)
         :param all_else: Collection or function - an optional parameter for tab completing any token that isn't at an
                                                   index in index_dict
-        :return: List[str] - a list of possible tab completions
+        :return: a list of possible tab completions
         """
         # Get all tokens through the one being completed
         tokens, _ = self.tokens_for_completion(line, begidx, endidx)
@@ -953,16 +985,17 @@ class Cmd(cmd.Cmd):
         return matches
 
     # noinspection PyUnusedLocal
-    def path_complete(self, text, line, begidx, endidx, dir_exe_only=False, dir_only=False):
+    def path_complete(self, text: str, line: str, begidx: int, endidx: int, dir_exe_only: bool=False,
+                      dir_only: bool=False) -> List[str]:
         """Performs completion of local file system paths
 
-        :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
-        :param line: str - the current input line with leading whitespace removed
-        :param begidx: int - the beginning index of the prefix text
-        :param endidx: int - the ending index of the prefix text
-        :param dir_exe_only: bool - only return directories and executables, not non-executable files
-        :param dir_only: bool - only return directories
-        :return: List[str] - a list of possible tab completions
+        :param text: the string prefix we are attempting to match (all returned matches must begin with it)
+        :param line: the current input line with leading whitespace removed
+        :param begidx: the beginning index of the prefix text
+        :param endidx: the ending index of the prefix text
+        :param dir_exe_only: only return directories and executables, not non-executable files
+        :param dir_only: only return directories
+        :return: a list of possible tab completions
         """
 
         # Used to complete ~ and ~user strings
@@ -1087,11 +1120,11 @@ class Cmd(cmd.Cmd):
         return matches
 
     @staticmethod
-    def get_exes_in_path(starts_with):
-        """
-        Returns names of executables in a user's path
-        :param starts_with: str - what the exes should start with. leave blank for all exes in path.
-        :return: List[str] - a list of matching exe names
+    def get_exes_in_path(starts_with: str) -> List[str]:
+        """Returns names of executables in a user's path
+
+        :param starts_with: what the exes should start with. leave blank for all exes in path.
+        :return: a list of matching exe names
         """
         # Purposely don't match any executable containing wildcards
         wildcards = ['*', '?']
@@ -1115,16 +1148,17 @@ class Cmd(cmd.Cmd):
 
         return list(exes_set)
 
-    def shell_cmd_complete(self, text, line, begidx, endidx, complete_blank=False):
+    def shell_cmd_complete(self, text: str, line: str, begidx: int, endidx: int,
+                           complete_blank: bool=False) -> List[str]:
         """Performs completion of executables either in a user's path or a given path
-        :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
-        :param line: str - the current input line with leading whitespace removed
-        :param begidx: int - the beginning index of the prefix text
-        :param endidx: int - the ending index of the prefix text
-        :param complete_blank: bool - If True, then a blank will complete all shell commands in a user's path
-                                      If False, then no completion is performed
-                                      Defaults to False to match Bash shell behavior
-        :return: List[str] - a list of possible tab completions
+        :param text: the string prefix we are attempting to match (all returned matches must begin with it)
+        :param line: the current input line with leading whitespace removed
+        :param begidx: the beginning index of the prefix text
+        :param endidx: the ending index of the prefix text
+        :param complete_blank: If True, then a blank will complete all shell commands in a user's path
+                               If False, then no completion is performed
+                               Defaults to False to match Bash shell behavior
+        :return: a list of possible tab completions
         """
         # Don't tab complete anything if no shell command has been started
         if not complete_blank and not text:
@@ -1138,19 +1172,18 @@ class Cmd(cmd.Cmd):
         else:
             return self.path_complete(text, line, begidx, endidx, dir_exe_only=True)
 
-    def _redirect_complete(self, text, line, begidx, endidx, compfunc):
-        """
-        Called by complete() as the first tab completion function for all commands
+    def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: Callable) -> List[str]:
+        """Called by complete() as the first tab completion function for all commands
         It determines if it should tab complete for redirection (|, <, >, >>) or use the
         completer function for the current command
 
-        :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
-        :param line: str - the current input line with leading whitespace removed
-        :param begidx: int - the beginning index of the prefix text
-        :param endidx: int - the ending index of the prefix text
-        :param compfunc: Callable - the completer function for the current command
-                                    this will be called if we aren't completing for redirection
-        :return: List[str] - a list of possible tab completions
+        :param text: the string prefix we are attempting to match (all returned matches must begin with it)
+        :param line: the current input line with leading whitespace removed
+        :param begidx: the beginning index of the prefix text
+        :param endidx: the ending index of the prefix text
+        :param compfunc: the completer function for the current command
+                         this will be called if we aren't completing for redirection
+        :return: a list of possible tab completions
         """
         if self.allow_redirection:
 
@@ -1193,9 +1226,8 @@ class Cmd(cmd.Cmd):
         return compfunc(text, line, begidx, endidx)
 
     @staticmethod
-    def _pad_matches_to_display(matches_to_display):  # pragma: no cover
-        """
-        Adds padding to the matches being displayed as tab completion suggestions.
+    def _pad_matches_to_display(matches_to_display: List[str]) -> Tuple[List[str], int]:  # pragma: no cover
+        """Adds padding to the matches being displayed as tab completion suggestions.
         The default padding of readline/pyreadine is small and not visually appealing
         especially if matches have spaces. It appears very squished together.
 
@@ -1215,14 +1247,14 @@ class Cmd(cmd.Cmd):
 
         return [cur_match + padding for cur_match in matches_to_display], len(padding)
 
-    def _display_matches_gnu_readline(self, substitution, matches, longest_match_length):  # pragma: no cover
-        """
-        Prints a match list using GNU readline's rl_display_match_list()
+    def _display_matches_gnu_readline(self, substitution: str, matches: List[str],
+                                      longest_match_length: int) -> None:  # pragma: no cover
+        """Prints a match list using GNU readline's rl_display_match_list()
         This exists to print self.display_matches if it has data. Otherwise matches prints.
 
-        :param substitution: str - the substitution written to the command line
-        :param matches: list[str] - the tab completion matches to display
-        :param longest_match_length: int - longest printed length of the matches
+        :param substitution: the substitution written to the command line
+        :param matches: the tab completion matches to display
+        :param longest_match_length: longest printed length of the matches
         """
         if rl_type == RlType.GNU:
 
@@ -1270,12 +1302,11 @@ class Cmd(cmd.Cmd):
             # Redraw prompt and input line
             rl_force_redisplay()
 
-    def _display_matches_pyreadline(self, matches):  # pragma: no cover
-        """
-        Prints a match list using pyreadline's _display_completions()
+    def _display_matches_pyreadline(self, matches: List[str]) -> None:  # pragma: no cover
+        """Prints a match list using pyreadline's _display_completions()
         This exists to print self.display_matches if it has data. Otherwise matches prints.
 
-        :param matches: list[str] - the tab completion matches to display
+        :param matches: the tab completion matches to display
         """
         if rl_type == RlType.PYREADLINE:
 
@@ -1297,7 +1328,7 @@ class Cmd(cmd.Cmd):
 
     # -----  Methods which override stuff in cmd -----
 
-    def complete(self, text, state):
+    def complete(self, text: str, state: int) -> Optional[str]:
         """Override of command method which returns the next possible completion for 'text'.
 
         If a command has not been entered, then complete against command list.
@@ -1308,8 +1339,8 @@ class Cmd(cmd.Cmd):
         This completer function is called as complete(text, state), for state in 0, 1, 2, …, until it returns a
         non-string value. It should return the next possible completion starting with text.
 
-        :param text: str - the current word that user is typing
-        :param state: int - non-negative integer
+        :param text: the current word that user is typing
+        :param state: non-negative integer
         """
         import functools
         if state == 0 and rl_type != RlType.NONE:
@@ -1525,16 +1556,12 @@ class Cmd(cmd.Cmd):
 
         return results
 
-    def get_all_commands(self):
-        """
-        Returns a list of all commands
-        """
+    def get_all_commands(self) -> List[str]:
+        """Returns a list of all commands."""
         return [cur_name[3:] for cur_name in self.get_names() if cur_name.startswith('do_')]
 
-    def get_visible_commands(self):
-        """
-        Returns a list of commands that have not been hidden
-        """
+    def get_visible_commands(self) -> List[str]:
+        """Returns a list of commands that have not been hidden."""
         commands = self.get_all_commands()
 
         # Remove the hidden commands
@@ -1544,11 +1571,11 @@ class Cmd(cmd.Cmd):
 
         return commands
 
-    def get_help_topics(self):
+    def get_help_topics(self) -> List[str]:
         """ Returns a list of help topics """
         return [name[5:] for name in self.get_names() if name.startswith('help_')]
 
-    def complete_help(self, text, line, begidx, endidx):
+    def complete_help(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
         """
         Override of parent class method to handle tab completing subcommands and not showing hidden commands
         Returns a list of possible tab completions
@@ -1592,12 +1619,12 @@ class Cmd(cmd.Cmd):
         return matches
 
     # noinspection PyUnusedLocal
-    def sigint_handler(self, signum, frame):
+    def sigint_handler(self, signum: int, frame) -> None:
         """Signal handler for SIGINTs which typically come from Ctrl-C events.
 
         If you need custom SIGINT behavior, then override this function.
 
-        :param signum: int - signal number
+        :param signum: signal number
         :param frame
         """
 
@@ -1610,8 +1637,8 @@ class Cmd(cmd.Cmd):
         # Re-raise a KeyboardInterrupt so other parts of the code can catch it
         raise KeyboardInterrupt("Got a keyboard interrupt")
 
-    def preloop(self):
-        """"Hook method executed once when the cmdloop() method is called."""
+    def preloop(self) -> None:
+        """Hook method executed once when the cmdloop() method is called."""
         import signal
         # Register a default SIGINT signal handler for Ctrl+C
         signal.signal(signal.SIGINT, self.sigint_handler)
@@ -1619,8 +1646,8 @@ class Cmd(cmd.Cmd):
     def precmd(self, statement: Statement) -> Statement:
         """Hook method executed just before the command is processed by ``onecmd()`` and after adding it to the history.
 
-        :param statement: Statement - subclass of str which also contains the parsed input
-        :return: Statement - a potentially modified version of the input Statement object
+        :param statement: subclass of str which also contains the parsed input
+        :return: a potentially modified version of the input Statement object
         """
         return statement
 
@@ -1630,8 +1657,8 @@ class Cmd(cmd.Cmd):
     def preparse(self, raw: str) -> str:
         """Hook method executed just before the command line is interpreted, but after the input prompt is generated.
 
-        :param raw: str - raw command line input
-        :return: str - potentially modified raw command line input
+        :param raw: raw command line input
+        :return: potentially modified raw command line input
         """
         return raw
 
@@ -1672,25 +1699,24 @@ class Cmd(cmd.Cmd):
                 proc.communicate()
         return stop
 
-    def parseline(self, line):
+    def parseline(self, line: str) -> Tuple[str, str, str]:
         """Parse the line into a command name and a string containing the arguments.
 
         NOTE: This is an override of a parent class method.  It is only used by other parent class methods.
 
         Different from the parent class method, this ignores self.identchars.
 
-        :param line: str - line read by readline
-        :return: (str, str, str) - tuple containing (command, args, line)
+        :param line: line read by readline
+        :return: tuple containing (command, args, line)
         """
-
         statement = self.statement_parser.parse_command_only(line)
         return statement.command, statement.args, statement.command_and_args
 
-    def onecmd_plus_hooks(self, line):
+    def onecmd_plus_hooks(self, line: str) -> bool:
         """Top-level function called by cmdloop() to handle parsing a line and running the command and all of its hooks.
 
-        :param line: str - line of text read from input
-        :return: bool - True if cmdloop() should exit, False otherwise
+        :param line: line of text read from input
+        :return: True if cmdloop() should exit, False otherwise
         """
         import datetime
         stop = False
@@ -1720,11 +1746,11 @@ class Cmd(cmd.Cmd):
             # 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__)
+            self.perror(ex)
         finally:
             return self.postparsing_postcmd(stop)
 
-    def runcmds_plus_hooks(self, cmds):
+    def runcmds_plus_hooks(self, cmds: List[str]) -> bool:
         """Convenience method to run multiple commands by onecmd_plus_hooks.
 
         This method adds the given cmds to the command queue and processes the
@@ -1742,8 +1768,8 @@ class Cmd(cmd.Cmd):
 
         Example: cmd_obj.runcmds_plus_hooks(['load myscript.txt'])
 
-        :param cmds: list - Command strings suitable for onecmd_plus_hooks.
-        :return: bool - True implies the entire application should exit.
+        :param cmds: command strings suitable for onecmd_plus_hooks.
+        :return: True implies the entire application should exit.
 
         """
         stop = False
@@ -1766,7 +1792,7 @@ class Cmd(cmd.Cmd):
             # necessary/desired here.
             return stop
 
-    def _complete_statement(self, line):
+    def _complete_statement(self, line: str) -> Statement:
         """Keep accepting lines of input until the command is complete.
 
         There is some pretty hacky code here to handle some quirks of
@@ -1807,10 +1833,10 @@ class Cmd(cmd.Cmd):
             raise EmptyStatement()
         return statement
 
-    def _redirect_output(self, statement):
+    def _redirect_output(self, statement: Statement) -> None:
         """Handles output redirection for >, >>, and |.
 
-        :param statement: Statement - a parsed statement from the user
+        :param statement: a parsed statement from the user
         """
         import io
         import subprocess
@@ -1867,12 +1893,11 @@ class Cmd(cmd.Cmd):
                 if statement.output == constants.REDIRECTION_APPEND:
                     self.poutput(get_paste_buffer())
 
-    def _restore_output(self, statement):
+    def _restore_output(self, statement: Statement) -> None:
         """Handles restoring state after output redirection as well as
         the actual pipe operation if present.
 
-        :param statement: Statement object which contains the parsed
-                          input from the user
+        :param statement: Statement object which contains the parsed input from the user
         """
         # If we have redirected output to a file or the clipboard or piped it to a shell command, then restore state
         if self.kept_state is not None:
@@ -1903,11 +1928,11 @@ class Cmd(cmd.Cmd):
 
         self.redirecting = False
 
-    def _func_named(self, arg):
+    def _func_named(self, arg: str) -> str:
         """Gets the method name associated with a given command.
 
-        :param arg: str - command to look up method name which implements it
-        :return: str - method name which implements the given command
+        :param arg: command to look up method name which implements it
+        :return: method name which implements the given command
         """
         result = None
         target = 'do_' + arg
@@ -1915,17 +1940,18 @@ class Cmd(cmd.Cmd):
             result = target
         return result
 
-    def onecmd(self, statement):
+    def onecmd(self, statement: Statement) -> Optional[bool]:
         """ This executes the actual do_* method for a command.
 
         If the command provided doesn't exist, then it executes _default() instead.
 
         :param statement: Command - a parsed command from the input stream
-        :return: bool - a flag indicating whether the interpretation of commands should stop
+        :return: a flag indicating whether the interpretation of commands should stop
         """
         funcname = self._func_named(statement.command)
         if not funcname:
-            return self.default(statement)
+            self.default(statement)
+            return
 
         # Since we have a valid command store it in the history
         if statement.command not in self.exclude_from_history:
@@ -1934,16 +1960,16 @@ class Cmd(cmd.Cmd):
         try:
             func = getattr(self, funcname)
         except AttributeError:
-            return self.default(statement)
+            self.default(statement)
+            return
 
         stop = func(statement)
         return stop
 
-    def default(self, statement):
+    def default(self, statement: Statement) -> None:
         """Executed when the command given isn't a recognized command implemented by a do_* method.
 
         :param statement: Statement object with parsed input
-        :return:
         """
         arg = statement.raw
         if self.default_to_shell:
@@ -1956,13 +1982,13 @@ class Cmd(cmd.Cmd):
         self.poutput('*** Unknown syntax: {}\n'.format(arg))
 
     @staticmethod
-    def _surround_ansi_escapes(prompt, start="\x01", end="\x02"):
+    def _surround_ansi_escapes(prompt: str, start: str="\x01", end: str="\x02") -> str:
         """Overcome bug in GNU Readline in relation to calculation of prompt length in presence of ANSI escape codes.
 
-        :param prompt: str - original prompt
-        :param start: str - start code to tell GNU Readline about beginning of invisible characters
-        :param end: str - end code to tell GNU Readline about end of invisible characters
-        :return: str - prompt safe to pass to GNU Readline
+        :param prompt: original prompt
+        :param start: start code to tell GNU Readline about beginning of invisible characters
+        :param end: end code to tell GNU Readline about end of invisible characters
+        :return: prompt safe to pass to GNU Readline
         """
         # Windows terminals don't use ANSI escape codes and Windows readline isn't based on GNU Readline
         if sys.platform == "win32":
@@ -1983,9 +2009,8 @@ class Cmd(cmd.Cmd):
 
         return result
 
-    def pseudo_raw_input(self, prompt):
-        """
-        began life as a copy of cmd's cmdloop; like raw_input but
+    def pseudo_raw_input(self, prompt: str) -> str:
+        """Began life as a copy of cmd's cmdloop; like raw_input but
 
         - accounts for changed stdin, stdout
         - if input is a pipe (instead of a tty), look at self.echo
@@ -2026,14 +2051,14 @@ class Cmd(cmd.Cmd):
                     line = 'eof'
         return line.strip()
 
-    def _cmdloop(self):
+    def _cmdloop(self) -> bool:
         """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.
+        :return: 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
@@ -2063,7 +2088,7 @@ class Cmd(cmd.Cmd):
             # Enable tab completion
             readline.parse_and_bind(self.completekey + ": complete")
 
-        stop = None
+        stop = False
         try:
             while not stop:
                 if self.cmdqueue:
@@ -2104,7 +2129,7 @@ class Cmd(cmd.Cmd):
             return stop
 
     @with_argument_list
-    def do_alias(self, arglist):
+    def do_alias(self, arglist: List[str]) -> None:
         """Define or display aliases
 
 Usage:  Usage: alias [name] | [<name> <value>]
@@ -2160,7 +2185,7 @@ Usage:  Usage: alias [name] | [<name> <value>]
                 errmsg = "Aliases can not contain: {}".format(invalidchars)
                 self.perror(errmsg, traceback_war=False)
 
-    def complete_alias(self, text, line, begidx, endidx):
+    def complete_alias(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
         """ Tab completion for alias """
         alias_names = set(self.aliases.keys())
         visible_commands = set(self.get_visible_commands())
@@ -2173,7 +2198,7 @@ Usage:  Usage: alias [name] | [<name> <value>]
         return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete)
 
     @with_argument_list
-    def do_unalias(self, arglist):
+    def do_unalias(self, arglist: List[str]) -> None:
         """Unsets aliases
 
 Usage:  Usage: unalias [-a] name [name ...]
@@ -2184,7 +2209,7 @@ Usage:  Usage: unalias [-a] name [name ...]
         -a     remove all alias definitions
 """
         if not arglist:
-            self.do_help('unalias')
+            self.do_help(['unalias'])
 
         if '-a' in arglist:
             self.aliases.clear()
@@ -2201,12 +2226,12 @@ Usage:  Usage: unalias [-a] name [name ...]
                 else:
                     self.perror("Alias {!r} does not exist".format(cur_arg), traceback_war=False)
 
-    def complete_unalias(self, text, line, begidx, endidx):
+    def complete_unalias(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
         """ Tab completion for unalias """
         return self.basic_complete(text, line, begidx, endidx, self.aliases)
 
     @with_argument_list
-    def do_help(self, arglist):
+    def do_help(self, arglist: List[str]) -> None:
         """List available commands with "help" or detailed help with "help cmd"."""
         if not arglist or (len(arglist) == 1 and arglist[0] in ('--verbose', '-v')):
             verbose = len(arglist) == 1 and arglist[0] in ('--verbose', '-v')
@@ -2233,7 +2258,7 @@ Usage:  Usage: unalias [-a] name [name ...]
                 # This could be a help topic
                 cmd.Cmd.do_help(self, arglist[0])
 
-    def _help_menu(self, verbose=False):
+    def _help_menu(self, verbose: bool=False) -> None:
         """Show a list of commands which help can be displayed for.
         """
         # Get a sorted list of help topics
@@ -2276,7 +2301,7 @@ Usage:  Usage: unalias [-a] name [name ...]
         self.print_topics(self.misc_header, help_topics, 15, 80)
         self.print_topics(self.undoc_header, cmds_undoc, 15, 80)
 
-    def _print_topics(self, header, cmds, verbose):
+    def _print_topics(self, header: str, cmds: List[str], verbose: bool) -> None:
         """Customized version of print_topics that can switch between verbose or traditional output"""
         import io
 
@@ -2347,23 +2372,23 @@ Usage:  Usage: unalias [-a] name [name ...]
                         command = ''
                 self.stdout.write("\n")
 
-    def do_shortcuts(self, _):
+    def do_shortcuts(self, _: str) -> None:
         """Lists shortcuts (aliases) available."""
         result = "\n".join('%s: %s' % (sc[0], sc[1]) for sc in sorted(self.shortcuts))
         self.poutput("Shortcuts for other commands:\n{}\n".format(result))
 
-    def do_eof(self, _):
+    def do_eof(self, _: str) -> bool:
         """Called when <Ctrl>-D is pressed."""
         # End of script should not exit app, but <Ctrl>-D should.
         print('')  # Required for clearing line when exiting submenu
         return self._STOP_AND_EXIT
 
-    def do_quit(self, _):
+    def do_quit(self, _: str) -> bool:
         """Exits this application."""
         self._should_quit = True
         return self._STOP_AND_EXIT
 
-    def select(self, opts, prompt='Your choice? '):
+    def select(self, opts: Union[str, List[str], List[Tuple[str, Optional[str]]]], prompt: str='Your choice? ') -> str:
         """Presents a numbered menu to the user.  Modelled after
            the bash shell's SELECT.  Returns the item chosen.
 
@@ -2397,18 +2422,18 @@ Usage:  Usage: unalias [-a] name [name ...]
                     readline.remove_history_item(hlen - 1)
 
             try:
-                response = int(response)
-                result = fulloptions[response - 1][0]
+                choice = int(response)
+                result = fulloptions[choice - 1][0]
                 break
             except (ValueError, IndexError):
                 self.poutput("{!r} isn't a valid choice. Pick a number between 1 and {}:\n".format(response,
                                                                                                    len(fulloptions)))
         return result
 
-    def cmdenvironment(self):
+    def cmdenvironment(self) -> str:
         """Get a summary report of read-only settings which the user cannot modify at runtime.
 
-        :return: str - summary report of read-only settings which the user cannot modify at runtime
+        :return: summary report of read-only settings which the user cannot modify at runtime
         """
         read_only_settings = """
         Commands may be terminated with: {}
@@ -2416,7 +2441,13 @@ Usage:  Usage: unalias [-a] name [name ...]
         Output redirection and pipes allowed: {}"""
         return read_only_settings.format(str(self.terminators), self.allow_cli_args, self.allow_redirection)
 
-    def show(self, args, parameter):
+    def show(self, args: argparse.Namespace, parameter: str) -> None:
+        """Shows current settings of parameters.
+
+        :param args: argparse parsed arguments from the set command
+        :param parameter:
+        :return:
+        """
         param = ''
         if parameter:
             param = parameter.strip().lower()
@@ -2445,7 +2476,7 @@ Usage:  Usage: unalias [-a] name [name ...]
     set_parser.add_argument('settable', nargs=(0, 2), help='[param_name] [value]')
 
     @with_argparser(set_parser)
-    def do_set(self, args):
+    def do_set(self, args: argparse.Namespace) -> None:
         """Sets a settable parameter or shows current settings of parameters.
 
         Accepts abbreviated parameter names so long as there is no ambiguity.
@@ -2480,7 +2511,7 @@ Usage:  Usage: unalias [-a] name [name ...]
                 param = args.settable[0]
             self.show(args, param)
 
-    def do_shell(self, command):
+    def do_shell(self, command: str) -> None:
         """Execute a command as if at the OS prompt.
 
     Usage:  shell <command> [arguments]"""
@@ -2512,14 +2543,14 @@ Usage:  Usage: unalias [-a] name [name ...]
         proc = subprocess.Popen(expanded_command, stdout=self.stdout, shell=True)
         proc.communicate()
 
-    def complete_shell(self, text, line, begidx, endidx):
+    def complete_shell(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
         """Handles tab completion of executable commands and local file system paths for the shell command
 
-        :param text: str - the string prefix we are attempting to match (all returned matches must begin with it)
-        :param line: str - the current input line with leading whitespace removed
-        :param begidx: int - the beginning index of the prefix text
-        :param endidx: int - the ending index of the prefix text
-        :return: List[str] - a list of possible tab completions
+        :param text: the string prefix we are attempting to match (all returned matches must begin with it)
+        :param line: the current input line with leading whitespace removed
+        :param begidx: the beginning index of the prefix text
+        :param endidx: the ending index of the prefix text
+        :return: a list of possible tab completions
         """
         index_dict = {1: self.shell_cmd_complete}
         return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete)
@@ -2548,7 +2579,7 @@ Usage:  Usage: unalias [-a] name [name ...]
         sys.displayhook = sys.__displayhook__
         sys.excepthook = sys.__excepthook__
 
-    def do_py(self, arg):
+    def do_py(self, arg: str) -> bool:
         """
         Invoke python command, shell, or script
 
@@ -2561,7 +2592,7 @@ Usage:  Usage: unalias [-a] name [name ...]
         from .pyscript_bridge import PyscriptBridge
         if self._in_py:
             self.perror("Recursively entering interactive Python consoles is not allowed.", traceback_war=False)
-            return
+            return False
         self._in_py = True
 
         # noinspection PyBroadException
@@ -2704,7 +2735,7 @@ Usage:  Usage: unalias [-a] name [name ...]
         return self._should_quit
 
     @with_argument_list
-    def do_pyscript(self, arglist):
+    def do_pyscript(self, arglist: List[str]) -> None:
         """\nRuns a python script file inside the console
 
     Usage: pyscript <script_path> [script_arguments]
@@ -2715,7 +2746,7 @@ Paths or arguments that contain spaces must be enclosed in quotes
 """
         if not arglist:
             self.perror("pyscript command requires at least 1 argument ...", traceback_war=False)
-            self.do_help('pyscript')
+            self.do_help(['pyscript'])
             return
 
         # Get the absolute path of the script
@@ -2734,15 +2765,15 @@ Paths or arguments that contain spaces must be enclosed in quotes
         # Restore command line arguments to original state
         sys.argv = orig_args
 
-    # Enable tab-completion for pyscript command
-    def complete_pyscript(self, text, line, begidx, endidx):
+    def complete_pyscript(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
+        """Enable tab-completion for pyscript command."""
         index_dict = {1: self.path_complete}
         return self.index_based_complete(text, line, begidx, endidx, index_dict)
 
     # Only include the do_ipy() method if IPython is available on the system
     if ipython_available:
         # noinspection PyMethodMayBeStatic,PyUnusedLocal
-        def do_ipy(self, arg):
+        def do_ipy(self, arg: str) -> None:
             """Enters an interactive IPython shell.
 
             Run python code from external files with ``run filename.py``
@@ -2780,7 +2811,7 @@ a..b, a:b, a:, ..b  items by indices (inclusive)
     history_parser.add_argument('arg', nargs='?', help=_history_arg_help)
 
     @with_argparser(history_parser)
-    def do_history(self, args):
+    def do_history(self, args: argparse.Namespace) -> None:
         """View, run, edit, and save previously entered commands."""
         # If an argument was supplied, then retrieve partial contents of the history
         cowardly_refuse_to_run = False
@@ -2845,7 +2876,7 @@ a..b, a:b, a:, ..b  items by indices (inclusive)
                 else:
                     self.poutput(hi.pr())
 
-    def _generate_transcript(self, history, transcript_file):
+    def _generate_transcript(self, history: List[HistoryItem], transcript_file: str) -> None:
         """Generate a transcript file from a given history of commands."""
         # Save the current echo state, and turn it off. We inject commands into the
         # output using a different mechanism
@@ -2913,7 +2944,7 @@ a..b, a:b, a:, ..b  items by indices (inclusive)
             self.pfeedback(msg.format(len(history), plural, transcript_file))
 
     @with_argument_list
-    def do_edit(self, arglist):
+    def do_edit(self, arglist: List[str]) -> None:
         """Edit a file in a text editor.
 
 Usage:  edit [file_path]
@@ -2931,13 +2962,13 @@ The editor used is determined by the ``editor`` settable parameter.
         else:
             os.system('"{}"'.format(self.editor))
 
-    # Enable tab-completion for edit command
-    def complete_edit(self, text, line, begidx, endidx):
+    def complete_edit(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
+        """Enable tab-completion for edit command."""
         index_dict = {1: self.path_complete}
         return self.index_based_complete(text, line, begidx, endidx, index_dict)
 
     @property
-    def _current_script_dir(self):
+    def _current_script_dir(self) -> Optional[str]:
         """Accessor to get the current script directory from the _script_dir LIFO queue."""
         if self._script_dir:
             return self._script_dir[-1]
@@ -2945,7 +2976,7 @@ The editor used is determined by the ``editor`` settable parameter.
             return None
 
     @with_argument_list
-    def do__relative_load(self, arglist):
+    def do__relative_load(self, arglist: List[str]) -> None:
         """Runs commands in script file that is encoded as either ASCII or UTF-8 text.
 
     Usage:  _relative_load <file_path>
@@ -2968,15 +2999,15 @@ NOTE: This command is intended to only be used within text file scripts.
         file_path = arglist[0].strip()
         # NOTE: Relative path is an absolute path, it is just relative to the current script directory
         relative_path = os.path.join(self._current_script_dir or '', file_path)
-        self.do_load(relative_path)
+        self.do_load([relative_path])
 
-    def do_eos(self, _):
+    def do_eos(self, _: str) -> None:
         """Handles cleanup when a script has finished executing."""
         if self._script_dir:
             self._script_dir.pop()
 
     @with_argument_list
-    def do_load(self, arglist):
+    def do_load(self, arglist: List[str]) -> None:
         """Runs commands in script file that is encoded as either ASCII or UTF-8 text.
 
     Usage:  load <file_path>
@@ -3020,21 +3051,22 @@ Script should contain one command per line, just like command would be typed in
 
         self._script_dir.append(os.path.dirname(expanded_path))
 
-    # Enable tab-completion for load command
-    def complete_load(self, text, line, begidx, endidx):
+    def complete_load(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
+        """Enable tab-completion for load command."""
         index_dict = {1: self.path_complete}
         return self.index_based_complete(text, line, begidx, endidx, index_dict)
 
-    def run_transcript_tests(self, callargs):
+    def run_transcript_tests(self, callargs: List[str]) -> None:
         """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
+        :param callargs: list of transcript test file names
         """
         import unittest
         from .transcript import Cmd2TestCase
+
         class TestMyAppCase(Cmd2TestCase):
             cmdapp = self
 
@@ -3044,7 +3076,7 @@ Script should contain one command per line, just like command would be typed in
         runner = unittest.TextTestRunner()
         runner.run(testcase)
 
-    def cmdloop(self, intro=None):
+    def cmdloop(self, intro: Optional[str]=None) -> 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
@@ -3053,7 +3085,7 @@ Script should contain one command per line, just like command would be typed in
         - transcript testing
         - intro banner
 
-        :param intro: str - if provided this overrides self.intro and serves as the intro banner printed once at start
+        :param intro: if provided this overrides self.intro and serves as the intro banner printed once at start
         """
         if self.allow_cli_args:
             parser = argparse.ArgumentParser()
@@ -3091,41 +3123,18 @@ Script should contain one command per line, just like command would be typed in
         self.postloop()
 
 
-class HistoryItem(str):
-    """Class used to represent an item in the History list.
-
-    Thin 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 = '-------------------------[{}]\n{}\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.format(self.idx, str(self).rstrip())
-
-
 class History(list):
     """ A list of HistoryItems that knows how to respond to user requests. """
 
     # noinspection PyMethodMayBeStatic
-    def _zero_based_index(self, onebased):
+    def _zero_based_index(self, onebased: int) -> int:
+        """Convert a one-based index to a zero-based index."""
         result = onebased
         if result > 0:
             result -= 1
         return result
 
-    def _to_index(self, raw):
+    def _to_index(self, raw: str) -> Optional[int]:
         if raw:
             result = self._zero_based_index(int(raw))
         else:
@@ -3134,11 +3143,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):
+    def span(self, raw: str) -> List[HistoryItem]:
         """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
+        :param raw: string potentially containing a span of the forms a..b, a:b, a:, ..b
+        :return: slice from the History list
         """
         if raw.lower() in ('*', '-', 'all'):
             raw = ':'
@@ -3162,20 +3171,20 @@ class History(list):
 
     rangePattern = re.compile(r'^\s*(?P<start>[\d]+)?\s*-\s*(?P<end>[\d]+)?\s*$')
 
-    def append(self, new):
+    def append(self, new: str) -> None:
         """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
+        :param new: 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 get(self, getme=None):
+    def get(self, getme: Optional[Union[int, str]]=None) -> List[HistoryItem]:
         """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
+        :param getme: item(s) to get - either an integer index or string to search for
+        :return: list of HistoryItems matching the retrieval criteria
         """
         if not getme:
             return self
@@ -3224,23 +3233,23 @@ class History(list):
 
 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):
+    def __init__(self, obj: Any, attribs: Iterable) -> None:
         """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
+        :param attribs: tuple of strings listing attributes of obj to save a copy of
         """
         self.obj = obj
         self.attribs = attribs
         if self.obj:
             self._save()
 
-    def _save(self):
+    def _save(self) -> None:
         """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):
+    def restore(self) -> None:
         """Overwrite attributes in self.obj with the saved values stored in this Statekeeper instance."""
         if self.obj:
             for attrib in self.attribs:
@@ -3264,6 +3273,6 @@ class CmdResult(utils.namedtuple_with_two_defaults('CmdResult', ['out', 'err', '
 
     NOTE: Named tuples are immutable.  So the contents are there for access, not for modification.
     """
-    def __bool__(self):
+    def __bool__(self) -> bool:
         """If err is an empty string, treat the result as a success; otherwise treat it as a failure."""
         return not self.err
diff --git a/cmd2/pyscript_bridge.py b/cmd2/pyscript_bridge.py
index 196be82b..9353e611 100644
--- a/cmd2/pyscript_bridge.py
+++ b/cmd2/pyscript_bridge.py
@@ -10,7 +10,7 @@ Released under MIT license, see LICENSE file
 import argparse
 import functools
 import sys
-from typing import List, Tuple, Callable
+from typing import List, Callable
 
 # Python 3.4 require contextlib2 for temporarily redirecting stderr and stdout
 if sys.version_info < (3, 5):
@@ -40,7 +40,7 @@ class CommandResult(namedtuple_with_defaults('CmdResult', ['stdout', 'stderr', '
 
 class CopyStream(object):
     """Copies all data written to a stream"""
-    def __init__(self, inner_stream, echo: bool = False):
+    def __init__(self, inner_stream, echo: bool = False) -> None:
         self.buffer = ''
         self.inner_stream = inner_stream
         self.echo = echo
@@ -212,7 +212,7 @@ class ArgparseFunctor:
         def process_flag(action, value):
             if isinstance(action, argparse._CountAction):
                 if isinstance(value, int):
-                    for c in range(value):
+                    for _ in range(value):
                         cmd_str[0] += '{} '.format(action.option_strings[0])
                     return
                 else:
@@ -298,4 +298,5 @@ class PyscriptBridge(object):
         return commands
 
     def __call__(self, args: str):
-        return _exec_cmd(self._cmd2_app, functools.partial(self._cmd2_app.onecmd_plus_hooks, args + '\n'), self.cmd_echo)
+        return _exec_cmd(self._cmd2_app, functools.partial(self._cmd2_app.onecmd_plus_hooks, args + '\n'),
+                         self.cmd_echo)
diff --git a/cmd2/transcript.py b/cmd2/transcript.py
index 8a9837a6..5ba8d20d 100644
--- a/cmd2/transcript.py
+++ b/cmd2/transcript.py
@@ -12,9 +12,11 @@ classes are used in cmd2.py::run_transcript_tests()
 import re
 import glob
 import unittest
+from typing import Tuple
 
 from . import utils
 
+
 class Cmd2TestCase(unittest.TestCase):
     """A unittest class used for transcript testing.
 
@@ -50,7 +52,7 @@ class Cmd2TestCase(unittest.TestCase):
             for (fname, transcript) in its:
                 self._test_transcript(fname, transcript)
 
-    def _test_transcript(self, fname, transcript):
+    def _test_transcript(self, fname: str, transcript):
         line_num = 0
         finished = False
         line = utils.strip_ansi(next(transcript))
@@ -103,7 +105,7 @@ class Cmd2TestCase(unittest.TestCase):
                       fname, line_num, command, expected, result)
             self.assertTrue(re.match(expected, result, re.MULTILINE | re.DOTALL), message)
 
-    def _transform_transcript_expected(self, s):
+    def _transform_transcript_expected(self, s: str) -> str:
         """Parse the string with slashed regexes into a valid regex.
 
         Given a string like:
@@ -151,7 +153,7 @@ class Cmd2TestCase(unittest.TestCase):
         return regex
 
     @staticmethod
-    def _escaped_find(regex, s, start, in_regex):
+    def _escaped_find(regex: str, s: str, start: int, in_regex: bool) -> Tuple[str, int, int]:
         """Find the next slash in {s} after {start} that is not preceded by a backslash.
 
         If we find an escaped slash, add everything up to and including it to regex,
@@ -162,7 +164,6 @@ class Cmd2TestCase(unittest.TestCase):
         {in_regex} specifies whether we are currently searching in a regex, we behave
         differently if we are or if we aren't.
         """
-
         while True:
             pos = s.find('/', start)
             if pos == -1:
@@ -211,14 +212,11 @@ class OutputTrap(object):
     def __init__(self):
         self.contents = ''
 
-    def write(self, txt):
-        """Add text to the internal contents.
-
-        :param txt: str
-        """
+    def write(self, txt: str):
+        """Add text to the internal contents."""
         self.contents += txt
 
-    def read(self):
+    def read(self) -> str:
         """Read from the internal contents and then clear them out.
 
         :return: str - text from the internal contents
diff --git a/cmd2/utils.py b/cmd2/utils.py
index 07969ff1..11d48b78 100644
--- a/cmd2/utils.py
+++ b/cmd2/utils.py
@@ -4,10 +4,11 @@
 
 import collections
 import os
-from typing import Optional
+from typing import Any, List, Optional, Union
 
 from . import constants
 
+
 def strip_ansi(text: str) -> str:
     """Strip ANSI escape codes from a string.
 
@@ -30,7 +31,8 @@ def strip_quotes(arg: str) -> str:
     return arg
 
 
-def namedtuple_with_defaults(typename, field_names, default_values=()):
+def namedtuple_with_defaults(typename: str, field_names: Union[str, List[str]],
+                             default_values: collections.Iterable=()):
     """
     Convenience function for defining a namedtuple with default values
 
@@ -58,7 +60,9 @@ def namedtuple_with_defaults(typename, field_names, default_values=()):
     T.__new__.__defaults__ = tuple(prototype)
     return T
 
-def namedtuple_with_two_defaults(typename, field_names, default_values=('', '')):
+
+def namedtuple_with_two_defaults(typename: str, field_names: Union[str, List[str]],
+                                 default_values: collections.Iterable=('', '')):
     """Wrapper around namedtuple which lets you treat the last value as optional.
 
     :param typename: str - type name for the Named tuple
@@ -72,7 +76,8 @@ def namedtuple_with_two_defaults(typename, field_names, default_values=('', ''))
     T.__new__.__defaults__ = default_values
     return T
 
-def cast(current, new):
+
+def cast(current: Any, new: str) -> Any:
     """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
@@ -101,6 +106,7 @@ def cast(current, new):
     print("Problem setting parameter (now %s) to %s; incorrect type?" % (current, new))
     return current
 
+
 def which(editor: str) -> Optional[str]:
     """Find the full path of a given editor.
 
@@ -118,7 +124,8 @@ def which(editor: str) -> Optional[str]:
         editor_path = None
     return editor_path
 
-def is_text_file(file_path):
+
+def is_text_file(file_path: str) -> bool:
     """Returns if a file contains only ASCII or UTF-8 encoded text
 
     :param file_path: path to the file being checked


Test patch:
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index 37592da1..f167793e 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -230,7 +230,7 @@ def test_pyscript_with_nonexist_file(base_app, capsys):
     python_script = 'does_not_exist.py'
     run_cmd(base_app, "pyscript {}".format(python_script))
     out, err = capsys.readouterr()
-    assert err.startswith('ERROR: [Errno 2] No such file or directory:')
+    assert err.startswith("EXCEPTION of type 'FileNotFoundError' occurred with message:")
 
 def test_pyscript_with_exception(base_app, capsys, request):
     test_dir = os.path.dirname(request.module.__file__)
@@ -1309,7 +1309,7 @@ def test_select_invalid_option(select_app):
     expected = normalize("""
    1. sweet
    2. salty
-3 isn't a valid choice. Pick a number between 1 and 2:
+'3' isn't a valid choice. Pick a number between 1 and 2:
 {} with sweet sauce, yum!
 """.format(food))