Skip to content

Commit f79c8dd

Browse files
authored
Restore macros (#1464)
* Restored macros. * Added Cmd.macro_arg_complete() to allow custom tab completion for macro arguments.
1 parent 353cf14 commit f79c8dd

22 files changed

+952
-68
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22

33
- Breaking Changes
44

5-
- Removed macros
65
- No longer setting parser's `prog` value in `with_argparser()` since it gets set in
76
`Cmd._build_parser()`. This code had previously been restored to support backward
87
compatibility in `cmd2` 2.0 family.
98

109
- Enhancements
10+
1111
- Simplified the process to set a custom parser for `cmd2's` built-in commands. See
1212
[custom_parser.py](https://github.com/python-cmd2/cmd2/blob/main/examples/custom_parser.py)
1313
example for more details.
1414

15+
- Added `Cmd.macro_arg_complete()` which tab completes arguments to a macro. Its default
16+
behavior is to perform path completion, but it can be overridden as needed.
17+
1518
## 2.7.0 (June 30, 2025)
1619

1720
- Enhancements

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ first pillar of 'ease of command discovery'. The following is a list of features
6969

7070
<a href="https://imgflip.com/i/66t0y0"><img src="https://i.imgflip.com/66t0y0.jpg" title="made at imgflip.com" width="70%" height="70%"/></a>
7171

72-
cmd2 creates the second pillar of 'ease of transition to automation' through alias creation, command
73-
line argument parsing and execution of cmd2 scripting.
72+
cmd2 creates the second pillar of 'ease of transition to automation' through alias/macro creation,
73+
command line argument parsing and execution of cmd2 scripting.
7474

75-
- Flexible alias creation for quick abstraction of commands.
75+
- Flexible alias and macro creation for quick abstraction of commands.
7676
- Text file scripting of your application with `run_script` (`@`) and `_relative_run_script` (`@@`)
7777
- Powerful and flexible built-in Python scripting of your application using the `run_pyscript`
7878
command

cmd2/cmd2.py

Lines changed: 423 additions & 13 deletions
Large diffs are not rendered by default.

cmd2/parsing.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,56 @@ def shlex_split(str_to_split: str) -> list[str]:
3535
return shlex.split(str_to_split, comments=False, posix=False)
3636

3737

38+
@dataclass(frozen=True)
39+
class MacroArg:
40+
"""Information used to replace or unescape arguments in a macro value when the macro is resolved.
41+
42+
Normal argument syntax: {5}
43+
Escaped argument syntax: {{5}}.
44+
"""
45+
46+
# The starting index of this argument in the macro value
47+
start_index: int
48+
49+
# The number string that appears between the braces
50+
# This is a string instead of an int because we support unicode digits and must be able
51+
# to reproduce this string later
52+
number_str: str
53+
54+
# Tells if this argument is escaped and therefore needs to be unescaped
55+
is_escaped: bool
56+
57+
# Pattern used to find normal argument
58+
# Digits surrounded by exactly 1 brace on a side and 1 or more braces on the opposite side
59+
# Match strings like: {5}, {{{{{4}, {2}}}}}
60+
macro_normal_arg_pattern = re.compile(r'(?<!{){\d+}|{\d+}(?!})')
61+
62+
# Pattern used to find escaped arguments
63+
# Digits surrounded by 2 or more braces on both sides
64+
# Match strings like: {{5}}, {{{{{4}}, {{2}}}}}
65+
macro_escaped_arg_pattern = re.compile(r'{{2}\d+}{2}')
66+
67+
# Finds a string of digits
68+
digit_pattern = re.compile(r'\d+')
69+
70+
71+
@dataclass(frozen=True)
72+
class Macro:
73+
"""Defines a cmd2 macro."""
74+
75+
# Name of the macro
76+
name: str
77+
78+
# The string the macro resolves to
79+
value: str
80+
81+
# The minimum number of args the user has to pass to this macro
82+
minimum_arg_count: int
83+
84+
# Used to fill in argument placeholders in the macro
85+
arg_list: list[MacroArg] = field(default_factory=list)
86+
87+
3888
@dataclass(frozen=True)
3989
class Statement(str): # type: ignore[override] # noqa: SLOT000
4090
"""String subclass with additional attributes to store the results of parsing.
@@ -155,10 +205,10 @@ def expanded_command_line(self) -> str:
155205
def argv(self) -> list[str]:
156206
"""A list of arguments a-la ``sys.argv``.
157207
158-
The first element of the list is the command after shortcut expansion.
159-
Subsequent elements of the list contain any additional arguments,
160-
with quotes removed, just like bash would. This is very useful if
161-
you are going to use ``argparse.parse_args()``.
208+
The first element of the list is the command after shortcut and macro
209+
expansion. Subsequent elements of the list contain any additional
210+
arguments, with quotes removed, just like bash would. This is very
211+
useful if you are going to use ``argparse.parse_args()``.
162212
163213
If you want to strip quotes from the input, you can use ``argv[1:]``.
164214
"""

docs/examples/first_app.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Here's a quick walkthrough of a simple application which demonstrates 8 features
77
- [Argument Processing](../features/argument_processing.md)
88
- [Generating Output](../features/generating_output.md)
99
- [Help](../features/help.md)
10-
- [Shortcuts](../features/shortcuts_aliases.md#shortcuts)
10+
- [Shortcuts](../features/shortcuts_aliases_macros.md#shortcuts)
1111
- [Multiline Commands](../features/multiline_commands.md)
1212
- [History](../features/history.md)
1313

@@ -166,9 +166,10 @@ With those few lines of code, we created a [command](../features/commands.md), u
166166
## Shortcuts
167167

168168
`cmd2` has several capabilities to simplify repetitive user input:
169-
[Shortcuts and Aliases](../features/shortcuts_aliases.md). Let's add a shortcut to our application.
170-
Shortcuts are character strings that can be used instead of a command name. For example, `cmd2` has
171-
support for a shortcut `!` which runs the `shell` command. So instead of typing this:
169+
[Shortcuts, Aliases, and Macros](../features/shortcuts_aliases_macros.md). Let's add a shortcut to
170+
our application. Shortcuts are character strings that can be used instead of a command name. For
171+
example, `cmd2` has support for a shortcut `!` which runs the `shell` command. So instead of typing
172+
this:
172173

173174
```shell
174175
(Cmd) shell ls -al

docs/features/builtin_commands.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ to be part of the application.
99
### alias
1010

1111
This command manages aliases via subcommands `create`, `delete`, and `list`. See
12-
[Aliases](shortcuts_aliases.md#aliases) for more information.
12+
[Aliases](shortcuts_aliases_macros.md#aliases) for more information.
1313

1414
### edit
1515

@@ -38,6 +38,12 @@ history. See [History](history.md) for more information.
3838
This optional opt-in command enters an interactive IPython shell. See
3939
[IPython (optional)](./embedded_python_shells.md#ipython-optional) for more information.
4040

41+
### macro
42+
43+
This command manages macros via subcommands `create`, `delete`, and `list`. A macro is similar to an
44+
alias, but it can contain argument placeholders. See [Macros](./shortcuts_aliases_macros.md#macros)
45+
for more information.
46+
4147
### py
4248

4349
This command invokes a Python command or shell. See
@@ -108,8 +114,8 @@ Execute a command as if at the operating system shell prompt:
108114

109115
### shortcuts
110116

111-
This command lists available shortcuts. See [Shortcuts](./shortcuts_aliases.md#shortcuts) for more
112-
information.
117+
This command lists available shortcuts. See [Shortcuts](./shortcuts_aliases_macros.md#shortcuts) for
118+
more information.
113119

114120
## Remove Builtin Commands
115121

docs/features/commands.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ backwards compatibility.
6161
- quoted arguments
6262
- output redirection and piping
6363
- multi-line commands
64-
- shortcut and alias expansion
64+
- shortcut, alias, and macro expansion
6565

6666
In addition to parsing all of these elements from the user input, `cmd2` also has code to make all
6767
of these items work; it's almost transparent to you and to the commands you write in your own

docs/features/help.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ command. The `help` command by itself displays a list of the commands available:
1414
1515
Documented commands (use 'help -v' for verbose/'help <topic>' for details):
1616
===========================================================================
17-
alias help ipy quit run_script shell
18-
edit history py run_pyscript set shortcuts
17+
alias help ipy py run_pyscript set shortcuts
18+
edit history macro quit run_script shell
1919
```
2020

2121
The `help` command can also be used to provide detailed help for a specific command:
@@ -53,8 +53,8 @@ By default, the `help` command displays:
5353

5454
Documented commands (use 'help -v' for verbose/'help <topic>' for details):
5555
===========================================================================
56-
alias help ipy quit run_script shell
57-
edit history py run_pyscript set shortcuts
56+
alias help ipy py run_pyscript set shortcuts
57+
edit history macro quit run_script shell
5858

5959
If you have a large number of commands, you can optionally group your commands into categories.
6060
Here's the output from the example `help_categories.py`:
@@ -80,8 +80,8 @@ Here's the output from the example `help_categories.py`:
8080

8181
Other
8282
=====
83-
alias edit history run_pyscript set shortcuts
84-
config help quit run_script shell version
83+
alias edit history py run_pyscript set shortcuts
84+
config help macro quit run_script shell version
8585

8686
There are 2 methods of specifying command categories, using the `@with_category` decorator or with
8787
the `categorize()` function. Once a single command category is detected, the help output switches to
@@ -143,9 +143,9 @@ categories with per-command Help Messages:
143143
findleakers Find Leakers command.
144144
list List command.
145145
redeploy Redeploy command.
146-
restart Restart
146+
restart Restart command.
147147
sessions Sessions command.
148-
start Start
148+
start Start command.
149149
stop Stop command.
150150
undeploy Undeploy command.
151151

@@ -164,9 +164,9 @@ categories with per-command Help Messages:
164164
resources Resources command.
165165
serverinfo Server Info command.
166166
sslconnectorciphers SSL Connector Ciphers command is an example of a command that contains
167-
multiple lines of help information for the user. Each line of help in a
168-
contiguous set of lines will be printed and aligned in the verbose output
169-
provided with 'help --verbose'.
167+
multiple lines of help information for the user. Each line of help in a
168+
contiguous set of lines will be printed and aligned in the verbose output
169+
provided with 'help --verbose'.
170170
status Status command.
171171
thread_dump Thread Dump command.
172172
vminfo VM Info command.
@@ -178,6 +178,7 @@ categories with per-command Help Messages:
178178
edit Run a text editor and optionally open a file with it.
179179
help List available commands or provide detailed help for a specific command.
180180
history View, run, edit, save, or clear previously entered commands.
181+
macro Manage macros.
181182
quit Exit this application.
182183
run_pyscript Run Python script within this application's environment.
183184
run_script Run text script.

docs/features/history.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,8 @@ without line numbers, so you can copy them to the clipboard:
198198

199199
(Cmd) history -s 1:3
200200

201-
`cmd2` supports aliases which allow you to substitute a short, more convenient input string with a
202-
longer replacement string. Say we create an alias like this, and then use it:
201+
`cmd2` supports both aliases and macros, which allow you to substitute a short, more convenient
202+
input string with a longer replacement string. Say we create an alias like this, and then use it:
203203

204204
(Cmd) alias create ls shell ls -aF
205205
Alias 'ls' created
@@ -212,7 +212,7 @@ By default, the `history` command shows exactly what we typed:
212212
1 alias create ls shell ls -aF
213213
2 ls -d h*
214214

215-
There are two ways to modify the display so you can see what aliases and shortcuts were expanded to.
215+
There are two ways to modify the display so you can see what aliases and macros were expanded to.
216216
The first is to use `-x` or `--expanded`. These options show the expanded command instead of the
217217
entered command:
218218

@@ -229,5 +229,5 @@ option:
229229
2x shell ls -aF -d h*
230230

231231
If the entered command had no expansion, it is displayed as usual. However, if there is some change
232-
as the result of expanding aliases, then the entered command is displayed with the number, and the
233-
expanded command is displayed with the number followed by an `x`.
232+
as the result of expanding macros and aliases, then the entered command is displayed with the
233+
number, and the expanded command is displayed with the number followed by an `x`.

docs/features/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
- [Output Redirection and Pipes](redirection.md)
2525
- [Scripting](scripting.md)
2626
- [Settings](settings.md)
27-
- [Shortcuts and Aliases](shortcuts_aliases.md)
27+
- [Shortcuts, Aliases, and Macros](shortcuts_aliases_macros.md)
2828
- [Startup Commands](startup_commands.md)
2929
- [Table Creation](table_creation.md)
3030
- [Transcripts](transcripts.md)

0 commit comments

Comments
 (0)