summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd2/cmd2.py84
-rw-r--r--cmd2/parsing.py37
2 files changed, 78 insertions, 43 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index e35d216e..1c020323 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -47,7 +47,7 @@ from . import utils
from . import plugin
from .argparse_completer import AutoCompleter, ACArgumentParser, ACTION_ARG_CHOICES
from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer
-from .parsing import StatementParser, Statement, Macro, normal_arg_pattern, escaped_arg_pattern, digit_pattern
+from .parsing import StatementParser, Statement, Macro, MacroArgInfo, macro_normal_arg_pattern, macro_escaped_arg_pattern, digit_pattern
# Set up readline
from .rl_utils import rl_type, RlType
@@ -1513,7 +1513,7 @@ class Cmd(cmd.Cmd):
else:
# Complete token against anything a user can run
self.completion_matches = self.basic_complete(text, line, begidx, endidx,
- self._get_all_runnable_names())
+ self._get_completable_runnables())
# Handle single result
if len(self.completion_matches) == 1:
@@ -1540,8 +1540,8 @@ class Cmd(cmd.Cmd):
except IndexError:
return None
- def _get_all_runnable_names(self) -> List[str]:
- """Return a list of all commands, aliases, and macros"""
+ def _get_completable_runnables(self) -> List[str]:
+ """Return a list of all commands, aliases, and macros that would show up in tab completion"""
visible_commands = set(self.get_visible_commands())
alias_names = set(self._get_alias_names())
macro_names = set(self._get_macro_names())
@@ -2247,17 +2247,20 @@ class Cmd(cmd.Cmd):
self.perror(errmsg, traceback_war=False)
return
- stripped_command = args.command.strip()
- if not stripped_command:
- errmsg = "An alias cannot resolve to an empty string"
+ if not args.command.strip():
+ errmsg = "Aliases cannot resolve to an empty command"
self.perror(errmsg, traceback_war=False)
return
- # Build the alias command string
- command = stripped_command + ' ' + ' '.join(utils.quote_string_if_needed(args.command_args))
+ # Build the alias value string
+ arg_str = ''
+ for cur_arg in args.command_args:
+ arg_str += ' ' + utils.quote_string_if_needed(cur_arg)
+
+ value = utils.quote_string_if_needed(args.command) + arg_str
# Set the alias
- self.aliases[args.name] = command
+ self.aliases[args.name] = value
self.poutput("Alias {!r} created".format(args.name))
def alias_delete(self, args: argparse.Namespace):
@@ -2315,9 +2318,9 @@ class Cmd(cmd.Cmd):
description=alias_create_description,
epilog=alias_create_epilog)
setattr(alias_create_parser.add_argument('name', type=str, help='Name of this alias'),
- ACTION_ARG_CHOICES, _get_all_runnable_names)
+ ACTION_ARG_CHOICES, _get_completable_runnables)
setattr(alias_create_parser.add_argument('command', type=str, help='what the alias resolves to'),
- ACTION_ARG_CHOICES, _get_all_runnable_names)
+ ACTION_ARG_CHOICES, _get_completable_runnables)
setattr(alias_create_parser.add_argument('command_args', type=str, nargs=argparse.REMAINDER,
help='arguments being passed to command'),
ACTION_ARG_CHOICES, ('path_complete',))
@@ -2378,18 +2381,21 @@ class Cmd(cmd.Cmd):
self.perror(errmsg, traceback_war=False)
return
- stripped_command = args.command.strip()
- if not stripped_command:
- errmsg = "An macro cannot resolve to an empty string"
+ if not args.command.strip():
+ errmsg = "Macros cannot resolve to an empty command"
self.perror(errmsg, traceback_war=False)
return
# Build the macro value string
- value = stripped_command + ' ' + ' '.join(utils.quote_string_if_needed(args.command_args))
+ arg_str = ''
+ for cur_arg in args.command_args:
+ arg_str += ' ' + utils.quote_string_if_needed(cur_arg)
+
+ value = utils.quote_string_if_needed(args.command) + arg_str
# Find all normal arguments
arg_info_list = []
- normal_matches = re.finditer(normal_arg_pattern, value)
+ normal_matches = re.finditer(macro_normal_arg_pattern, value)
max_arg_num = 0
num_set = set()
@@ -2403,9 +2409,9 @@ class Cmd(cmd.Cmd):
if cur_num > max_arg_num:
max_arg_num = cur_num
- arg_info_list.append(Macro.ArgInfo(start_index=cur_match.start(),
- number=cur_num,
- is_escaped=False))
+ arg_info_list.append(MacroArgInfo(start_index=cur_match.start(),
+ number=cur_num,
+ is_escaped=False))
except StopIteration:
break
@@ -2417,7 +2423,7 @@ class Cmd(cmd.Cmd):
return
# Find all escaped arguments
- escaped_matches = re.finditer(escaped_arg_pattern, value)
+ escaped_matches = re.finditer(macro_escaped_arg_pattern, value)
while True:
try:
@@ -2426,9 +2432,9 @@ class Cmd(cmd.Cmd):
# Get the number between the braces
cur_num = int(re.findall(digit_pattern, cur_match.group())[0])
- arg_info_list.append(Macro.ArgInfo(start_index=cur_match.start(),
- number=cur_num,
- is_escaped=True))
+ arg_info_list.append(MacroArgInfo(start_index=cur_match.start(),
+ number=cur_num,
+ is_escaped=True))
except StopIteration:
break
@@ -2437,6 +2443,24 @@ class Cmd(cmd.Cmd):
min_arg_count=max_arg_num, arg_info_list=arg_info_list)
self.poutput("Macro {!r} created".format(args.name))
+ def macro_delete(self, args: argparse.Namespace):
+ """ Deletes macros """
+ if args.all:
+ self.macros.clear()
+ self.poutput("All macros deleted")
+ elif not args.name:
+ self.do_help(['macro', 'delete'])
+ else:
+ # Get rid of duplicates
+ macros_to_delete = utils.remove_duplicates(args.name)
+
+ for cur_arg in macros_to_delete:
+ if cur_arg in self.macros:
+ del self.macros[cur_arg]
+ self.poutput("Macro {!r} cleared".format(cur_arg))
+ else:
+ self.perror("Macro {!r} does not exist".format(cur_arg), traceback_war=False)
+
def macro_list(self, args: argparse.Namespace):
""" Lists some or all macros """
if args.name:
@@ -2483,12 +2507,22 @@ class Cmd(cmd.Cmd):
setattr(macro_create_parser.add_argument('name', type=str, help='Name of this macro'),
ACTION_ARG_CHOICES, _get_macro_names)
setattr(macro_create_parser.add_argument('command', type=str, help='what the macro resolves to'),
- ACTION_ARG_CHOICES, _get_all_runnable_names)
+ ACTION_ARG_CHOICES, _get_completable_runnables)
setattr(macro_create_parser.add_argument('command_args', type=str, nargs=argparse.REMAINDER,
help='arguments being passed to command'),
ACTION_ARG_CHOICES, ('path_complete',))
macro_create_parser.set_defaults(func=macro_create)
+ # macro -> delete
+ macro_delete_help = "delete macros"
+ macro_delete_description = "Delete specified macros or all macros if --all is used"
+ macro_delete_parser = macro_subparsers.add_parser('delete', help=macro_delete_help,
+ description=macro_delete_description)
+ setattr(macro_delete_parser.add_argument('name', type=str, nargs='*', help='macro to delete'),
+ ACTION_ARG_CHOICES, _get_macro_names)
+ macro_delete_parser.add_argument('-a', '--all', action='store_true', help="all macros will be deleted")
+ macro_delete_parser.set_defaults(func=macro_delete)
+
# macro -> list
macro_list_help = "list macros"
macro_list_description = "List specified macros in a reusable form that can be saved to\n"
diff --git a/cmd2/parsing.py b/cmd2/parsing.py
index 4d17d07e..7a2af64b 100644
--- a/cmd2/parsing.py
+++ b/cmd2/parsing.py
@@ -14,35 +14,36 @@ from . import utils
# Pattern used to find normal argument
# Match strings like: {5}, {{{{{4}, {2}}}}}
-normal_arg_pattern = re.compile(r'(?<!\{)\{\d+\}|\{\d+\}(?!\})')
+macro_normal_arg_pattern = re.compile(r'(?<!\{)\{\d+\}|\{\d+\}(?!\})')
# Pattern used to find escaped arguments (2 or more braces on each side of digit)
# Match strings like: {{5}}, {{{{{4}}, {{2}}}}}, {{{4}}}
-escaped_arg_pattern = re.compile(r'\{{2}\d+\}{2}')
+macro_escaped_arg_pattern = re.compile(r'\{{2}\d+\}{2}')
# Finds a string of digits
digit_pattern = re.compile(r'\d+')
@attr.s(frozen=True)
-class Macro:
- """Defines a cmd2 macro"""
+class MacroArgInfo:
+ """
+ Information used to replace or unescape arguments in a macro value when the macro is resolved
+ Normal argument syntax : {5}
+ Escaped argument syntax: {{5}}
+ """
+ # The starting index of this argument in the macro value
+ start_index = attr.ib(validator=attr.validators.instance_of(int), type=int)
- @attr.s(frozen=True)
- class ArgInfo:
- """
- Information used to replace or unescape arguments in a macro value when the macro is resolved
- Normal argument syntax : {5}
- Escaped argument syntax: {{5}}
- """
- # The starting index of this argument in the macro value
- start_index = attr.ib(validator=attr.validators.instance_of(int), type=int)
+ # The number that appears between the braces
+ number = attr.ib(validator=attr.validators.instance_of(int), type=int)
+
+ # Tells if this argument is escaped and therefore needs to be unescaped
+ is_escaped = attr.ib(validator=attr.validators.instance_of(bool), type=bool)
- # The number that appears between the braces
- number = attr.ib(validator=attr.validators.instance_of(int), type=int)
- # Tells if this argument is escaped and therefore needs to be unescaped
- is_escaped = attr.ib(validator=attr.validators.instance_of(bool), type=bool)
+@attr.s(frozen=True)
+class Macro:
+ """Defines a cmd2 macro"""
# Name of the macro
name = attr.ib(validator=attr.validators.instance_of(str), type=str)
@@ -54,7 +55,7 @@ class Macro:
min_arg_count = attr.ib(validator=attr.validators.instance_of(int), type=int)
# Used to fill in argument placeholders in the macro
- arg_info_list = attr.ib(factory=list, validator=attr.validators.instance_of(list), type=List[ArgInfo])
+ arg_info_list = attr.ib(factory=list, validator=attr.validators.instance_of(list), type=List[MacroArgInfo])
@attr.s(frozen=True)