Skip to content

Commit 2a20619

Browse files
authored
More refactoring (#1580)
* Removed use of OrderedDict. * Renamed Macro.arg_list to Macro.args and changed it to a tuple.
1 parent 910f2eb commit 2a20619

File tree

4 files changed

+44
-37
lines changed

4 files changed

+44
-37
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: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,7 @@
4141
import tempfile
4242
import threading
4343
from code import InteractiveConsole
44-
from collections import (
45-
OrderedDict,
46-
namedtuple,
47-
)
44+
from collections import namedtuple
4845
from collections.abc import (
4946
Callable,
5047
Iterable,
@@ -2951,7 +2948,7 @@ def _resolve_macro(self, statement: Statement) -> str | None:
29512948
# Resolve the arguments in reverse and read their values from statement.argv since those
29522949
# are unquoted. Macro args should have been quoted when the macro was created.
29532950
resolved = macro.value
2954-
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)
29552952

29562953
for macro_arg in reverse_arg_list:
29572954
if macro_arg.is_escaped:
@@ -3743,7 +3740,7 @@ def _macro_create(self, args: argparse.Namespace) -> None:
37433740
value += ' ' + ' '.join(args.command_args)
37443741

37453742
# Find all normal arguments
3746-
arg_list = []
3743+
macro_args = []
37473744
normal_matches = re.finditer(MacroArg.macro_normal_arg_pattern, value)
37483745
max_arg_num = 0
37493746
arg_nums = set()
@@ -3762,7 +3759,7 @@ def _macro_create(self, args: argparse.Namespace) -> None:
37623759
arg_nums.add(cur_num)
37633760
max_arg_num = max(max_arg_num, cur_num)
37643761

3765-
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))
37663763
except StopIteration:
37673764
pass
37683765

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

3784-
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))
37853782
except StopIteration:
37863783
pass
37873784

37883785
# Set the macro
37893786
result = "overwritten" if args.name in self.macros else "created"
37903787
self.poutput(f"Macro '{args.name}' {result}")
37913788

3792-
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)
37933790
self.last_result = True
37943791

37953792
# macro -> delete
@@ -4961,15 +4958,15 @@ def do_history(self, args: argparse.Namespace) -> bool | None:
49614958
self.last_result = history
49624959
return None
49634960

4964-
def _get_history(self, args: argparse.Namespace) -> 'OrderedDict[int, HistoryItem]':
4961+
def _get_history(self, args: argparse.Namespace) -> dict[int, HistoryItem]:
49654962
"""If an argument was supplied, then retrieve partial contents of the history; otherwise retrieve entire history.
49664963
49674964
This function returns a dictionary with history items keyed by their 1-based index in ascending order.
49684965
"""
49694966
if args.arg:
49704967
try:
49714968
int_arg = int(args.arg)
4972-
return OrderedDict({int_arg: self.history.get(int_arg)})
4969+
return {int_arg: self.history.get(int_arg)}
49734970
except ValueError:
49744971
pass
49754972

cmd2/history.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import json
44
import re
5-
from collections import OrderedDict
65
from collections.abc import (
76
Callable,
87
Iterable,
@@ -224,7 +223,7 @@ def get(self, index: int) -> HistoryItem:
224223
#
225224
spanpattern = re.compile(r'^\s*(?P<start>-?[1-9]\d*)?(?P<separator>:|(\.{2,}))(?P<end>-?[1-9]\d*)?\s*$')
226225

227-
def span(self, span: str, include_persisted: bool = False) -> 'OrderedDict[int, HistoryItem]':
226+
def span(self, span: str, include_persisted: bool = False) -> dict[int, 'HistoryItem']:
228227
"""Return a slice of the History list.
229228
230229
:param span: string containing an index or a slice
@@ -273,7 +272,7 @@ def span(self, span: str, include_persisted: bool = False) -> 'OrderedDict[int,
273272

274273
return self._build_result_dictionary(start, end)
275274

276-
def str_search(self, search: str, include_persisted: bool = False) -> 'OrderedDict[int, HistoryItem]':
275+
def str_search(self, search: str, include_persisted: bool = False) -> dict[int, 'HistoryItem']:
277276
"""Find history items which contain a given string.
278277
279278
:param search: the string to search for
@@ -292,7 +291,7 @@ def isin(history_item: HistoryItem) -> bool:
292291
start = 0 if include_persisted else self.session_start_index
293292
return self._build_result_dictionary(start, len(self), isin)
294293

295-
def regex_search(self, regex: str, include_persisted: bool = False) -> 'OrderedDict[int, HistoryItem]':
294+
def regex_search(self, regex: str, include_persisted: bool = False) -> dict[int, 'HistoryItem']:
296295
"""Find history items which match a given regular expression.
297296
298297
:param regex: the regular expression to search for.
@@ -328,13 +327,13 @@ def truncate(self, max_length: int) -> None:
328327

329328
def _build_result_dictionary(
330329
self, start: int, end: int, filter_func: Callable[[HistoryItem], bool] | None = None
331-
) -> 'OrderedDict[int, HistoryItem]':
330+
) -> dict[int, 'HistoryItem']:
332331
"""Build history search results.
333332
334333
:param start: start index to search from
335334
:param end: end index to stop searching (exclusive).
336335
"""
337-
results: OrderedDict[int, HistoryItem] = OrderedDict()
336+
results: dict[int, HistoryItem] = {}
338337
for index in range(start, end):
339338
if filter_func is None or filter_func(self[index]):
340339
results[index + 1] = self[index]

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)