Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
94 changes: 0 additions & 94 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
13 changes: 0 additions & 13 deletions docs/features/completion.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 5 additions & 49 deletions examples/basic_completion.py
Original file line number Diff line number Diff line change
@@ -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 = [
Expand All @@ -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}")
Expand All @@ -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
Expand Down
42 changes: 0 additions & 42 deletions examples/modular_commands/commandset_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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}")
Expand Down
117 changes: 0 additions & 117 deletions tests/test_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down Expand Up @@ -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}"'
Expand Down
Loading