|
51 | 51 |
|
52 | 52 | # Set up readline |
53 | 53 | from .rl_utils import rl_force_redisplay, readline, rl_type, RlType |
| 54 | +from .argparse_completer import AutoCompleter, ACArgumentParser |
54 | 55 |
|
55 | 56 | if rl_type == RlType.PYREADLINE: |
56 | 57 |
|
@@ -266,23 +267,8 @@ def cmd_wrapper(instance, cmdline): |
266 | 267 |
|
267 | 268 | cmd_wrapper.__doc__ = argparser.format_help() |
268 | 269 |
|
269 | | - # Mark this function as having an argparse ArgumentParser (used by do_help) |
270 | | - cmd_wrapper.__dict__['has_parser'] = True |
271 | | - |
272 | | - # If there are subcommands, store their names in a list to support tab-completion of subcommand names |
273 | | - if argparser._subparsers is not None: |
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 |
| 270 | + # Mark this function as having an argparse ArgumentParser |
| 271 | + setattr(cmd_wrapper, 'argparser', argparser) |
286 | 272 |
|
287 | 273 | return cmd_wrapper |
288 | 274 |
|
@@ -318,24 +304,8 @@ def cmd_wrapper(instance, cmdline): |
318 | 304 |
|
319 | 305 | cmd_wrapper.__doc__ = argparser.format_help() |
320 | 306 |
|
321 | | - # Mark this function as having an argparse ArgumentParser (used by do_help) |
322 | | - cmd_wrapper.__dict__['has_parser'] = True |
323 | | - |
324 | | - # If there are subcommands, store their names in a list to support tab-completion of subcommand names |
325 | | - if argparser._subparsers is not None: |
326 | | - |
327 | | - # Key is subcommand name and value is completer function |
328 | | - subcommands = collections.OrderedDict() |
329 | | - |
330 | | - # Get all subcommands and check if they have completer functions |
331 | | - for name, parser in argparser._subparsers._group_actions[0]._name_parser_map.items(): |
332 | | - if 'completer' in parser._defaults: |
333 | | - completer = parser._defaults['completer'] |
334 | | - else: |
335 | | - completer = None |
336 | | - subcommands[name] = completer |
337 | | - |
338 | | - cmd_wrapper.__dict__['subcommands'] = subcommands |
| 307 | + # Mark this function as having an argparse ArgumentParser |
| 308 | + setattr(cmd_wrapper, 'argparser', argparser) |
339 | 309 |
|
340 | 310 | return cmd_wrapper |
341 | 311 |
|
@@ -1020,49 +990,6 @@ def colorize(self, val, color): |
1020 | 990 | return self._colorcodes[color][True] + val + self._colorcodes[color][False] |
1021 | 991 | return val |
1022 | 992 |
|
1023 | | - def get_subcommands(self, command): |
1024 | | - """ |
1025 | | - Returns a list of a command's subcommand names if they exist |
1026 | | - :param command: the command we are querying |
1027 | | - :return: A subcommand list or None |
1028 | | - """ |
1029 | | - |
1030 | | - subcommand_names = None |
1031 | | - |
1032 | | - # Check if is a valid command |
1033 | | - funcname = self._func_named(command) |
1034 | | - |
1035 | | - if funcname: |
1036 | | - # Check to see if this function was decorated with an argparse ArgumentParser |
1037 | | - func = getattr(self, funcname) |
1038 | | - subcommands = func.__dict__.get('subcommands', None) |
1039 | | - if subcommands is not None: |
1040 | | - subcommand_names = subcommands.keys() |
1041 | | - |
1042 | | - return subcommand_names |
1043 | | - |
1044 | | - def get_subcommand_completer(self, command, subcommand): |
1045 | | - """ |
1046 | | - Returns a subcommand's tab completion function if one exists |
1047 | | - :param command: command which owns the subcommand |
1048 | | - :param subcommand: the subcommand we are querying |
1049 | | - :return: A completer or None |
1050 | | - """ |
1051 | | - |
1052 | | - completer = None |
1053 | | - |
1054 | | - # Check if is a valid command |
1055 | | - funcname = self._func_named(command) |
1056 | | - |
1057 | | - if funcname: |
1058 | | - # Check to see if this function was decorated with an argparse ArgumentParser |
1059 | | - func = getattr(self, funcname) |
1060 | | - subcommands = func.__dict__.get('subcommands', None) |
1061 | | - if subcommands is not None: |
1062 | | - completer = subcommands[subcommand] |
1063 | | - |
1064 | | - return completer |
1065 | | - |
1066 | 993 | # ----- Methods related to tab completion ----- |
1067 | 994 |
|
1068 | 995 | def set_completion_defaults(self): |
@@ -1794,16 +1721,14 @@ def complete(self, text, state): |
1794 | 1721 | try: |
1795 | 1722 | compfunc = getattr(self, 'complete_' + command) |
1796 | 1723 | except AttributeError: |
1797 | | - compfunc = self.completedefault |
1798 | | - |
1799 | | - subcommands = self.get_subcommands(command) |
1800 | | - if subcommands is not None: |
1801 | | - # Since there are subcommands, then try completing those if the cursor is in |
1802 | | - # the token at index 1, otherwise default to using compfunc |
1803 | | - index_dict = {1: subcommands} |
1804 | | - compfunc = functools.partial(self.index_based_complete, |
1805 | | - index_dict=index_dict, |
1806 | | - all_else=compfunc) |
| 1724 | + # There's no completer function, next see if the command uses argparser |
| 1725 | + try: |
| 1726 | + cmd_func = getattr(self, 'do_' + command) |
| 1727 | + argparser = getattr(cmd_func, 'argparser') |
| 1728 | + # Command uses argparser, switch to the default argparse completer |
| 1729 | + compfunc = functools.partial(self._autocomplete_default, argparser=argparser) |
| 1730 | + except AttributeError: |
| 1731 | + compfunc = self.completedefault |
1807 | 1732 |
|
1808 | 1733 | # A valid command was not entered |
1809 | 1734 | else: |
@@ -1910,6 +1835,16 @@ def complete(self, text, state): |
1910 | 1835 | except IndexError: |
1911 | 1836 | return None |
1912 | 1837 |
|
| 1838 | + def _autocomplete_default(self, text: str, line: str, begidx: int, endidx: int, |
| 1839 | + argparser: argparse.ArgumentParser) -> List[str]: |
| 1840 | + """Default completion function for argparse commands.""" |
| 1841 | + completer = AutoCompleter(argparser) |
| 1842 | + |
| 1843 | + tokens, _ = self.tokens_for_completion(line, begidx, endidx) |
| 1844 | + results = completer.complete_command(tokens, text, line, begidx, endidx) |
| 1845 | + |
| 1846 | + return results |
| 1847 | + |
1913 | 1848 | def get_all_commands(self): |
1914 | 1849 | """ |
1915 | 1850 | Returns a list of all commands |
@@ -1964,12 +1899,15 @@ def complete_help(self, text, line, begidx, endidx): |
1964 | 1899 | strs_to_match = list(topics | visible_commands) |
1965 | 1900 | matches = self.basic_complete(text, line, begidx, endidx, strs_to_match) |
1966 | 1901 |
|
1967 | | - # Check if we are completing a subcommand |
1968 | | - elif index == subcmd_index: |
1969 | | - |
1970 | | - # Match subcommands if any exist |
1971 | | - command = tokens[cmd_index] |
1972 | | - matches = self.basic_complete(text, line, begidx, endidx, self.get_subcommands(command)) |
| 1902 | + # check if the command uses argparser |
| 1903 | + elif index >= subcmd_index: |
| 1904 | + try: |
| 1905 | + cmd_func = getattr(self, 'do_' + tokens[cmd_index]) |
| 1906 | + parser = getattr(cmd_func, 'argparser') |
| 1907 | + completer = AutoCompleter(parser) |
| 1908 | + matches = completer.complete_command_help(tokens[1:], text, line, begidx, endidx) |
| 1909 | + except AttributeError: |
| 1910 | + pass |
1973 | 1911 |
|
1974 | 1912 | return matches |
1975 | 1913 |
|
@@ -2620,7 +2558,7 @@ def do_help(self, arglist): |
2620 | 2558 | if funcname: |
2621 | 2559 | # Check to see if this function was decorated with an argparse ArgumentParser |
2622 | 2560 | func = getattr(self, funcname) |
2623 | | - if func.__dict__.get('has_parser', False): |
| 2561 | + if hasattr(func, 'argparser'): |
2624 | 2562 | # Function has an argparser, so get help based on all the arguments in case there are sub-commands |
2625 | 2563 | new_arglist = arglist[1:] |
2626 | 2564 | new_arglist.append('-h') |
@@ -2843,10 +2781,10 @@ def show(self, args, parameter): |
2843 | 2781 | else: |
2844 | 2782 | raise LookupError("Parameter '%s' not supported (type 'show' for list of parameters)." % param) |
2845 | 2783 |
|
2846 | | - set_parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) |
| 2784 | + set_parser = ACArgumentParser(formatter_class=argparse.RawTextHelpFormatter) |
2847 | 2785 | set_parser.add_argument('-a', '--all', action='store_true', help='display read-only settings as well') |
2848 | 2786 | set_parser.add_argument('-l', '--long', action='store_true', help='describe function of parameter') |
2849 | | - set_parser.add_argument('settable', nargs='*', help='[param_name] [value]') |
| 2787 | + set_parser.add_argument('settable', nargs=(0,2), help='[param_name] [value]') |
2850 | 2788 |
|
2851 | 2789 | @with_argparser(set_parser) |
2852 | 2790 | def do_set(self, args): |
@@ -2927,87 +2865,6 @@ def complete_shell(self, text, line, begidx, endidx): |
2927 | 2865 | index_dict = {1: self.shell_cmd_complete} |
2928 | 2866 | return self.index_based_complete(text, line, begidx, endidx, index_dict, self.path_complete) |
2929 | 2867 |
|
2930 | | - def cmd_with_subs_completer(self, text, line, begidx, endidx): |
2931 | | - """ |
2932 | | - This is a function provided for convenience to those who want an easy way to add |
2933 | | - tab completion to functions that implement subcommands. By setting this as the |
2934 | | - completer of the base command function, the correct completer for the chosen subcommand |
2935 | | - will be called. |
2936 | | -
|
2937 | | - The use of this function requires assigning a completer function to the subcommand's parser |
2938 | | - Example: |
2939 | | - A command called print has a subcommands called 'names' that needs a tab completer |
2940 | | - When you create the parser for names, include the completer function in the parser's defaults. |
2941 | | -
|
2942 | | - names_parser.set_defaults(func=print_names, completer=complete_print_names) |
2943 | | -
|
2944 | | - To make sure the names completer gets called, set the completer for the print function |
2945 | | - in a similar fashion to what follows. |
2946 | | -
|
2947 | | - complete_print = cmd2.Cmd.cmd_with_subs_completer |
2948 | | -
|
2949 | | - When the subcommand's completer is called, this function will have stripped off all content from the |
2950 | | - beginning of the command line before the subcommand, meaning the line parameter always starts with the |
2951 | | - subcommand name and the index parameters reflect this change. |
2952 | | -
|
2953 | | - For instance, the command "print names -d 2" becomes "names -d 2" |
2954 | | - begidx and endidx are incremented accordingly |
2955 | | -
|
2956 | | - :param text: str - the string prefix we are attempting to match (all returned matches must begin with it) |
2957 | | - :param line: str - the current input line with leading whitespace removed |
2958 | | - :param begidx: int - the beginning index of the prefix text |
2959 | | - :param endidx: int - the ending index of the prefix text |
2960 | | - :return: List[str] - a list of possible tab completions |
2961 | | - """ |
2962 | | - # The command is the token at index 0 in the command line |
2963 | | - cmd_index = 0 |
2964 | | - |
2965 | | - # The subcommand is the token at index 1 in the command line |
2966 | | - subcmd_index = 1 |
2967 | | - |
2968 | | - # Get all tokens through the one being completed |
2969 | | - tokens, _ = self.tokens_for_completion(line, begidx, endidx) |
2970 | | - if tokens is None: |
2971 | | - return [] |
2972 | | - |
2973 | | - matches = [] |
2974 | | - |
2975 | | - # Get the index of the token being completed |
2976 | | - index = len(tokens) - 1 |
2977 | | - |
2978 | | - # If the token being completed is past the subcommand name, then do subcommand specific tab-completion |
2979 | | - if index > subcmd_index: |
2980 | | - |
2981 | | - # Get the command name |
2982 | | - command = tokens[cmd_index] |
2983 | | - |
2984 | | - # Get the subcommand name |
2985 | | - subcommand = tokens[subcmd_index] |
2986 | | - |
2987 | | - # Find the offset into line where the subcommand name begins |
2988 | | - subcmd_start = 0 |
2989 | | - for cur_index in range(0, subcmd_index + 1): |
2990 | | - cur_token = tokens[cur_index] |
2991 | | - subcmd_start = line.find(cur_token, subcmd_start) |
2992 | | - |
2993 | | - if cur_index != subcmd_index: |
2994 | | - subcmd_start += len(cur_token) |
2995 | | - |
2996 | | - # Strip off everything before subcommand name |
2997 | | - orig_line = line |
2998 | | - line = line[subcmd_start:] |
2999 | | - |
3000 | | - # Update the indexes |
3001 | | - diff = len(orig_line) - len(line) |
3002 | | - begidx -= diff |
3003 | | - endidx -= diff |
3004 | | - |
3005 | | - # Call the subcommand specific completer if it exists |
3006 | | - compfunc = self.get_subcommand_completer(command, subcommand) |
3007 | | - if compfunc is not None: |
3008 | | - matches = compfunc(self, text, line, begidx, endidx) |
3009 | | - |
3010 | | - return matches |
3011 | 2868 |
|
3012 | 2869 | # noinspection PyBroadException |
3013 | 2870 | def do_py(self, arg): |
|
0 commit comments