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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ prompt is displayed.
the property.
- Added `traceback_kwargs` attribute to allow customization of Rich-based tracebacks.

## 3.5.1 (April 24, 2026)

- Bug Fixes
- Fixed `ArgparseCompleter.print_help()` not passing file stream to recursive call.
- Fixed issue where `constants.REDIRECTION_TOKENS` was being mutated.

## 3.5.0 (April 13, 2026)

- Bug Fixes
Expand Down
2 changes: 1 addition & 1 deletion cmd2/argparse_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ def print_help(self, tokens: Sequence[str], file: IO[str] | None = None) -> None
if parser is not None:
completer_type = self._cmd2_app._determine_ap_completer_type(parser)
completer = completer_type(parser, self._cmd2_app)
completer.print_help(tokens[1:])
completer.print_help(tokens[1:], file=file)
return
self._parser.print_help(file=file)

Expand Down
20 changes: 6 additions & 14 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1976,10 +1976,8 @@ def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> tuple[li
**On Failure**
- Two empty lists
"""
import copy

unclosed_quote = ""
quotes_to_try = copy.copy(constants.QUOTES)
quotes_to_try = [*constants.QUOTES]

tmp_line = line[:endidx]
tmp_endidx = endidx
Expand Down Expand Up @@ -3818,8 +3816,7 @@ def _alias_create(self, args: argparse.Namespace) -> None:
return

# Unquote redirection and terminator tokens
tokens_to_unquote = constants.REDIRECTION_TOKENS
tokens_to_unquote.extend(self.statement_parser.terminators)
tokens_to_unquote = (*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators)
utils.unquote_specific_tokens(args.command_args, tokens_to_unquote)

# Build the alias value string
Expand Down Expand Up @@ -3898,8 +3895,7 @@ def _alias_list(self, args: argparse.Namespace) -> None:
"""List some or all aliases as 'alias create' commands."""
self.last_result = {} # dict[alias_name, alias_value]

tokens_to_quote = constants.REDIRECTION_TOKENS
tokens_to_quote.extend(self.statement_parser.terminators)
tokens_to_quote = (*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators)

to_list = (
utils.remove_duplicates(args.names)
Expand Down Expand Up @@ -4065,8 +4061,7 @@ def _macro_create(self, args: argparse.Namespace) -> None:
return

# Unquote redirection and terminator tokens
tokens_to_unquote = constants.REDIRECTION_TOKENS
tokens_to_unquote.extend(self.statement_parser.terminators)
tokens_to_unquote = (*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators)
utils.unquote_specific_tokens(args.command_args, tokens_to_unquote)

# Build the macro value string
Expand Down Expand Up @@ -4188,8 +4183,7 @@ def _macro_list(self, args: argparse.Namespace) -> None:
"""List macros."""
self.last_result = {} # dict[macro_name, macro_value]

tokens_to_quote = constants.REDIRECTION_TOKENS
tokens_to_quote.extend(self.statement_parser.terminators)
tokens_to_quote = (*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators)

to_list = (
utils.remove_duplicates(args.names)
Expand Down Expand Up @@ -4917,9 +4911,7 @@ def py_quit() -> None:
"""Exit an interactive Python environment, callable from the interactive Python console."""
raise EmbeddedConsoleExit

from .py_bridge import (
PyBridge,
)
from .py_bridge import PyBridge

add_to_history = self.scripts_add_to_history if pyscript else True
py_bridge = PyBridge(self, add_to_history=add_to_history)
Expand Down
6 changes: 3 additions & 3 deletions cmd2/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
INFINITY = float("inf")

# Used for command parsing, output redirection, completion, and word breaks. Do not change.
QUOTES = ['"', "'"]
QUOTES = ('"', "'")
REDIRECTION_PIPE = "|"
REDIRECTION_OVERWRITE = ">"
REDIRECTION_APPEND = ">>"
REDIRECTION_CHARS = [REDIRECTION_PIPE, REDIRECTION_OVERWRITE]
REDIRECTION_TOKENS = [REDIRECTION_PIPE, REDIRECTION_OVERWRITE, REDIRECTION_APPEND]
REDIRECTION_CHARS = (REDIRECTION_PIPE, REDIRECTION_OVERWRITE)
REDIRECTION_TOKENS = (REDIRECTION_PIPE, REDIRECTION_OVERWRITE, REDIRECTION_APPEND)
COMMENT_CHAR = "#"
MULTILINE_TERMINATOR = ";"

Expand Down
20 changes: 10 additions & 10 deletions cmd2/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,10 +330,12 @@ def __init__(
# the string (\Z matches the end of the string even if it
# contains multiple lines)
#
invalid_command_chars = []
invalid_command_chars.extend(constants.QUOTES)
invalid_command_chars.extend(constants.REDIRECTION_CHARS)
invalid_command_chars.extend(self.terminators)
invalid_command_chars = (
*constants.QUOTES,
*constants.REDIRECTION_CHARS,
*self.terminators,
)

# escape each item so it will for sure get treated as a literal
second_group_items = [re.escape(x) for x in invalid_command_chars]
# add the whitespace and end of string, not escaped because they
Expand Down Expand Up @@ -384,9 +386,8 @@ def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> tuple[b
return False, errmsg

errmsg = "cannot contain: whitespace, quotes, "
errchars = []
errchars.extend(constants.REDIRECTION_CHARS)
errchars.extend(self.terminators)

errchars = (*constants.REDIRECTION_CHARS, *self.terminators)
errmsg += ", ".join([shlex.quote(x) for x in errchars])

match = self._command_pattern.search(word)
Expand Down Expand Up @@ -704,9 +705,8 @@ def split_on_punctuation(self, tokens: list[str]) -> list[str]:
:param tokens: the tokens as parsed by shlex
:return: a new list of tokens, further split using punctuation
"""
punctuation: list[str] = []
punctuation.extend(self.terminators)
punctuation.extend(constants.REDIRECTION_CHARS)
# Using a set for faster lookups
punctuation = {*self.terminators, *constants.REDIRECTION_CHARS}

punctuated_tokens = []

Expand Down
4 changes: 1 addition & 3 deletions cmd2/string_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
full-width characters (like those used in CJK languages).
"""

from collections.abc import (
Sequence,
)
from collections.abc import Sequence

from rich.align import AlignMethod
from rich.style import StyleType
Expand Down
Loading