33import re
44import shlex
55import sys
6- from collections .abc import Iterable
6+ from collections .abc import (
7+ Iterable ,
8+ Sequence ,
9+ )
710from dataclasses import (
811 asdict ,
912 dataclass ,
1013 field ,
1114)
12- from typing import Any
15+ from typing import (
16+ Any ,
17+ ClassVar ,
18+ )
1319
1420if 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 )
4147class 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 )
7477class 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