Skip to content

Commit 8f981f3

Browse files
committed
Resolves comments from PR
1 parent 9d1b7c7 commit 8f981f3

20 files changed

+385
-126
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,22 @@
1818
* Removed `with_argparser_and_unknown_args` since it was deprecated in 1.3.0.
1919
* Replaced `cmd2.Cmd.completion_header` with `cmd2.Cmd.formatted_completions`. See Enhancements
2020
for description of this new class member.
21+
* Settables now have new initialization parameters. It is now a required parameter to supply the reference to the
22+
object that holds the settable attribute. `cmd2.Cmd.settables` is no longer a public dict attribute - it is now a
23+
property that aggregates all Settables across all registered CommandSets.
2124
* Enhancements
2225
* Added support for custom tab completion and up-arrow input history to `cmd2.Cmd2.read_input`.
2326
See [read_input.py](https://github.com/python-cmd2/cmd2/blob/master/examples/read_input.py)
2427
for an example.
2528
* Added `cmd2.exceptions.PassThroughException` to raise unhandled command exceptions instead of printing them.
2629
* Added support for ANSI styles and newlines in tab completion results using `cmd2.Cmd.formatted_completions`.
2730
`cmd2` provides this capability automatically if you return argparse completion matches as `CompletionItems`.
28-
31+
* Settables enhancements:
32+
* Settables may be optionally scoped to a CommandSet. Settables added to CommandSets will appear when a
33+
CommandSet is registered and disappear when a CommandSet is unregistered. Optionally, scoped Settables
34+
may have a prepended prefix.
35+
* Settables now allow changes to be applied to any arbitrary object attribute. It no longer needs to match an
36+
attribute added to the cmd2 instance itself.
2937
## 1.5.0 (January 31, 2021)
3038
* Bug Fixes
3139
* Fixed bug where setting `always_show_hint=True` did not show a hint when completing `Settables`

cmd2/ansi.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def style_aware_write(fileobj: IO[str], msg: str) -> None:
230230
fileobj.write(msg)
231231

232232

233-
def fg_lookup(fg_name: Union[str, fg]) -> Fore:
233+
def fg_lookup(fg_name: Union[str, fg]) -> str:
234234
"""
235235
Look up ANSI escape codes based on foreground color name.
236236
@@ -239,16 +239,16 @@ def fg_lookup(fg_name: Union[str, fg]) -> Fore:
239239
:raises: ValueError: if the color cannot be found
240240
"""
241241
if isinstance(fg_name, fg):
242-
return fg_name.value
242+
return str(fg_name.value)
243243

244244
try:
245245
ansi_escape = fg[fg_name.lower()].value
246246
except KeyError:
247247
raise ValueError('Foreground color {!r} does not exist; must be one of: {}'.format(fg_name, fg.colors()))
248-
return ansi_escape
248+
return str(ansi_escape)
249249

250250

251-
def bg_lookup(bg_name: Union[str, bg]) -> Back:
251+
def bg_lookup(bg_name: Union[str, bg]) -> str:
252252
"""
253253
Look up ANSI escape codes based on background color name.
254254
@@ -257,13 +257,13 @@ def bg_lookup(bg_name: Union[str, bg]) -> Back:
257257
:raises: ValueError: if the color cannot be found
258258
"""
259259
if isinstance(bg_name, bg):
260-
return bg_name.value
260+
return str(bg_name.value)
261261

262262
try:
263263
ansi_escape = bg[bg_name.lower()].value
264264
except KeyError:
265265
raise ValueError('Background color {!r} does not exist; must be one of: {}'.format(bg_name, bg.colors()))
266-
return ansi_escape
266+
return str(ansi_escape)
267267

268268

269269
# noinspection PyShadowingNames
@@ -292,13 +292,13 @@ def style(
292292
:return: the stylized string
293293
"""
294294
# List of strings that add style
295-
additions = []
295+
additions: List[str] = []
296296

297297
# List of strings that remove style
298-
removals = []
298+
removals: List[str] = []
299299

300300
# Convert the text object into a string if it isn't already one
301-
text = "{}".format(text)
301+
text_formatted = "{}".format(text)
302302

303303
# Process the style settings
304304
if fg:
@@ -322,7 +322,7 @@ def style(
322322
removals.append(UNDERLINE_DISABLE)
323323

324324
# Combine the ANSI style sequences with the text
325-
return cast(str, "".join(additions) + text + "".join(removals))
325+
return "".join(additions) + text_formatted + "".join(removals)
326326

327327

328328
# Default styles for printing strings of various types.

cmd2/argparse_custom.py

Lines changed: 110 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,15 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
203203
gettext,
204204
)
205205
from typing import (
206+
IO,
206207
Any,
207208
Callable,
209+
Dict,
208210
Iterable,
209211
List,
210212
NoReturn,
211213
Optional,
214+
Sequence,
212215
Tuple,
213216
Type,
214217
Union,
@@ -220,6 +223,15 @@ def my_completer(self, text, line, begidx, endidx, arg_tokens)
220223
constants,
221224
)
222225

226+
try:
227+
from typing import (
228+
Protocol,
229+
)
230+
except ImportError:
231+
from typing_extensions import ( # type: ignore[misc]
232+
Protocol,
233+
)
234+
223235
############################################################################################################
224236
# The following are names of custom argparse argument attributes added by cmd2
225237
############################################################################################################
@@ -286,6 +298,59 @@ def __init__(self, value: object, desc: str = '', *args: Any) -> None:
286298
############################################################################################################
287299
# Class and functions related to ChoicesCallable
288300
############################################################################################################
301+
302+
303+
class ChoicesProviderFunc(Protocol):
304+
"""
305+
Function that returns a list of choices in support of tab completion
306+
"""
307+
308+
def __call__(self) -> List[str]:
309+
... # pragma: no cover
310+
311+
312+
class ChoicesProviderFuncWithTokens(Protocol):
313+
"""
314+
Function that returns a list of choices in support of tab completion and accepts a dictionary of prior arguments.
315+
"""
316+
317+
def __call__(self, *, arg_tokens: Dict[str, List[str]]) -> List[str]:
318+
... # pragma: no cover
319+
320+
321+
class CompleterFunc(Protocol):
322+
"""
323+
Function to support tab completion with the provided state of the user prompt
324+
"""
325+
326+
def __call__(
327+
self,
328+
text: str,
329+
line: str,
330+
begidx: int,
331+
endidx: int,
332+
) -> List[str]:
333+
... # pragma: no cover
334+
335+
336+
class CompleterFuncWithTokens(Protocol):
337+
"""
338+
Function to support tab completion with the provided state of the user prompt and accepts a dictionary of prior
339+
arguments.
340+
"""
341+
342+
def __call__(
343+
self,
344+
text: str,
345+
line: str,
346+
begidx: int,
347+
endidx: int,
348+
*,
349+
arg_tokens: Dict[str, List[str]],
350+
) -> List[str]:
351+
... # pragma: no cover
352+
353+
289354
class ChoicesCallable:
290355
"""
291356
Enables using a callable as the choices provider for an argparse argument.
@@ -295,7 +360,7 @@ class ChoicesCallable:
295360
def __init__(
296361
self,
297362
is_completer: bool,
298-
to_call: Union[Callable[[], List[str]], Callable[[str, str, int, int], List[str]]],
363+
to_call: Union[CompleterFunc, CompleterFuncWithTokens, ChoicesProviderFunc, ChoicesProviderFuncWithTokens],
299364
) -> None:
300365
"""
301366
Initializer
@@ -328,12 +393,18 @@ def _set_choices_callable(action: argparse.Action, choices_callable: ChoicesCall
328393
setattr(action, ATTR_CHOICES_CALLABLE, choices_callable)
329394

330395

331-
def set_choices_provider(action: argparse.Action, choices_provider: Callable[[], List[str]]) -> None:
396+
def set_choices_provider(
397+
action: argparse.Action,
398+
choices_provider: Union[ChoicesProviderFunc, ChoicesProviderFuncWithTokens],
399+
) -> None:
332400
"""Set choices_provider on an argparse action"""
333401
_set_choices_callable(action, ChoicesCallable(is_completer=False, to_call=choices_provider))
334402

335403

336-
def set_completer(action: argparse.Action, completer: Callable[[str, str, int, int], List[str]]) -> None:
404+
def set_completer(
405+
action: argparse.Action,
406+
completer: Union[CompleterFunc, CompleterFuncWithTokens],
407+
) -> None:
337408
"""Set completer on an argparse action"""
338409
_set_choices_callable(action, ChoicesCallable(is_completer=True, to_call=completer))
339410

@@ -351,11 +422,11 @@ def _add_argument_wrapper(
351422
self: argparse._ActionsContainer,
352423
*args: Any,
353424
nargs: Union[int, str, Tuple[int], Tuple[int, int], Tuple[int, float], None] = None,
354-
choices_provider: Optional[Callable[[], List[str]]] = None,
355-
completer: Optional[Callable[[str, str, int, int], List[str]]] = None,
425+
choices_provider: Optional[Union[ChoicesProviderFunc, ChoicesProviderFuncWithTokens]] = None,
426+
completer: Optional[Union[CompleterFunc, CompleterFuncWithTokens]] = None,
356427
suppress_tab_hint: bool = False,
357428
descriptive_header: Optional[str] = None,
358-
**kwargs: Any
429+
**kwargs: Any,
359430
) -> argparse.Action:
360431
"""
361432
Wrapper around _ActionsContainer.add_argument() which supports more settings used by cmd2
@@ -646,9 +717,9 @@ def _format_usage(
646717

647718
# helper for wrapping lines
648719
# noinspection PyMissingOrEmptyDocstring,PyShadowingNames
649-
def get_lines(parts: List[str], indent: str, prefix: Optional[str] = None):
650-
lines = []
651-
line = []
720+
def get_lines(parts: List[str], indent: str, prefix: Optional[str] = None) -> List[str]:
721+
lines: List[str] = []
722+
line: List[str] = []
652723
if prefix is not None:
653724
line_len = len(prefix) - 1
654725
else:
@@ -703,14 +774,14 @@ def get_lines(parts: List[str], indent: str, prefix: Optional[str] = None):
703774
# prefix with 'Usage:'
704775
return '%s%s\n\n' % (prefix, usage)
705776

706-
def _format_action_invocation(self, action) -> str:
777+
def _format_action_invocation(self, action: argparse.Action) -> str:
707778
if not action.option_strings:
708779
default = self._get_default_metavar_for_positional(action)
709780
(metavar,) = self._metavar_formatter(action, default)(1)
710781
return metavar
711782

712783
else:
713-
parts = []
784+
parts: List[str] = []
714785

715786
# if the Optional doesn't take a value, format is:
716787
# -s, --long
@@ -729,7 +800,11 @@ def _format_action_invocation(self, action) -> str:
729800
# End cmd2 customization
730801

731802
# noinspection PyMethodMayBeStatic
732-
def _determine_metavar(self, action, default_metavar) -> Union[str, Tuple]:
803+
def _determine_metavar(
804+
self,
805+
action: argparse.Action,
806+
default_metavar: Union[str, Tuple[str, ...]],
807+
) -> Union[str, Tuple[str, ...]]:
733808
"""Custom method to determine what to use as the metavar value of an action"""
734809
if action.metavar is not None:
735810
result = action.metavar
@@ -742,11 +817,15 @@ def _determine_metavar(self, action, default_metavar) -> Union[str, Tuple]:
742817
result = default_metavar
743818
return result
744819

745-
def _metavar_formatter(self, action, default_metavar) -> Callable:
820+
def _metavar_formatter(
821+
self,
822+
action: argparse.Action,
823+
default_metavar: Union[str, Tuple[str, ...]],
824+
) -> Callable[[int], Tuple[str, ...]]:
746825
metavar = self._determine_metavar(action, default_metavar)
747826

748827
# noinspection PyMissingOrEmptyDocstring
749-
def format(tuple_size):
828+
def format(tuple_size: int) -> Tuple[str, ...]:
750829
if isinstance(metavar, tuple):
751830
return metavar
752831
else:
@@ -755,7 +834,7 @@ def format(tuple_size):
755834
return format
756835

757836
# noinspection PyProtectedMember
758-
def _format_args(self, action, default_metavar) -> str:
837+
def _format_args(self, action: argparse.Action, default_metavar: Union[str, Tuple[str, ...]]) -> str:
759838
"""Customized to handle ranged nargs and make other output less verbose"""
760839
metavar = self._determine_metavar(action, default_metavar)
761840
metavar_formatter = self._metavar_formatter(action, default_metavar)
@@ -780,7 +859,7 @@ def _format_args(self, action, default_metavar) -> str:
780859
elif isinstance(action.nargs, int) and action.nargs > 1:
781860
return '{}{{{}}}'.format('%s' % metavar_formatter(1), action.nargs)
782861

783-
return super()._format_args(action, default_metavar)
862+
return super()._format_args(action, default_metavar) # type: ignore[arg-type]
784863

785864

786865
# noinspection PyCompatibility
@@ -789,18 +868,18 @@ class Cmd2ArgumentParser(argparse.ArgumentParser):
789868

790869
def __init__(
791870
self,
792-
prog=None,
793-
usage=None,
794-
description=None,
795-
epilog=None,
796-
parents=None,
797-
formatter_class=Cmd2HelpFormatter,
798-
prefix_chars='-',
799-
fromfile_prefix_chars=None,
800-
argument_default=None,
801-
conflict_handler='error',
802-
add_help=True,
803-
allow_abbrev=True,
871+
prog: Optional[str] = None,
872+
usage: Optional[str] = None,
873+
description: Optional[str] = None,
874+
epilog: Optional[str] = None,
875+
parents: Sequence[argparse.ArgumentParser] = [],
876+
formatter_class: Type[argparse.HelpFormatter] = Cmd2HelpFormatter,
877+
prefix_chars: str = '-',
878+
fromfile_prefix_chars: Optional[str] = None,
879+
argument_default: Optional[str] = None,
880+
conflict_handler: str = 'error',
881+
add_help: bool = True,
882+
allow_abbrev: bool = True,
804883
) -> None:
805884
super(Cmd2ArgumentParser, self).__init__(
806885
prog=prog,
@@ -817,7 +896,7 @@ def __init__(
817896
allow_abbrev=allow_abbrev,
818897
)
819898

820-
def add_subparsers(self, **kwargs):
899+
def add_subparsers(self, **kwargs: Any) -> argparse._SubParsersAction:
821900
"""
822901
Custom override. Sets a default title if one was not given.
823902
@@ -895,7 +974,7 @@ def format_help(self) -> str:
895974
# determine help from format above
896975
return formatter.format_help() + '\n'
897976

898-
def _print_message(self, message, file=None):
977+
def _print_message(self, message: str, file: Optional[IO[str]] = None) -> None:
899978
# Override _print_message to use style_aware_write() since we use ANSI escape characters to support color
900979
if message:
901980
if file is None:
@@ -923,7 +1002,7 @@ def set(self, new_val: Any) -> None:
9231002

9241003

9251004
# The default ArgumentParser class for a cmd2 app
926-
DEFAULT_ARGUMENT_PARSER = Cmd2ArgumentParser
1005+
DEFAULT_ARGUMENT_PARSER: Type[argparse.ArgumentParser] = Cmd2ArgumentParser
9271006

9281007

9291008
def set_default_argument_parser(parser: Type[argparse.ArgumentParser]) -> None:

0 commit comments

Comments
 (0)