4545import unittest
4646from code import InteractiveConsole
4747
48- try :
49- from enum34 import Enum
50- except ImportError :
51- from enum import Enum
52-
5348import pyparsing
5449import pyperclip
5550
51+ # Set up readline
52+ from .rl_utils import rl_force_redisplay , readline , rl_type , RlType
53+
54+ if rl_type == RlType .PYREADLINE :
55+
56+ # Save the original pyreadline display completion function since we need to override it and restore it
57+ # noinspection PyProtectedMember
58+ orig_pyreadline_display = readline .rl .mode ._display_completions
59+
60+ elif rl_type == RlType .GNU :
61+
62+ # We need wcswidth to calculate display width of tab completions
63+ from wcwidth import wcswidth
64+
65+ # Get the readline lib so we can make changes to it
66+ import ctypes
67+ from .rl_utils import readline_lib
68+
69+ # Save address that rl_basic_quote_characters is pointing to since we need to override and restore it
70+ rl_basic_quote_characters = ctypes .c_char_p .in_dll (readline_lib , "rl_basic_quote_characters" )
71+ orig_rl_basic_quote_characters_addr = ctypes .cast (rl_basic_quote_characters , ctypes .c_void_p ).value
72+
5673# Newer versions of pyperclip are released as a single file, but older versions had a more complicated structure
5774try :
5875 from pyperclip .exceptions import PyperclipException
5976except ImportError :
6077 # noinspection PyUnresolvedReferences
6178 from pyperclip import PyperclipException
62-
79+
6380# Collection is a container that is sizable and iterable
6481# It was introduced in Python 3.6. We will try to import it, otherwise use our implementation
6582try :
@@ -96,47 +113,6 @@ def __subclasshook__(cls, C):
96113except ImportError :
97114 ipython_available = False
98115
99- # Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit)
100- try :
101- import gnureadline as readline
102- except ImportError :
103- # Try to import readline, but allow failure for convenience in Windows unit testing
104- # Note: If this actually fails, you should install readline on Linux or Mac or pyreadline on Windows
105- try :
106- # noinspection PyUnresolvedReferences
107- import readline
108- except ImportError :
109- pass
110-
111- # Check what implementation of readline we are using
112- class RlType (Enum ):
113- GNU = 1
114- PYREADLINE = 2
115- NONE = 3
116-
117- rl_type = RlType .NONE
118-
119- if 'pyreadline' in sys .modules :
120- rl_type = RlType .PYREADLINE
121-
122- # Save the original pyreadline display completion function since we need to override it and restore it
123- # noinspection PyProtectedMember
124- orig_pyreadline_display = readline .rl .mode ._display_completions
125-
126- elif 'gnureadline' in sys .modules or 'readline' in sys .modules :
127- rl_type = RlType .GNU
128-
129- # We need wcswidth to calculate display width of tab completions
130- from wcwidth import wcswidth
131-
132- # Load the readline lib so we can make changes to it
133- import ctypes
134- readline_lib = ctypes .CDLL (readline .__file__ )
135-
136- # Save address that rl_basic_quote_characters is pointing to since we need to override and restore it
137- rl_basic_quote_characters = ctypes .c_char_p .in_dll (readline_lib , "rl_basic_quote_characters" )
138- orig_rl_basic_quote_characters_addr = ctypes .cast (rl_basic_quote_characters , ctypes .c_void_p ).value
139-
140116__version__ = '0.9.0'
141117
142118# Pyparsing enablePackrat() can greatly speed up parsing, but problems have been seen in Python 3 in the past
@@ -295,8 +271,18 @@ def cmd_wrapper(instance, cmdline):
295271
296272 # If there are subcommands, store their names in a list to support tab-completion of subcommand names
297273 if argparser ._subparsers is not None :
298- subcommand_names = argparser ._subparsers ._group_actions [0 ]._name_parser_map .keys ()
299- cmd_wrapper .__dict__ ['subcommand_names' ] = subcommand_names
274+ # Key is subcommand name and value is completer function
275+ subcommands = collections .OrderedDict ()
276+
277+ # Get all subcommands and check if they have completer functions
278+ for name , parser in argparser ._subparsers ._group_actions [0 ]._name_parser_map .items ():
279+ if 'completer' in parser ._defaults :
280+ completer = parser ._defaults ['completer' ]
281+ else :
282+ completer = None
283+ subcommands [name ] = completer
284+
285+ cmd_wrapper .__dict__ ['subcommands' ] = subcommands
300286
301287 return cmd_wrapper
302288
@@ -1605,7 +1591,7 @@ def _redirect_complete(self, text, line, begidx, endidx, compfunc):
16051591 return compfunc (text , line , begidx , endidx )
16061592
16071593 @staticmethod
1608- def _pad_matches_to_display (matches_to_display ):
1594+ def _pad_matches_to_display (matches_to_display ): # pragma: no cover
16091595 """
16101596 Adds padding to the matches being displayed as tab completion suggestions.
16111597 The default padding of readline/pyreadine is small and not visually appealing
@@ -1627,7 +1613,7 @@ def _pad_matches_to_display(matches_to_display):
16271613
16281614 return [cur_match + padding for cur_match in matches_to_display ], len (padding )
16291615
1630- def _display_matches_gnu_readline (self , substitution , matches , longest_match_length ):
1616+ def _display_matches_gnu_readline (self , substitution , matches , longest_match_length ): # pragma: no cover
16311617 """
16321618 Prints a match list using GNU readline's rl_display_match_list()
16331619 This exists to print self.display_matches if it has data. Otherwise matches prints.
@@ -1675,15 +1661,10 @@ def _display_matches_gnu_readline(self, substitution, matches, longest_match_len
16751661 # rl_display_match_list(strings_array, number of completion matches, longest match length)
16761662 readline_lib .rl_display_match_list (strings_array , len (encoded_matches ), longest_match_length )
16771663
1678- # rl_forced_update_display() is the proper way to redraw the prompt and line, but we
1679- # have to use ctypes to do it since Python's readline API does not wrap the function
1680- readline_lib .rl_forced_update_display ()
1681-
1682- # Since we updated the display, readline asks that rl_display_fixed be set for efficiency
1683- display_fixed = ctypes .c_int .in_dll (readline_lib , "rl_display_fixed" )
1684- display_fixed .value = 1
1664+ # Redraw prompt and input line
1665+ rl_force_redisplay ()
16851666
1686- def _display_matches_pyreadline (self , matches ):
1667+ def _display_matches_pyreadline (self , matches ): # pragma: no cover
16871668 """
16881669 Prints a match list using pyreadline's _display_completions()
16891670 This exists to print self.display_matches if it has data. Otherwise matches prints.
@@ -1701,7 +1682,7 @@ def _display_matches_pyreadline(self, matches):
17011682 # Add padding for visual appeal
17021683 matches_to_display , _ = self ._pad_matches_to_display (matches_to_display )
17031684
1704- # Display the matches
1685+ # Display matches using actual display function. This also redraws the prompt and line.
17051686 orig_pyreadline_display (matches_to_display )
17061687
17071688 # ----- Methods which override stuff in cmd -----
@@ -3363,7 +3344,7 @@ def do_load(self, arglist):
33633344 # self._script_dir list when done.
33643345 with open (expanded_path , encoding = 'utf-8' ) as target :
33653346 self .cmdqueue = target .read ().splitlines () + ['eos' ] + self .cmdqueue
3366- except IOError as e :
3347+ except IOError as e : # pragma: no cover
33673348 self .perror ('Problem accessing script from {}:\n {}' .format (expanded_path , e ))
33683349 return
33693350
@@ -3390,7 +3371,7 @@ def is_text_file(file_path):
33903371 # noinspection PyUnusedLocal
33913372 if sum (1 for line in f ) > 0 :
33923373 valid_text_file = True
3393- except IOError :
3374+ except IOError : # pragma: no cover
33943375 pass
33953376 except UnicodeDecodeError :
33963377 # The file is not ASCII. Check if it is UTF-8.
@@ -3400,7 +3381,7 @@ def is_text_file(file_path):
34003381 # noinspection PyUnusedLocal
34013382 if sum (1 for line in f ) > 0 :
34023383 valid_text_file = True
3403- except IOError :
3384+ except IOError : # pragma: no cover
34043385 pass
34053386 except UnicodeDecodeError :
34063387 # Not UTF-8
@@ -4066,10 +4047,3 @@ def __bool__(self):
40664047 return not self .err
40674048
40684049
4069- if __name__ == '__main__' :
4070- # If run as the main application, simply start a bare-bones cmd2 application with only built-in functionality.
4071-
4072- # Set "use_ipython" to True to include the ipy command if IPython is installed, which supports advanced interactive
4073- # debugging of your application via introspection on self.
4074- app = Cmd (use_ipython = False )
4075- app .cmdloop ()
0 commit comments