diff --git a/CHANGELOG.md b/CHANGELOG.md index f2cc418a2..291956618 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ shell, and the option for a persistent bottom bar that can display realtime stat 1. `Cmd.formatted_completions` -> `Completions.completion_table` 1. `Cmd.matches_delimited` -> `Completions.is_delimited` 1. `Cmd.allow_appended_space/allow_closing_quote` -> `Completions.allow_finalization` + - Removed `flag_based_complete` and `index_based_complete` functions since their functionality + is already provided in arpgarse-based completion. - Enhancements - New `cmd2.Cmd` parameters - **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index c491a0551..a03f937c3 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1895,100 +1895,6 @@ def delimiter_complete( return Completions(items, allow_finalization=allow_finalization, is_delimited=True) - def flag_based_complete( - self, - text: str, - line: str, - begidx: int, - endidx: int, - flag_dict: dict[str, Iterable[Matchable] | CompleterBound], - *, - all_else: None | Iterable[Matchable] | CompleterBound = None, - ) -> Completions: - """Completes based on a particular flag preceding the token being completed. - - :param text: the string prefix we are attempting to match (all matches must begin with it) - :param line: the current input line with leading whitespace removed - :param begidx: the beginning index of the prefix text - :param endidx: the ending index of the prefix text - :param flag_dict: dictionary whose structure is the following: - `keys` - flags (ex: -c, --create) that result in completion for the next argument in the - command line - `values` - there are two types of values: - 1. iterable of Matchables to match against - 2. function that performs completion (ex: path_complete) - :param all_else: an optional parameter for completing any token that isn't preceded by a flag in flag_dict - :return: a Completions object - """ - # Get all tokens through the one being completed - tokens, _ = self.tokens_for_completion(line, begidx, endidx) - if not tokens: # pragma: no cover - return Completions() - - match_against = all_else - - # Must have at least 2 args for a flag to precede the token being completed - if len(tokens) > 1: - flag = tokens[-2] - if flag in flag_dict: - match_against = flag_dict[flag] - - # Perform completion using an Iterable - if isinstance(match_against, Iterable): - return self.basic_complete(text, line, begidx, endidx, match_against) - - # Perform completion using a function - if callable(match_against): - return match_against(text, line, begidx, endidx) - - return Completions() - - def index_based_complete( - self, - text: str, - line: str, - begidx: int, - endidx: int, - index_dict: Mapping[int, Iterable[Matchable] | CompleterBound], - *, - all_else: Iterable[Matchable] | CompleterBound | None = None, - ) -> Completions: - """Completes based on a fixed position in the input string. - - :param text: the string prefix we are attempting to match (all matches must begin with it) - :param line: the current input line with leading whitespace removed - :param begidx: the beginning index of the prefix text - :param endidx: the ending index of the prefix text - :param index_dict: dictionary whose structure is the following: - `keys` - 0-based token indexes into command line that determine which tokens perform tab - completion - `values` - there are two types of values: - 1. iterable of Matchables to match against - 2. function that performs completion (ex: path_complete) - :param all_else: an optional parameter for completing any token that isn't at an index in index_dict - :return: a Completions object - """ - # Get all tokens through the one being completed - tokens, _ = self.tokens_for_completion(line, begidx, endidx) - if not tokens: # pragma: no cover - return Completions() - - # Get the index of the token being completed - index = len(tokens) - 1 - - # Check if token is at an index in the dictionary - match_against: Iterable[Matchable] | CompleterBound | None = index_dict.get(index, all_else) - - # Perform completion using a Iterable - if isinstance(match_against, Iterable): - return self.basic_complete(text, line, begidx, endidx, match_against) - - # Perform completion using a function - if callable(match_against): - return match_against(text, line, begidx, endidx) - - return Completions() - @staticmethod def _complete_users(text: str, add_trailing_sep_if_dir: bool) -> Completions: """Complete ~ and ~user strings. diff --git a/docs/features/completion.md b/docs/features/completion.md index dc358aa1a..d58d0cef5 100644 --- a/docs/features/completion.md +++ b/docs/features/completion.md @@ -56,19 +56,6 @@ complete_bar = functools.partialmethod(cmd2.Cmd.path_complete, path_filter=os.pa > [basic_completion](https://github.com/python-cmd2/cmd2/blob/main/examples/basic_completion.py) > example for a demonstration of how to use this feature -- [flag_based_complete][cmd2.Cmd.flag_based_complete] - helper method for tab completion based on a - particular flag preceding the token being completed - -- [index_based_complete][cmd2.Cmd.index_based_complete] - helper method for tab completion based on - a fixed position in the input string - - > - See the - > [basic_completion](https://github.com/python-cmd2/cmd2/blob/main/examples/basic_completion.py) - > example for a demonstration of how to use these features - > - `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](#argparse-based) completion. - ## Raising Exceptions During Completion There are times when an error occurs while tab completing and a message needs to be reported to the diff --git a/examples/basic_completion.py b/examples/basic_completion.py index b48c3fb2f..b41e2732d 100755 --- a/examples/basic_completion.py +++ b/examples/basic_completion.py @@ -1,24 +1,18 @@ #!/usr/bin/env python """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 features included with cmd2: - CompletionError exceptions - delimiter_complete() -- 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 +The recommended approach for tab completing is to use argparse-based completion. +For an example integrating tab completion with argparse, see argparse_completion.py. """ import functools +from typing import NoReturn import cmd2 -from cmd2 import Completions - -# 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 = [ @@ -34,44 +28,6 @@ class BasicCompletion(cmd2.Cmd): def __init__(self) -> None: super().__init__(auto_suggest=False, include_py=True) - def do_flag_based(self, statement: cmd2.Statement) -> None: - """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(f"Args: {statement.args}") - - def complete_flag_based(self, text, line, begidx, endidx) -> Completions: - """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) -> None: - """Tab completes first 3 arguments using index_based_complete.""" - self.poutput(f"Args: {statement.args}") - - def complete_index_based(self, text, line, begidx, endidx) -> Completions: - """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) -> None: """Tab completes files from a list using delimiter_complete.""" self.poutput(f"Args: {statement.args}") @@ -83,7 +39,7 @@ def do_raise_error(self, statement: cmd2.Statement) -> None: """Demonstrates effect of raising CompletionError.""" self.poutput(f"Args: {statement.args}") - def complete_raise_error(self, _text, _line, _begidx, _endidx) -> Completions: + def complete_raise_error(self, _text: str, _line: str, _begidx: int, _endidx: int) -> NoReturn: """CompletionErrors can be raised if an error occurs while tab completing. Example use cases diff --git a/examples/modular_commands/commandset_basic.py b/examples/modular_commands/commandset_basic.py index 8ef0a9d06..b84e57ab3 100644 --- a/examples/modular_commands/commandset_basic.py +++ b/examples/modular_commands/commandset_basic.py @@ -11,10 +11,6 @@ @with_default_category('Basic Completion') class BasicCompletionCommandSet(CommandSet): - # 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', @@ -24,44 +20,6 @@ class BasicCompletionCommandSet(CommandSet): '/home/other user/tests.db', ) - def do_flag_based(self, statement: Statement) -> None: - """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._cmd.poutput(f"Args: {statement.args}") - - def complete_flag_based(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: - """Completion function for do_flag_based.""" - flag_dict = { - # Tab complete food items after -f and --food flags in command line - '-f': self.food_item_strs, - '--food': self.food_item_strs, - # Tab complete sport items after -s and --sport flags in command line - '-s': self.sport_item_strs, - '--sport': self.sport_item_strs, - # Tab complete using path_complete function after -p and --path flags in command line - '-p': self._cmd.path_complete, - '--path': self._cmd.path_complete, - } - - return self._cmd.flag_based_complete(text, line, begidx, endidx, flag_dict=flag_dict) - - def do_index_based(self, statement: Statement) -> None: - """Tab completes first 3 arguments using index_based_complete.""" - self._cmd.poutput(f"Args: {statement.args}") - - def complete_index_based(self, text: str, line: str, begidx: int, endidx: int) -> list[str]: - """Completion function for do_index_based.""" - index_dict = { - 1: self.food_item_strs, # Tab complete food items at index 1 in command line - 2: self.sport_item_strs, # Tab complete sport items at index 2 in command line - 3: self._cmd.path_complete, # Tab complete using path_complete function at index 3 in command line - } - - return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) - def do_delimiter_complete(self, statement: Statement) -> None: """Tab completes files from a list using delimiter_complete.""" self._cmd.poutput(f"Args: {statement.args}") diff --git a/tests/test_completion.py b/tests/test_completion.py index b8d497aaf..0436abaf7 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -137,22 +137,6 @@ def do_alternate(self, args) -> None: '/home/other user/tests', ] -# Dictionary used with flag based completion functions -flag_dict = { - # Tab complete food items after -f and --food flag in command line - '-f': food_item_strs, - '--food': food_item_strs, - # Tab complete sport items after -s and --sport flag in command line - '-s': sport_item_strs, - '--sport': sport_item_strs, -} - -# Dictionary used with index based completion functions -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 -} - class CompletionsExample(cmd2.Cmd): """Example cmd2 application used to exercise tab completion tests""" @@ -720,107 +704,6 @@ def test_delimiter_completion_nomatch(cmd2_app) -> None: assert not completions -def test_flag_based_completion(cmd2_app) -> None: - text = 'P' - line = f'list_food -f {text}' - endidx = len(line) - begidx = endidx - len(text) - - expected = ['Pizza', 'Potato'] - completions = cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict) - assert completions.to_strings() == Completions.from_values(expected).to_strings() - - -def test_flag_based_completion_nomatch(cmd2_app) -> None: - text = 'q' - line = f'list_food -f {text}' - endidx = len(line) - begidx = endidx - len(text) - - completions = cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict) - assert not completions - - -def test_flag_based_default_completer(cmd2_app, request) -> None: - test_dir = os.path.dirname(request.module.__file__) - - text = os.path.join(test_dir, 'c') - line = f'list_food {text}' - - endidx = len(line) - begidx = endidx - len(text) - - expected = [text + 'onftest.py'] - completions = cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict, all_else=cmd2_app.path_complete) - assert completions.to_strings() == Completions.from_values(expected).to_strings() - - -def test_flag_based_callable_completer(cmd2_app, request) -> None: - test_dir = os.path.dirname(request.module.__file__) - - text = os.path.join(test_dir, 'c') - line = f'list_food -o {text}' - - endidx = len(line) - begidx = endidx - len(text) - - flag_dict['-o'] = cmd2_app.path_complete - - expected = [text + 'onftest.py'] - completions = cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict) - assert completions.to_strings() == Completions.from_values(expected).to_strings() - - -def test_index_based_completion(cmd2_app) -> None: - text = '' - line = f'command Pizza {text}' - endidx = len(line) - begidx = endidx - len(text) - - expected = sport_item_strs - completions = cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict) - assert completions.to_strings() == Completions.from_values(expected).to_strings() - - -def test_index_based_completion_nomatch(cmd2_app) -> None: - text = 'q' - line = f'command {text}' - endidx = len(line) - begidx = endidx - len(text) - completions = cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict) - assert not completions - - -def test_index_based_default_completer(cmd2_app, request) -> None: - test_dir = os.path.dirname(request.module.__file__) - - text = os.path.join(test_dir, 'c') - line = f'command Pizza Bat Computer {text}' - - endidx = len(line) - begidx = endidx - len(text) - - expected = [text + 'onftest.py'] - completions = cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict, all_else=cmd2_app.path_complete) - assert completions.to_strings() == Completions.from_values(expected).to_strings() - - -def test_index_based_callable_completer(cmd2_app, request) -> None: - test_dir = os.path.dirname(request.module.__file__) - - text = os.path.join(test_dir, 'c') - line = f'command Pizza Bat {text}' - - endidx = len(line) - begidx = endidx - len(text) - - index_dict[3] = cmd2_app.path_complete - - expected = [text + 'onftest.py'] - completions = cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict) - assert completions.to_strings() == Completions.from_values(expected).to_strings() - - def test_tokens_for_completion_quoted(cmd2_app) -> None: text = 'Pi' line = f'list_food "{text}"'