summaryrefslogtreecommitdiff
path: root/cmd2/cmd2.py
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2/cmd2.py')
-rw-r--r--cmd2/cmd2.py102
1 files changed, 55 insertions, 47 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index c49ec0cc..2c086033 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -692,8 +692,7 @@ class Cmd(cmd.Cmd):
elif rl_type == RlType.PYREADLINE:
readline.rl.mode._display_completions = self._display_matches_pyreadline
- def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> Tuple[Optional[List[str]],
- Optional[List[str]]]:
+ def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> Tuple[List[str], List[str]]:
"""
Used by tab completion functions to get all tokens through the one being completed
:param line: the current input line with leading whitespace removed
@@ -710,7 +709,7 @@ class Cmd(cmd.Cmd):
The last item in both lists is the token being tab completed
On Failure
- Both items are None
+ Two empty lists
"""
import copy
unclosed_quote = ''
@@ -730,21 +729,21 @@ class Cmd(cmd.Cmd):
if not unclosed_quote and begidx == tmp_endidx:
initial_tokens.append('')
break
- except ValueError:
- # ValueError can be caused by missing closing quote
- if not quotes_to_try:
- # Since we have no more quotes to try, something else
- # is causing the parsing error. Return None since
- # this means the line is malformed.
- return None, None
-
- # Add a closing quote and try to parse again
- unclosed_quote = quotes_to_try[0]
- quotes_to_try = quotes_to_try[1:]
-
- tmp_line = line[:endidx]
- tmp_line += unclosed_quote
- tmp_endidx = endidx + 1
+ except ValueError as ex:
+ # Make sure the exception was due to an unclosed quote and
+ # we haven't exhausted the closing quotes to try
+ if str(ex) == "No closing quotation" and quotes_to_try:
+ # Add a closing quote and try to parse again
+ unclosed_quote = quotes_to_try[0]
+ quotes_to_try = quotes_to_try[1:]
+
+ tmp_line = line[:endidx]
+ tmp_line += unclosed_quote
+ tmp_endidx = endidx + 1
+ else:
+ # The parsing error is not caused by unclosed quotes.
+ # Return empty lists since this means the line is malformed.
+ return [], []
if self.allow_redirection:
@@ -910,7 +909,7 @@ class Cmd(cmd.Cmd):
"""
# Get all tokens through the one being completed
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
- if tokens is None:
+ if not tokens:
return []
completions_matches = []
@@ -953,7 +952,7 @@ class Cmd(cmd.Cmd):
"""
# Get all tokens through the one being completed
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
- if tokens is None:
+ if not tokens:
return []
matches = []
@@ -1190,7 +1189,7 @@ class Cmd(cmd.Cmd):
# Get all tokens through the one being completed. We want the raw tokens
# so we can tell if redirection strings are quoted and ignore them.
_, raw_tokens = self.tokens_for_completion(line, begidx, endidx)
- if raw_tokens is None:
+ if not raw_tokens:
return []
if len(raw_tokens) > 1:
@@ -1398,9 +1397,9 @@ class Cmd(cmd.Cmd):
# Get all tokens through the one being completed
tokens, raw_tokens = self.tokens_for_completion(line, begidx, endidx)
- # Either had a parsing error or are trying to complete the command token
+ # Check if we either had a parsing error or are trying to complete the command token
# The latter can happen if " or ' was entered as the command
- if tokens is None or len(tokens) == 1:
+ if len(tokens) <= 1:
self.completion_matches = []
return None
@@ -1550,9 +1549,10 @@ class Cmd(cmd.Cmd):
completer = AutoCompleter(argparser, cmd2_app=self)
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
- results = completer.complete_command(tokens, text, line, begidx, endidx)
+ if not tokens:
+ return []
- return results
+ return completer.complete_command(tokens, text, line, begidx, endidx)
def get_all_commands(self) -> List[str]:
"""Returns a list of all commands."""
@@ -1589,7 +1589,7 @@ class Cmd(cmd.Cmd):
# Get all tokens through the one being completed
tokens, _ = self.tokens_for_completion(line, begidx, endidx)
- if tokens is None:
+ if not tokens:
return []
matches = []
@@ -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])