Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 0.9.6 (TBD)
* Enhancements
* All platforms now depend on [wcwidth](https://pypi.python.org/pypi/wcwidth) to assist with asynchronous alerts.
* Macros now accept extra arguments when called. These will be tacked onto the resolved command.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a good thing for macros to be able to do


## 0.9.5 (October 11, 2018)
* Bug Fixes
Expand Down
20 changes: 15 additions & 5 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2033,18 +2033,22 @@ def _run_macro(self, statement: Statement) -> bool:
:param statement: the parsed statement from the command line
:return: a flag indicating whether the interpretation of commands should stop
"""
from itertools import islice

if statement.command not in self.macros.keys():
raise KeyError('{} is not a macro'.format(statement.command))

macro = self.macros[statement.command]

# For macros, every argument must be provided and there can be no extra arguments.
if len(statement.arg_list) != macro.required_arg_count:
self.perror("The macro '{}' expects {} argument(s)".format(statement.command, macro.required_arg_count),
# Make sure enough arguments were passed in
if len(statement.arg_list) < macro.minimum_arg_count:
self.perror("The macro '{}' expects at least {} argument(s)".format(statement.command,
macro.minimum_arg_count),
traceback_war=False)
return False

# Resolve the arguments in reverse
# Resolve the arguments in reverse and read their values from statement.argv since those
# are unquoted. Macro args should have been quoted when the macro was created.
resolved = macro.value
reverse_arg_list = sorted(macro.arg_list, key=lambda ma: ma.start_index, reverse=True)

Expand All @@ -2059,6 +2063,10 @@ def _run_macro(self, statement: Statement) -> bool:
parts = resolved.rsplit(to_replace, maxsplit=1)
resolved = parts[0] + replacement + parts[1]

# Append extra arguments and use statement.arg_list since these arguments need their quotes preserved
for arg in islice(statement.arg_list, macro.minimum_arg_count, None):
resolved += ' ' + arg

# Run the resolved command
return self.onecmd_plus_hooks(resolved)

Expand Down Expand Up @@ -2407,7 +2415,7 @@ def macro_create(self, args: argparse.Namespace):

# Set the macro
result = "overwritten" if args.name in self.macros else "created"
self.macros[args.name] = Macro(name=args.name, value=value, required_arg_count=max_arg_num, arg_list=arg_list)
self.macros[args.name] = Macro(name=args.name, value=value, minimum_arg_count=max_arg_num, arg_list=arg_list)
self.poutput("Macro '{}' {}".format(args.name, result))

def macro_delete(self, args: argparse.Namespace):
Expand Down Expand Up @@ -2469,6 +2477,8 @@ def macro_list(self, args: argparse.Namespace):
"Notes:\n"
" To use the literal string {1} in your command, escape it this way: {{1}}.\n"
"\n"
" Extra arguments passed when calling a macro are tacked onto resolved command.\n"
"\n"
" An argument number can be repeated in a macro. In the following example the\n"
" first argument will populate both {1} instances.\n"
"\n"
Expand Down
4 changes: 2 additions & 2 deletions cmd2/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ class Macro:
# The string the macro resolves to
value = attr.ib(validator=attr.validators.instance_of(str))

# The required number of args the user has to pass to this macro
required_arg_count = attr.ib(validator=attr.validators.instance_of(int))
# The minimum number of args the user has to pass to this macro
minimum_arg_count = attr.ib(validator=attr.validators.instance_of(int))

# Used to fill in argument placeholders in the macro
arg_list = attr.ib(default=attr.Factory(list), validator=attr.validators.instance_of(list))
Expand Down
31 changes: 20 additions & 11 deletions tests/test_cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2014,6 +2014,25 @@ def test_macro_create_with_escaped_args(base_app, capsys):
out = run_cmd(base_app, 'fake')
assert 'No help on {1}' in out[0]

def test_macro_usage_with_missing_args(base_app, capsys):
# Create the macro
out = run_cmd(base_app, 'macro create fake help {1} {2}')
assert out == normalize("Macro 'fake' created")

# Run the macro
run_cmd(base_app, 'fake arg1')
out, err = capsys.readouterr()
assert "expects at least 2 argument(s)" in err

def test_macro_usage_with_exta_args(base_app, capsys):
# Create the macro
out = run_cmd(base_app, 'macro create fake help {1}')
assert out == normalize("Macro 'fake' created")

# Run the macro
out = run_cmd(base_app, 'fake alias create')
assert "Usage: alias create" in out[0]

def test_macro_create_with_missing_arg_nums(base_app, capsys):
# Create the macro
run_cmd(base_app, 'macro create fake help {1} {3}')
Expand All @@ -2026,16 +2045,6 @@ def test_macro_create_with_invalid_arg_num(base_app, capsys):
out, err = capsys.readouterr()
assert "Argument numbers must be greater than 0" in err

def test_macro_create_with_wrong_arg_count(base_app, capsys):
# Create the macro
out = run_cmd(base_app, 'macro create fake help {1} {2}')
assert out == normalize("Macro 'fake' created")

# Run the macro
run_cmd(base_app, 'fake arg1')
out, err = capsys.readouterr()
assert "expects 2 argument(s)" in err

def test_macro_create_with_unicode_numbered_arg(base_app, capsys):
# Create the macro expecting 1 argument
out = run_cmd(base_app, 'macro create fake help {\N{ARABIC-INDIC DIGIT ONE}}')
Expand All @@ -2044,7 +2053,7 @@ def test_macro_create_with_unicode_numbered_arg(base_app, capsys):
# Run the macro
out = run_cmd(base_app, 'fake')
out, err = capsys.readouterr()
assert "expects 1 argument(s)" in err
assert "expects at least 1 argument(s)" in err

def test_macro_create_with_missing_unicode_arg_nums(base_app, capsys):
run_cmd(base_app, 'macro create fake help {1} {\N{ARABIC-INDIC DIGIT THREE}}')
Expand Down