Skip to content

Commit cc66318

Browse files
committed
Renamed Macro.arg_list to Macro.args and changed it to a tuple.
1 parent 2d79945 commit cc66318

File tree

3 files changed

+36
-25
lines changed

3 files changed

+36
-25
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ shell, and the option for a persistent bottom bar that can display realtime stat
4747
- Removed `Statement.pipe_to` since it can be handled by `Statement.redirector` and
4848
`Statement.redirect_to`.
4949
- Changed `StatementParser.parse_command_only()` to return a `PartialStatement` object.
50+
- Renamed `Macro.arg_list` to `Macro.args`.
5051
- Enhancements
5152
- New `cmd2.Cmd` parameters
5253
- **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These

cmd2/cmd2.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2948,7 +2948,7 @@ def _resolve_macro(self, statement: Statement) -> str | None:
29482948
# Resolve the arguments in reverse and read their values from statement.argv since those
29492949
# are unquoted. Macro args should have been quoted when the macro was created.
29502950
resolved = macro.value
2951-
reverse_arg_list = sorted(macro.arg_list, key=lambda ma: ma.start_index, reverse=True)
2951+
reverse_arg_list = sorted(macro.args, key=lambda ma: ma.start_index, reverse=True)
29522952

29532953
for macro_arg in reverse_arg_list:
29542954
if macro_arg.is_escaped:
@@ -3740,7 +3740,7 @@ def _macro_create(self, args: argparse.Namespace) -> None:
37403740
value += ' ' + ' '.join(args.command_args)
37413741

37423742
# Find all normal arguments
3743-
arg_list = []
3743+
macro_args = []
37443744
normal_matches = re.finditer(MacroArg.macro_normal_arg_pattern, value)
37453745
max_arg_num = 0
37463746
arg_nums = set()
@@ -3759,7 +3759,7 @@ def _macro_create(self, args: argparse.Namespace) -> None:
37593759
arg_nums.add(cur_num)
37603760
max_arg_num = max(max_arg_num, cur_num)
37613761

3762-
arg_list.append(MacroArg(start_index=cur_match.start(), number_str=cur_num_str, is_escaped=False))
3762+
macro_args.append(MacroArg(start_index=cur_match.start(), number_str=cur_num_str, is_escaped=False))
37633763
except StopIteration:
37643764
pass
37653765

@@ -3778,15 +3778,15 @@ def _macro_create(self, args: argparse.Namespace) -> None:
37783778
# Get the number string between the braces
37793779
cur_num_str = re.findall(MacroArg.digit_pattern, cur_match.group())[0]
37803780

3781-
arg_list.append(MacroArg(start_index=cur_match.start(), number_str=cur_num_str, is_escaped=True))
3781+
macro_args.append(MacroArg(start_index=cur_match.start(), number_str=cur_num_str, is_escaped=True))
37823782
except StopIteration:
37833783
pass
37843784

37853785
# Set the macro
37863786
result = "overwritten" if args.name in self.macros else "created"
37873787
self.poutput(f"Macro '{args.name}' {result}")
37883788

3789-
self.macros[args.name] = Macro(name=args.name, value=value, minimum_arg_count=max_arg_num, arg_list=arg_list)
3789+
self.macros[args.name] = Macro(name=args.name, value=value, minimum_arg_count=max_arg_num, args=macro_args)
37903790
self.last_result = True
37913791

37923792
# macro -> delete

cmd2/parsing.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33
import re
44
import shlex
55
import sys
6-
from collections.abc import Iterable
6+
from collections.abc import (
7+
Iterable,
8+
Sequence,
9+
)
710
from dataclasses import (
811
asdict,
912
dataclass,
1013
field,
1114
)
12-
from typing import Any
15+
from typing import (
16+
Any,
17+
ClassVar,
18+
)
1319

1420
if sys.version_info >= (3, 11):
1521
from typing import Self
@@ -37,13 +43,9 @@ def shlex_split(str_to_split: str) -> list[str]:
3743
return shlex.split(str_to_split, comments=False, posix=False)
3844

3945

40-
@dataclass(frozen=True)
46+
@dataclass(frozen=True, slots=True)
4147
class MacroArg:
42-
"""Information used to replace or unescape arguments in a macro value when the macro is resolved.
43-
44-
Normal argument syntax: {5}
45-
Escaped argument syntax: {{5}}.
46-
"""
48+
"""Information used to resolve or unescape macro arguments."""
4749

4850
# The starting index of this argument in the macro value
4951
start_index: int
@@ -56,21 +58,22 @@ class MacroArg:
5658
# Tells if this argument is escaped and therefore needs to be unescaped
5759
is_escaped: bool
5860

59-
# Pattern used to find normal argument
60-
# Digits surrounded by exactly 1 brace on a side and 1 or more braces on the opposite side
61-
# Match strings like: {5}, {{{{{4}, {2}}}}}
62-
macro_normal_arg_pattern = re.compile(r'(?<!{){\d+}|{\d+}(?!})')
61+
# Matches normal args like {5}
62+
# Uses lookarounds to ensure exactly one brace.
63+
# (?<!{){ -> Match '{' not preceded by '{'
64+
# \d+ -> Match digits
65+
# }(?!}) -> Match '}' not followed by '}'
66+
macro_normal_arg_pattern: ClassVar[re.Pattern[str]] = re.compile(r'(?<!{){\d+}|{\d+}(?!})')
6367

64-
# Pattern used to find escaped arguments
65-
# Digits surrounded by 2 or more braces on both sides
66-
# Match strings like: {{5}}, {{{{{4}}, {{2}}}}}
67-
macro_escaped_arg_pattern = re.compile(r'{{2}\d+}{2}')
68+
# Matches escaped args like {{5}}
69+
# Specifically looking for exactly two braces on each side.
70+
macro_escaped_arg_pattern: ClassVar[re.Pattern[str]] = re.compile(r'{{2}\d+}{2}')
6871

6972
# Finds a string of digits
70-
digit_pattern = re.compile(r'\d+')
73+
digit_pattern: ClassVar[re.Pattern[str]] = re.compile(r'\d+')
7174

7275

73-
@dataclass(frozen=True)
76+
@dataclass(frozen=True, slots=True)
7477
class Macro:
7578
"""Defines a cmd2 macro."""
7679

@@ -83,8 +86,15 @@ class Macro:
8386
# The minimum number of args the user has to pass to this macro
8487
minimum_arg_count: int
8588

86-
# Used to fill in argument placeholders in the macro
87-
arg_list: list[MacroArg] = field(default_factory=list)
89+
# Metadata for argument placeholders and escaped sequences found in 'value'.
90+
# This is stored internally as a tuple.
91+
args: Sequence[MacroArg] = field(default_factory=tuple)
92+
93+
def __post_init__(self) -> None:
94+
"""Finalize the object after initialization."""
95+
# Convert args to an immutable tuple.
96+
if not isinstance(self.args, tuple):
97+
object.__setattr__(self, 'args', tuple(self.args))
8898

8999

90100
@dataclass(frozen=True)

0 commit comments

Comments
 (0)