summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2020-09-30 14:19:13 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2020-09-30 14:19:13 -0400
commit7f696097d1d9a224773face073c5d860e10dc0e9 (patch)
tree2371683db4d989507ef12942d741e2b7d9e1ca6f
parented7b9e5185fd14808aa26e61a26968b9753beee0 (diff)
downloadcmd2-git-redirected_aliases.tar.gz
Fixed issue where quoted redirectors and terminators in aliases and macros were not beingredirected_aliases
restored when read from a startup script.
-rw-r--r--CHANGELOG.md5
-rw-r--r--cmd2/cmd2.py70
-rw-r--r--cmd2/utils.py16
-rw-r--r--docs/api/utils.rst4
-rwxr-xr-xtests/test_cmd2.py21
5 files changed, 90 insertions, 26 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1f33931..e2ada12f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 1.3.11 (TBD, 2020)
+* Bug Fixes
+ * Fixed issue where quoted redirectors and terminators in aliases and macros were not being
+ restored when read from a startup script.
+
## 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/cmd2/cmd2.py b/cmd2/cmd2.py
index d1a26a2b..8810025a 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -2821,20 +2821,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
@@ -3029,20 +3048,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"""
diff --git a/cmd2/utils.py b/cmd2/utils.py
index b6dadf1c..292c608b 100644
--- a/cmd2/utils.py
+++ b/cmd2/utils.py
@@ -318,10 +318,24 @@ def natural_sort(list_to_sort: Iterable[str]) -> List[str]:
return sorted(list_to_sort, key=natural_keys)
+def quote_specific_tokens(args: List[str], tokens_to_quote: List[str]) -> None:
+ """
+ Quote specific tokens in a list of command-line arguments
+ This is used mainly to restore 'alias create' and 'macro create' commands
+
+ :param args: the command line args
+ :param tokens_to_quote: the tokens, which if present in args, to unquote
+ """
+ for i, arg in enumerate(args):
+ if arg in tokens_to_quote:
+ args[i] = quote_string(arg)
+
+
def unquote_specific_tokens(args: List[str], tokens_to_unquote: List[str]) -> None:
"""
- Unquote a specific tokens in a list of command-line arguments
+ Unquote 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
"""
diff --git a/docs/api/utils.rst b/docs/api/utils.rst
index d9166401..0013bb7a 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/tests/test_cmd2.py b/tests/test_cmd2.py
index 1e4f4844..8d64780a 100755
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -1639,16 +1639,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)
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):
@@ -1748,15 +1749,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
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):