diff options
author | kotfu <kotfu@kotfu.net> | 2018-04-19 21:51:24 -0600 |
---|---|---|
committer | kotfu <kotfu@kotfu.net> | 2018-04-19 21:51:24 -0600 |
commit | 477666d0b3e097fb831729644b8861a983805981 (patch) | |
tree | 02217d3d2bb03499e511d8dd1774d161bb019a0f /cmd2/cmd2.py | |
parent | b7cfb130c7c914478936366b748b04234b031119 (diff) | |
parent | 58fdd089cc064e71502dc1f094fd906d30523886 (diff) | |
download | cmd2-git-477666d0b3e097fb831729644b8861a983805981.tar.gz |
Merge branch 'master' into ply
Diffstat (limited to 'cmd2/cmd2.py')
-rwxr-xr-x | cmd2/cmd2.py | 114 |
1 files changed, 44 insertions, 70 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index b9928a4b..871b356b 100755 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -45,21 +45,38 @@ import traceback import unittest from code import InteractiveConsole -try: - from enum34 import Enum -except ImportError: - from enum import Enum - import pyparsing import pyperclip +# Set up readline +from .rl_utils import rl_force_redisplay, readline, rl_type, RlType + +if rl_type == RlType.PYREADLINE: + + # Save the original pyreadline display completion function since we need to override it and restore it + # noinspection PyProtectedMember + orig_pyreadline_display = readline.rl.mode._display_completions + +elif rl_type == RlType.GNU: + + # We need wcswidth to calculate display width of tab completions + from wcwidth import wcswidth + + # Get the readline lib so we can make changes to it + import ctypes + from .rl_utils import readline_lib + + # Save address that rl_basic_quote_characters is pointing to since we need to override and restore it + rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters") + orig_rl_basic_quote_characters_addr = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value + # Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure try: from pyperclip.exceptions import PyperclipException except ImportError: # noinspection PyUnresolvedReferences from pyperclip import PyperclipException - + # Collection is a container that is sizable and iterable # It was introduced in Python 3.6. We will try to import it, otherwise use our implementation try: @@ -96,47 +113,6 @@ try: except ImportError: ipython_available = False -# Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit) -try: - import gnureadline as readline -except ImportError: - # Try to import readline, but allow failure for convenience in Windows unit testing - # Note: If this actually fails, you should install readline on Linux or Mac or pyreadline on Windows - try: - # noinspection PyUnresolvedReferences - import readline - except ImportError: - pass - -# Check what implementation of readline we are using -class RlType(Enum): - GNU = 1 - PYREADLINE = 2 - NONE = 3 - -rl_type = RlType.NONE - -if 'pyreadline' in sys.modules: - rl_type = RlType.PYREADLINE - - # Save the original pyreadline display completion function since we need to override it and restore it - # noinspection PyProtectedMember - orig_pyreadline_display = readline.rl.mode._display_completions - -elif 'gnureadline' in sys.modules or 'readline' in sys.modules: - rl_type = RlType.GNU - - # We need wcswidth to calculate display width of tab completions - from wcwidth import wcswidth - - # Load the readline lib so we can make changes to it - import ctypes - readline_lib = ctypes.CDLL(readline.__file__) - - # Save address that rl_basic_quote_characters is pointing to since we need to override and restore it - rl_basic_quote_characters = ctypes.c_char_p.in_dll(readline_lib, "rl_basic_quote_characters") - orig_rl_basic_quote_characters_addr = ctypes.cast(rl_basic_quote_characters, ctypes.c_void_p).value - __version__ = '0.9.0' # Pyparsing enablePackrat() can greatly speed up parsing, but problems have been seen in Python 3 in the past @@ -295,8 +271,18 @@ def with_argparser_and_unknown_args(argparser): # If there are subcommands, store their names in a list to support tab-completion of subcommand names if argparser._subparsers is not None: - subcommand_names = argparser._subparsers._group_actions[0]._name_parser_map.keys() - cmd_wrapper.__dict__['subcommand_names'] = subcommand_names + # Key is subcommand name and value is completer function + subcommands = collections.OrderedDict() + + # Get all subcommands and check if they have completer functions + for name, parser in argparser._subparsers._group_actions[0]._name_parser_map.items(): + if 'completer' in parser._defaults: + completer = parser._defaults['completer'] + else: + completer = None + subcommands[name] = completer + + cmd_wrapper.__dict__['subcommands'] = subcommands return cmd_wrapper @@ -1605,7 +1591,7 @@ class Cmd(cmd.Cmd): return compfunc(text, line, begidx, endidx) @staticmethod - def _pad_matches_to_display(matches_to_display): + def _pad_matches_to_display(matches_to_display): # 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 @@ -1627,7 +1613,7 @@ 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): + 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() This exists to print self.display_matches if it has data. Otherwise matches prints. @@ -1675,15 +1661,10 @@ class Cmd(cmd.Cmd): # rl_display_match_list(strings_array, number of completion matches, longest match length) readline_lib.rl_display_match_list(strings_array, len(encoded_matches), longest_match_length) - # rl_forced_update_display() is the proper way to redraw the prompt and line, but we - # have to use ctypes to do it since Python's readline API does not wrap the function - readline_lib.rl_forced_update_display() - - # Since we updated the display, readline asks that rl_display_fixed be set for efficiency - display_fixed = ctypes.c_int.in_dll(readline_lib, "rl_display_fixed") - display_fixed.value = 1 + # Redraw prompt and input line + rl_force_redisplay() - def _display_matches_pyreadline(self, matches): + def _display_matches_pyreadline(self, matches): # 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. @@ -1701,7 +1682,7 @@ class Cmd(cmd.Cmd): # Add padding for visual appeal matches_to_display, _ = self._pad_matches_to_display(matches_to_display) - # Display the matches + # Display matches using actual display function. This also redraws the prompt and line. orig_pyreadline_display(matches_to_display) # ----- Methods which override stuff in cmd ----- @@ -3363,7 +3344,7 @@ Script should contain one command per line, just like command would be typed in # self._script_dir list when done. with open(expanded_path, encoding='utf-8') as target: self.cmdqueue = target.read().splitlines() + ['eos'] + self.cmdqueue - except IOError as e: + except IOError as e: # pragma: no cover self.perror('Problem accessing script from {}:\n{}'.format(expanded_path, e)) return @@ -3390,7 +3371,7 @@ Script should contain one command per line, just like command would be typed in # noinspection PyUnusedLocal if sum(1 for line in f) > 0: valid_text_file = True - except IOError: + except IOError: # pragma: no cover pass except UnicodeDecodeError: # The file is not ASCII. Check if it is UTF-8. @@ -3400,7 +3381,7 @@ Script should contain one command per line, just like command would be typed in # noinspection PyUnusedLocal if sum(1 for line in f) > 0: valid_text_file = True - except IOError: + except IOError: # pragma: no cover pass except UnicodeDecodeError: # Not UTF-8 @@ -4066,10 +4047,3 @@ class CmdResult(namedtuple_with_two_defaults('CmdResult', ['out', 'err', 'war']) return not self.err -if __name__ == '__main__': - # If run as the main application, simply start a bare-bones cmd2 application with only built-in functionality. - - # Set "use_ipython" to True to include the ipy command if IPython is installed, which supports advanced interactive - # debugging of your application via introspection on self. - app = Cmd(use_ipython=False) - app.cmdloop() |