summaryrefslogtreecommitdiff
path: root/cmd2
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2')
-rw-r--r--cmd2/cmd2.py52
-rw-r--r--cmd2/parsing.py26
2 files changed, 43 insertions, 35 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index c49ec0cc..1c8bb6b7 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -1720,7 +1720,7 @@ class Cmd(cmd.Cmd):
:return: tuple containing (command, args, line)
"""
statement = self.statement_parser.parse_command_only(line)
- return statement.command, statement.args, statement.command_and_args
+ return statement.command, statement, statement.command_and_args
def onecmd_plus_hooks(self, line: str) -> bool:
"""Top-level function called by cmdloop() to handle parsing a line and running the command and all of its hooks.
@@ -2217,8 +2217,7 @@ class Cmd(cmd.Cmd):
return stop
- @with_argument_list
- def do_alias(self, arglist: List[str]) -> None:
+ def do_alias(self, statement: Statement) -> None:
"""Define or display aliases
Usage: Usage: alias [name] | [<name> <value>]
@@ -2237,22 +2236,27 @@ Usage: Usage: alias [name] | [<name> <value>]
Example: alias ls !ls -lF
- If you want to use redirection or pipes in the alias, then either quote the tokens with these
- characters or quote the entire alias value.
+ If you want to use redirection or pipes in the alias, then quote them to avoid
+ the alias command itself from being redirected
Examples:
alias save_results print_results ">" out.txt
- alias save_results print_results "> out.txt"
- alias save_results "print_results > out.txt"
+ alias save_results print_results '>' out.txt
"""
+ # Get alias arguments as a list with quotes preserved
+ alias_arg_list = statement.arg_list
+
# If no args were given, then print a list of current aliases
- if not arglist:
+ if not alias_arg_list:
for cur_alias in self.aliases:
self.poutput("alias {} {}".format(cur_alias, self.aliases[cur_alias]))
+ return
+
+ # Get the alias name
+ name = alias_arg_list[0]
# The user is looking up an alias
- elif len(arglist) == 1:
- name = arglist[0]
+ if len(alias_arg_list) == 1:
if name in self.aliases:
self.poutput("alias {} {}".format(name, self.aliases[name]))
else:
@@ -2260,8 +2264,16 @@ Usage: Usage: alias [name] | [<name> <value>]
# The user is creating an alias
else:
- name = arglist[0]
- value = ' '.join(arglist[1:])
+ # Unquote redirection and pipes
+ index = 1
+ while index < len(alias_arg_list):
+ unquoted_arg = utils.strip_quotes(alias_arg_list[index])
+ if unquoted_arg in constants.REDIRECTION_TOKENS:
+ alias_arg_list[index] = unquoted_arg
+ index += 1
+
+ # Build the alias value string
+ value = ' '.join(alias_arg_list[1:])
# Validate the alias to ensure it doesn't include weird characters
# like terminators, output redirection, or whitespace
@@ -2598,24 +2610,20 @@ Usage: Usage: unalias [-a] name [name ...]
param = args.settable[0]
self.show(args, param)
- def do_shell(self, command: str) -> None:
+ def do_shell(self, statement: Statement) -> None:
"""Execute a command as if at the OS prompt.
Usage: shell <command> [arguments]"""
-
import subprocess
- try:
- # Use non-POSIX parsing to keep the quotes around the tokens
- tokens = shlex.split(command, posix=False)
- except ValueError as err:
- self.perror(err, traceback_war=False)
- return
+
+ # Get list of arguments to shell with quotes preserved
+ tokens = statement.arg_list
# Support expanding ~ in quoted paths
for index, _ in enumerate(tokens):
if tokens[index]:
- # Check if the token is quoted. Since shlex.split() passed, there isn't
- # an unclosed quote, so we only need to check the first character.
+ # Check if the token is quoted. Since parsing already passed, there isn't
+ # an unclosed quote. So we only need to check the first character.
first_char = tokens[index][0]
if first_char in constants.QUOTES:
tokens[index] = utils.strip_quotes(tokens[index])
diff --git a/cmd2/parsing.py b/cmd2/parsing.py
index b67cef10..2a4ae56f 100644
--- a/cmd2/parsing.py
+++ b/cmd2/parsing.py
@@ -5,7 +5,7 @@
import os
import re
import shlex
-from typing import List, Tuple, Dict
+from typing import List, Tuple, Dict, Union
from . import constants
from . import utils
@@ -33,10 +33,10 @@ class Statement(str):
:var multiline_command: if the command is a multiline command, the name of the
command, otherwise None
:type command: str or None
- :var args: the arguments to the command, not including any output
+ :var arg_list: list of arguments to the command, not including any output
redirection or terminators. quoted arguments remain
quoted.
- :type args: str or None
+ :type arg_list: list
:var: argv: a list of arguments a la sys.argv. Quotes, if any, are removed
from the elements of the list, and aliases and shortcuts
are expanded
@@ -61,7 +61,7 @@ class Statement(str):
*,
raw: str = None,
command: str = None,
- args: str = None,
+ arg_list: List[str] = None,
argv: List[str] = None,
multiline_command: str = None,
terminator: str = None,
@@ -78,7 +78,9 @@ class Statement(str):
stmt = str.__new__(cls, obj)
object.__setattr__(stmt, "raw", raw)
object.__setattr__(stmt, "command", command)
- object.__setattr__(stmt, "args", args)
+ if arg_list is None:
+ arg_list = []
+ object.__setattr__(stmt, "arg_list", arg_list)
if argv is None:
argv = []
object.__setattr__(stmt, "argv", argv)
@@ -96,10 +98,10 @@ class Statement(str):
Quoted arguments remain quoted.
"""
- if self.command and self.args:
- rtn = '{} {}'.format(self.command, self.args)
+ if self.command and self:
+ rtn = '{} {}'.format(self.command, self)
elif self.command:
- # we are trusting that if we get here that self.args is None
+ # there were no arguments to the command
rtn = self.command
else:
rtn = None
@@ -392,7 +394,7 @@ class StatementParser:
statement = Statement('' if args is None else args,
raw=line,
command=command,
- args=args,
+ arg_list=[] if len(argv) <= 1 else argv[1:],
argv=list(map(lambda x: utils.strip_quotes(x), argv)),
multiline_command=multiline_command,
terminator=terminator,
@@ -417,10 +419,9 @@ class StatementParser:
values in the following attributes:
- raw
- command
- - args
Different from parse(), this method does not remove redundant whitespace
- within statement.args. It does however, ensure args does not have
+ within the statement. It does however, ensure statement does not have
leading or trailing whitespace.
"""
# expand shortcuts and aliases
@@ -442,7 +443,7 @@ class StatementParser:
# no trailing whitespace
args = line[match.end(2):].rstrip()
# if the command is none that means the input was either empty
- # or something wierd like '>'. args should be None if we couldn't
+ # or something weird like '>'. args should be None if we couldn't
# parse a command
if not command or not args:
args = None
@@ -459,7 +460,6 @@ class StatementParser:
statement = Statement('' if args is None else args,
raw=rawinput,
command=command,
- args=args,
multiline_command=multiline_command,
)
return statement