Skip to content

Commit 49216ef

Browse files
committed
Fixed issue where constants.REDIRECTION_TOKENS was being mutated.
1 parent 3559159 commit 49216ef

4 files changed

Lines changed: 19 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

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

67
## 3.5.0 (April 13, 2026)
78

cmd2/cmd2.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,10 +1666,8 @@ def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> tuple[li
16661666
**On Failure**
16671667
- Two empty lists
16681668
"""
1669-
import copy
1670-
16711669
unclosed_quote = ''
1672-
quotes_to_try = copy.copy(constants.QUOTES)
1670+
quotes_to_try = [*constants.QUOTES]
16731671

16741672
tmp_line = line[:endidx]
16751673
tmp_endidx = endidx
@@ -3721,8 +3719,7 @@ def _alias_create(self, args: argparse.Namespace) -> None:
37213719
return
37223720

37233721
# Unquote redirection and terminator tokens
3724-
tokens_to_unquote = constants.REDIRECTION_TOKENS
3725-
tokens_to_unquote.extend(self.statement_parser.terminators)
3722+
tokens_to_unquote = [*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators]
37263723
utils.unquote_specific_tokens(args.command_args, tokens_to_unquote)
37273724

37283725
# Build the alias value string
@@ -3801,8 +3798,7 @@ def _alias_list(self, args: argparse.Namespace) -> None:
38013798
"""List some or all aliases as 'alias create' commands."""
38023799
self.last_result = {} # dict[alias_name, alias_value]
38033800

3804-
tokens_to_quote = constants.REDIRECTION_TOKENS
3805-
tokens_to_quote.extend(self.statement_parser.terminators)
3801+
tokens_to_quote = [*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators]
38063802

38073803
to_list = utils.remove_duplicates(args.names) if args.names else sorted(self.aliases, key=self.default_sort_key)
38083804

@@ -3964,8 +3960,7 @@ def _macro_create(self, args: argparse.Namespace) -> None:
39643960
return
39653961

39663962
# Unquote redirection and terminator tokens
3967-
tokens_to_unquote = constants.REDIRECTION_TOKENS
3968-
tokens_to_unquote.extend(self.statement_parser.terminators)
3963+
tokens_to_unquote = [*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators]
39693964
utils.unquote_specific_tokens(args.command_args, tokens_to_unquote)
39703965

39713966
# Build the macro value string
@@ -4087,8 +4082,7 @@ def _macro_list(self, args: argparse.Namespace) -> None:
40874082
"""List macros."""
40884083
self.last_result = {} # dict[macro_name, macro_value]
40894084

4090-
tokens_to_quote = constants.REDIRECTION_TOKENS
4091-
tokens_to_quote.extend(self.statement_parser.terminators)
4085+
tokens_to_quote = [*constants.REDIRECTION_TOKENS, *self.statement_parser.terminators]
40924086

40934087
to_list = utils.remove_duplicates(args.names) if args.names else sorted(self.macros, key=self.default_sort_key)
40944088

cmd2/constants.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77

88
# Used for command parsing, output redirection, tab completion and word
99
# breaks. Do not change.
10-
QUOTES = ['"', "'"]
10+
QUOTES = ('"', "'")
1111
REDIRECTION_PIPE = '|'
1212
REDIRECTION_OUTPUT = '>'
1313
REDIRECTION_APPEND = '>>'
14-
REDIRECTION_CHARS = [REDIRECTION_PIPE, REDIRECTION_OUTPUT]
15-
REDIRECTION_TOKENS = [REDIRECTION_PIPE, REDIRECTION_OUTPUT, REDIRECTION_APPEND]
14+
REDIRECTION_CHARS = (REDIRECTION_PIPE, REDIRECTION_OUTPUT)
15+
REDIRECTION_TOKENS = (REDIRECTION_PIPE, REDIRECTION_OUTPUT, REDIRECTION_APPEND)
1616
COMMENT_CHAR = '#'
1717
MULTILINE_TERMINATOR = ';'
1818

cmd2/parsing.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -298,10 +298,12 @@ def __init__(
298298
# the string (\Z matches the end of the string even if it
299299
# contains multiple lines)
300300
#
301-
invalid_command_chars = []
302-
invalid_command_chars.extend(constants.QUOTES)
303-
invalid_command_chars.extend(constants.REDIRECTION_CHARS)
304-
invalid_command_chars.extend(self.terminators)
301+
invalid_command_chars = (
302+
*constants.QUOTES,
303+
*constants.REDIRECTION_CHARS,
304+
*self.terminators,
305+
)
306+
305307
# escape each item so it will for sure get treated as a literal
306308
second_group_items = [re.escape(x) for x in invalid_command_chars]
307309
# add the whitespace and end of string, not escaped because they
@@ -352,9 +354,8 @@ def is_valid_command(self, word: str, *, is_subcommand: bool = False) -> tuple[b
352354
return False, errmsg
353355

354356
errmsg = 'cannot contain: whitespace, quotes, '
355-
errchars = []
356-
errchars.extend(constants.REDIRECTION_CHARS)
357-
errchars.extend(self.terminators)
357+
358+
errchars = (*constants.REDIRECTION_CHARS, *self.terminators)
358359
errmsg += ', '.join([shlex.quote(x) for x in errchars])
359360

360361
match = self._command_pattern.search(word)
@@ -677,9 +678,8 @@ def split_on_punctuation(self, tokens: list[str]) -> list[str]:
677678
:param tokens: the tokens as parsed by shlex
678679
:return: a new list of tokens, further split using punctuation
679680
"""
680-
punctuation: list[str] = []
681-
punctuation.extend(self.terminators)
682-
punctuation.extend(constants.REDIRECTION_CHARS)
681+
# Using a set for faster lookups
682+
punctuation = {*self.terminators, *constants.REDIRECTION_CHARS}
683683

684684
punctuated_tokens = []
685685

0 commit comments

Comments
 (0)