diff options
-rwxr-xr-x | cmd2/argparse_completer.py | 2 | ||||
-rwxr-xr-x | cmd2/cmd2.py | 35 | ||||
-rwxr-xr-x | examples/tab_autocompletion.py | 196 |
3 files changed, 102 insertions, 131 deletions
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py index 6e53a518..e87e9c04 100755 --- a/cmd2/argparse_completer.py +++ b/cmd2/argparse_completer.py @@ -415,7 +415,7 @@ class AutoCompleter(object): return completion_results def complete_command_help(self, tokens: List[str], text: str, line: str, begidx: int, endidx: int) -> List[str]: - """Supports the completion of sub-commands for commands thhrough the cmd2 help command.""" + """Supports the completion of sub-commands for commands through the cmd2 help command.""" for idx, token in enumerate(tokens): if idx >= self._token_start_index: if self._positional_completers: diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 10f790c0..288a506b 100755 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -267,8 +267,7 @@ def with_argparser_and_unknown_args(argparser: argparse.ArgumentParser) -> Calla cmd_wrapper.__doc__ = argparser.format_help() - # Mark this function as having an argparse ArgumentParser (used by do_help) - cmd_wrapper.__dict__['has_parser'] = True + # Mark this function as having an argparse ArgumentParser setattr(cmd_wrapper, 'argparser', argparser) return cmd_wrapper @@ -305,8 +304,7 @@ def with_argparser(argparser: argparse.ArgumentParser) -> Callable: cmd_wrapper.__doc__ = argparser.format_help() - # Mark this function as having an argparse ArgumentParser (used by do_help) - cmd_wrapper.__dict__['has_parser'] = True + # Mark this function as having an argparse ArgumentParser setattr(cmd_wrapper, 'argparser', argparser) return cmd_wrapper @@ -1724,14 +1722,12 @@ class Cmd(cmd.Cmd): compfunc = getattr(self, 'complete_' + command) except AttributeError: # There's no completer function, next see if the command uses argparser - cmd_func = getattr(self, 'do_' + command) - if hasattr(cmd_func, 'has_parser') and hasattr(cmd_func, 'argparser') and \ - getattr(cmd_func, 'has_parser'): - # Command uses argparser, switch to the default argparse completer + try: + cmd_func = getattr(self, 'do_' + command) argparser = getattr(cmd_func, 'argparser') - compfunc = functools.partial(self._autocomplete_default, - argparser=argparser) - else: + # Command uses argparser, switch to the default argparse completer + compfunc = functools.partial(self._autocomplete_default, argparser=argparser) + except AttributeError: compfunc = self.completedefault # A valid command was not entered @@ -1904,13 +1900,14 @@ class Cmd(cmd.Cmd): matches = self.basic_complete(text, line, begidx, endidx, strs_to_match) # check if the command uses argparser - elif index >= subcmd_index and hasattr(self, 'do_' + tokens[cmd_index]) and\ - hasattr(getattr(self, 'do_' + tokens[cmd_index]), 'has_parser'): - command = tokens[cmd_index] - cmd_func = getattr(self, 'do_' + command) - parser = getattr(cmd_func, 'argparser') - completer = AutoCompleter(parser) - matches = completer.complete_command_help(tokens[1:], text, line, begidx, endidx) + elif index >= subcmd_index: + try: + cmd_func = getattr(self, 'do_' + tokens[cmd_index]) + parser = getattr(cmd_func, 'argparser') + completer = AutoCompleter(parser) + matches = completer.complete_command_help(tokens[1:], text, line, begidx, endidx) + except AttributeError: + pass return matches @@ -2561,7 +2558,7 @@ Usage: Usage: unalias [-a] name [name ...] if funcname: # Check to see if this function was decorated with an argparse ArgumentParser func = getattr(self, funcname) - if func.__dict__.get('has_parser', False): + if hasattr(func, 'argparser'): # Function has an argparser, so get help based on all the arguments in case there are sub-commands new_arglist = arglist[1:] new_arglist.append('-h') diff --git a/examples/tab_autocompletion.py b/examples/tab_autocompletion.py index 75ea1f00..17c8391d 100755 --- a/examples/tab_autocompletion.py +++ b/examples/tab_autocompletion.py @@ -120,15 +120,6 @@ class TabCompleteExample(cmd2.Cmd): if not args.type: self.do_help('suggest') - def complete_suggest(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: - """ Adds tab completion to media""" - completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser, 1) - - tokens, _ = self.tokens_for_completion(line, begidx, endidx) - results = completer.complete_command(tokens, text, line, begidx, endidx) - - return results - # If you prefer the original argparse help output but would like narg ranges, it's possible # to enable narg ranges without the help changes using this method @@ -148,15 +139,6 @@ class TabCompleteExample(cmd2.Cmd): if not args.type: self.do_help('orig_suggest') - def complete_hybrid_suggest(self, text, line, begidx, endidx): - """ Adds tab completion to media""" - completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser_hybrid) - - tokens, _ = self.tokens_for_completion(line, begidx, endidx) - results = completer.complete_command(tokens, text, line, begidx, endidx) - - return results - # This variant demonstrates the AutoCompleter working with the orginial argparse. # Base argparse is unable to specify narg ranges. Autocompleter will keep expecting additional arguments # for the -d/--duration flag until you specify a new flaw or end the list it with '--' @@ -175,20 +157,12 @@ class TabCompleteExample(cmd2.Cmd): if not args.type: self.do_help('orig_suggest') - def complete_orig_suggest(self, text, line, begidx, endidx) -> List[str]: - """ Adds tab completion to media""" - completer = argparse_completer.AutoCompleter(TabCompleteExample.suggest_parser_orig) - - tokens, _ = self.tokens_for_completion(line, begidx, endidx) - results = completer.complete_command(tokens, text, line, begidx, endidx) - - return results - ################################################################################### # The media command demonstrates a completer with multiple layers of subcommands - # - This example uses a flat completion lookup dictionary + # - This example demonstrates how to tag a completion attribute on each action, enabling argument + # completion without implementing a complete_COMMAND function - def _do_media_movies(self, args) -> None: + def _do_vid_media_movies(self, args) -> None: if not args.command: self.do_help('media movies') elif args.command == 'list': @@ -199,7 +173,7 @@ class TabCompleteExample(cmd2.Cmd): ', '.join(movie['director']), '\n '.join(movie['actor']))) - def _do_media_shows(self, args) -> None: + def _do_vid_media_shows(self, args) -> None: if not args.command: self.do_help('media shows') @@ -215,72 +189,67 @@ class TabCompleteExample(cmd2.Cmd): '\n '.join(ep_list))) print() - media_parser = argparse_completer.ACArgumentParser(prog='media') + video_parser = argparse_completer.ACArgumentParser(prog='media') - media_types_subparsers = media_parser.add_subparsers(title='Media Types', dest='type') + video_types_subparsers = video_parser.add_subparsers(title='Media Types', dest='type') - movies_parser = media_types_subparsers.add_parser('movies') - movies_parser.set_defaults(func=_do_media_movies) + vid_movies_parser = video_types_subparsers.add_parser('movies') + vid_movies_parser.set_defaults(func=_do_vid_media_movies) - movies_commands_subparsers = movies_parser.add_subparsers(title='Commands', dest='command') + vid_movies_commands_subparsers = vid_movies_parser.add_subparsers(title='Commands', dest='command') - movies_list_parser = movies_commands_subparsers.add_parser('list') + vid_movies_list_parser = vid_movies_commands_subparsers.add_parser('list') - movies_list_parser.add_argument('-t', '--title', help='Title Filter') - movies_list_parser.add_argument('-r', '--rating', help='Rating Filter', nargs='+', - choices=ratings_types) - movies_list_parser.add_argument('-d', '--director', help='Director Filter') - movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append') + vid_movies_list_parser.add_argument('-t', '--title', help='Title Filter') + vid_movies_list_parser.add_argument('-r', '--rating', help='Rating Filter', nargs='+', + choices=ratings_types) + # save a reference to the action object + director_action = vid_movies_list_parser.add_argument('-d', '--director', help='Director Filter') + actor_action = vid_movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append') - movies_add_parser = movies_commands_subparsers.add_parser('add') - movies_add_parser.add_argument('title', help='Movie Title') - movies_add_parser.add_argument('rating', help='Movie Rating', choices=ratings_types) - movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2), required=True) - movies_add_parser.add_argument('actor', help='Actors', nargs='*') + # tag the action objects with completion providers. This can be a collection or a callable + setattr(director_action, argparse_completer.ACTION_ARG_CHOICES, static_list_directors) + setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES, query_actors) - movies_delete_parser = movies_commands_subparsers.add_parser('delete') + vid_movies_add_parser = vid_movies_commands_subparsers.add_parser('add') + vid_movies_add_parser.add_argument('title', help='Movie Title') + vid_movies_add_parser.add_argument('rating', help='Movie Rating', choices=ratings_types) - shows_parser = media_types_subparsers.add_parser('shows') - shows_parser.set_defaults(func=_do_media_shows) + # save a reference to the action object + director_action = vid_movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2), + required=True) + actor_action = vid_movies_add_parser.add_argument('actor', help='Actors', nargs='*') - shows_commands_subparsers = shows_parser.add_subparsers(title='Commands', dest='command') + # tag the action objects with completion providers. This can be a collection or a callable + setattr(director_action, argparse_completer.ACTION_ARG_CHOICES, static_list_directors) + setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES, query_actors) - shows_list_parser = shows_commands_subparsers.add_parser('list') + vid_movies_delete_parser = vid_movies_commands_subparsers.add_parser('delete') + + vid_shows_parser = video_types_subparsers.add_parser('shows') + vid_shows_parser.set_defaults(func=_do_vid_media_shows) + + vid_shows_commands_subparsers = vid_shows_parser.add_subparsers(title='Commands', dest='command') + + vid_shows_list_parser = vid_shows_commands_subparsers.add_parser('list') @with_category(CAT_AUTOCOMPLETE) - @with_argparser(media_parser) - def do_media(self, args): - """Media management command demonstrates multiple layers of subcommands being handled by AutoCompleter""" + @with_argparser(video_parser) + def do_video(self, args): + """Video management command demonstrates multiple layers of subcommands being handled by AutoCompleter""" func = getattr(args, 'func', None) if func is not None: # Call whatever subcommand function was selected func(self, args) else: # No subcommand was provided, so call help - self.do_help('media') - - # This completer is implemented using a single dictionary to look up completion lists for all layers of - # subcommands. For each argument, AutoCompleter will search for completion values from the provided - # arg_choices dict. This requires careful naming of argparse arguments so that there are no unintentional - # name collisions. - def complete_media(self, text, line, begidx, endidx): - """ Adds tab completion to media""" - choices = {'actor': query_actors, # function - 'director': TabCompleteExample.static_list_directors # static list - } - completer = argparse_completer.AutoCompleter(TabCompleteExample.media_parser, arg_choices=choices) - - tokens, _ = self.tokens_for_completion(line, begidx, endidx) - results = completer.complete_command(tokens, text, line, begidx, endidx) - - return results + self.do_help('video') ################################################################################### # The media command demonstrates a completer with multiple layers of subcommands - # - This example demonstrates how to tag a completion attribute on each action, enabling argument - # completion without implementing a complete_COMMAND function + # - This example uses a flat completion lookup dictionary - def _do_vid_media_movies(self, args) -> None: + def _do_media_movies(self, args) -> None: if not args.command: self.do_help('media movies') elif args.command == 'list': @@ -291,7 +260,7 @@ class TabCompleteExample(cmd2.Cmd): ', '.join(movie['director']), '\n '.join(movie['actor']))) - def _do_vid_media_shows(self, args) -> None: + def _do_media_shows(self, args) -> None: if not args.command: self.do_help('media shows') @@ -307,60 +276,65 @@ class TabCompleteExample(cmd2.Cmd): '\n '.join(ep_list))) print() - video_parser = argparse_completer.ACArgumentParser(prog='media') - - video_types_subparsers = video_parser.add_subparsers(title='Media Types', dest='type') - - vid_movies_parser = video_types_subparsers.add_parser('movies') - vid_movies_parser.set_defaults(func=_do_vid_media_movies) - - vid_movies_commands_subparsers = vid_movies_parser.add_subparsers(title='Commands', dest='command') + media_parser = argparse_completer.ACArgumentParser(prog='media') - vid_movies_list_parser = vid_movies_commands_subparsers.add_parser('list') + media_types_subparsers = media_parser.add_subparsers(title='Media Types', dest='type') - vid_movies_list_parser.add_argument('-t', '--title', help='Title Filter') - vid_movies_list_parser.add_argument('-r', '--rating', help='Rating Filter', nargs='+', - choices=ratings_types) - # save a reference to the action object - director_action = vid_movies_list_parser.add_argument('-d', '--director', help='Director Filter') - actor_action = vid_movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append') + movies_parser = media_types_subparsers.add_parser('movies') + movies_parser.set_defaults(func=_do_media_movies) - # tag the action objects with completion providers. This can be a collection or a callable - setattr(director_action, argparse_completer.ACTION_ARG_CHOICES, static_list_directors) - setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES, query_actors) + movies_commands_subparsers = movies_parser.add_subparsers(title='Commands', dest='command') - vid_movies_add_parser = vid_movies_commands_subparsers.add_parser('add') - vid_movies_add_parser.add_argument('title', help='Movie Title') - vid_movies_add_parser.add_argument('rating', help='Movie Rating', choices=ratings_types) + movies_list_parser = movies_commands_subparsers.add_parser('list') - # save a reference to the action object - director_action = vid_movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2), required=True) - actor_action = vid_movies_add_parser.add_argument('actor', help='Actors', nargs='*') + movies_list_parser.add_argument('-t', '--title', help='Title Filter') + movies_list_parser.add_argument('-r', '--rating', help='Rating Filter', nargs='+', + choices=ratings_types) + movies_list_parser.add_argument('-d', '--director', help='Director Filter') + movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append') - # tag the action objects with completion providers. This can be a collection or a callable - setattr(director_action, argparse_completer.ACTION_ARG_CHOICES, static_list_directors) - setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES, query_actors) + movies_add_parser = movies_commands_subparsers.add_parser('add') + movies_add_parser.add_argument('title', help='Movie Title') + movies_add_parser.add_argument('rating', help='Movie Rating', choices=ratings_types) + movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2), required=True) + movies_add_parser.add_argument('actor', help='Actors', nargs='*') - vid_movies_delete_parser = vid_movies_commands_subparsers.add_parser('delete') + movies_delete_parser = movies_commands_subparsers.add_parser('delete') - vid_shows_parser = video_types_subparsers.add_parser('shows') - vid_shows_parser.set_defaults(func=_do_vid_media_shows) + shows_parser = media_types_subparsers.add_parser('shows') + shows_parser.set_defaults(func=_do_media_shows) - vid_shows_commands_subparsers = vid_shows_parser.add_subparsers(title='Commands', dest='command') + shows_commands_subparsers = shows_parser.add_subparsers(title='Commands', dest='command') - vid_shows_list_parser = vid_shows_commands_subparsers.add_parser('list') + shows_list_parser = shows_commands_subparsers.add_parser('list') @with_category(CAT_AUTOCOMPLETE) - @with_argparser(video_parser) - def do_video(self, args): - """Video management command demonstrates multiple layers of subcommands being handled by AutoCompleter""" + @with_argparser(media_parser) + def do_media(self, args): + """Media management command demonstrates multiple layers of subcommands being handled by AutoCompleter""" func = getattr(args, 'func', None) if func is not None: # Call whatever subcommand function was selected func(self, args) else: # No subcommand was provided, so call help - self.do_help('video') + self.do_help('media') + + # This completer is implemented using a single dictionary to look up completion lists for all layers of + # subcommands. For each argument, AutoCompleter will search for completion values from the provided + # arg_choices dict. This requires careful naming of argparse arguments so that there are no unintentional + # name collisions. + def complete_media(self, text, line, begidx, endidx): + """ Adds tab completion to media""" + choices = {'actor': query_actors, # function + 'director': TabCompleteExample.static_list_directors # static list + } + completer = argparse_completer.AutoCompleter(TabCompleteExample.media_parser, arg_choices=choices) + + tokens, _ = self.tokens_for_completion(line, begidx, endidx) + results = completer.complete_command(tokens, text, line, begidx, endidx) + + return results ################################################################################### # The library command demonstrates a completer with multiple layers of subcommands |