diff --git a/CHANGELOG.md b/CHANGELOG.md index e3e06fa4..78ec8717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.0.0 (TBD) + +- Breaking Change + - Removed macros + ## 2.7.0 (June 30, 2025) - Enhancements diff --git a/README.md b/README.md index 5271480f..221257ab 100755 --- a/README.md +++ b/README.md @@ -69,10 +69,10 @@ first pillar of 'ease of command discovery'. The following is a list of features -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 diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 898aad07..d0de8782 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -38,7 +38,6 @@ import os import pprint import pydoc -import re import sys import tempfile import threading @@ -116,8 +115,6 @@ single_line_format, ) from .parsing import ( - Macro, - MacroArg, Statement, StatementParser, shlex_split, @@ -431,9 +428,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] = {} - # Keeps track of typed command history in the Python shell self._py_history: list[str] = [] @@ -479,7 +473,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 = '' @@ -550,7 +544,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: Callable[[str], str] = Cmd.ALPHABETICAL_SORT_KEY @@ -823,11 +817,6 @@ def _install_command_function(self, command_func_name: str, command_method: Comm 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] - setattr(self, command_func_name, command_method) def _install_completer_function(self, cmd_name: str, cmd_completer: CompleterFunc) -> None: @@ -2060,12 +2049,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) @@ -2091,8 +2076,7 @@ def _perform_completion( else: completer_func = self.completedefault # type: ignore[assignment] - # Not a recognized macro or command - # Check if this command should be run as a shell command + # Not a recognized command. Check if it should be run as a shell command. elif self.default_to_shell and command in utils.get_exes_in_path(command): completer_func = self.path_complete else: @@ -2250,8 +2234,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) @@ -2339,19 +2323,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) @@ -2365,12 +2336,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.""" @@ -2509,7 +2479,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) @@ -2753,99 +2723,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 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: - 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 |. @@ -3014,7 +2891,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: @@ -3340,8 +3217,7 @@ def _cmdloop(self) -> None: # Top-level parser for alias alias_description = "Manage aliases\n\nAn 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_parser.add_subparsers(metavar='SUBCOMMAND', required=True) # Preserve quotes since we are passing strings to other commands @@ -3374,7 +3250,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 @@ -3395,10 +3271,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) @@ -3499,237 +3371,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\nA 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_parser.add_subparsers(metavar='SUBCOMMAND', 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() - - try: - while True: - 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) - max_arg_num = max(max_arg_num, cur_num) - - arg_list.append(MacroArg(start_index=cur_match.start(), number_str=cur_num_str, is_escaped=False)) - except StopIteration: - pass - - # 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) - - try: - while True: - 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: - pass - - # 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) - - to_list = utils.remove_duplicates(args.names) if args.names else 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.""" # Complete token against topics and visible commands @@ -4651,7 +4292,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\nmacros 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 e12f799c..e488aad1 100644 --- a/cmd2/parsing.py +++ b/cmd2/parsing.py @@ -35,56 +35,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/examples/first_app.md b/docs/examples/first_app.md index 86efd70f..64e1c1c0 100644 --- a/docs/examples/first_app.md +++ b/docs/examples/first_app.md @@ -7,7 +7,7 @@ Here's a quick walkthrough of a simple application which demonstrates 8 features - [Argument Processing](../features/argument_processing.md) - [Generating Output](../features/generating_output.md) - [Help](../features/help.md) -- [Shortcuts](../features/shortcuts_aliases_macros.md#shortcuts) +- [Shortcuts](../features/shortcuts_aliases.md#shortcuts) - [Multiline Commands](../features/multiline_commands.md) - [History](../features/history.md) @@ -166,10 +166,9 @@ With those few lines of code, we created a [command](../features/commands.md), u ## Shortcuts `cmd2` has several capabilities to simplify repetitive user input: -[Shortcuts, Aliases, and Macros](../features/shortcuts_aliases_macros.md). 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: +[Shortcuts and Aliases](../features/shortcuts_aliases.md). 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: ```shell (Cmd) shell ls -al diff --git a/docs/features/builtin_commands.md b/docs/features/builtin_commands.md index ed0e2479..42822a53 100644 --- a/docs/features/builtin_commands.md +++ b/docs/features/builtin_commands.md @@ -9,7 +9,7 @@ to be part of the application. ### alias This command manages aliases via subcommands `create`, `delete`, and `list`. See -[Aliases](shortcuts_aliases_macros.md#aliases) for more information. +[Aliases](shortcuts_aliases.md#aliases) for more information. ### edit @@ -38,12 +38,6 @@ history. See [History](history.md) for more information. This optional opt-in command enters an interactive IPython shell. See [IPython (optional)](./embedded_python_shells.md#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 [Macros](./shortcuts_aliases_macros.md#macros) -for more information. - ### py This command invokes a Python command or shell. See @@ -114,8 +108,8 @@ Execute a command as if at the operating system shell prompt: ### shortcuts -This command lists available shortcuts. See [Shortcuts](./shortcuts_aliases_macros.md#shortcuts) for -more information. +This command lists available shortcuts. See [Shortcuts](./shortcuts_aliases.md#shortcuts) for more +information. ## Remove Builtin Commands diff --git a/docs/features/commands.md b/docs/features/commands.md index 5497ce44..2693add3 100644 --- a/docs/features/commands.md +++ b/docs/features/commands.md @@ -61,7 +61,7 @@ backwards compatibility. - 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 the commands you write in your own diff --git a/docs/features/help.md b/docs/features/help.md index 56a47b3b..aa2e9d70 100644 --- a/docs/features/help.md +++ b/docs/features/help.md @@ -14,8 +14,8 @@ command. The `help` command by itself displays a list 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: @@ -53,8 +53,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`: @@ -80,8 +80,8 @@ 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 single command category is detected, the help output switches to @@ -137,51 +137,54 @@ categories with per-command Help Messages: 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 + sessions Sessions command. + start Start + stop Stop command. + undeploy Undeploy command. + + Command Management + ====================================================================================================== + disable_commands Disable the Application Management commands. + enable_commands Enable the Application Management commands. 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 associated `do_*` method. diff --git a/docs/features/history.md b/docs/features/history.md index 59ecf5f1..bc98dcf4 100644 --- a/docs/features/history.md +++ b/docs/features/history.md @@ -198,8 +198,8 @@ without line numbers, so you can copy them to the 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 @@ -212,7 +212,7 @@ By default, the `history` command shows exactly what we typed: 1 alias create ls shell ls -aF 2 ls -d h* -There are two ways to modify that display so you can see what aliases and macros were expanded to. +There are two ways to modify the display so you can see what aliases and shortcuts were expanded to. The first is to use `-x` or `--expanded`. These options show the expanded command instead of the entered command: @@ -229,5 +229,5 @@ option: 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`. +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.md b/docs/features/index.md index 13f99715..13ea9afe 100644 --- a/docs/features/index.md +++ b/docs/features/index.md @@ -24,7 +24,7 @@ - [Output Redirection and Pipes](redirection.md) - [Scripting](scripting.md) - [Settings](settings.md) -- [Shortcuts, Aliases, and Macros](shortcuts_aliases_macros.md) +- [Shortcuts and Aliases](shortcuts_aliases.md) - [Startup Commands](startup_commands.md) - [Table Creation](table_creation.md) - [Transcripts](transcripts.md) diff --git a/docs/features/initialization.md b/docs/features/initialization.md index 279238f0..85735b87 100644 --- a/docs/features/initialization.md +++ b/docs/features/initialization.md @@ -92,7 +92,7 @@ The `cmd2.Cmd` class provides a large number of public instance attributes which ### Public instance attributes -Here are instance attributes of `cmd2.Cmd` which developers might wish override: +Here are instance attributes of `cmd2.Cmd` which developers might wish to override: - **always_show_hint**: if `True`, display tab completion hint even when completion suggestions print (Default: `False`) - **broken_pipe_warning**: if non-empty, this string will be displayed if a broken pipe error occurs @@ -112,7 +112,6 @@ Here are instance attributes of `cmd2.Cmd` which developers might wish override: - **help_error**: the error that prints when no help information can be found - **hidden_commands**: commands to exclude from the help menu and tab completion - **last_result**: stores results from the last command run to enable usage 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 displaying wrapped output using a pager - **pager_chop**: sets the pager command used by the `Cmd.ppaged()` method for displaying chopped/truncated output using a pager diff --git a/docs/features/os.md b/docs/features/os.md index d1da31bf..83ffe6de 100644 --- a/docs/features/os.md +++ b/docs/features/os.md @@ -10,8 +10,8 @@ See [Output Redirection and Pipes](./redirection.md#output-redirection-and-pipes (Cmd) shell ls -al -If you use the default [Shortcuts](./shortcuts_aliases_macros.md#shortcuts) defined in `cmd2` you'll -get a `!` shortcut for `shell`, which allows you to type: +If you use the default [Shortcuts](./shortcuts_aliases.md#shortcuts) defined in `cmd2` you'll get a +`!` shortcut for `shell`, which allows you to type: (Cmd) !ls -al @@ -89,8 +89,8 @@ shell, and execute those commands before entering the command 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.md b/docs/features/shortcuts_aliases.md similarity index 56% rename from docs/features/shortcuts_aliases_macros.md rename to docs/features/shortcuts_aliases.md index 9c87ec44..17642ace 100644 --- a/docs/features/shortcuts_aliases_macros.md +++ b/docs/features/shortcuts_aliases.md @@ -1,4 +1,4 @@ -# Shortcuts, Aliases, and Macros +# Shortcuts and Aliases ## Shortcuts @@ -26,7 +26,7 @@ class App(Cmd): Shortcuts need to be created by updating the `shortcuts` dictionary attribute prior to calling the `cmd2.Cmd` super class `__init__()` method. Moreover, that super class init method needs to be called after 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 @@ -57,38 +57,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]{#macro} that expects two arguments: - - macro create my[macro]{#macro} make[dinner]{#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]{#macro} beef broccoli ---> make[dinner]{#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.md b/docs/migrating/incompatibilities.md index 030959d1..1b5e3f99 100644 --- a/docs/migrating/incompatibilities.md +++ b/docs/migrating/incompatibilities.md @@ -28,7 +28,7 @@ and arguments on whitespace. We opted for this breaking change because while 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 [Tab Completion](../features/completion.md) and -[Shortcuts, Aliases, and Macros](../features/shortcuts_aliases_macros.md). +[Shortcuts and Aliases](../features/shortcuts_aliases.md). If you really need this functionality in your app, you can add it back in by writing a [Postparsing Hook](../features/hooks.md#postparsing-hooks). diff --git a/docs/migrating/why.md b/docs/migrating/why.md index 060ef0c0..44fbe2a8 100644 --- a/docs/migrating/why.md +++ b/docs/migrating/why.md @@ -37,8 +37,8 @@ and capabilities, without you having to do anything: Before you do, you might consider the various ways `cmd2` has of [Generatoring Output](../features/generating_output.md). - Users can load script files, which contain a series of commands to be executed. -- Users can create [Shortcuts, Aliases, and Macros](../features/shortcuts_aliases_macros.md) to - reduce the typing required for repetitive commands. +- Users can create [Shortcuts and Aliases](../features/shortcuts_aliases.md) 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. - [Clipboard Integration](../features/clipboard.md) allows you to save command output to the operating system clipboard. diff --git a/examples/help_categories.py b/examples/help_categories.py index 7a9b4aca..8b213c74 100755 --- a/examples/help_categories.py +++ b/examples/help_categories.py @@ -35,6 +35,9 @@ class HelpCategories(cmd2.Cmd): def __init__(self) -> None: super().__init__() + # Set the default category for uncategorized commands + self.default_category = 'Other' + def do_connect(self, _) -> None: """Connect command.""" self.poutput('Connect') diff --git a/mkdocs.yml b/mkdocs.yml index 77a3d3d7..a8090308 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -183,7 +183,7 @@ nav: - features/redirection.md - features/scripting.md - features/settings.md - - features/shortcuts_aliases_macros.md + - features/shortcuts_aliases.md - features/startup_commands.md - features/table_creation.md - features/transcripts.md diff --git a/tests/conftest.py b/tests/conftest.py index b9c64375..0f745047 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -72,8 +72,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 8e23b7ab..ee666784 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1616,8 +1616,8 @@ def test_multiline_complete_statement_with_unclosed_quotes(multiline_app) -> Non assert statement.terminator == ';' -def test_multiline_input_line_to_statement(multiline_app) -> None: - # Verify _input_line_to_statement saves the fully entered input line for multiline commands +def test_multiline_complete_statement(multiline_app) -> None: + # 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 @@ -1625,7 +1625,7 @@ def test_multiline_input_line_to_statement(multiline_app) -> None: 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' @@ -2002,8 +2002,7 @@ def test_poutput_ansi_never(outsim_app) -> None: assert out == expected -# These are invalid names for aliases and macros -invalid_command_name = [ +invalid_alias_names = [ '""', # Blank name constants.COMMENT_CHAR, '!no_shortcut', @@ -2029,19 +2028,6 @@ def test_get_alias_completion_items(base_app) -> None: assert cur_res.description.rstrip() == base_app.aliases[cur_res] -def test_get_macro_completion_items(base_app) -> None: - 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) -> None: results = base_app._get_settable_completion_items() assert len(results) == len(base_app.settables) @@ -2117,7 +2103,7 @@ def test_alias_create_with_quoted_tokens(base_app) -> None: 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) -> None: out, err = run_cmd(base_app, f'alias create {alias_name} help') assert "Invalid alias name" in err[0] @@ -2130,14 +2116,6 @@ def test_alias_create_with_command_name(base_app) -> None: assert base_app.last_result is False -def test_alias_create_with_macro_name(base_app) -> None: - macro = "my_macro" - run_cmd(base_app, f'macro create {macro} help') - out, err = run_cmd(base_app, f'alias create {macro} help') - 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) -> None: # Create the alias out, err = run_cmd(base_app, 'alias create fake ' + constants.COMMENT_CHAR + ' blah blah') @@ -2196,228 +2174,6 @@ def test_multiple_aliases(base_app) -> None: verify_help_text(base_app, out) -def test_macro_no_subcommand(base_app) -> None: - 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) -> None: - # 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) -> None: - """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) -> None: - out, err = run_cmd(base_app, f'macro create {macro_name} help') - assert "Invalid macro name" in err[0] - assert base_app.last_result is False - - -def test_macro_create_with_command_name(base_app) -> None: - 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) -> None: - macro = "my_macro" - run_cmd(base_app, f'alias create {macro} help') - out, err = run_cmd(base_app, f'macro create {macro} help') - 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) -> None: - # 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) -> None: - # 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) -> None: - # 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) -> None: - # 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) -> None: - # 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) -> None: - # 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) -> None: - # 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) -> None: - 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) -> None: - # 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) -> None: - # 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) -> None: - # 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) -> None: - 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) -> None: - 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) -> None: - 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) -> None: - macro1 = 'h1' - macro2 = 'h2' - run_cmd(base_app, f'macro create {macro1} help') - run_cmd(base_app, f'macro create {macro2} help -v') - 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) -> None: - 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) -> None: msg = 'testing...' @@ -2557,7 +2313,6 @@ def test_get_all_commands(base_app) -> None: 'help', 'history', 'ipy', - 'macro', 'py', 'quit', 'run_pyscript', diff --git a/tests/test_completion.py b/tests/test_completion.py index 1d9e9256..2361c222 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -24,8 +24,6 @@ from .conftest import ( complete_tester, - normalize, - run_cmd, ) # List of strings used with completion functions @@ -182,26 +180,6 @@ def test_complete_exception(cmd2_app, capsys) -> None: assert "IndexError" in err -def test_complete_macro(base_app, request) -> None: - # 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 = f'fake {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 - assert base_app.completion_matches == expected - - def test_default_sort_key(cmd2_app) -> None: text = '' line = f'test_sort_key {text}' diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 711868ca..969d00d7 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -1039,111 +1039,3 @@ def test_is_valid_command_valid(parser) -> None: valid, errmsg = parser.is_valid_command('!subcmd', is_subcommand=True) assert valid assert not errmsg - - -def test_macro_normal_arg_pattern() -> None: - # 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() -> None: - # 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 171f4a29..6a3d66ab 100644 --- a/tests_isolated/test_commandset/conftest.py +++ b/tests_isolated/test_commandset/conftest.py @@ -74,8 +74,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 7498e145..9ea4eb3b 100644 --- a/tests_isolated/test_commandset/test_commandset.py +++ b/tests_isolated/test_commandset/test_commandset.py @@ -343,17 +343,17 @@ def test_load_commandset_errors(command_sets_manual, capsys) -> None: 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 aliases with names which conflict with commands + 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 command functions which don't start with "do_" raise an exception with pytest.raises(CommandSetRegistrationError):