diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/argparse_completion.py | 122 | ||||
-rwxr-xr-x | examples/basic_completion.py | 91 | ||||
-rwxr-xr-x | examples/python_scripting.py | 1 | ||||
-rwxr-xr-x | examples/tab_autocompletion.py | 268 | ||||
-rwxr-xr-x | examples/tab_completion.py | 81 |
5 files changed, 214 insertions, 349 deletions
diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py new file mode 100644 index 00000000..90975d3f --- /dev/null +++ b/examples/argparse_completion.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +A simple example demonstrating how to integrate tab completion with argparse-based commands. +""" +import argparse +from typing import Dict, List + +from cmd2 import Cmd, Cmd2ArgumentParser, with_argparser, CompletionError, CompletionItem +from cmd2.utils import basic_complete + +# Data source for argparse.choices +food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato'] + + +def choices_function() -> List[str]: + """Choices functions are useful when the choice list is dynamically generated (e.g. from data in a database)""" + return ['a', 'dynamic', 'list', 'goes', 'here'] + + +def completer_function(text: str, line: str, begidx: int, endidx: int) -> List[str]: + """ + A tab completion function not dependent on instance data. Since custom tab completion operations commonly + need to modify cmd2's instance variables related to tab completion, it will be rare to need a completer + function. completer_method should be used in those cases. + """ + match_against = ['a', 'dynamic', 'list', 'goes', 'here'] + return basic_complete(text, line, begidx, endidx, match_against) + + +def choices_completion_item() -> List[CompletionItem]: + """Return CompletionItem instead of strings. These give more context to what's being tab completed.""" + items = \ + { + 1: "My item", + 2: "Another item", + 3: "Yet another item" + } + return [CompletionItem(item_id, description) for item_id, description in items.items()] + + +def choices_arg_tokens(arg_tokens: Dict[str, List[str]]) -> List[str]: + """ + If a choices or completer function/method takes a value called arg_tokens, then it will be + passed a dictionary that maps the command line tokens up through the one being completed + to their argparse argument name. All values of the arg_tokens dictionary are lists, even if + a particular argument expects only 1 token. + """ + # Check if choices_function flag has appeared + values = ['choices_function', 'flag'] + if 'choices_function' in arg_tokens: + values.append('is {}'.format(arg_tokens['choices_function'][0])) + else: + values.append('not supplied') + return values + + +class ArgparseCompletion(Cmd): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] + + def choices_method(self) -> List[str]: + """Choices methods are useful when the choice list is based on instance data of your application""" + return self.sport_item_strs + + def choices_completion_error(self) -> List[str]: + """ + CompletionErrors can be raised if an error occurs while tab completing. + + Example use cases + - Reading a database to retrieve a tab completion data set failed + - A previous command line argument that determines the data set being completed is invalid + """ + if self.debug: + return self.sport_item_strs + raise CompletionError("debug must be true") + + # Parser for example command + example_parser = Cmd2ArgumentParser(description="Command demonstrating tab completion with argparse\n" + "Notice even the flags of this command tab complete") + + # Tab complete from a list using argparse choices. Set metavar if you don't + # want the entire choices list showing in the usage text for this command. + example_parser.add_argument('--choices', choices=food_item_strs, metavar="CHOICE", + help="tab complete using choices") + + # Tab complete from choices provided by a choices function and choices method + example_parser.add_argument('--choices_function', choices_function=choices_function, + help="tab complete using a choices_function") + example_parser.add_argument('--choices_method', choices_method=choices_method, + help="tab complete using a choices_method") + + # Tab complete using a completer function and completer method + example_parser.add_argument('--completer_function', completer_function=completer_function, + help="tab complete using a completer_function") + example_parser.add_argument('--completer_method', completer_method=Cmd.path_complete, + help="tab complete using a completer_method") + + # Demonstrate raising a CompletionError while tab completing + example_parser.add_argument('--completion_error', choices_method=choices_completion_error, + help="raise a CompletionError while tab completing if debug is False") + + # Demonstrate returning CompletionItems instead of strings + example_parser.add_argument('--completion_item', choices_function=choices_completion_item, metavar="ITEM_ID", + descriptive_header="Description", + help="demonstrate use of CompletionItems") + + # Demonstrate use of arg_tokens dictionary + example_parser.add_argument('--arg_tokens', choices_function=choices_arg_tokens, + help="demonstrate use of arg_tokens dictionary") + + @with_argparser(example_parser) + def do_example(self, _: argparse.Namespace) -> None: + """The example command""" + self.poutput("I do nothing") + + +if __name__ == '__main__': + import sys + app = ArgparseCompletion() + sys.exit(app.cmdloop()) diff --git a/examples/basic_completion.py b/examples/basic_completion.py new file mode 100755 index 00000000..e021828b --- /dev/null +++ b/examples/basic_completion.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +A simple example demonstrating how to enable tab completion by assigning a completer function to do_* commands. +This also demonstrates capabilities of the following completer methods included with cmd2: +- delimiter_completer +- flag_based_complete (see note below) +- index_based_complete (see note below) + +flag_based_complete() and index_based_complete() are basic methods and should only be used if you are not +familiar with argparse. The recommended approach for tab completing positional tokens and flags is to use +argparse-based completion. For an example integrating tab completion with argparse, see argparse_completion.py +""" +import functools + +import cmd2 + +# List of strings used with completion functions +food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato'] +sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] + +# This data is used to demonstrate delimiter_complete +file_strs = \ + [ + '/home/user/file.db', + '/home/user/file space.db', + '/home/user/another.db', + '/home/other user/maps.db', + '/home/other user/tests.db' + ] + + +class BasicCompletion(cmd2.Cmd): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def do_flag_based(self, statement: cmd2.Statement): + """Tab completes arguments based on a preceding flag using flag_based_complete + -f, --food [completes food items] + -s, --sport [completes sports] + -p, --path [completes local file system paths] + """ + self.poutput("Args: {}".format(statement.args)) + + def complete_flag_based(self, text, line, begidx, endidx): + """Completion function for do_flag_based""" + flag_dict = \ + { + # Tab complete food items after -f and --food flags in command line + '-f': food_item_strs, + '--food': food_item_strs, + + # Tab complete sport items after -s and --sport flags in command line + '-s': sport_item_strs, + '--sport': sport_item_strs, + + # Tab complete using path_complete function after -p and --path flags in command line + '-p': self.path_complete, + '--path': self.path_complete, + } + + return self.flag_based_complete(text, line, begidx, endidx, flag_dict=flag_dict) + + def do_index_based(self, statement: cmd2.Statement): + """Tab completes first 3 arguments using index_based_complete""" + self.poutput("Args: {}".format(statement.args)) + + def complete_index_based(self, text, line, begidx, endidx): + """Completion function for do_index_based""" + index_dict = \ + { + 1: food_item_strs, # Tab complete food items at index 1 in command line + 2: sport_item_strs, # Tab complete sport items at index 2 in command line + 3: self.path_complete, # Tab complete using path_complete function at index 3 in command line + } + + return self.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) + + def do_delimiter_complete(self, statement: cmd2.Statement): + """Tab completes files from a list using delimiter_complete""" + self.poutput("Args: {}".format(statement.args)) + + # Use a partialmethod to set arguments to delimiter_complete + complete_delimiter_complete = functools.partialmethod(cmd2.Cmd.delimiter_complete, + match_against=file_strs, delimiter='/') + + +if __name__ == '__main__': + import sys + app = BasicCompletion() + sys.exit(app.cmdloop()) diff --git a/examples/python_scripting.py b/examples/python_scripting.py index fc23c562..198e784d 100755 --- a/examples/python_scripting.py +++ b/examples/python_scripting.py @@ -87,6 +87,7 @@ class CmdLineApp(cmd2.Cmd): # Enable tab completion for cd command def complete_cd(self, text, line, begidx, endidx): + # Tab complete only directories return self.path_complete(text, line, begidx, endidx, path_filter=os.path.isdir) dir_parser = argparse.ArgumentParser() diff --git a/examples/tab_autocompletion.py b/examples/tab_autocompletion.py deleted file mode 100755 index 3561f968..00000000 --- a/examples/tab_autocompletion.py +++ /dev/null @@ -1,268 +0,0 @@ -#!/usr/bin/env python3 -# coding=utf-8 -""" -A example usage of the AutoCompleter -""" -import argparse -import functools -from typing import List - -import cmd2 -from cmd2 import utils, Cmd2ArgumentParser, CompletionItem - -actors = ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher', 'Alec Guinness', 'Peter Mayhew', - 'Anthony Daniels', 'Adam Driver', 'Daisy Ridley', 'John Boyega', 'Oscar Isaac', - 'Lupita Nyong\'o', 'Andy Serkis', 'Liam Neeson', 'Ewan McGregor', 'Natalie Portman', - 'Jake Lloyd', 'Hayden Christensen', 'Christopher Lee'] - - -def query_actors() -> List[str]: - """Simulating a function that queries and returns a completion values""" - return actors - - -class TabCompleteExample(cmd2.Cmd): - """ Example cmd2 application where we a base command which has a couple subcommands.""" - - CAT_AUTOCOMPLETE = 'AutoComplete Examples' - - def __init__(self): - super().__init__() - - # For mocking a data source for the example commands - ratings_types = ['G', 'PG', 'PG-13', 'R', 'NC-17'] - show_ratings = ['TV-Y', 'TV-Y7', 'TV-G', 'TV-PG', 'TV-14', 'TV-MA'] - static_list_directors = ['J. J. Abrams', 'Irvin Kershner', 'George Lucas', 'Richard Marquand', - 'Rian Johnson', 'Gareth Edwards'] - USER_MOVIE_LIBRARY = ['ROGUE1', 'SW_EP04', 'SW_EP05'] - MOVIE_DATABASE_IDS = ['SW_EP1', 'SW_EP02', 'SW_EP03', 'ROGUE1', 'SW_EP04', - 'SW_EP05', 'SW_EP06', 'SW_EP07', 'SW_EP08', 'SW_EP09'] - MOVIE_DATABASE = {'SW_EP04': {'title': 'Star Wars: Episode IV - A New Hope', - 'rating': 'PG', - 'director': ['George Lucas'], - 'actor': ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher', - 'Alec Guinness', 'Peter Mayhew', 'Anthony Daniels'] - }, - 'SW_EP05': {'title': 'Star Wars: Episode V - The Empire Strikes Back', - 'rating': 'PG', - 'director': ['Irvin Kershner'], - 'actor': ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher', - 'Alec Guinness', 'Peter Mayhew', 'Anthony Daniels'] - }, - 'SW_EP06': {'title': 'Star Wars: Episode VI - Return of the Jedi', - 'rating': 'PG', - 'director': ['Richard Marquand'], - 'actor': ['Mark Hamill', 'Harrison Ford', 'Carrie Fisher', - 'Alec Guinness', 'Peter Mayhew', 'Anthony Daniels'] - }, - 'SW_EP1': {'title': 'Star Wars: Episode I - The Phantom Menace', - 'rating': 'PG', - 'director': ['George Lucas'], - 'actor': ['Liam Neeson', 'Ewan McGregor', 'Natalie Portman', 'Jake Lloyd'] - }, - 'SW_EP02': {'title': 'Star Wars: Episode II - Attack of the Clones', - 'rating': 'PG', - 'director': ['George Lucas'], - 'actor': ['Liam Neeson', 'Ewan McGregor', 'Natalie Portman', - 'Hayden Christensen', 'Christopher Lee'] - }, - 'SW_EP03': {'title': 'Star Wars: Episode III - Revenge of the Sith', - 'rating': 'PG-13', - 'director': ['George Lucas'], - 'actor': ['Liam Neeson', 'Ewan McGregor', 'Natalie Portman', - 'Hayden Christensen'] - }, - - } - USER_SHOW_LIBRARY = {'SW_REB': ['S01E01', 'S02E02']} - SHOW_DATABASE_IDS = ['SW_CW', 'SW_TCW', 'SW_REB'] - SHOW_DATABASE = {'SW_CW': {'title': 'Star Wars: Clone Wars', - 'rating': 'TV-Y7', - 'seasons': {1: ['S01E01', 'S01E02', 'S01E03'], - 2: ['S02E01', 'S02E02', 'S02E03']} - }, - 'SW_TCW': {'title': 'Star Wars: The Clone Wars', - 'rating': 'TV-PG', - 'seasons': {1: ['S01E01', 'S01E02', 'S01E03'], - 2: ['S02E01', 'S02E02', 'S02E03']} - }, - 'SW_REB': {'title': 'Star Wars: Rebels', - 'rating': 'TV-Y7', - 'seasons': {1: ['S01E01', 'S01E02', 'S01E03'], - 2: ['S02E01', 'S02E02', 'S02E03']} - }, - } - - file_list = \ - [ - '/home/user/file.db', - '/home/user/file space.db', - '/home/user/another.db', - '/home/other user/maps.db', - '/home/other user/tests.db' - ] - - # noinspection PyMethodMayBeStatic - def instance_query_actors(self) -> List[str]: - """Simulating a function that queries and returns a completion values""" - return actors - - def instance_query_movie_ids(self) -> List[str]: - """Demonstrates showing tabular hinting of tab completion information""" - completions_with_desc = [] - - # Sort the movie id strings with a natural sort since they contain numbers - for movie_id in utils.natural_sort(self.MOVIE_DATABASE_IDS): - if movie_id in self.MOVIE_DATABASE: - movie_entry = self.MOVIE_DATABASE[movie_id] - completions_with_desc.append(CompletionItem(movie_id, movie_entry['title'])) - - # Mark that we already sorted the matches - self.matches_sorted = True - return completions_with_desc - - # This demonstrates a number of customizations of the AutoCompleter version of ArgumentParser - # - The help output will separately group required vs optional flags - # - The help output for arguments with multiple flags or with append=True is more concise - # - cmd2 adds the ability to specify ranges of argument counts in 'nargs' - - suggest_description = "Suggest command demonstrates argparse customizations.\n" - suggest_description += "See hybrid_suggest and orig_suggest to compare the help output." - suggest_parser = Cmd2ArgumentParser(description=suggest_description) - - suggest_parser.add_argument('-t', '--type', choices=['movie', 'show'], required=True) - suggest_parser.add_argument('-d', '--duration', nargs=(1, 2), action='append', - help='Duration constraint in minutes.\n' - '\tsingle value - maximum duration\n' - '\t[a, b] - duration range') - - @cmd2.with_category(CAT_AUTOCOMPLETE) - @cmd2.with_argparser(suggest_parser) - def do_suggest(self, args) -> None: - """Suggest command demonstrates argparse customizations""" - if not args.type: - self.do_help('suggest') - - # 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 - - suggest_parser_hybrid = argparse.ArgumentParser() - suggest_parser_hybrid.add_argument('-t', '--type', choices=['movie', 'show'], required=True) - suggest_parser_hybrid.add_argument('-d', '--duration', nargs=(1, 2), action='append', - help='Duration constraint in minutes.\n' - '\tsingle value - maximum duration\n' - '\t[a, b] - duration range') - - @cmd2.with_category(CAT_AUTOCOMPLETE) - @cmd2.with_argparser(suggest_parser_hybrid) - def do_hybrid_suggest(self, args): - if not args.type: - self.do_help('orig_suggest') - - # 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 flag or end processing of flags with '--' - - suggest_parser_orig = argparse.ArgumentParser() - - suggest_parser_orig.add_argument('-t', '--type', choices=['movie', 'show'], required=True) - suggest_parser_orig.add_argument('-d', '--duration', nargs='+', action='append', - help='Duration constraint in minutes.\n' - '\tsingle value - maximum duration\n' - '\t[a, b] - duration range') - - @cmd2.with_argparser(suggest_parser_orig) - @cmd2.with_category(CAT_AUTOCOMPLETE) - def do_orig_suggest(self, args) -> None: - if not args.type: - self.do_help('orig_suggest') - - def _do_vid_movies(self, args) -> None: - if not args.command: - self.do_help('video movies') - elif args.command == 'list': - for movie_id in TabCompleteExample.MOVIE_DATABASE: - movie = TabCompleteExample.MOVIE_DATABASE[movie_id] - print('{}\n-----------------------------\n{} ID: {}\nDirector: {}\nCast:\n {}\n\n' - .format(movie['title'], movie['rating'], movie_id, - ', '.join(movie['director']), - '\n '.join(movie['actor']))) - - def _do_vid_shows(self, args) -> None: - if not args.command: - self.do_help('video shows') - - elif args.command == 'list': - for show_id in TabCompleteExample.SHOW_DATABASE: - show = TabCompleteExample.SHOW_DATABASE[show_id] - print('{}\n-----------------------------\n{} ID: {}' - .format(show['title'], show['rating'], show_id)) - for season in show['seasons']: - ep_list = show['seasons'][season] - print(' Season {}:\n {}' - .format(season, - '\n '.join(ep_list))) - print() - - video_parser = Cmd2ArgumentParser() - - 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_movies) - - vid_movies_commands_subparsers = vid_movies_parser.add_subparsers(title='Commands', dest='command') - - vid_movies_list_parser = vid_movies_commands_subparsers.add_parser('list') - - 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) - vid_movies_list_parser.add_argument('-d', '--director', help='Director Filter', choices=static_list_directors) - vid_movies_list_parser.add_argument('-a', '--actor', help='Actor Filter', action='append', - choices_function=query_actors) - - 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) - - vid_movies_add_parser.add_argument('-d', '--director', help='Director', nargs=(1, 2), required=True, - choices=static_list_directors) - vid_movies_add_parser.add_argument('actor', help='Actors', nargs='*', choices_method=instance_query_actors) - - vid_movies_load_parser = vid_movies_commands_subparsers.add_parser('load') - vid_movies_load_parser.add_argument('movie_file', help='Movie database', - completer_method=functools.partial(cmd2.Cmd.delimiter_complete, - delimiter='/', match_against=file_list)) - - vid_movies_read_parser = vid_movies_commands_subparsers.add_parser('read') - vid_movies_read_parser.add_argument('movie_file', help='Movie database', completer_method=cmd2.Cmd.path_complete) - - vid_movies_delete_parser = vid_movies_commands_subparsers.add_parser('delete') - vid_movies_delete_parser.add_argument('movie_id', help='Movie ID', choices_method=instance_query_movie_ids, - descriptive_header='Title') - - vid_shows_parser = video_types_subparsers.add_parser('shows') - vid_shows_parser.set_defaults(func=_do_vid_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') - - @cmd2.with_category(CAT_AUTOCOMPLETE) - @cmd2.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('video') - - -if __name__ == '__main__': - import sys - app = TabCompleteExample() - sys.exit(app.cmdloop()) diff --git a/examples/tab_completion.py b/examples/tab_completion.py deleted file mode 100755 index 1a25238f..00000000 --- a/examples/tab_completion.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -""" -A simple example demonstrating how to use flag and index based tab-completion functions -For argparse-based tab completion, see tab_autocompletion.py -""" -import argparse - -import cmd2 - -# List of strings used with flag and index based completion functions -food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato'] -sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] - - -class TabCompleteExample(cmd2.Cmd): - """ Example cmd2 application where we a base command which has a couple subcommands.""" - - def __init__(self): - super().__init__() - - add_item_parser = argparse.ArgumentParser() - add_item_group = add_item_parser.add_mutually_exclusive_group() - add_item_group.add_argument('-f', '--food', help='Adds food item') - add_item_group.add_argument('-s', '--sport', help='Adds sport item') - add_item_group.add_argument('-o', '--other', help='Adds other item') - - @cmd2.with_argparser(add_item_parser) - def do_add_item(self, args): - """Add item command help""" - if args.food: - add_item = args.food - elif args.sport: - add_item = args.sport - elif args.other: - add_item = args.other - else: - add_item = 'no items' - - self.poutput("You added {}".format(add_item)) - - # Add flag-based tab-completion to add_item command - def complete_add_item(self, text, line, begidx, endidx): - flag_dict = \ - { - # Tab-complete food items after -f and --food flags in command line - '-f': food_item_strs, - '--food': food_item_strs, - - # Tab-complete sport items after -s and --sport flags in command line - '-s': sport_item_strs, - '--sport': sport_item_strs, - - # Tab-complete using path_complete function after -o and --other flags in command line - '-o': self.path_complete, - '--other': self.path_complete, - } - - return self.flag_based_complete(text, line, begidx, endidx, flag_dict=flag_dict) - - @cmd2.with_argument_list - def do_list_item(self, args): - """List item command help""" - self.poutput("You listed {}".format(args)) - - # Add index-based tab-completion to list_item command - def complete_list_item(self, text, line, begidx, endidx): - index_dict = \ - { - 1: food_item_strs, # Tab-complete food items at index 1 in command line - 2: sport_item_strs, # Tab-complete sport items at index 2 in command line - 3: self.path_complete, # Tab-complete using path_complete function at index 3 in command line - } - - return self.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) - - -if __name__ == '__main__': - import sys - app = TabCompleteExample() - sys.exit(app.cmdloop()) |