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
18 changes: 11 additions & 7 deletions cmd2/argparse_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
defaultdict,
deque,
)
from collections.abc import Sequence
from collections.abc import (
Mapping,
MutableSequence,
Sequence,
)
from typing import (
IO,
TYPE_CHECKING,
Expand Down Expand Up @@ -164,13 +168,13 @@ def __init__(
parser: argparse.ArgumentParser,
cmd2_app: 'Cmd',
*,
parent_tokens: dict[str, list[str]] | None = None,
parent_tokens: Mapping[str, MutableSequence[str]] | None = None,
) -> None:
"""Create an ArgparseCompleter.

:param parser: ArgumentParser instance
:param cmd2_app: reference to the Cmd2 application that owns this ArgparseCompleter
:param parent_tokens: optional dictionary mapping parent parsers' arg names to their tokens
: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
"""
Expand Down Expand Up @@ -216,7 +220,7 @@ def complete(
line: str,
begidx: int,
endidx: int,
tokens: list[str],
tokens: Sequence[str],
*,
cmd_set: CommandSet | None = None,
) -> Completions:
Expand All @@ -226,7 +230,7 @@ def complete(
: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 tokens: list of argument tokens being passed to the parser
:param tokens: Sequence of argument tokens being passed to the parser
:param cmd_set: if completing a command, the CommandSet the command's function belongs to, if applicable.
Defaults to None.
:return: a Completions object
Expand Down Expand Up @@ -638,7 +642,7 @@ def _format_completions(self, arg_state: _ArgumentState, completions: Completion
completion_table=capture.get(),
)

def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: int, tokens: list[str]) -> Completions:
def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: int, tokens: Sequence[str]) -> Completions:
"""Supports cmd2's help command in the completion of subcommand names.

:param text: the string prefix we are attempting to match (all matches must begin with it)
Expand All @@ -664,7 +668,7 @@ def complete_subcommand_help(self, text: str, line: str, begidx: int, endidx: in
break
return Completions()

def print_help(self, tokens: list[str], file: IO[str] | None = None) -> None:
def print_help(self, tokens: Sequence[str], file: IO[str] | None = None) -> None:
"""Supports cmd2's help command in the printing of help text.

:param tokens: arguments passed to help command
Expand Down
52 changes: 28 additions & 24 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
Callable,
Iterable,
Mapping,
MutableSequence,
Sequence,
)
from types import FrameType
from typing import (
Expand Down Expand Up @@ -299,14 +301,14 @@ def __init__(
include_ipy: bool = False,
include_py: bool = False,
intro: RenderableType = '',
multiline_commands: list[str] | None = None,
multiline_commands: Iterable[str] | None = None,
persistent_history_file: str = '',
persistent_history_length: int = 1000,
shortcuts: dict[str, str] | None = None,
shortcuts: Mapping[str, str] | None = None,
silence_startup_script: bool = False,
startup_script: str = '',
suggest_similar_command: bool = False,
terminators: list[str] | None = None,
terminators: Iterable[str] | None = None,
) -> None:
"""Easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package.

Expand Down Expand Up @@ -337,24 +339,24 @@ def __init__(
:param include_ipy: should the "ipy" command be included for an embedded IPython shell
:param include_py: should the "py" command be included for an embedded Python shell
:param intro: introduction to display at startup
:param multiline_commands: list of commands allowed to accept multi-line input
:param multiline_commands: Iterable of commands allowed to accept multi-line input
:param persistent_history_file: file path to load a persistent cmd2 command history from
:param persistent_history_length: max number of history items to write
to the persistent history file
:param shortcuts: dictionary containing shortcuts for commands. If not supplied,
:param shortcuts: Mapping containing shortcuts for commands. If not supplied,
then defaults to constants.DEFAULT_SHORTCUTS. If you do not want
any shortcuts, pass an empty dictionary.
any shortcuts, pass None and an empty dictionary will be created.
:param silence_startup_script: if ``True``, then the startup script's output will be
suppressed. Anything written to stderr will still display.
:param startup_script: file path to a script to execute at startup
:param suggest_similar_command: if ``True``, then when a command is not found,
[cmd2.Cmd][] will look for similar commands and suggest them.
:param terminators: list of characters that terminate a command. These are mainly
:param terminators: Iterable of characters that terminate a command. These are mainly
intended for terminating multiline commands, but will also
terminate single-line commands. If not supplied, the default
is a semicolon. If your app only contains single-line commands
and you want terminators to be treated as literals by the parser,
then set this to an empty list.
then set this to None.
"""
# Check if py or ipy need to be disabled in this instance
if not include_py:
Expand Down Expand Up @@ -996,7 +998,9 @@ def _register_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:
f"Could not find argparser for command '{command_name}' needed by subcommand: {method}"
)

def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) -> argparse.ArgumentParser:
def find_subcommand(
action: argparse.ArgumentParser, subcmd_names: MutableSequence[str]
) -> argparse.ArgumentParser:
if not subcmd_names:
return action
cur_subcmd = subcmd_names.pop(0)
Expand Down Expand Up @@ -2766,7 +2770,7 @@ def _run_cmdfinalization_hooks(self, stop: bool, statement: Statement | None) ->

def runcmds_plus_hooks(
self,
cmds: list[HistoryItem] | list[str],
cmds: Iterable[HistoryItem] | Iterable[str],
*,
add_to_history: bool = True,
stop_on_keyboard_interrupt: bool = False,
Expand Down Expand Up @@ -3169,7 +3173,7 @@ def default(self, statement: Statement) -> bool | None:
self.perror(err_msg, style=None)
return None

def completedefault(self, *_ignored: list[str]) -> Completions:
def completedefault(self, *_ignored: Sequence[str]) -> Completions:
"""Call to complete an input line when no command-specific complete_*() method is available.

This method is only called for non-argparse-based commands.
Expand All @@ -3185,7 +3189,7 @@ def read_input(
self,
prompt: str = '',
*,
history: list[str] | None = None,
history: Iterable[str] | None = None,
completion_mode: utils.CompletionMode = utils.CompletionMode.NONE,
preserve_quotes: bool = False,
choices: Iterable[Any] | None = None,
Expand All @@ -3198,7 +3202,7 @@ def read_input(
Also supports completion and up-arrow history while input is being entered.

:param prompt: prompt to display to user
:param history: optional list of strings to use for up-arrow history. If completion_mode is
:param history: optional Iterable of strings to use for up-arrow history. If completion_mode is
CompletionMode.COMMANDS and this is None, then cmd2's command list history will
be used. The passed in history will not be edited. It is the caller's responsibility
to add the returned input to history if desired. Defaults to None.
Expand Down Expand Up @@ -3873,7 +3877,7 @@ def complete_help_command(self, text: str, line: str, begidx: int, endidx: int)
return self.basic_complete(text, line, begidx, endidx, strs_to_match)

def complete_help_subcommands(
self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]]
self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Mapping[str, Sequence[str]]
) -> Completions:
"""Completes the subcommands argument of help."""
# Make sure we have a command whose subcommands we will complete
Expand Down Expand Up @@ -4014,13 +4018,13 @@ def do_help(self, args: argparse.Namespace) -> None:
self.perror(err_msg, style=None)
self.last_result = False

def print_topics(self, header: str, cmds: list[str] | None, cmdlen: int, maxcol: int) -> None: # noqa: ARG002
def print_topics(self, header: str, cmds: Sequence[str] | None, cmdlen: int, maxcol: int) -> None: # noqa: ARG002
"""Print groups of commands and topics in columns and an optional header.

Override of cmd's print_topics() to use Rich.

:param header: string to print above commands being printed
:param cmds: list of topics to print
:param cmds: Sequence of topics to print
:param cmdlen: unused, even by cmd's version
:param maxcol: max number of display columns to fit into
"""
Expand All @@ -4039,7 +4043,7 @@ def print_topics(self, header: str, cmds: list[str] | None, cmdlen: int, maxcol:
self.columnize(cmds, maxcol)
self.poutput()

def _print_documented_command_topics(self, header: str, cmds: list[str], verbose: bool) -> None:
def _print_documented_command_topics(self, header: str, cmds: Sequence[str], verbose: bool) -> None:
"""Print topics which are documented commands, switching between verbose or traditional output."""
import io

Expand Down Expand Up @@ -4103,14 +4107,14 @@ def _print_documented_command_topics(self, header: str, cmds: list[str], verbose
self.poutput(category_grid, soft_wrap=False)
self.poutput()

def render_columns(self, str_list: list[str] | None, display_width: int = 80) -> str:
def render_columns(self, str_list: Sequence[str] | None, display_width: int = 80) -> str:
"""Render a list of single-line strings as a compact set of columns.

This method correctly handles strings containing ANSI style sequences and
full-width characters (like those used in CJK languages). Each column is
only as wide as necessary and columns are separated by two spaces.

:param str_list: list of single-line strings to display
:param str_list: Sequence of single-line strings to display
:param display_width: max number of display columns to fit into
:return: a string containing the columnized output
"""
Expand Down Expand Up @@ -4162,14 +4166,14 @@ def render_columns(self, str_list: list[str] | None, display_width: int = 80) ->

return "\n".join(rows)

def columnize(self, str_list: list[str] | None, display_width: int = 80) -> None:
def columnize(self, str_list: Sequence[str] | None, display_width: int = 80) -> None:
"""Display a list of single-line strings as a compact set of columns.

Override of cmd's columnize() that uses the render_columns() method.
The method correctly handles strings with ANSI style sequences and
full-width characters (like those used in CJK languages).

:param str_list: list of single-line strings to display
:param str_list: Sequence of single-line strings to display
:param display_width: max number of display columns to fit into
"""
columnized_strs = self.render_columns(str_list, display_width)
Expand Down Expand Up @@ -4220,7 +4224,7 @@ def do_quit(self, _: argparse.Namespace) -> bool | None:
self.last_result = True
return True

def select(self, opts: str | list[str] | list[tuple[Any, str | None]], prompt: str = 'Your choice? ') -> Any:
def select(self, opts: str | Iterable[str] | Iterable[tuple[Any, str | None]], prompt: str = 'Your choice? ') -> Any:
"""Present a numbered menu to the user.

Modeled after the bash shell's SELECT. Returns the item chosen.
Expand All @@ -4233,7 +4237,7 @@ def select(self, opts: str | list[str] | list[tuple[Any, str | None]], prompt: s
that the return value can differ from
the text advertised to the user
"""
local_opts: list[str] | list[tuple[Any, str | None]]
local_opts: Iterable[str] | Iterable[tuple[Any, str | None]]
if isinstance(opts, str):
local_opts = cast(list[tuple[Any, str | None]], list(zip(opts.split(), opts.split(), strict=False)))
else:
Expand Down Expand Up @@ -4295,7 +4299,7 @@ def _build_base_set_parser(cls) -> Cmd2ArgumentParser:
return base_set_parser

def complete_set_value(
self, text: str, line: str, begidx: int, endidx: int, arg_tokens: dict[str, list[str]]
self, text: str, line: str, begidx: int, endidx: int, arg_tokens: Mapping[str, Sequence[str]]
) -> Completions:
"""Completes the value argument of set."""
param = arg_tokens['param'][0]
Expand Down
3 changes: 2 additions & 1 deletion cmd2/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Collection,
Iterable,
Iterator,
Mapping,
Sequence,
)
from dataclasses import (
Expand Down Expand Up @@ -270,7 +271,7 @@ def all_display_numeric(items: Collection[CompletionItem]) -> bool:
#############################################

# Represents the parsed tokens from argparse during completion
ArgTokens: TypeAlias = dict[str, list[str]]
ArgTokens: TypeAlias = Mapping[str, Sequence[str]]

# Unbound choices_provider function types used by argparse-based completion.
# These expect a Cmd or CommandSet instance as the first argument.
Expand Down
2 changes: 1 addition & 1 deletion cmd2/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ def as_subcommand_to(
| Callable[[CommandParentClass], argparse.ArgumentParser], # Cmd or CommandSet classmethod
*,
help: str | None = None, # noqa: A002
aliases: list[str] | None = None,
aliases: Sequence[str] | None = None,
) -> Callable[[ArgparseCommandFunc[CommandParent]], ArgparseCommandFunc[CommandParent]]:
"""Tag this method as a subcommand to an existing argparse decorated command.

Expand Down
7 changes: 4 additions & 3 deletions cmd2/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sys
from collections.abc import (
Iterable,
Mapping,
Sequence,
)
from dataclasses import (
Expand Down Expand Up @@ -284,8 +285,8 @@ def __init__(
self,
terminators: Iterable[str] | None = None,
multiline_commands: Iterable[str] | None = None,
aliases: dict[str, str] | None = None,
shortcuts: dict[str, str] | None = None,
aliases: Mapping[str, str] | None = None,
shortcuts: Mapping[str, str] | None = None,
) -> None:
"""Initialize an instance of StatementParser.

Expand All @@ -303,7 +304,7 @@ def __init__(
else:
self.terminators = tuple(terminators)
self.multiline_commands: tuple[str, ...] = tuple(multiline_commands) if multiline_commands is not None else ()
self.aliases: dict[str, str] = aliases if aliases is not None else {}
self.aliases: dict[str, str] = dict(aliases) if aliases is not None else {}

if shortcuts is None:
shortcuts = constants.DEFAULT_SHORTCUTS
Expand Down
11 changes: 6 additions & 5 deletions cmd2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from collections.abc import (
Callable,
Iterable,
MutableSequence,
)
from difflib import SequenceMatcher
from enum import Enum
Expand Down Expand Up @@ -247,7 +248,7 @@ def natural_sort(list_to_sort: Iterable[str]) -> list[str]:
return sorted(list_to_sort, key=natural_keys)


def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None:
def quote_specific_tokens(tokens: MutableSequence[str], tokens_to_quote: Iterable[str]) -> None:
"""Quote specific tokens in a list.

:param tokens: token list being edited
Expand All @@ -258,7 +259,7 @@ def quote_specific_tokens(tokens: list[str], tokens_to_quote: list[str]) -> None
tokens[i] = su.quote(token)


def unquote_specific_tokens(tokens: list[str], tokens_to_unquote: list[str]) -> None:
def unquote_specific_tokens(tokens: MutableSequence[str], tokens_to_unquote: Iterable[str]) -> None:
"""Unquote specific tokens in a list.

:param tokens: token list being edited
Expand Down Expand Up @@ -291,7 +292,7 @@ def expand_user(token: str) -> str:
return token


def expand_user_in_tokens(tokens: list[str]) -> None:
def expand_user_in_tokens(tokens: MutableSequence[str]) -> None:
"""Call expand_user() on all tokens in a list of strings.

:param tokens: tokens to expand.
Expand Down Expand Up @@ -344,12 +345,12 @@ def files_from_glob_pattern(pattern: str, access: int = os.F_OK) -> list[str]:
return [f for f in glob.glob(pattern) if os.path.isfile(f) and os.access(f, access)]


def files_from_glob_patterns(patterns: list[str], access: int = os.F_OK) -> list[str]:
def files_from_glob_patterns(patterns: Iterable[str], access: int = os.F_OK) -> list[str]:
"""Return a list of file paths based on a list of glob patterns.

Only files are returned, not directories, and optionally only files for which the user has a specified access to.

:param patterns: list of file names and/or glob patterns
:param patterns: Iterable of file names and/or glob patterns
:param access: file access type to verify (os.* where * is F_OK, R_OK, W_OK, or X_OK)
:return: list of files matching the names and/or glob patterns
"""
Expand Down
Loading
Loading