Skip to content

Commit e6da859

Browse files
committed
Replaced choices_function / choices_method with choices_provider.
Replaced completer_function / completer_method with completer. ArgparseCompleter now always passes cmd2.Cmd or CommandSet instance as the self argument to choices_provider and completer functions. Moved basic_complete from utils into cmd2.Cmd class. Moved CompletionError to exceptions.py
1 parent 5dd2d03 commit e6da859

19 files changed

+329
-514
lines changed

Diff for: CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 2.0.0 (TBD)
2+
* Breaking changes
3+
* Argparse Completion / Settables
4+
* Replaced `choices_function` / `choices_method` with `choices_provider`.
5+
* Replaced `completer_function` / `completer_method` with `completer`.
6+
* ArgparseCompleter now always passes `cmd2.Cmd` or `CommandSet` instance as the
7+
`self argument` to choices_provider and completer functions.
8+
* Moved `basic_complete` from utils into `cmd2.Cmd` class.
9+
* Moved `CompletionError` to exceptions.py
10+
111
## 1.3.3 (August 13, 2020)
212
* Breaking changes
313
* CommandSet command functions (do_, complete_, help_) will no longer have the cmd2 app

Diff for: cmd2/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
from .constants import COMMAND_NAME, DEFAULT_SHORTCUTS
3333
from .decorators import with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category, \
3434
as_subcommand_to
35-
from .exceptions import Cmd2ArgparseError, SkipPostcommandHooks, CommandSetRegistrationError
35+
from .exceptions import Cmd2ArgparseError, CommandSetRegistrationError, CompletionError, SkipPostcommandHooks
3636
from . import plugin
3737
from .parsing import Statement
3838
from .py_bridge import CommandResult
39-
from .utils import categorize, CompletionError, Settable
39+
from .utils import categorize, Settable

Diff for: cmd2/argparse_completer.py

+12-13
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
generate_range_error,
2525
)
2626
from .command_definition import CommandSet
27+
from .exceptions import CompletionError
2728
from .table_creator import Column, SimpleTable
28-
from .utils import CompletionError, basic_complete
2929

3030
# If no descriptive header is supplied, then this will be used instead
3131
DEFAULT_DESCRIPTIVE_HEADER = 'Description'
@@ -459,7 +459,7 @@ def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matche
459459
if action.help != argparse.SUPPRESS:
460460
match_against.append(flag)
461461

462-
return basic_complete(text, line, begidx, endidx, match_against)
462+
return self._cmd2_app.basic_complete(text, line, begidx, endidx, match_against)
463463

464464
def _format_completions(self, arg_state: _ArgumentState, completions: List[Union[str, CompletionItem]]) -> List[str]:
465465
# Check if the results are CompletionItems and that there aren't too many to display
@@ -533,7 +533,7 @@ def complete_subcommand_help(self, tokens: List[str], text: str, line: str, begi
533533
return completer.complete_subcommand_help(tokens[token_index:], text, line, begidx, endidx)
534534
elif token_index == len(tokens) - 1:
535535
# Since this is the last token, we will attempt to complete it
536-
return basic_complete(text, line, begidx, endidx, self._subcommand_action.choices)
536+
return self._cmd2_app.basic_complete(text, line, begidx, endidx, self._subcommand_action.choices)
537537
else:
538538
break
539539
return []
@@ -577,16 +577,15 @@ def _complete_for_arg(self, arg_state: _ArgumentState,
577577
args = []
578578
kwargs = {}
579579
if isinstance(arg_choices, ChoicesCallable):
580-
if arg_choices.is_method:
581-
# The completer may or may not be defined in the same class as the command. Since completer
582-
# functions are registered with the command argparser before anything is instantiated, we
583-
# need to find an instance at runtime that matches the types during declaration
584-
cmd_set = self._cmd2_app._resolve_func_self(arg_choices.to_call, cmd_set)
585-
if cmd_set is None:
586-
# No cases matched, raise an error
587-
raise CompletionError('Could not find CommandSet instance matching defining type for completer')
580+
# The completer may or may not be defined in the same class as the command. Since completer
581+
# functions are registered with the command argparser before anything is instantiated, we
582+
# need to find an instance at runtime that matches the types during declaration
583+
self_arg = self._cmd2_app._resolve_func_self(arg_choices.to_call, cmd_set)
584+
if self_arg is None:
585+
# No cases matched, raise an error
586+
raise CompletionError('Could not find CommandSet instance matching defining type for completer')
588587

589-
args.append(cmd_set)
588+
args.append(self_arg)
590589

591590
# Check if arg_choices.to_call expects arg_tokens
592591
to_call_params = inspect.signature(arg_choices.to_call).parameters
@@ -630,6 +629,6 @@ def _complete_for_arg(self, arg_state: _ArgumentState,
630629
arg_choices = [choice for choice in arg_choices if choice not in used_values]
631630

632631
# Do tab completion on the choices
633-
results = basic_complete(text, line, begidx, endidx, arg_choices)
632+
results = self._cmd2_app.basic_complete(text, line, begidx, endidx, arg_choices)
634633

635634
return self._format_completions(arg_state, results)

Diff for: cmd2/argparse_custom.py

+48-100
Original file line numberDiff line numberDiff line change
@@ -39,100 +39,68 @@
3939
you can add tab completion for each argument's values using parameters passed
4040
to add_argument().
4141
42-
Below are the 5 add_argument() parameters for enabling tab completion of an
42+
Below are the 3 add_argument() parameters for enabling tab completion of an
4343
argument's value. Only one can be used at a time.
4444
4545
``choices`` - pass a list of values to the choices parameter.
4646
4747
Example::
4848
49-
parser.add_argument('-o', '--options', choices=['An Option', 'SomeOtherOption'])
49+
my_list = ['An Option', 'SomeOtherOption']
5050
parser.add_argument('-o', '--options', choices=my_list)
5151
52-
``choices_function`` - pass a function that returns choices. This is good in
52+
``choices_provider`` - pass a function that returns choices. This is good in
5353
cases where the choice list is dynamically generated when the user hits tab.
5454
5555
Example::
5656
57-
def my_choices_function():
57+
def my_choices_provider(self):
5858
...
5959
return my_generated_list
6060
61-
parser.add_argument('-o', '--options', choices_function=my_choices_function)
61+
parser.add_argument("arg", choices_provider=my_choices_provider)
6262
63-
``choices_method`` - this is equivalent to choices_function, but the function
64-
needs to be an instance method of a cmd2.Cmd or cmd2.CommandSet subclass. When
65-
ArgparseCompleter calls the method, it well detect whether is is bound to a
66-
CommandSet or Cmd subclass.
67-
If bound to a cmd2.Cmd subclass, it will pass the app instance as the `self`
68-
argument. This is good in cases where the list of choices being generated
69-
relies on state data of the cmd2-based app.
70-
If bound to a cmd2.CommandSet subclass, it will pass the CommandSet instance
71-
as the `self` argument.
63+
``completer`` - pass a tab completion function that does custom completion.
7264
73-
Example::
74-
75-
def my_choices_method(self):
76-
...
77-
return my_generated_list
78-
79-
parser.add_argument("arg", choices_method=my_choices_method)
80-
81-
82-
``completer_function`` - pass a tab completion function that does custom
83-
completion. Since custom tab completion operations commonly need to modify
84-
cmd2's instance variables related to tab completion, it will be rare to need a
85-
completer function. completer_method should be used in those cases.
86-
87-
Example::
88-
89-
def my_completer_function(text, line, begidx, endidx):
90-
...
91-
return completions
92-
parser.add_argument('-o', '--options', completer_function=my_completer_function)
93-
94-
``completer_method`` - this is equivalent to completer_function, but the function
95-
needs to be an instance method of a cmd2.Cmd or cmd2.CommandSet subclass. When
96-
ArgparseCompleter calls the method, it well detect whether is is bound to a
97-
CommandSet or Cmd subclass.
98-
If bound to a cmd2.Cmd subclass, it will pass the app instance as the `self`
99-
argument. This is good in cases where the list of choices being generated
100-
relies on state data of the cmd2-based app.
101-
If bound to a cmd2.CommandSet subclass, it will pass the CommandSet instance
102-
as the `self` argument, and the app instance as the positional argument.
103-
cmd2 provides a few completer methods for convenience (e.g.,
104-
path_complete, delimiter_complete)
65+
cmd2 provides a few completer methods for convenience (e.g., path_complete,
66+
delimiter_complete)
10567
10668
Example::
10769
10870
# This adds file-path completion to an argument
109-
parser.add_argument('-o', '--options', completer_method=cmd2.Cmd.path_complete)
110-
71+
parser.add_argument('-o', '--options', completer=cmd2.Cmd.path_complete)
11172
11273
You can use functools.partial() to prepopulate values of the underlying
11374
choices and completer functions/methods.
11475
11576
Example::
11677
11778
# This says to call path_complete with a preset value for its path_filter argument
118-
completer_method = functools.partial(path_complete,
119-
path_filter=lambda path: os.path.isdir(path))
120-
parser.add_argument('-o', '--options', choices_method=completer_method)
121-
122-
Of the 5 tab completion parameters, choices is the only one where argparse
79+
dir_completer = functools.partial(path_complete,
80+
path_filter=lambda path: os.path.isdir(path))
81+
parser.add_argument('-o', '--options', completer=dir_completer)
82+
83+
For `choices_provider` and `completer`, do not set them to a bound method. This
84+
is because ArgparseCompleter passes the `self` argument explicitly to these
85+
functions. When ArgparseCompleter calls one, it will detect whether it is bound
86+
to a `Cmd` subclass or `CommandSet`. If bound to a `cmd2.Cmd subclass`, it will
87+
pass the app instance as the `self` argument. If bound to a `cmd2.CommandSet`
88+
subclass, it will pass the `CommandSet` instance as the `self` argument.
89+
Therefore instead of passing something like `self.path_complete`, pass
90+
`cmd2.Cmd.path_complete`.
91+
92+
Of the 3 tab completion parameters, choices is the only one where argparse
12393
validates user input against items in the choices list. This is because the
124-
other 4 parameters are meant to tab complete data sets that are viewed as
94+
other 2 parameters are meant to tab complete data sets that are viewed as
12595
dynamic. Therefore it is up to the developer to validate if the user has typed
12696
an acceptable value for these arguments.
12797
12898
The following functions exist in cases where you may want to manually add a
12999
choice-providing function/method to an existing argparse action. For instance,
130100
in __init__() of a custom action class.
131101
132-
- set_choices_function(action, func)
133-
- set_choices_method(action, method)
134-
- set_completer_function(action, func)
135-
- set_completer_method(action, method)
102+
- set_choices_provider(action, func)
103+
- set_completer(action, func)
136104
137105
There are times when what's being tab completed is determined by a previous
138106
argument on the command line. In theses cases, Autocompleter can pass a
@@ -142,8 +110,8 @@ def my_completer_function(text, line, begidx, endidx):
142110
143111
Example::
144112
145-
def my_choices_method(self, arg_tokens)
146-
def my_completer_method(self, text, line, begidx, endidx, arg_tokens)
113+
def my_choices_provider(self, arg_tokens)
114+
def my_completer(self, text, line, begidx, endidx, arg_tokens)
147115
148116
All values of the arg_tokens dictionary are lists, even if a particular
149117
argument expects only 1 token. Since ArgparseCompleter is for tab completion,
@@ -295,15 +263,13 @@ class ChoicesCallable:
295263
Enables using a callable as the choices provider for an argparse argument.
296264
While argparse has the built-in choices attribute, it is limited to an iterable.
297265
"""
298-
def __init__(self, is_method: bool, is_completer: bool, to_call: Callable):
266+
def __init__(self, is_completer: bool, to_call: Callable):
299267
"""
300268
Initializer
301-
:param is_method: True if to_call is an instance method of a cmd2 app. False if it is a function.
302269
:param is_completer: True if to_call is a tab completion routine which expects
303270
the args: text, line, begidx, endidx
304271
:param to_call: the callable object that will be called to provide choices for the argument
305272
"""
306-
self.is_method = is_method
307273
self.is_completer = is_completer
308274
self.to_call = to_call
309275

@@ -318,34 +284,24 @@ def _set_choices_callable(action: argparse.Action, choices_callable: ChoicesCall
318284
# Verify consistent use of parameters
319285
if action.choices is not None:
320286
err_msg = ("None of the following parameters can be used alongside a choices parameter:\n"
321-
"choices_function, choices_method, completer_function, completer_method")
287+
"choices_provider, completer")
322288
raise (TypeError(err_msg))
323289
elif action.nargs == 0:
324290
err_msg = ("None of the following parameters can be used on an action that takes no arguments:\n"
325-
"choices_function, choices_method, completer_function, completer_method")
291+
"choices_provider, completer")
326292
raise (TypeError(err_msg))
327293

328294
setattr(action, ATTR_CHOICES_CALLABLE, choices_callable)
329295

330296

331-
def set_choices_function(action: argparse.Action, choices_function: Callable) -> None:
332-
"""Set choices_function on an argparse action"""
333-
_set_choices_callable(action, ChoicesCallable(is_method=False, is_completer=False, to_call=choices_function))
334-
335-
336-
def set_choices_method(action: argparse.Action, choices_method: Callable) -> None:
337-
"""Set choices_method on an argparse action"""
338-
_set_choices_callable(action, ChoicesCallable(is_method=True, is_completer=False, to_call=choices_method))
339-
340-
341-
def set_completer_function(action: argparse.Action, completer_function: Callable) -> None:
342-
"""Set completer_function on an argparse action"""
343-
_set_choices_callable(action, ChoicesCallable(is_method=False, is_completer=True, to_call=completer_function))
297+
def set_choices_provider(action: argparse.Action, choices_provider: Callable) -> None:
298+
"""Set choices_provider on an argparse action"""
299+
_set_choices_callable(action, ChoicesCallable(is_completer=False, to_call=choices_provider))
344300

345301

346-
def set_completer_method(action: argparse.Action, completer_method: Callable) -> None:
347-
"""Set completer_method on an argparse action"""
348-
_set_choices_callable(action, ChoicesCallable(is_method=True, is_completer=True, to_call=completer_method))
302+
def set_completer(action: argparse.Action, completer: Callable) -> None:
303+
"""Set completer on an argparse action"""
304+
_set_choices_callable(action, ChoicesCallable(is_completer=True, to_call=completer))
349305

350306

351307
############################################################################################################
@@ -359,10 +315,8 @@ def set_completer_method(action: argparse.Action, completer_method: Callable) ->
359315

360316
def _add_argument_wrapper(self, *args,
361317
nargs: Union[int, str, Tuple[int], Tuple[int, int], None] = None,
362-
choices_function: Optional[Callable] = None,
363-
choices_method: Optional[Callable] = None,
364-
completer_function: Optional[Callable] = None,
365-
completer_method: Optional[Callable] = None,
318+
choices_provider: Optional[Callable] = None,
319+
completer: Optional[Callable] = None,
366320
suppress_tab_hint: bool = False,
367321
descriptive_header: Optional[str] = None,
368322
**kwargs) -> argparse.Action:
@@ -378,10 +332,8 @@ def _add_argument_wrapper(self, *args,
378332
to specify a max value with no upper bound, use a 1-item tuple (min,)
379333
380334
# Added args used by ArgparseCompleter
381-
:param choices_function: function that provides choices for this argument
382-
:param choices_method: cmd2-app method that provides choices for this argument
383-
:param completer_function: tab completion function that provides choices for this argument
384-
:param completer_method: cmd2-app tab completion method that provides choices for this argument
335+
:param choices_provider: function that provides choices for this argument
336+
:param completer: tab completion function that provides choices for this argument
385337
:param suppress_tab_hint: when ArgparseCompleter has no results to show during tab completion, it displays the
386338
current argument's help text as a hint. Set this to True to suppress the hint. If this
387339
argument's help text is set to argparse.SUPPRESS, then tab hints will not display
@@ -393,20 +345,20 @@ def _add_argument_wrapper(self, *args,
393345
:param kwargs: keyword-arguments recognized by argparse._ActionsContainer.add_argument
394346
395347
Note: You can only use 1 of the following in your argument:
396-
choices, choices_function, choices_method, completer_function, completer_method
348+
choices, choices_provider, completer
397349
398350
See the header of this file for more information
399351
400352
:return: the created argument action
401353
:raises: ValueError on incorrect parameter usage
402354
"""
403355
# Verify consistent use of arguments
404-
choices_callables = [choices_function, choices_method, completer_function, completer_method]
356+
choices_callables = [choices_provider, completer]
405357
num_params_set = len(choices_callables) - choices_callables.count(None)
406358

407359
if num_params_set > 1:
408360
err_msg = ("Only one of the following parameters may be used at a time:\n"
409-
"choices_function, choices_method, completer_function, completer_method")
361+
"choices_provider, completer")
410362
raise (ValueError(err_msg))
411363

412364
# Pre-process special ranged nargs
@@ -465,14 +417,10 @@ def _add_argument_wrapper(self, *args,
465417
# Set the custom attributes
466418
setattr(new_arg, ATTR_NARGS_RANGE, nargs_range)
467419

468-
if choices_function:
469-
set_choices_function(new_arg, choices_function)
470-
elif choices_method:
471-
set_choices_method(new_arg, choices_method)
472-
elif completer_function:
473-
set_completer_function(new_arg, completer_function)
474-
elif completer_method:
475-
set_completer_method(new_arg, completer_method)
420+
if choices_provider:
421+
set_choices_provider(new_arg, choices_provider)
422+
elif completer:
423+
set_completer(new_arg, completer)
476424

477425
setattr(new_arg, ATTR_SUPPRESS_TAB_HINT, suppress_tab_hint)
478426
setattr(new_arg, ATTR_DESCRIPTIVE_COMPLETION_HEADER, descriptive_header)

0 commit comments

Comments
 (0)