diff --git a/CHANGELOG.md b/CHANGELOG.md index 417d9b427..291397a47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.0 (TBD) +* Breaking Change + * Removed macros + ## 2.5.0 (October 23, 2024) * Breaking Change * `cmd2` 2.5 supports Python 3.8+ (removed support for Python 3.6 and 3.7) diff --git a/README.md b/README.md index ac65e91c0..5ccd594a4 100755 --- a/README.md +++ b/README.md @@ -62,9 +62,9 @@ Deep extensive tab completion and help text generation based on the argparse lib -cmd2 creates the second pillar of 'ease of transition to automation' through alias/macro creation, command line argument parsing and execution of cmd2 scripting. +cmd2 creates the second pillar of 'ease of transition to automation' through alias creation, command line argument parsing and execution of cmd2 scripting. -- Flexible alias and macro creation for quick abstraction of commands. +- Flexible alias creation for quick abstraction of commands. - Text file scripting of your application with `run_script` (`@`) and `_relative_run_script` (`@@`) - Powerful and flexible built-in Python scripting of your application using the `run_pyscript` command - Transcripts for use with built-in regression can be automatically generated from `history -t` or `run_script -t` diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index dbd993495..b53ae7a20 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -37,7 +37,6 @@ import inspect import os import pydoc -import re import sys import tempfile import threading @@ -122,8 +121,6 @@ single_line_format, ) from .parsing import ( - Macro, - MacroArg, Statement, StatementParser, shlex_split, @@ -363,9 +360,6 @@ def __init__( # Commands to exclude from the history command self.exclude_from_history = ['eof', 'history'] - # Dictionary of macro names and their values - self.macros: Dict[str, Macro] = dict() - # Keeps track of typed command history in the Python shell self._py_history: List[str] = [] @@ -411,7 +405,7 @@ def __init__( self.help_error = "No help on {}" # The error that prints when a non-existent command is run - self.default_error = "{} is not a recognized command, alias, or macro." + self.default_error = "{} is not a recognized command or alias." # If non-empty, this string will be displayed if a broken pipe error occurs self.broken_pipe_warning = '' @@ -482,7 +476,7 @@ def __init__( # If natural sorting is preferred, then set this to NATURAL_SORT_KEY. # cmd2 uses this key for sorting: # command and category names - # alias, macro, settable, and shortcut names + # alias, settable, and shortcut names # tab completion results when self.matches_sorted is False self.default_sort_key = Cmd.ALPHABETICAL_SORT_KEY @@ -749,11 +743,6 @@ def _install_command_function(self, command: str, command_wrapper: Callable[..., self.pwarning(f"Deleting alias '{command}' because it shares its name with a new command") del self.aliases[command] - # Check if command shares a name with a macro - if command in self.macros: - self.pwarning(f"Deleting macro '{command}' because it shares its name with a new command") - del self.macros[command] - self._register_command_parser(command, command_wrapper) setattr(self, cmd_func_name, command_wrapper) @@ -2077,12 +2066,8 @@ def _perform_completion( # Determine the completer function to use for the command's argument if custom_settings is None: - # Check if a macro was entered - if command in self.macros: - completer_func = self.path_complete - # Check if a command was entered - elif command in self.get_all_commands(): + if command in self.get_all_commands(): # Get the completer function for this command func_attr = getattr(self, constants.COMPLETER_FUNC_PREFIX + command, None) @@ -2108,7 +2093,7 @@ def _perform_completion( else: completer_func = self.completedefault # type: ignore[assignment] - # Not a recognized macro or command + # Not a recognized command else: # Check if this command should be run as a shell command if self.default_to_shell and command in utils.get_exes_in_path(command): @@ -2271,8 +2256,8 @@ def complete( # type: ignore[override] parser.add_argument( 'command', metavar="COMMAND", - help="command, alias, or macro name", - choices=self._get_commands_aliases_and_macros_for_completion(), + help="command or alias name", + choices=self._get_commands_and_aliases_for_completion(), ) custom_settings = utils.CustomCompletionSettings(parser) @@ -2360,19 +2345,6 @@ def _get_alias_completion_items(self) -> List[CompletionItem]: return results - # Table displayed when tab completing macros - _macro_completion_table = SimpleTable([Column('Value', width=80)], divider_char=None) - - def _get_macro_completion_items(self) -> List[CompletionItem]: - """Return list of macro names and values as CompletionItems""" - results: List[CompletionItem] = [] - - for cur_key in self.macros: - row_data = [self.macros[cur_key].value] - results.append(CompletionItem(cur_key, self._macro_completion_table.generate_data_row(row_data))) - - return results - # Table displayed when tab completing Settables _settable_completion_table = SimpleTable([Column('Value', width=30), Column('Description', width=60)], divider_char=None) @@ -2386,12 +2358,11 @@ def _get_settable_completion_items(self) -> List[CompletionItem]: return results - def _get_commands_aliases_and_macros_for_completion(self) -> List[str]: - """Return a list of visible commands, aliases, and macros for tab completion""" + def _get_commands_and_aliases_for_completion(self) -> List[str]: + """Return a list of visible commands and aliases for tab completion""" visible_commands = set(self.get_visible_commands()) alias_names = set(self.aliases) - macro_names = set(self.macros) - return list(visible_commands | alias_names | macro_names) + return list(visible_commands | alias_names) def get_help_topics(self) -> List[str]: """Return a list of help topics""" @@ -2540,7 +2511,7 @@ def onecmd_plus_hooks( try: # Convert the line into a Statement - statement = self._input_line_to_statement(line, orig_rl_history_length=orig_rl_history_length) + statement = self._complete_statement(line, orig_rl_history_length=orig_rl_history_length) # call the postparsing hooks postparsing_data = plugin.PostparsingData(False, statement) @@ -2785,101 +2756,6 @@ def combine_rl_history(statement: Statement) -> None: return statement - def _input_line_to_statement(self, line: str, *, orig_rl_history_length: Optional[int] = None) -> Statement: - """ - Parse the user's input line and convert it to a Statement, ensuring that all macros are also resolved - - :param line: the line being parsed - :param orig_rl_history_length: Optional length of the readline history before the current command was typed. - This is used to assist in combining multiline readline history entries and is only - populated by cmd2. Defaults to None. - :return: parsed command line as a Statement - :raises: Cmd2ShlexError if a shlex error occurs (e.g. No closing quotation) - :raises: EmptyStatement when the resulting Statement is blank - """ - used_macros = [] - orig_line = None - - # Continue until all macros are resolved - while True: - # Make sure all input has been read and convert it to a Statement - statement = self._complete_statement(line, orig_rl_history_length=orig_rl_history_length) - - # If this is the first loop iteration, save the original line and stop - # combining multiline history entries in the remaining iterations. - if orig_line is None: - orig_line = statement.raw - orig_rl_history_length = None - - # Check if this command matches a macro and wasn't already processed to avoid an infinite loop - if statement.command in self.macros.keys() and statement.command not in used_macros: - used_macros.append(statement.command) - resolve_result = self._resolve_macro(statement) - if resolve_result is None: - raise EmptyStatement - line = resolve_result - else: - break - - # This will be true when a macro was used - if orig_line != statement.raw: - # Build a Statement that contains the resolved macro line - # but the originally typed line for its raw member. - statement = Statement( - statement.args, - raw=orig_line, - command=statement.command, - arg_list=statement.arg_list, - multiline_command=statement.multiline_command, - terminator=statement.terminator, - suffix=statement.suffix, - pipe_to=statement.pipe_to, - output=statement.output, - output_to=statement.output_to, - ) - return statement - - def _resolve_macro(self, statement: Statement) -> Optional[str]: - """ - Resolve a macro and return the resulting string - - :param statement: the parsed statement from the command line - :return: the resolved macro or None on error - """ - if statement.command not in self.macros.keys(): - raise KeyError(f"{statement.command} is not a macro") - - macro = self.macros[statement.command] - - # Make sure enough arguments were passed in - if len(statement.arg_list) < macro.minimum_arg_count: - plural = '' if macro.minimum_arg_count == 1 else 's' - self.perror(f"The macro '{statement.command}' expects at least {macro.minimum_arg_count} argument{plural}") - return None - - # 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) - - for macro_arg in reverse_arg_list: - if macro_arg.is_escaped: - to_replace = '{{' + macro_arg.number_str + '}}' - replacement = '{' + macro_arg.number_str + '}' - else: - to_replace = '{' + macro_arg.number_str + '}' - replacement = statement.argv[int(macro_arg.number_str)] - - 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 stmt_arg in statement.arg_list[macro.minimum_arg_count :]: - resolved += ' ' + stmt_arg - - # Restore any terminator, suffix, redirection, etc. - return resolved + statement.post_command - def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState: """Set up a command's output redirection for >, >>, and |. @@ -3063,7 +2939,7 @@ def onecmd(self, statement: Union[Statement, str], *, add_to_history: bool = Tru """ # For backwards compatibility with cmd, allow a str to be passed in if not isinstance(statement, Statement): - statement = self._input_line_to_statement(statement) + statement = self._complete_statement(statement) func = self.cmd_func(statement.command) if func: @@ -3395,8 +3271,7 @@ def _cmdloop(self) -> None: # Top-level parser for alias alias_description = "Manage aliases\n" "\n" "An alias is a command that enables replacement of a word by another string." - alias_epilog = "See also:\n" " macro" - alias_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_description, epilog=alias_epilog) + alias_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=alias_description) alias_subparsers = alias_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND') alias_subparsers.required = True @@ -3430,7 +3305,7 @@ def do_alias(self, args: argparse.Namespace) -> None: ) alias_create_parser.add_argument('name', help='name of this alias') alias_create_parser.add_argument( - 'command', help='what the alias resolves to', choices_provider=_get_commands_aliases_and_macros_for_completion + 'command', help='what the alias resolves to', choices_provider=_get_commands_and_aliases_for_completion ) alias_create_parser.add_argument( 'command_args', nargs=argparse.REMAINDER, help='arguments to pass to command', completer=path_complete @@ -3451,10 +3326,6 @@ def _alias_create(self, args: argparse.Namespace) -> None: self.perror("Alias cannot have the same name as a command") return - if args.name in self.macros: - self.perror("Alias cannot have the same name as a macro") - return - # Unquote redirection and terminator tokens tokens_to_unquote = constants.REDIRECTION_TOKENS tokens_to_unquote.extend(self.statement_parser.terminators) @@ -3558,243 +3429,6 @@ def _alias_list(self, args: argparse.Namespace) -> None: for name in not_found: self.perror(f"Alias '{name}' not found") - ############################################################# - # Parsers and functions for macro command and subcommands - ############################################################# - - # Top-level parser for macro - macro_description = "Manage macros\n" "\n" "A macro is similar to an alias, but it can contain argument placeholders." - macro_epilog = "See also:\n" " alias" - macro_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=macro_description, epilog=macro_epilog) - macro_subparsers = macro_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND') - macro_subparsers.required = True - - # Preserve quotes since we are passing strings to other commands - @with_argparser(macro_parser, preserve_quotes=True) - def do_macro(self, args: argparse.Namespace) -> None: - """Manage macros""" - # Call handler for whatever subcommand was selected - handler = args.cmd2_handler.get() - handler(args) - - # macro -> create - macro_create_help = "create or overwrite a macro" - macro_create_description = "Create or overwrite a macro" - - macro_create_epilog = ( - "A macro is similar to an alias, but it can contain argument placeholders.\n" - "Arguments are expressed when creating a macro using {#} notation where {1}\n" - "means the first argument.\n" - "\n" - "The following creates a macro called my_macro that expects two arguments:\n" - "\n" - " macro create my_macro make_dinner --meat {1} --veggie {2}\n" - "\n" - "When the macro is called, the provided arguments are resolved and the\n" - "assembled command is run. For example:\n" - "\n" - " my_macro beef broccoli ---> make_dinner --meat beef --veggie broccoli\n" - "\n" - "Notes:\n" - " To use the literal string {1} in your command, escape it this way: {{1}}.\n" - "\n" - " Extra arguments passed to a macro are appended to 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" - " macro create ft file_taxes -p {1} -q {2} -r {1}\n" - "\n" - " To quote an argument in the resolved command, quote it during creation.\n" - "\n" - " macro create backup !cp \"{1}\" \"{1}.orig\"\n" - "\n" - " If you want to use redirection, pipes, or terminators in the value of the\n" - " macro, then quote them.\n" - "\n" - " macro create show_results print_results -type {1} \"|\" less\n" - "\n" - " Because macros do not resolve until after hitting Enter, tab completion\n" - " will only complete paths while typing a macro." - ) - - macro_create_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER( - description=macro_create_description, epilog=macro_create_epilog - ) - macro_create_parser.add_argument('name', help='name of this macro') - macro_create_parser.add_argument( - 'command', help='what the macro resolves to', choices_provider=_get_commands_aliases_and_macros_for_completion - ) - macro_create_parser.add_argument( - 'command_args', nargs=argparse.REMAINDER, help='arguments to pass to command', completer=path_complete - ) - - @as_subcommand_to('macro', 'create', macro_create_parser, help=macro_create_help) - def _macro_create(self, args: argparse.Namespace) -> None: - """Create or overwrite a macro""" - self.last_result = False - - # Validate the macro name - valid, errmsg = self.statement_parser.is_valid_command(args.name) - if not valid: - self.perror(f"Invalid macro name: {errmsg}") - return - - if args.name in self.get_all_commands(): - self.perror("Macro cannot have the same name as a command") - return - - if args.name in self.aliases: - self.perror("Macro cannot have the same name as an alias") - return - - # Unquote redirection and terminator tokens - tokens_to_unquote = constants.REDIRECTION_TOKENS - tokens_to_unquote.extend(self.statement_parser.terminators) - utils.unquote_specific_tokens(args.command_args, tokens_to_unquote) - - # Build the macro value string - value = args.command - if args.command_args: - value += ' ' + ' '.join(args.command_args) - - # Find all normal arguments - arg_list = [] - normal_matches = re.finditer(MacroArg.macro_normal_arg_pattern, value) - max_arg_num = 0 - arg_nums = set() - - while True: - try: - cur_match = normal_matches.__next__() - - # Get the number string between the braces - cur_num_str = re.findall(MacroArg.digit_pattern, cur_match.group())[0] - cur_num = int(cur_num_str) - if cur_num < 1: - self.perror("Argument numbers must be greater than 0") - return - - arg_nums.add(cur_num) - if cur_num > max_arg_num: - max_arg_num = cur_num - - arg_list.append(MacroArg(start_index=cur_match.start(), number_str=cur_num_str, is_escaped=False)) - - except StopIteration: - break - - # Make sure the argument numbers are continuous - if len(arg_nums) != max_arg_num: - self.perror(f"Not all numbers between 1 and {max_arg_num} are present in the argument placeholders") - return - - # Find all escaped arguments - escaped_matches = re.finditer(MacroArg.macro_escaped_arg_pattern, value) - - while True: - try: - cur_match = escaped_matches.__next__() - - # Get the number string between the braces - cur_num_str = re.findall(MacroArg.digit_pattern, cur_match.group())[0] - - arg_list.append(MacroArg(start_index=cur_match.start(), number_str=cur_num_str, is_escaped=True)) - except StopIteration: - break - - # Set the macro - result = "overwritten" if args.name in self.macros else "created" - self.poutput(f"Macro '{args.name}' {result}") - - self.macros[args.name] = Macro(name=args.name, value=value, minimum_arg_count=max_arg_num, arg_list=arg_list) - self.last_result = True - - # macro -> delete - macro_delete_help = "delete macros" - macro_delete_description = "Delete specified macros or all macros if --all is used" - macro_delete_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=macro_delete_description) - macro_delete_parser.add_argument('-a', '--all', action='store_true', help="delete all macros") - macro_delete_parser.add_argument( - 'names', - nargs=argparse.ZERO_OR_MORE, - help='macro(s) to delete', - choices_provider=_get_macro_completion_items, - descriptive_header=_macro_completion_table.generate_header(), - ) - - @as_subcommand_to('macro', 'delete', macro_delete_parser, help=macro_delete_help) - def _macro_delete(self, args: argparse.Namespace) -> None: - """Delete macros""" - self.last_result = True - - if args.all: - self.macros.clear() - self.poutput("All macros deleted") - elif not args.names: - self.perror("Either --all or macro name(s) must be specified") - self.last_result = False - else: - for cur_name in utils.remove_duplicates(args.names): - if cur_name in self.macros: - del self.macros[cur_name] - self.poutput(f"Macro '{cur_name}' deleted") - else: - self.perror(f"Macro '{cur_name}' does not exist") - - # macro -> list - macro_list_help = "list macros" - macro_list_description = ( - "List specified macros in a reusable form that can be saved to a startup script\n" - "to preserve macros across sessions\n" - "\n" - "Without arguments, all macros will be listed." - ) - - macro_list_parser = argparse_custom.DEFAULT_ARGUMENT_PARSER(description=macro_list_description) - macro_list_parser.add_argument( - 'names', - nargs=argparse.ZERO_OR_MORE, - help='macro(s) to list', - choices_provider=_get_macro_completion_items, - descriptive_header=_macro_completion_table.generate_header(), - ) - - @as_subcommand_to('macro', 'list', macro_list_parser, help=macro_list_help) - def _macro_list(self, args: argparse.Namespace) -> None: - """List some or all macros as 'macro create' commands""" - self.last_result = {} # Dict[macro_name, macro_value] - - tokens_to_quote = constants.REDIRECTION_TOKENS - tokens_to_quote.extend(self.statement_parser.terminators) - - if args.names: - to_list = utils.remove_duplicates(args.names) - else: - to_list = sorted(self.macros, key=self.default_sort_key) - - not_found: List[str] = [] - for name in to_list: - if name not in self.macros: - not_found.append(name) - continue - - # Quote redirection and terminator tokens for the 'macro create' command - tokens = shlex_split(self.macros[name].value) - command = tokens[0] - command_args = tokens[1:] - utils.quote_specific_tokens(command_args, tokens_to_quote) - - val = command - if command_args: - val += ' ' + ' '.join(command_args) - - self.poutput(f"macro create {name} {val}") - self.last_result[name] = val - - for name in not_found: - self.perror(f"Macro '{name}' not found") - def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: """Completes the command argument of help""" @@ -4712,7 +4346,7 @@ def do_ipy(self, _: argparse.Namespace) -> Optional[bool]: # pragma: no cover '-x', '--expanded', action='store_true', - help='output fully parsed commands with any aliases and\n' 'macros expanded, instead of typed commands', + help='output fully parsed commands with aliases and shortcuts expanded', ) history_format_group.add_argument( '-v', diff --git a/cmd2/parsing.py b/cmd2/parsing.py index e84f7c4fc..7ef1cc3de 100755 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -38,56 +38,6 @@ def shlex_split(str_to_split: str) -> List[str]: return shlex.split(str_to_split, comments=False, posix=False) -@dataclass(frozen=True) -class MacroArg: - """ - 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: int - - # The number string that appears between the braces - # This is a string instead of an int because we support unicode digits and must be able - # to reproduce this string later - number_str: str - - # Tells if this argument is escaped and therefore needs to be unescaped - is_escaped: bool - - # Pattern used to find normal argument - # Digits surrounded by exactly 1 brace on a side and 1 or more braces on the opposite side - # Match strings like: {5}, {{{{{4}, {2}}}}} - macro_normal_arg_pattern = re.compile(r'(? str: def argv(self) -> List[str]: """a list of arguments a-la ``sys.argv``. - The first element of the list is the command after shortcut and macro - expansion. Subsequent elements of the list contain any additional - arguments, with quotes removed, just like bash would. This is very - useful if you are going to use ``argparse.parse_args()``. + The first element of the list is the command after shortcut expansion. + Subsequent elements of the list contain any additional arguments, + with quotes removed, just like bash would. This is very useful if + you are going to use ``argparse.parse_args()``. If you want to strip quotes from the input, you can use ``argv[1:]``. """ diff --git a/docs/api/cmd.rst b/docs/api/cmd.rst index 4fbb8ccd5..fef2c78c7 100644 --- a/docs/api/cmd.rst +++ b/docs/api/cmd.rst @@ -9,7 +9,7 @@ cmd2.Cmd .. attribute:: default_error The error message displayed when a non-existent command is run. - Default: ``{} is not a recognized command, alias, or macro.`` + Default: ``{} is not a recognized command or alias.`` .. attribute:: help_error diff --git a/docs/api/parsing.rst b/docs/api/parsing.rst index fa726700b..490e56559 100644 --- a/docs/api/parsing.rst +++ b/docs/api/parsing.rst @@ -15,7 +15,7 @@ Classes for parsing and storing user input. .. attribute:: command - The name of the command after shortcuts and macros have been expanded + The name of the command after shortcuts have been expanded .. attribute:: args diff --git a/docs/examples/first_app.rst b/docs/examples/first_app.rst index d90f96d86..b0164f81d 100644 --- a/docs/examples/first_app.rst +++ b/docs/examples/first_app.rst @@ -11,7 +11,7 @@ features of ``cmd2``: * :ref:`features/argument_processing:Argument Processing` * :ref:`features/generating_output:Generating Output` * :ref:`features/help:Help` -* :ref:`features/shortcuts_aliases_macros:Shortcuts` +* :ref:`features/shortcuts_aliases:Shortcuts` * :ref:`features/multiline_commands:Multiline Commands` * :ref:`features/history:History` @@ -178,8 +178,8 @@ Shortcuts --------- ``cmd2`` has several capabilities to simplify repetitive user input: -:ref:`Shortcuts, Aliases, and Macros -`. Let's add +:ref:`Shortcuts and Aliases +`. Let's add a shortcut to our application. Shortcuts are character strings that can be used instead of a command name. For example, ``cmd2`` has support for a shortcut ``!`` which runs the ``shell`` command. So instead of typing this: diff --git a/docs/features/builtin_commands.rst b/docs/features/builtin_commands.rst index 4925fc3d7..93d98a7eb 100644 --- a/docs/features/builtin_commands.rst +++ b/docs/features/builtin_commands.rst @@ -13,7 +13,7 @@ alias ~~~~~ This command manages aliases via subcommands ``create``, ``delete``, and -``list``. See :ref:`features/shortcuts_aliases_macros:Aliases` for more +``list``. See :ref:`features/shortcuts_aliases:Aliases` for more information. edit @@ -50,14 +50,6 @@ ipy This optional opt-in command enters an interactive IPython shell. See :ref:`features/embedded_python_shells:IPython (optional)` for more information. -macro -~~~~~ - -This command manages macros via subcommands ``create``, ``delete``, and -``list``. A macro is similar to an alias, but it can contain argument -placeholders. See :ref:`features/shortcuts_aliases_macros:Macros` for more -information. - py ~~ @@ -143,7 +135,7 @@ shortcuts ~~~~~~~~~ This command lists available shortcuts. See -:ref:`features/shortcuts_aliases_macros:Shortcuts` for more information. +:ref:`features/shortcuts_aliases:Shortcuts` for more information. Remove Builtin Commands diff --git a/docs/features/commands.rst b/docs/features/commands.rst index 5dd5a163b..1b3da218a 100644 --- a/docs/features/commands.rst +++ b/docs/features/commands.rst @@ -67,7 +67,7 @@ the cmd_ module. This parsing handles: - quoted arguments - output redirection and piping - multi-line commands -- shortcut, macro, and alias expansion +- shortcut and alias expansion In addition to parsing all of these elements from the user input, ``cmd2`` also has code to make all of these items work; it's almost transparent to you and to diff --git a/docs/features/help.rst b/docs/features/help.rst index b98e4164d..759a5f150 100644 --- a/docs/features/help.rst +++ b/docs/features/help.rst @@ -19,8 +19,8 @@ of the commands available: Documented commands (use 'help -v' for verbose/'help ' for details): =========================================================================== - alias help ipy py run_pyscript set shortcuts - edit history macro quit run_script shell + alias help ipy quit run_script shell + edit history py run_pyscript set shortcuts The ``help`` command can also be used to provide detailed help for a specific command: @@ -63,8 +63,8 @@ By default, the ``help`` command displays:: Documented commands (use 'help -v' for verbose/'help ' for details): =========================================================================== - alias help ipy py run_pyscript set shortcuts - edit history macro quit run_script shell + alias help ipy quit run_script shell + edit history py run_pyscript set shortcuts If you have a large number of commands, you can optionally group your commands into categories. Here's the output from the example ``help_categories.py``:: @@ -90,8 +90,8 @@ into categories. Here's the output from the example ``help_categories.py``:: Other ===== - alias edit history py run_pyscript set shortcuts - config help macro quit run_script shell version + alias edit history run_pyscript set shortcuts + config help quit run_script shell version There are 2 methods of specifying command categories, using the ``@with_category`` decorator or with the ``categorize()`` function. Once a @@ -142,51 +142,49 @@ The ``help`` command also has a verbose option (``help -v`` or ``help Documented commands (use 'help -v' for verbose/'help ' for details): Application Management - ================================================================================ - deploy Deploy command - expire Expire command - findleakers Find Leakers command - list List command - redeploy Redeploy command - restart usage: restart [-h] {now,later,sometime,whenever} - sessions Sessions command - start Start command - stop Stop command - undeploy Undeploy command + ====================================================================================================== + deploy Deploy command + expire Expire command + findleakers Find Leakers command + list List command + redeploy Redeploy command + restart Restart command + sessions Sessions command + start Start command + stop Stop command + undeploy Undeploy command Connecting - ================================================================================ - connect Connect command - which Which command + ====================================================================================================== + connect Connect command + which Which command Server Information - ================================================================================ - resources Resources command - serverinfo Server Info command - sslconnectorciphers SSL Connector Ciphers command is an example of a command that contains - multiple lines of help information for the user. Each line of help in a - contiguous set of lines will be printed and aligned in the verbose output - provided with 'help --verbose' - status Status command - thread_dump Thread Dump command - vminfo VM Info command + ====================================================================================================== + resources Resources command + serverinfo Server Info command + sslconnectorciphers SSL Connector Ciphers command is an example of a command that contains + multiple lines of help information for the user. Each line of help in a + contiguous set of lines will be printed and aligned in the verbose output + provided with 'help --verbose' + status Status command + thread_dump Thread Dump command + vminfo VM Info command Other - ================================================================================ - alias Manage aliases - config Config command - edit Run a text editor and optionally open a file with it - help List available commands or provide detailed help for a specific command - history View, run, edit, save, or clear previously entered commands - macro Manage macros - py Invoke Python command or shell - quit Exits this application - run_pyscript Runs a python script file inside the console - run_script Runs commands in script file that is encoded as either ASCII or UTF-8 text - set Set a settable parameter or show current settings of parameters - shell Execute a command as if at the OS prompt - shortcuts List available shortcuts - version Version command + ====================================================================================================== + alias Manage aliases + config Config command + edit Run a text editor and optionally open a file with it + help List available commands or provide detailed help for a specific command + history View, run, edit, save, or clear previously entered commands + quit Exit this application + run_pyscript Run a Python script file inside the console + run_script Run commands in script file that is encoded as either ASCII or UTF-8 text. + set Set a settable parameter or show current settings of parameters + shell Execute a command as if at the OS prompt + shortcuts List available shortcuts + version Version command When called with the ``-v`` flag for verbose help, the one-line description for each command is provided by the first line of the docstring for that command's diff --git a/docs/features/history.rst b/docs/features/history.rst index 056e02a0b..e421588aa 100644 --- a/docs/features/history.rst +++ b/docs/features/history.rst @@ -232,9 +232,9 @@ clipboard:: (Cmd) history -s 1:3 -``cmd2`` supports both aliases and macros, which allow you to substitute a -short, more convenient input string with a longer replacement string. Say we -create an alias like this, and then use it:: +``cmd2`` supports aliases which allow you to substitute a short, more +convenient input string with a longer replacement string. Say we create +an alias like this, and then use it:: (Cmd) alias create ls shell ls -aF Alias 'ls' created @@ -248,7 +248,7 @@ By default, the ``history`` command shows exactly what we typed:: 2 ls -d h* There are two ways to modify that display so you can see what aliases and -macros were expanded to. The first is to use ``-x`` or ``--expanded``. These +shortcuts were expanded to. The first is to use ``-x`` or ``--expanded``. These options show the expanded command instead of the entered command:: (Cmd) history -x @@ -264,6 +264,6 @@ If you want to see both the entered command and the expanded command, use the 2x shell ls -aF -d h* If the entered command had no expansion, it is displayed as usual. However, if -there is some change as the result of expanding macros and aliases, then the -entered command is displayed with the number, and the expanded command is -displayed with the number followed by an ``x``. +there is some change as the result of expanding aliases, then the entered +command is displayed with the number, and the expanded command is displayed +with the number followed by an ``x``. diff --git a/docs/features/index.rst b/docs/features/index.rst index 48590b6ad..8e6fc595b 100644 --- a/docs/features/index.rst +++ b/docs/features/index.rst @@ -26,7 +26,7 @@ Features redirection scripting settings - shortcuts_aliases_macros + shortcuts_aliases startup_commands table_creation transcripts diff --git a/docs/features/initialization.rst b/docs/features/initialization.rst index 3ee96cf9e..b06f65266 100644 --- a/docs/features/initialization.rst +++ b/docs/features/initialization.rst @@ -141,7 +141,6 @@ override: of results in a Python script or interactive console. Built-in commands don't make use of this. It is purely there for user-defined commands and convenience. -- **macros**: dictionary of macro names and their values - **max_completion_items**: max number of CompletionItems to display during tab completion (Default: 50) - **pager**: sets the pager command used by the ``Cmd.ppaged()`` method for diff --git a/docs/features/os.rst b/docs/features/os.rst index 77bc6a668..722e761ad 100644 --- a/docs/features/os.rst +++ b/docs/features/os.rst @@ -14,7 +14,7 @@ operating system shell:: (Cmd) shell ls -al -If you use the default :ref:`features/shortcuts_aliases_macros:Shortcuts` +If you use the default :ref:`features/shortcuts_aliases:Shortcuts` defined in ``cmd2`` you'll get a ``!`` shortcut for ``shell``, which allows you to type:: @@ -107,8 +107,8 @@ loop:: Documented commands (use 'help -v' for verbose/'help ' for details): =========================================================================== - alias help macro orate quit run_script set shortcuts - edit history mumble py run_pyscript say shell speak + alias help ipy quit run_script shell + edit history py run_pyscript set shortcuts (Cmd) diff --git a/docs/features/shortcuts_aliases_macros.rst b/docs/features/shortcuts_aliases.rst similarity index 57% rename from docs/features/shortcuts_aliases_macros.rst rename to docs/features/shortcuts_aliases.rst index 58d6d83cd..243257feb 100644 --- a/docs/features/shortcuts_aliases_macros.rst +++ b/docs/features/shortcuts_aliases.rst @@ -1,5 +1,5 @@ -Shortcuts, Aliases, and Macros -============================== +Shortcuts and Aliases +===================== Shortcuts --------- @@ -38,7 +38,7 @@ To define more shortcuts, update the dict ``App.shortcuts`` with the updating the ``shortcuts`` attribute This warning applies in general to many other attributes which are not settable at runtime. -Note: Command, alias, and macro names cannot start with a shortcut +Note: Command and alias names cannot start with a shortcut Aliases ------- @@ -74,44 +74,4 @@ Use ``alias delete`` to remove aliases For more details run: ``help alias delete`` -Note: Aliases cannot have the same name as a command or macro - -Macros ------- - -``cmd2`` provides a feature that is similar to aliases called macros. The major -difference between macros and aliases is that macros can contain argument -placeholders. Arguments are expressed when creating a macro using {#} notation -where {1} means the first argument. - -The following creates a macro called my_macro that expects two arguments: - - macro create my_macro make_dinner -meat {1} -veggie {2} - -When the macro is called, the provided arguments are resolved and the assembled -command is run. For example: - - my_macro beef broccoli ---> make_dinner -meat beef -veggie broccoli - -Similar to aliases, pipes and redirectors need to be quoted in the definition -of a macro:: - - macro create lc !cat "{1}" "|" less - -To use the literal string ``{1}`` in your command, escape it this way: -``{{1}}``. Because macros do not resolve until after hitting ````, -tab completion will only complete paths while typing a macro. - - -For more details run: ``help macro create`` - -The macro command has ``list`` and ``delete`` subcommands that function -identically to the alias subcommands of the same name. Like aliases, macros can -be created via a ``cmd2`` startup script to preserve them across application -sessions. - -For more details on listing macros run: ``help macro list`` - -For more details on deleting macros run: ``help macro delete`` - -Note: Macros cannot have the same name as a command or alias +Note: Aliases cannot have the same name as a command diff --git a/docs/migrating/incompatibilities.rst b/docs/migrating/incompatibilities.rst index ba6f2ed10..bc922c380 100644 --- a/docs/migrating/incompatibilities.rst +++ b/docs/migrating/incompatibilities.rst @@ -38,7 +38,7 @@ characters in command names while simultaneously using ``identchars`` functionality can be somewhat painful. Requiring white space to delimit arguments also ensures reliable operation of many other useful ``cmd2`` features, including :ref:`features/completion:Completion` and -:ref:`features/shortcuts_aliases_macros:Shortcuts, Aliases, and Macros`. +:ref:`features/shortcuts_aliases:Shortcuts and Aliases`. If you really need this functionality in your app, you can add it back in by writing a :ref:`Postparsing Hook `. diff --git a/docs/migrating/why.rst b/docs/migrating/why.rst index 2bfd45f14..bbfccb650 100644 --- a/docs/migrating/why.rst +++ b/docs/migrating/why.rst @@ -55,8 +55,8 @@ new features and capabilities, without you having to do anything: - Users can load script files, which contain a series of commands to be executed. -- Users can create :ref:`features/shortcuts_aliases_macros:Shortcuts, Aliases, - and Macros` to reduce the typing required for repetitive commands. +- Users can create :ref:`features/shortcuts_aliases:Shortcuts and Aliases` + to reduce the typing required for repetitive commands. - Embedded python shell allows a user to execute python code from within your ``cmd2`` app. How meta. diff --git a/examples/help_categories.py b/examples/help_categories.py index 5c349422c..8059ca90a 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -37,6 +37,9 @@ class HelpCategories(cmd2.Cmd): def __init__(self): super().__init__() + # Set the default category for uncategorized commands + self.default_category = 'Other' + def do_connect(self, _): """Connect command""" self.poutput('Connect') diff --git a/tests/conftest.py b/tests/conftest.py index 644ae7cca..0b3a01786 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -81,8 +81,7 @@ def verify_help_text( formatting: -s, --script output commands in script format, i.e. without command numbers - -x, --expanded output fully parsed commands with any aliases and - macros expanded, instead of typed commands + -x, --expanded output fully parsed commands with aliases and shortcuts expanded -v, --verbose display history and include expanded commands if they differ from the typed command -a, --all display all commands, including ones persisted from diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 374ba10a2..efa4868b5 100755 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1630,8 +1630,8 @@ def test_multiline_complete_statement_with_unclosed_quotes(multiline_app): assert statement.terminator == ';' -def test_multiline_input_line_to_statement(multiline_app): - # Verify _input_line_to_statement saves the fully entered input line for multiline commands +def test_multiline_complete_statement(multiline_app): + # Verify _complete_statement saves the fully entered input line for multiline commands # Mock out the input call so we don't actually wait for a user's response # on stdin when it looks for more input @@ -1639,7 +1639,7 @@ def test_multiline_input_line_to_statement(multiline_app): builtins.input = m line = 'orate hi' - statement = multiline_app._input_line_to_statement(line) + statement = multiline_app._complete_statement(line) assert statement.raw == 'orate hi\nperson\n' assert statement == 'hi person' assert statement.command == 'orate' @@ -1998,8 +1998,7 @@ def test_poutput_ansi_never(outsim_app): assert out == expected -# These are invalid names for aliases and macros -invalid_command_name = [ +invalid_alias_names = [ '""', # Blank name constants.COMMENT_CHAR, '!no_shortcut', @@ -2025,19 +2024,6 @@ def test_get_alias_completion_items(base_app): assert cur_res.description.rstrip() == base_app.aliases[cur_res] -def test_get_macro_completion_items(base_app): - run_cmd(base_app, 'macro create foo !echo foo') - run_cmd(base_app, 'macro create bar !echo bar') - - results = base_app._get_macro_completion_items() - assert len(results) == len(base_app.macros) - - for cur_res in results: - assert cur_res in base_app.macros - # Strip trailing spaces from table output - assert cur_res.description.rstrip() == base_app.macros[cur_res].value - - def test_get_settable_completion_items(base_app): results = base_app._get_settable_completion_items() assert len(results) == len(base_app.settables) @@ -2113,7 +2099,7 @@ def test_alias_create_with_quoted_tokens(base_app): assert base_app.last_result[alias_name] == alias_command -@pytest.mark.parametrize('alias_name', invalid_command_name) +@pytest.mark.parametrize('alias_name', invalid_alias_names) def test_alias_create_invalid_name(base_app, alias_name, capsys): out, err = run_cmd(base_app, 'alias create {} help'.format(alias_name)) assert "Invalid alias name" in err[0] @@ -2126,14 +2112,6 @@ def test_alias_create_with_command_name(base_app): assert base_app.last_result is False -def test_alias_create_with_macro_name(base_app): - macro = "my_macro" - run_cmd(base_app, 'macro create {} help'.format(macro)) - out, err = run_cmd(base_app, 'alias create {} help'.format(macro)) - assert "Alias cannot have the same name as a macro" in err[0] - assert base_app.last_result is False - - def test_alias_that_resolves_into_comment(base_app): # Create the alias out, err = run_cmd(base_app, 'alias create fake ' + constants.COMMENT_CHAR + ' blah blah') @@ -2192,228 +2170,6 @@ def test_multiple_aliases(base_app): verify_help_text(base_app, out) -def test_macro_no_subcommand(base_app): - out, err = run_cmd(base_app, 'macro') - assert "Usage: macro [-h]" in err[0] - assert "Error: the following arguments are required: SUBCOMMAND" in err[1] - - -def test_macro_create(base_app): - # Create the macro - out, err = run_cmd(base_app, 'macro create fake run_pyscript') - assert out == normalize("Macro 'fake' created") - assert base_app.last_result is True - - # Use the macro - out, err = run_cmd(base_app, 'fake') - assert "the following arguments are required: script_path" in err[1] - - # See a list of macros - out, err = run_cmd(base_app, 'macro list') - assert out == normalize('macro create fake run_pyscript') - assert len(base_app.last_result) == len(base_app.macros) - assert base_app.last_result['fake'] == "run_pyscript" - - # Look up the new macro - out, err = run_cmd(base_app, 'macro list fake') - assert out == normalize('macro create fake run_pyscript') - assert len(base_app.last_result) == 1 - assert base_app.last_result['fake'] == "run_pyscript" - - # Overwrite macro - out, err = run_cmd(base_app, 'macro create fake help') - assert out == normalize("Macro 'fake' overwritten") - assert base_app.last_result is True - - # Look up the updated macro - out, err = run_cmd(base_app, 'macro list fake') - assert out == normalize('macro create fake help') - assert len(base_app.last_result) == 1 - assert base_app.last_result['fake'] == "help" - - -def test_macro_create_with_quoted_tokens(base_app): - """Demonstrate that quotes in macro value will be preserved""" - macro_name = "fake" - macro_command = 'help ">" "out file.txt" ";"' - create_command = f"macro create {macro_name} {macro_command}" - - # Create the macro - out, err = run_cmd(base_app, create_command) - assert out == normalize("Macro 'fake' created") - - # Look up the new macro and verify all quotes are preserved - out, err = run_cmd(base_app, 'macro list fake') - assert out == normalize(create_command) - assert len(base_app.last_result) == 1 - assert base_app.last_result[macro_name] == macro_command - - -@pytest.mark.parametrize('macro_name', invalid_command_name) -def test_macro_create_invalid_name(base_app, macro_name): - out, err = run_cmd(base_app, 'macro create {} help'.format(macro_name)) - assert "Invalid macro name" in err[0] - assert base_app.last_result is False - - -def test_macro_create_with_command_name(base_app): - out, err = run_cmd(base_app, 'macro create help stuff') - assert "Macro cannot have the same name as a command" in err[0] - assert base_app.last_result is False - - -def test_macro_create_with_alias_name(base_app): - macro = "my_macro" - run_cmd(base_app, 'alias create {} help'.format(macro)) - out, err = run_cmd(base_app, 'macro create {} help'.format(macro)) - assert "Macro cannot have the same name as an alias" in err[0] - assert base_app.last_result is False - - -def test_macro_create_with_args(base_app): - # Create the macro - out, err = run_cmd(base_app, 'macro create fake {1} {2}') - assert out == normalize("Macro 'fake' created") - - # Run the macro - out, err = run_cmd(base_app, 'fake help -v') - verify_help_text(base_app, out) - - -def test_macro_create_with_escaped_args(base_app): - # Create the macro - out, err = run_cmd(base_app, 'macro create fake help {{1}}') - assert out == normalize("Macro 'fake' created") - - # Run the macro - out, err = run_cmd(base_app, 'fake') - assert err[0].startswith('No help on {1}') - - -def test_macro_usage_with_missing_args(base_app): - # Create the macro - out, err = run_cmd(base_app, 'macro create fake help {1} {2}') - assert out == normalize("Macro 'fake' created") - - # Run the macro - out, err = run_cmd(base_app, 'fake arg1') - assert "expects at least 2 arguments" in err[0] - - -def test_macro_usage_with_exta_args(base_app): - # Create the macro - out, err = run_cmd(base_app, 'macro create fake help {1}') - assert out == normalize("Macro 'fake' created") - - # Run the macro - out, err = run_cmd(base_app, 'fake alias create') - assert "Usage: alias create" in out[0] - - -def test_macro_create_with_missing_arg_nums(base_app): - # Create the macro - out, err = run_cmd(base_app, 'macro create fake help {1} {3}') - assert "Not all numbers between 1 and 3" in err[0] - assert base_app.last_result is False - - -def test_macro_create_with_invalid_arg_num(base_app): - # Create the macro - out, err = run_cmd(base_app, 'macro create fake help {1} {-1} {0}') - assert "Argument numbers must be greater than 0" in err[0] - assert base_app.last_result is False - - -def test_macro_create_with_unicode_numbered_arg(base_app): - # Create the macro expecting 1 argument - out, err = run_cmd(base_app, 'macro create fake help {\N{ARABIC-INDIC DIGIT ONE}}') - assert out == normalize("Macro 'fake' created") - - # Run the macro - out, err = run_cmd(base_app, 'fake') - assert "expects at least 1 argument" in err[0] - - -def test_macro_create_with_missing_unicode_arg_nums(base_app): - out, err = run_cmd(base_app, 'macro create fake help {1} {\N{ARABIC-INDIC DIGIT THREE}}') - assert "Not all numbers between 1 and 3" in err[0] - assert base_app.last_result is False - - -def test_macro_that_resolves_into_comment(base_app): - # Create the macro - out, err = run_cmd(base_app, 'macro create fake {1} blah blah') - assert out == normalize("Macro 'fake' created") - - # Use the macro - out, err = run_cmd(base_app, 'fake ' + constants.COMMENT_CHAR) - assert not out - assert not err - - -def test_macro_list_invalid_macro(base_app): - # Look up invalid macro - out, err = run_cmd(base_app, 'macro list invalid') - assert "Macro 'invalid' not found" in err[0] - assert base_app.last_result == {} - - -def test_macro_delete(base_app): - # Create an macro - run_cmd(base_app, 'macro create fake run_pyscript') - - # Delete the macro - out, err = run_cmd(base_app, 'macro delete fake') - assert out == normalize("Macro 'fake' deleted") - assert base_app.last_result is True - - -def test_macro_delete_all(base_app): - out, err = run_cmd(base_app, 'macro delete --all') - assert out == normalize("All macros deleted") - assert base_app.last_result is True - - -def test_macro_delete_non_existing(base_app): - out, err = run_cmd(base_app, 'macro delete fake') - assert "Macro 'fake' does not exist" in err[0] - assert base_app.last_result is True - - -def test_macro_delete_no_name(base_app): - out, err = run_cmd(base_app, 'macro delete') - assert "Either --all or macro name(s)" in err[0] - assert base_app.last_result is False - - -def test_multiple_macros(base_app): - macro1 = 'h1' - macro2 = 'h2' - run_cmd(base_app, 'macro create {} help'.format(macro1)) - run_cmd(base_app, 'macro create {} help -v'.format(macro2)) - out, err = run_cmd(base_app, macro1) - verify_help_text(base_app, out) - - out2, err2 = run_cmd(base_app, macro2) - verify_help_text(base_app, out2) - assert len(out2) > len(out) - - -def test_nonexistent_macro(base_app): - from cmd2.parsing import ( - StatementParser, - ) - - exception = None - - try: - base_app._resolve_macro(StatementParser().parse('fake')) - except KeyError as e: - exception = e - - assert exception is not None - - @with_ansi_style(ansi.AllowStyle.ALWAYS) def test_perror_style(base_app, capsys): msg = 'testing...' @@ -2567,7 +2323,6 @@ def test_get_all_commands(base_app): 'help', 'history', 'ipy', - 'macro', 'py', 'quit', 'run_pyscript', diff --git a/tests/test_completion.py b/tests/test_completion.py index 18b7c0f27..2bb08d3a5 100755 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -26,8 +26,6 @@ from .conftest import ( complete_tester, - normalize, - run_cmd, ) # List of strings used with completion functions @@ -186,25 +184,6 @@ def test_complete_exception(cmd2_app, capsys): assert "IndexError" in err -def test_complete_macro(base_app, request): - # Create the macro - out, err = run_cmd(base_app, 'macro create fake run_pyscript {1}') - assert out == normalize("Macro 'fake' created") - - # Macros do path completion - test_dir = os.path.dirname(request.module.__file__) - - text = os.path.join(test_dir, 's') - line = 'fake {}'.format(text) - - endidx = len(line) - begidx = endidx - len(text) - - expected = [text + 'cript.py', text + 'cript.txt', text + 'cripts' + os.path.sep] - first_match = complete_tester(text, line, begidx, endidx, base_app) - assert first_match is not None and base_app.completion_matches == expected - - def test_default_sort_key(cmd2_app): text = '' line = 'test_sort_key {}'.format(text) diff --git a/tests/test_parsing.py b/tests/test_parsing.py index e3d42d7c7..ed5a00f99 100755 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -1034,111 +1034,3 @@ def test_is_valid_command_valid(parser): valid, errmsg = parser.is_valid_command('!subcmd', is_subcommand=True) assert valid assert not errmsg - - -def test_macro_normal_arg_pattern(): - # This pattern matches digits surrounded by exactly 1 brace on a side and 1 or more braces on the opposite side - from cmd2.parsing import ( - MacroArg, - ) - - pattern = MacroArg.macro_normal_arg_pattern - - # Valid strings - matches = pattern.findall('{5}') - assert matches == ['{5}'] - - matches = pattern.findall('{233}') - assert matches == ['{233}'] - - matches = pattern.findall('{{{{{4}') - assert matches == ['{4}'] - - matches = pattern.findall('{2}}}}}') - assert matches == ['{2}'] - - matches = pattern.findall('{3}{4}{5}') - assert matches == ['{3}', '{4}', '{5}'] - - matches = pattern.findall('{3} {4} {5}') - assert matches == ['{3}', '{4}', '{5}'] - - matches = pattern.findall('{3} {{{4} {5}}}}') - assert matches == ['{3}', '{4}', '{5}'] - - matches = pattern.findall('{3} text {4} stuff {5}}}}') - assert matches == ['{3}', '{4}', '{5}'] - - # Unicode digit - matches = pattern.findall('{\N{ARABIC-INDIC DIGIT ONE}}') - assert matches == ['{\N{ARABIC-INDIC DIGIT ONE}}'] - - # Invalid strings - matches = pattern.findall('5') - assert not matches - - matches = pattern.findall('{5') - assert not matches - - matches = pattern.findall('5}') - assert not matches - - matches = pattern.findall('{{5}}') - assert not matches - - matches = pattern.findall('{5text}') - assert not matches - - -def test_macro_escaped_arg_pattern(): - # This pattern matches digits surrounded by 2 or more braces on both sides - from cmd2.parsing import ( - MacroArg, - ) - - pattern = MacroArg.macro_escaped_arg_pattern - - # Valid strings - matches = pattern.findall('{{5}}') - assert matches == ['{{5}}'] - - matches = pattern.findall('{{233}}') - assert matches == ['{{233}}'] - - matches = pattern.findall('{{{{{4}}') - assert matches == ['{{4}}'] - - matches = pattern.findall('{{2}}}}}') - assert matches == ['{{2}}'] - - matches = pattern.findall('{{3}}{{4}}{{5}}') - assert matches == ['{{3}}', '{{4}}', '{{5}}'] - - matches = pattern.findall('{{3}} {{4}} {{5}}') - assert matches == ['{{3}}', '{{4}}', '{{5}}'] - - matches = pattern.findall('{{3}} {{{4}} {{5}}}}') - assert matches == ['{{3}}', '{{4}}', '{{5}}'] - - matches = pattern.findall('{{3}} text {{4}} stuff {{5}}}}') - assert matches == ['{{3}}', '{{4}}', '{{5}}'] - - # Unicode digit - matches = pattern.findall('{{\N{ARABIC-INDIC DIGIT ONE}}}') - assert matches == ['{{\N{ARABIC-INDIC DIGIT ONE}}}'] - - # Invalid strings - matches = pattern.findall('5') - assert not matches - - matches = pattern.findall('{{5') - assert not matches - - matches = pattern.findall('5}}') - assert not matches - - matches = pattern.findall('{5}') - assert not matches - - matches = pattern.findall('{{5text}}') - assert not matches diff --git a/tests_isolated/test_commandset/conftest.py b/tests_isolated/test_commandset/conftest.py index c8c6d34b4..e70185a89 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -83,8 +83,7 @@ def verify_help_text( formatting: -s, --script output commands in script format, i.e. without command numbers - -x, --expanded output fully parsed commands with any aliases and - macros expanded, instead of typed commands + -x, --expanded output fully parsed commands with aliases and shortcuts expanded -v, --verbose display history and include expanded commands if they differ from the typed command -a, --all display all commands, including ones persisted from diff --git a/tests_isolated/test_commandset/test_commandset.py b/tests_isolated/test_commandset/test_commandset.py index c7293d411..7064b3dba 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -299,17 +299,17 @@ def test_load_commandset_errors(command_sets_manual, capsys): delattr(command_sets_manual, 'do_durian') - # pre-create intentionally conflicting macro and alias names - command_sets_manual.app_cmd('macro create apple run_pyscript') + # pre-create intentionally conflicting aliases + command_sets_manual.app_cmd('alias create apple run_pyscript') command_sets_manual.app_cmd('alias create banana run_pyscript') # now install a command set and verify the commands are now present command_sets_manual.register_command_set(cmd_set) out, err = capsys.readouterr() - # verify aliases and macros are deleted with warning if they conflict with a command + # verify aliases are deleted with warning if they conflict with a command + assert "Deleting alias 'apple'" in err assert "Deleting alias 'banana'" in err - assert "Deleting macro 'apple'" in err # verify duplicate commands are detected with pytest.raises(CommandSetRegistrationError):