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 @@ -110,6 +110,8 @@ prompt is displayed.
now relies exclusively on command function docstrings.
- Removed `feedback_to_output` settable and changed `cmd2.Cmd.pfeedback` to always print to
`self.stdout`
- Removed `Cmd.parseline()` since it was unused and merely wrapped
Comment thread
tleonhardt marked this conversation as resolved.
`StatementParser.parse_command_only()`.
- Enhancements
- New `cmd2.Cmd` parameters
- **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These
Expand Down
28 changes: 14 additions & 14 deletions cmd2/argparse_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,20 +158,20 @@ class ArgparseCompleter:
def __init__(
self,
parser: Cmd2ArgumentParser,
cmd2_app: "Cmd",
cmd_app: "Cmd",
*,
parent_tokens: Mapping[str, MutableSequence[str]] | None = None,
) -> None:
"""Create an ArgparseCompleter.

:param parser: Cmd2ArgumentParser instance
:param cmd2_app: reference to the Cmd2 application that owns this ArgparseCompleter
:param cmd_app: reference to the cmd2.Cmd instance that owns this ArgparseCompleter
:param parent_tokens: optional Mapping of parent parsers' arg names to their tokens
This is only used by ArgparseCompleter when recursing on subcommand parsers
Defaults to None
"""
self._parser = parser
self._cmd2_app = cmd2_app
self._cmd_app = cmd_app

if parent_tokens is None:
parent_tokens = {}
Expand Down Expand Up @@ -366,8 +366,8 @@ def consume_argument(arg_state: _ArgumentState, arg_token: str) -> None:
parent_tokens[action.dest] = [token]

parser = self._subcommand_action.choices[token]
completer_type = self._cmd2_app._determine_ap_completer_type(parser)
completer = completer_type(parser, self._cmd2_app, parent_tokens=parent_tokens)
completer_type = self._cmd_app._determine_ap_completer_type(parser)
completer = completer_type(parser, self._cmd_app, parent_tokens=parent_tokens)
return completer.complete(text, line, begidx, endidx, tokens[token_index + 1 :], cmd_set=cmd_set)

# Invalid subcommand entered, so no way to complete remaining tokens
Expand Down Expand Up @@ -544,7 +544,7 @@ def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, used_f

# Keep flags sorted in the order provided by argparse so our completion
# suggestions display the same as argparse help text.
matched_flags = self._cmd2_app.basic_complete(text, line, begidx, endidx, match_against, sort=False)
matched_flags = self._cmd_app.basic_complete(text, line, begidx, endidx, match_against, sort=False)

for flag in matched_flags.to_strings():
action = self._flag_to_action[flag]
Expand Down Expand Up @@ -613,7 +613,7 @@ def _build_completion_table(self, arg_state: _ArgumentState, completions: Comple
# Skip table generation if results are outside thresholds or no columns are defined
if (
len(completions) < 2
or len(completions) > self._cmd2_app.max_completion_table_items
or len(completions) > self._cmd_app.max_completion_table_items
or table_columns is None
): # fmt: skip
return completions
Expand Down Expand Up @@ -668,13 +668,13 @@ def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: in
for token_index, token in enumerate(tokens):
if token in self._subcommand_action.choices:
parser = self._subcommand_action.choices[token]
completer_type = self._cmd2_app._determine_ap_completer_type(parser)
completer = completer_type(parser, self._cmd2_app)
completer_type = self._cmd_app._determine_ap_completer_type(parser)
completer = completer_type(parser, self._cmd_app)
return completer.complete_subcommand_help(text, line, begidx, endidx, tokens[token_index + 1 :])

if token_index == len(tokens) - 1:
# Since this is the last token, we will attempt to complete it
return self._cmd2_app.basic_complete(text, line, begidx, endidx, self._subcommand_action.choices)
return self._cmd_app.basic_complete(text, line, begidx, endidx, self._subcommand_action.choices)
break
return Completions()

Expand All @@ -690,8 +690,8 @@ def print_help(self, tokens: Sequence[str], file: IO[str] | None = None) -> None
if tokens and self._subcommand_action is not None:
parser = self._subcommand_action.choices.get(tokens[0])
if parser is not None:
completer_type = self._cmd2_app._determine_ap_completer_type(parser)
completer = completer_type(parser, self._cmd2_app)
completer_type = self._cmd_app._determine_ap_completer_type(parser)
completer = completer_type(parser, self._cmd_app)
completer.print_help(tokens[1:], file)
return
self._parser.print_help(file)
Expand Down Expand Up @@ -732,7 +732,7 @@ def _prepare_callable_params(
kwargs: dict[str, Any] = {}

# Resolve the 'self' instance for the method
self_arg = self._cmd2_app._resolve_func_self(to_call, cmd_set)
self_arg = self._cmd_app._resolve_func_self(to_call, cmd_set)
if self_arg is None:
raise CompletionError("Could not find CommandSet instance matching defining type")

Expand Down Expand Up @@ -794,7 +794,7 @@ def _complete_arg(
# Filter used values and run basic completion
used_values = consumed_arg_values.get(arg_state.action.dest, [])
filtered = [choice for choice in all_choices if choice.text not in used_values]
completions = self._cmd2_app.basic_complete(text, line, begidx, endidx, filtered)
completions = self._cmd_app.basic_complete(text, line, begidx, endidx, filtered)

return self._build_completion_table(arg_state, completions)

Expand Down
19 changes: 5 additions & 14 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,12 @@ class CommandParsers:
Parser creation and retrieval are accomplished through the get() method.
"""

def __init__(self, cmd: "Cmd") -> None:
def __init__(self, cmd_app: "Cmd") -> None:
"""Initialize CommandParsers.

:param cmd: the Cmd instance whose parsers are being managed
:param cmd_app: the Cmd instance whose parsers are being managed
"""
self._cmd = cmd
self._cmd_app = cmd_app

# Keyed by the fully qualified method names. This is more reliable than
# the methods themselves, since wrapping a method will change its address.
Expand Down Expand Up @@ -285,8 +285,8 @@ def get(self, command_method: BoundCommandFunc) -> Cmd2ArgumentParser | None:
if parser_builder is None:
return None

parent = self._cmd.find_commandset_for_command(command) or self._cmd
parser = self._cmd._build_parser(parent, parser_builder)
parent = self._cmd_app.find_commandset_for_command(command) or self._cmd_app
parser = self._cmd_app._build_parser(parent, parser_builder)

# To ensure accurate usage strings, recursively update 'prog' values
# within the parser to match the command name.
Expand Down Expand Up @@ -2883,15 +2883,6 @@ def postloop(self) -> None:
See [Hooks](../features/hooks.md) for more information.
"""

def parseline(self, line: str) -> tuple[str, str, str]:
"""Parse the line into a command name and a string containing the arguments.

:param line: line read by prompt-toolkit
:return: tuple containing (command, args, line)
"""
partial_statement = self.statement_parser.parse_command_only(line)
return partial_statement.command, partial_statement.args, partial_statement.command_and_args

def onecmd_plus_hooks(
self,
line: str,
Expand Down
20 changes: 10 additions & 10 deletions cmd2/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,13 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> bool | None:
"""Command function wrapper which translates command line into an argument list and calls actual command function.

:param args: All positional arguments to this function. We're expecting there to be:
cmd2_app, statement: Union[Statement, str]
cmd_app, statement: Union[Statement, str]
contiguously somewhere in the list
:param kwargs: any keyword arguments being passed to command function
:return: return value of command function
"""
cmd2_app, statement = _parse_positionals(args)
_, command_arg_list = cmd2_app.statement_parser.get_command_arg_list(command_name, statement, preserve_quotes)
cmd_app, statement = _parse_positionals(args)
_, command_arg_list = cmd_app.statement_parser.get_command_arg_list(command_name, statement, preserve_quotes)
func_arg_list = _arg_swap(args, statement, command_arg_list)
return func(*func_arg_list, **kwargs)

Expand Down Expand Up @@ -275,19 +275,19 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> bool | None:
"""Command function wrapper which translates command line into argparse Namespace and call actual command function.

:param args: All positional arguments to this function. We're expecting there to be:
cmd2_app, statement: Union[Statement, str]
cmd_app, statement: Union[Statement, str]
contiguously somewhere in the list
:param kwargs: any keyword arguments being passed to command function
:return: return value of command function
:raises Cmd2ArgparseError: if argparse has error parsing command line
"""
cmd2_app, statement_arg = _parse_positionals(args)
statement, command_arg_list = cmd2_app.statement_parser.get_command_arg_list(
cmd_app, statement_arg = _parse_positionals(args)
statement, command_arg_list = cmd_app.statement_parser.get_command_arg_list(
command_name, statement_arg, preserve_quotes
)

# Pass cmd_wrapper instead of func, since it contains the parser info.
arg_parser = cmd2_app.command_parsers.get(cmd_wrapper)
arg_parser = cmd_app.command_parsers.get(cmd_wrapper)
if arg_parser is None:
# This shouldn't be possible to reach
raise ValueError(f"No argument parser found for {command_name}") # pragma: no cover
Expand All @@ -298,12 +298,12 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> bool | None:
# The namespace provider may or may not be defined in the same class as the command. Since provider
# functions are registered with the command argparser before anything is instantiated, we
# need to find an instance at runtime that matches the types during declaration
provider_self = cmd2_app._resolve_func_self(ns_provider, args[0])
initial_namespace = ns_provider(provider_self if provider_self is not None else cmd2_app)
provider_self = cmd_app._resolve_func_self(ns_provider, args[0])
initial_namespace = ns_provider(provider_self if provider_self is not None else cmd_app)

try:
parsing_results: tuple[argparse.Namespace] | tuple[argparse.Namespace, list[str]]
with arg_parser.output_to(cmd2_app.stdout):
with arg_parser.output_to(cmd_app.stdout):
if with_unknown_args:
parsing_results = arg_parser.parse_known_args(command_arg_list, initial_namespace)
else:
Expand Down
24 changes: 12 additions & 12 deletions cmd2/pt_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def __init__(
custom_settings: utils.CustomCompletionSettings | None = None,
) -> None:
"""Initialize prompt_toolkit based completer class."""
self.cmd_app = cmd_app
self._cmd_app = cmd_app
self.custom_settings = custom_settings

def get_completions(self, document: Document, _complete_event: object) -> Iterable[Completion]:
Expand All @@ -143,7 +143,7 @@ def get_completions(self, document: Document, _complete_event: object) -> Iterab

# Define delimiters for completion to match cmd2/readline behavior
delimiters = BASE_DELIMITERS
delimiters += "".join(self.cmd_app.statement_parser.terminators)
delimiters += "".join(self._cmd_app.statement_parser.terminators)

# Find last delimiter before cursor to determine the word being completed
begidx = 0
Expand All @@ -155,7 +155,7 @@ def get_completions(self, document: Document, _complete_event: object) -> Iterab
endidx = cursor_pos
text = line[begidx:endidx]

completions = self.cmd_app.complete(
completions = self._cmd_app.complete(
text, line=line, begidx=begidx, endidx=endidx, custom_settings=self.custom_settings
)

Expand All @@ -165,7 +165,7 @@ def get_completions(self, document: Document, _complete_event: object) -> Iterab

# Print completion table if present
if completions.table is not None:
console = ru.Cmd2GeneralConsole(file=self.cmd_app.stdout)
console = ru.Cmd2GeneralConsole(file=self._cmd_app.stdout)
with console.capture() as capture:
console.print(completions.table, end="", soft_wrap=False)
print_formatted_text(pt_filter_style("\n" + capture.get()))
Expand Down Expand Up @@ -252,7 +252,7 @@ def append_string(self, string: str) -> None:
super().append_string(string)

def store_string(self, string: str) -> None:
"""No-op: Persistent history data is stored in cmd_app.history."""
"""No-op: Persistent history data is stored in cmd2.Cmd.history."""

def load_history_strings(self) -> Iterable[str]:
"""Yield strings from newest to oldest."""
Expand Down Expand Up @@ -287,7 +287,7 @@ def __init__(
:param cmd_app: cmd2.Cmd instance
"""
super().__init__()
self.cmd_app = cmd_app
self._cmd_app = cmd_app

_lexers.add(self)
self.set_colors()
Expand All @@ -306,7 +306,7 @@ def lex_document(self, document: Document) -> Callable[[int], Any]:
"""Lex the document."""
# Get redirection tokens and terminators to avoid highlighting them as values
exclude_tokens = set(constants.REDIRECTION_TOKENS)
exclude_tokens.update(self.cmd_app.statement_parser.terminators)
exclude_tokens.update(self._cmd_app.statement_parser.terminators)
arg_pattern = re.compile(r'(\s+)|(--?[^\s\'"]+)|("[^"]*"?|\'[^\']*\'?)|([^\s\'"]+)')

def highlight_args(text: str, tokens: list[tuple[str, str]]) -> None:
Expand Down Expand Up @@ -337,7 +337,7 @@ def get_line(lineno: int) -> list[tuple[str, str]]:
# Only attempt to match a command on the first line
if lineno == 0:
# Use cmd2's command pattern to find the first word (the command)
match = self.cmd_app.statement_parser._command_pattern.search(line)
match = self._cmd_app.statement_parser._command_pattern.search(line)
if match:
# Group 1 is the command, Group 2 is the character(s) that terminated the command match
command = match.group(1)
Expand All @@ -351,7 +351,7 @@ def get_line(lineno: int) -> list[tuple[str, str]]:
if command:
# Determine the style for the command
shortcut_found = False
for shortcut, _ in self.cmd_app.statement_parser.shortcuts:
for shortcut, _ in self._cmd_app.statement_parser.shortcuts:
if command.startswith(shortcut):
# Add the shortcut with the command style
tokens.append((self.command_color, shortcut))
Expand All @@ -365,11 +365,11 @@ def get_line(lineno: int) -> list[tuple[str, str]]:

if not shortcut_found:
style = ""
if command in self.cmd_app.get_all_commands():
if command in self._cmd_app.get_all_commands():
style = self.command_color
elif command in self.cmd_app.aliases:
elif command in self._cmd_app.aliases:
style = self.alias_color
elif command in self.cmd_app.macros:
elif command in self._cmd_app.macros:
style = self.macro_color

# Add the command with the determined style
Expand Down
24 changes: 12 additions & 12 deletions cmd2/py_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ def __bool__(self) -> bool:
class PyBridge:
"""Provides a Python API wrapper for application commands.

:param cmd2_app: app being controlled by this PyBridge.
:param cmd_app: app being controlled by this PyBridge.
:param add_to_history: If True, then add all commands run by this PyBridge to history.
Defaults to True.
"""

def __init__(self, cmd2_app: "Cmd", *, add_to_history: bool = True) -> None:
def __init__(self, cmd_app: "Cmd", *, add_to_history: bool = True) -> None:
"""Initialize PyBridge instances."""
self._cmd2_app = cmd2_app
self._cmd_app = cmd_app
self._add_to_history = add_to_history
self.cmd_echo = False

Expand All @@ -100,42 +100,42 @@ def __call__(self, command: str, *, echo: bool | None = None) -> CommandResult:
ex: app('help')
:param command: command line being run
:param echo: If provided, this temporarily overrides the value of self.cmd_echo
while the command runs. If True, output will be echoed to _cmd2_app.stdout
while the command runs. If True, output will be echoed to _cmd_app.stdout
and sys.stderr. (Defaults to None)
"""
if echo is None:
echo = self.cmd_echo

# This will be used to capture _cmd2_app.stdout
copy_cmd_stdout = StdSim(cast(TextIO | StdSim, self._cmd2_app.stdout), echo=echo)
# This will be used to capture _cmd_app.stdout
copy_cmd_stdout = StdSim(cast(TextIO | StdSim, self._cmd_app.stdout), echo=echo)

# Pause the storing of stdout until onecmd_plus_hooks enables it
copy_cmd_stdout.pause_storage = True

# This will be used to capture sys.stderr
copy_stderr = StdSim(sys.stderr, echo=echo)

self._cmd2_app.last_result = None
self._cmd_app.last_result = None

stop = False
try:
self._cmd2_app.stdout = cast(TextIO, copy_cmd_stdout)
self._cmd_app.stdout = cast(TextIO, copy_cmd_stdout)

with redirect_stderr(cast(IO[str], copy_stderr)):
stop = self._cmd2_app.onecmd_plus_hooks(
stop = self._cmd_app.onecmd_plus_hooks(
command,
add_to_history=self._add_to_history,
py_bridge_call=True,
)
finally:
with self._cmd2_app.sigint_protection:
self._cmd2_app.stdout = cast(TextIO, copy_cmd_stdout.inner_stream)
with self._cmd_app.sigint_protection:
self._cmd_app.stdout = cast(TextIO, copy_cmd_stdout.inner_stream)
self.stop = stop or self.stop

# Save the result
return CommandResult(
stdout=copy_cmd_stdout.getvalue(),
stderr=copy_stderr.getvalue(),
stop=stop,
data=self._cmd2_app.last_result,
data=self._cmd_app.last_result,
)
Loading
Loading