summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2020-11-11 21:50:44 -0500
committerTodd Leonhardt <todd.leonhardt@gmail.com>2020-11-11 21:50:44 -0500
commitf02cf54284c4feacee5647d29665158fa5137f5f (patch)
treee421ec9b49338a312c0db759d4b5c87c7299e1a7
parent0f11ffa3b992e3f777b96dfe46d4274bfca0dcc8 (diff)
parentd4dc6b6a98fdb44b08701a3826ee88b6c22b72fd (diff)
downloadcmd2-git-f02cf54284c4feacee5647d29665158fa5137f5f.tar.gz
Merge branch 'master' into 2.0
# Conflicts: # CHANGELOG.md
-rw-r--r--.github/workflows/codeql-analysis.yml66
-rw-r--r--CHANGELOG.md13
-rwxr-xr-xREADME.md5
-rw-r--r--cmd2/argparse_custom.py24
-rw-r--r--cmd2/cmd2.py105
-rwxr-xr-xcmd2/parsing.py5
-rw-r--r--cmd2/utils.py30
-rw-r--r--docs/api/utils.rst4
-rw-r--r--docs/features/argument_processing.rst19
-rw-r--r--docs/features/modular_commands.rst2
-rw-r--r--docs/features/settings.rst4
-rwxr-xr-xsetup.py3
-rwxr-xr-xtests/test_cmd2.py38
13 files changed, 240 insertions, 78 deletions
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 00000000..0b2a764c
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,66 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [master]
+ schedule:
+ - cron: '0 6 * * 4'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ # Override automatic language detection by changing the below list
+ # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
+ language: ['python']
+ # Learn more...
+ # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ with:
+ # We must fetch at least the immediate parents so that if this is
+ # a pull request then we can checkout the head.
+ fetch-depth: 2
+
+ # If this run was triggered by a pull request event, then checkout
+ # the head of the pull request instead of the merge commit.
+ - run: git checkout HEAD^2
+ if: ${{ github.event_name == 'pull_request' }}
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ # â„šī¸ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fab390d9..460a2018 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,19 @@
See [read_input.py](https://github.com/python-cmd2/cmd2/blob/master/examples/read_input.py)
for an example.
+## 1.4.0 (November 11, 2020)
+* Bug Fixes
+ * Fixed tab completion crash on Windows
+* Enhancements
+ * Changed how multiline doc string help is formatted to match style of other help messages
+
+## 1.3.11 (October 1, 2020)
+* Bug Fixes
+ * Fixed issue where quoted redirectors and terminators in aliases and macros were not being
+ restored when read from a startup script.
+ * Fixed issue where instantiating more than one cmd2-based class which uses the `@as_subcommand_to`
+ decorator resulted in duplicated help text in the base command the subcommands belong to.
+
## 1.3.10 (September 17, 2020)
* Enhancements
* Added user-settable option called `always_show_hint`. If True, then tab completion hints will always
diff --git a/README.md b/README.md
index 8bb8b930..946e2d1f 100755
--- a/README.md
+++ b/README.md
@@ -123,9 +123,8 @@ Instructions for implementing each feature follow.
example in conjunction with the [conditional.py](https://github.com/python-cmd2/cmd2/blob/master/examples/scripts/conditional.py) script
- Parsing commands with `argparse`
- - Two decorators provide built-in capability for using `argparse.ArgumentParser` to parse command arguments
- - `cmd2.with_argparser` - all arguments are parsed by the `ArgumentParser`
- - `cmd2.with_argparser_and_unknown_args` - any arguments not parsed by the `ArgumentParser` get passed as a list
+ - The built-in `cmd2.with_argparser` decorator will parse arguments using `argparse.ArgumentParser`
+ - Optionally, `cmd2.with_argparser(.., with_unknown_args=True)` can be used to pass all unknown arguments as a list
```Python
import argparse
diff --git a/cmd2/argparse_custom.py b/cmd2/argparse_custom.py
index 4e0a9708..3cf4d1ab 100644
--- a/cmd2/argparse_custom.py
+++ b/cmd2/argparse_custom.py
@@ -503,6 +503,7 @@ argparse.ArgumentParser._match_argument = _match_argument_wrapper
# Patch argparse._SubParsersAction to add remove_parser function
############################################################################################################
+# noinspection PyPep8Naming
def _SubParsersAction_remove_parser(self, name: str):
"""
Removes a sub-parser from a sub-parsers group
@@ -511,23 +512,23 @@ def _SubParsersAction_remove_parser(self, name: str):
class so cmd2 can remove subcommands from a parser.
:param self: instance of the _SubParsersAction being edited
- :param name: name of the sub-parser to remove
+ :param name: name of the subcommand for the sub-parser to remove
"""
+ # Remove this subcommand from its base command's help text
for choice_action in self._choices_actions:
if choice_action.dest == name:
self._choices_actions.remove(choice_action)
break
- subparser = self._name_parser_map[name]
- to_remove = []
- for name, parser in self._name_parser_map.items():
- if parser is subparser:
- to_remove.append(name)
- for name in to_remove:
- del self._name_parser_map[name]
-
- if name in self.choices:
- del self.choices[name]
+ # Remove this subcommand and all its aliases from the base command
+ subparser = self._name_parser_map.get(name)
+ if subparser is not None:
+ to_remove = []
+ for cur_name, cur_parser in self._name_parser_map.items():
+ if cur_parser is subparser:
+ to_remove.append(cur_name)
+ for cur_name in to_remove:
+ del self._name_parser_map[cur_name]
# noinspection PyProtectedMember
@@ -686,6 +687,7 @@ class Cmd2HelpFormatter(argparse.RawTextHelpFormatter):
return ', '.join(action.option_strings) + ' ' + args_string
# End cmd2 customization
+ # noinspection PyMethodMayBeStatic
def _determine_metavar(self, action, default_metavar) -> Union[str, Tuple]:
"""Custom method to determine what to use as the metavar value of an action"""
if action.metavar is not None:
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index af046612..9fe31fd7 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -35,6 +35,7 @@ import glob
import inspect
import os
import pickle
+import pydoc
import re
import sys
import threading
@@ -678,10 +679,6 @@ class Cmd(cmd.Cmd):
raise CommandSetRegistrationError('Could not find argparser for command "{}" needed by subcommand: {}'
.format(command_name, str(method)))
- # Set the subcommand handler function
- defaults = {constants.NS_ATTR_SUBCMD_HANDLER: method}
- subcmd_parser.set_defaults(**defaults)
-
def find_subcommand(action: argparse.ArgumentParser, subcmd_names: List[str]) -> argparse.ArgumentParser:
if not subcmd_names:
return action
@@ -698,6 +695,14 @@ class Cmd(cmd.Cmd):
for action in target_parser._actions:
if isinstance(action, argparse._SubParsersAction):
+ # Temporary workaround for avoiding subcommand help text repeatedly getting added to
+ # action._choices_actions. Until we have instance-specific parser objects, we will remove
+ # any existing subcommand which has the same name before replacing it. This problem is
+ # exercised when more than one cmd2.Cmd-based object is created and the same subcommands
+ # get added each time. Argparse overwrites the previous subcommand but keeps growing the help
+ # text which is shown by running something like 'alias -h'.
+ action.remove_parser(subcommand_name)
+
# Get the kwargs for add_parser()
add_parser_kwargs = getattr(method, constants.SUBCMD_ATTR_ADD_PARSER_KWARGS, {})
@@ -722,6 +727,12 @@ class Cmd(cmd.Cmd):
add_parser_kwargs['add_help'] = False
attached_parser = action.add_parser(subcommand_name, **add_parser_kwargs)
+
+ # Set the subcommand handler
+ defaults = {constants.NS_ATTR_SUBCMD_HANDLER: method}
+ attached_parser.set_defaults(**defaults)
+
+ # Set what instance the handler is bound to
setattr(attached_parser, constants.PARSER_ATTR_COMMANDSET, cmdset)
break
@@ -1597,7 +1608,7 @@ class Cmd(cmd.Cmd):
matches_to_display, _ = self._pad_matches_to_display(matches_to_display)
# Print any metadata like a hint or table header
- readline.rl.mode.console.write(sys.stdout.write(self._build_completion_metadata_string()))
+ readline.rl.mode.console.write(self._build_completion_metadata_string())
# Display matches using actual display function. This also redraws the prompt and line.
orig_pyreadline_display(matches_to_display)
@@ -2914,20 +2925,39 @@ class Cmd(cmd.Cmd):
@as_subcommand_to('alias', 'list', alias_list_parser, help=alias_delete_help)
def _alias_list(self, args: argparse.Namespace) -> None:
- """List some or all aliases"""
+ """List some or all aliases as 'alias create' commands"""
create_cmd = "alias create"
if args.with_silent:
create_cmd += " --silent"
+ tokens_to_quote = constants.REDIRECTION_TOKENS
+ tokens_to_quote.extend(self.statement_parser.terminators)
+
if args.names:
- for cur_name in utils.remove_duplicates(args.names):
- if cur_name in self.aliases:
- self.poutput("{} {} {}".format(create_cmd, cur_name, self.aliases[cur_name]))
- else:
- self.perror("Alias '{}' not found".format(cur_name))
+ to_list = utils.remove_duplicates(args.names)
else:
- for cur_alias in sorted(self.aliases, key=self.default_sort_key):
- self.poutput("{} {} {}".format(create_cmd, cur_alias, self.aliases[cur_alias]))
+ to_list = sorted(self.aliases, key=self.default_sort_key)
+
+ not_found = [] # type: List[str]
+ for name in to_list:
+ if name not in self.aliases:
+ not_found.append(name)
+ continue
+
+ # Quote redirection and terminator tokens for the 'alias create' command
+ tokens = shlex_split(self.aliases[name])
+ command = tokens[0]
+ args = tokens[1:]
+ utils.quote_specific_tokens(args, tokens_to_quote)
+
+ val = command
+ if args:
+ val += ' ' + ' '.join(args)
+
+ self.poutput("{} {} {}".format(create_cmd, name, val))
+
+ for name in not_found:
+ self.perror("Alias '{}' not found".format(name))
#############################################################
# Parsers and functions for macro command and subcommands
@@ -3122,20 +3152,39 @@ class Cmd(cmd.Cmd):
@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"""
+ """List some or all macros as 'macro create' commands"""
create_cmd = "macro create"
if args.with_silent:
create_cmd += " --silent"
+ tokens_to_quote = constants.REDIRECTION_TOKENS
+ tokens_to_quote.extend(self.statement_parser.terminators)
+
if args.names:
- for cur_name in utils.remove_duplicates(args.names):
- if cur_name in self.macros:
- self.poutput("{} {} {}".format(create_cmd, cur_name, self.macros[cur_name].value))
- else:
- self.perror("Macro '{}' not found".format(cur_name))
+ to_list = utils.remove_duplicates(args.names)
else:
- for cur_macro in sorted(self.macros, key=self.default_sort_key):
- self.poutput("{} {} {}".format(create_cmd, cur_macro, self.macros[cur_macro].value))
+ to_list = sorted(self.macros, key=self.default_sort_key)
+
+ not_found = [] # type: 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]
+ args = tokens[1:]
+ utils.quote_specific_tokens(args, tokens_to_quote)
+
+ val = command
+ if args:
+ val += ' ' + ' '.join(args)
+
+ self.poutput("{} {} {}".format(create_cmd, name, val))
+
+ for name in not_found:
+ self.perror("Macro '{}' not found".format(name))
def complete_help_command(self, text: str, line: str, begidx: int, endidx: int) -> List[str]:
"""Completes the command argument of help"""
@@ -3198,17 +3247,21 @@ class Cmd(cmd.Cmd):
# Set end to blank so the help output matches how it looks when "command -h" is used
self.poutput(completer.format_help(args.subcommands), end='')
+ # If there is a help func delegate to do_help
+ elif help_func is not None:
+ super().do_help(args.command)
+
+ # If there's no help_func __doc__ then format and output it
+ elif func is not None and func.__doc__ is not None:
+ self.poutput(pydoc.getdoc(func))
+
# If there is no help information then print an error
- elif help_func is None and (func is None or not func.__doc__):
+ else:
err_msg = self.help_error.format(args.command)
# Set apply_style to False so help_error's style is not overridden
self.perror(err_msg, apply_style=False)
- # Otherwise delegate to cmd base class do_help()
- else:
- super().do_help(args.command)
-
def _help_menu(self, verbose: bool = False) -> None:
"""Show a list of commands which help can be displayed for"""
cmds_cats, cmds_doc, cmds_undoc, help_topics = self._build_command_info()
diff --git a/cmd2/parsing.py b/cmd2/parsing.py
index 657db32c..c420e9aa 100755
--- a/cmd2/parsing.py
+++ b/cmd2/parsing.py
@@ -91,9 +91,8 @@ class Statement(str):
Tips:
1. `argparse <https://docs.python.org/3/library/argparse.html>`_ is your
- friend for anything complex. ``cmd2`` has two decorators
- (:func:`~cmd2.decorators.with_argparser`, and
- :func:`~cmd2.decorators.with_argparser_and_unknown_args`) which you can
+ friend for anything complex. ``cmd2`` has the decorator
+ (:func:`~cmd2.decorators.with_argparser`) which you can
use to make your command method receive a namespace of parsed arguments,
whether positional or denoted with switches.
diff --git a/cmd2/utils.py b/cmd2/utils.py
index cd716083..7c5f1560 100644
--- a/cmd2/utils.py
+++ b/cmd2/utils.py
@@ -281,17 +281,29 @@ def natural_sort(list_to_sort: Iterable[str]) -> List[str]:
return sorted(list_to_sort, key=natural_keys)
-def unquote_specific_tokens(args: List[str], tokens_to_unquote: List[str]) -> None:
+def quote_specific_tokens(tokens: List[str], tokens_to_quote: List[str]) -> None:
"""
- Unquote a specific tokens in a list of command-line arguments
- This is used when certain tokens have to be passed to another command
- :param args: the command line args
- :param tokens_to_unquote: the tokens, which if present in args, to unquote
+ Quote specific tokens in a list
+
+ :param tokens: token list being edited
+ :param tokens_to_quote: the tokens, which if present in tokens, to quote
+ """
+ for i, token in enumerate(tokens):
+ if token in tokens_to_quote:
+ tokens[i] = quote_string(token)
+
+
+def unquote_specific_tokens(tokens: List[str], tokens_to_unquote: List[str]) -> None:
+ """
+ Unquote specific tokens in a list
+
+ :param tokens: token list being edited
+ :param tokens_to_unquote: the tokens, which if present in tokens, to unquote
"""
- for i, arg in enumerate(args):
- unquoted_arg = strip_quotes(arg)
- if unquoted_arg in tokens_to_unquote:
- args[i] = unquoted_arg
+ for i, token in enumerate(tokens):
+ unquoted_token = strip_quotes(token)
+ if unquoted_token in tokens_to_unquote:
+ tokens[i] = unquoted_token
def expand_user(token: str) -> str:
diff --git a/docs/api/utils.rst b/docs/api/utils.rst
index 9276587f..81c978c9 100644
--- a/docs/api/utils.rst
+++ b/docs/api/utils.rst
@@ -22,6 +22,10 @@ Quote Handling
.. autofunction:: cmd2.utils.strip_quotes
+.. autofunction:: cmd2.utils.quote_specific_tokens
+
+.. autofunction:: cmd2.utils.unquote_specific_tokens
+
IO Handling
-----------
diff --git a/docs/features/argument_processing.rst b/docs/features/argument_processing.rst
index 9abd9c65..bcc68633 100644
--- a/docs/features/argument_processing.rst
+++ b/docs/features/argument_processing.rst
@@ -35,7 +35,6 @@ applications.
passed to commands:
* :func:`cmd2.decorators.with_argparser`
-* :func:`cmd2.decorators.with_argparser_and_unknown_args`
* :func:`cmd2.decorators.with_argument_list`
All of these decorators accept an optional **preserve_quotes** argument which
@@ -262,12 +261,12 @@ Unknown Positional Arguments
If you want all unknown arguments to be passed to your command as a list of
strings, then decorate the command method with the
-``@with_argparser_and_unknown_args`` decorator.
+``@with_argparser(..., with_unknown_args=True)`` decorator.
Here's what it looks like::
import argparse
- from cmd2 import with_argparser_and_unknown_args
+ from cmd2 import with_argparser
dir_parser = argparse.ArgumentParser()
dir_parser.add_argument('-l', '--long', action='store_true', help="display in long format with one item per line")
@@ -292,9 +291,8 @@ Using A Custom Namespace
In some cases, it may be necessary to write custom ``argparse`` code that is
dependent on state data of your application. To support this ability while
-still allowing use of the decorators, both ``@with_argparser`` and
-``@with_argparser_and_unknown_args`` have an optional argument called
-``ns_provider``.
+still allowing use of the decorators, ``@with_argparser`` has an optional
+argument called ``ns_provider``.
``ns_provider`` is a Callable that accepts a ``cmd2.Cmd`` object as an argument
and returns an ``argparse.Namespace``::
@@ -320,9 +318,8 @@ logic.
Subcommands
------------
-Subcommands are supported for commands using either the ``@with_argparser`` or
-``@with_argparser_and_unknown_args`` decorator. The syntax for supporting them
-is based on argparse sub-parsers.
+Subcommands are supported for commands using the ``@with_argparser`` decorator.
+The syntax is based on argparse sub-parsers.
You may add multiple layers of subcommands for your command. ``cmd2`` will
automatically traverse and tab complete subcommands for all commands using
@@ -350,8 +347,8 @@ help output.
Decorator Order
---------------
-If you are using custom decorators in combination with either
-``@cmd2.with_argparser`` or ``@cmd2.with_argparser_and_unknown_args``, then the
+If you are using custom decorators in combination with
+``@cmd2.with_argparser``, then the
order of your custom decorator(s) relative to the ``cmd2`` decorator matters
when it comes to runtime behavior and ``argparse`` errors. There is nothing
``cmd2``-specific here, this is just a side-effect of how decorators work in
diff --git a/docs/features/modular_commands.rst b/docs/features/modular_commands.rst
index 6159bc41..790b933e 100644
--- a/docs/features/modular_commands.rst
+++ b/docs/features/modular_commands.rst
@@ -127,7 +127,7 @@ CommandSets and pass in the constructor to Cmd2.
Dynamic Commands
~~~~~~~~~~~~~~~~
-You man also dynamically load and unload commands by installing and removing CommandSets at runtime. For example,
+You can also dynamically load and unload commands by installing and removing CommandSets at runtime. For example,
if you could support runtime loadable plugins or add/remove commands based on your state.
You may need to disable command auto-loading if you need dynamically load commands at runtime.
diff --git a/docs/features/settings.rst b/docs/features/settings.rst
index aa3e5cec..c21b3258 100644
--- a/docs/features/settings.rst
+++ b/docs/features/settings.rst
@@ -134,9 +134,9 @@ changes a setting, and will receive both the old value and the new value.
.. code-block:: text
- (Cmd) set --long | grep sunny
+ (Cmd) set --verbose | grep sunny
sunny: False # Is it sunny outside?
- (Cmd) set --long | grep degrees
+ (Cmd) set --verbose | grep degrees
degrees_c: 22 # Temperature in Celsius
(Cmd) sunbathe
Too dim.
diff --git a/setup.py b/setup.py
index 4eb93cd2..123a045f 100755
--- a/setup.py
+++ b/setup.py
@@ -31,14 +31,13 @@ Programming Language :: Python :: Implementation :: CPython
Topic :: Software Development :: Libraries :: Python Modules
""".splitlines()))) # noqa: E128
-SETUP_REQUIRES = ['setuptools_scm >= 3.0']
+SETUP_REQUIRES = ['setuptools >= 34.4', 'setuptools_scm >= 3.0']
INSTALL_REQUIRES = [
'attrs >= 16.3.0',
'colorama >= 0.3.7',
'importlib_metadata>=1.6.0;python_version<"3.8"',
'pyperclip >= 1.6',
- 'setuptools >= 34.4',
'wcwidth >= 0.1.7',
]
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index d913b4fc..a8f2f993 100755
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -981,6 +981,16 @@ class HelpApp(cmd2.Cmd):
def do_undoc(self, arg):
pass
+ def do_multiline_docstr(self, arg):
+ """
+ This documentation
+ is multiple lines
+ and there are no
+ tabs
+ """
+ pass
+
+
@pytest.fixture
def help_app():
app = HelpApp()
@@ -1004,6 +1014,11 @@ def test_help_overridden_method(help_app):
expected = normalize('This overrides the edit command and does nothing.')
assert out == expected
+def test_help_multiline_docstring(help_app):
+ out, err = run_cmd(help_app, 'help multiline_docstr')
+ expected = normalize('This documentation\nis multiple lines\nand there are no\ntabs')
+ assert out == expected
+
class HelpCategoriesApp(cmd2.Cmd):
"""Class for testing custom help_* methods which override docstring help."""
@@ -1675,16 +1690,17 @@ def test_alias_create(base_app):
out, err = run_cmd(base_app, 'alias list --with_silent fake')
assert out == normalize('alias create --silent fake set')
-def test_alias_create_with_quoted_value(base_app):
- """Demonstrate that quotes in alias value will be preserved (except for redirectors and terminators)"""
+def test_alias_create_with_quoted_tokens(base_app):
+ """Demonstrate that quotes in alias value will be preserved"""
+ create_command = 'alias create fake help ">" "out file.txt" ";"'
# Create the alias
- out, err = run_cmd(base_app, 'alias create fake help ">" "out file.txt" ";"')
+ out, err = run_cmd(base_app, create_command)
assert out == normalize("Alias 'fake' created")
- # Look up the new alias (Only the redirector should be unquoted)
+ # Look up the new alias and verify all quotes are preserved
out, err = run_cmd(base_app, 'alias list fake')
- assert out == normalize('alias create fake help > "out file.txt" ;')
+ assert out == normalize(create_command)
@pytest.mark.parametrize('alias_name', invalid_command_name)
def test_alias_create_invalid_name(base_app, alias_name, capsys):
@@ -1784,15 +1800,17 @@ def test_macro_create(base_app):
out, err = run_cmd(base_app, 'macro list --with_silent fake')
assert out == normalize('macro create --silent fake set')
-def test_macro_create_with_quoted_value(base_app):
- """Demonstrate that quotes in macro value will be preserved (except for redirectors and terminators)"""
+def test_macro_create_with_quoted_tokens(base_app):
+ """Demonstrate that quotes in macro value will be preserved"""
+ create_command = 'macro create fake help ">" "out file.txt" ";"'
+
# Create the macro
- out, err = run_cmd(base_app, 'macro create fake help ">" "out file.txt" ";"')
+ out, err = run_cmd(base_app, create_command)
assert out == normalize("Macro 'fake' created")
- # Look up the new macro (Only the redirector should be unquoted)
+ # Look up the new macro and verify all quotes are preserved
out, err = run_cmd(base_app, 'macro list fake')
- assert out == normalize('macro create fake help > "out file.txt" ;')
+ assert out == normalize(create_command)
@pytest.mark.parametrize('macro_name', invalid_command_name)
def test_macro_create_invalid_name(base_app, macro_name):