summaryrefslogtreecommitdiff
path: root/cmd2.py
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2018-04-13 19:31:52 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2018-04-13 19:31:52 -0400
commit202cc074d10f61742e22dcdcea217ee03cc3cc17 (patch)
treea5b8b6bc4f565d9700a3e9ffcf2f77f951be0128 /cmd2.py
parent9d9e843845b57419e4be1abc65119b1d04dfa0f0 (diff)
downloadcmd2-git-202cc074d10f61742e22dcdcea217ee03cc3cc17.tar.gz
Simplifying adding opening quotes
Diffstat (limited to 'cmd2.py')
-rwxr-xr-xcmd2.py238
1 files changed, 72 insertions, 166 deletions
diff --git a/cmd2.py b/cmd2.py
index 167e71c7..ccddbbc3 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -1321,7 +1321,7 @@ class Cmd(cmd.Cmd):
On Success
tokens: list of unquoted tokens
this is generally the list needed for tab completion functions
- raw_tokens: list of tokens as they appear on the command line, meaning their quotes are preserved
+ raw_tokens: list of tokens with any quotes preserved
this can be used to know if a token was quoted or is missing a closing quote
Both lists are guaranteed to have at least 1 item
@@ -1349,7 +1349,7 @@ class Cmd(cmd.Cmd):
break
except ValueError:
# ValueError can be caused by missing closing quote
- if len(quotes_to_try) == 0:
+ 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.
@@ -1481,7 +1481,7 @@ class Cmd(cmd.Cmd):
matches = self.basic_complete(text, line, begidx, endidx, match_against)
# Display only the portion of the match that's being completed based on delimiter
- if len(matches) > 0:
+ if matches:
# Get the common beginning for the matches
common_prefix = os.path.commonprefix(matches)
@@ -1489,7 +1489,7 @@ class Cmd(cmd.Cmd):
# Calculate what portion of the match we are completing
display_token_index = 0
- if len(prefix_tokens) > 0:
+ if prefix_tokens:
display_token_index = len(prefix_tokens) - 1
# Get this portion for each match and store them in self.display_matches
@@ -1497,7 +1497,7 @@ class Cmd(cmd.Cmd):
match_tokens = cur_match.split(delimiter)
display_token = match_tokens[display_token_index]
- if len(display_token) == 0:
+ if not display_token:
display_token = delimiter
self.display_matches.append(display_token)
@@ -1759,7 +1759,7 @@ class Cmd(cmd.Cmd):
:return: List[str] - a list of possible tab completions
"""
# Don't tab complete anything if no shell command has been started
- if not complete_blank and len(text) == 0:
+ if not complete_blank and not text:
return []
# If there are no path characters in the search text, then do shell command completion in the user's path
@@ -1862,7 +1862,7 @@ class Cmd(cmd.Cmd):
if rl_type == RlType.GNU:
# Check if we should show display_matches
- if len(self.display_matches) > 0:
+ if self.display_matches:
matches_to_display = self.display_matches
# Recalculate longest_match_length for display_matches
@@ -1920,7 +1920,7 @@ class Cmd(cmd.Cmd):
if rl_type == RlType.PYREADLINE:
# Check if we should show display_matches
- if len(self.display_matches) > 0:
+ if self.display_matches:
matches_to_display = self.display_matches
else:
matches_to_display = matches
@@ -1931,117 +1931,6 @@ class Cmd(cmd.Cmd):
# Display the matches
orig_pyreadline_display(matches_to_display)
- def _handle_completion_token_quote(self, raw_completion_token):
- """
- This is called by complete() to add an opening quote to the token being completed if it is needed
- The readline input buffer is then updated with the new string
- :param raw_completion_token: str - the token being completed as it appears on the command line
- :return: True if a quote was added, False otherwise
- """
- if len(self.completion_matches) == 0:
- return False
-
- quote_added = False
-
- # Check if token on screen is already quoted
- if len(raw_completion_token) == 0 or raw_completion_token[0] not in QUOTES:
-
- # Get the common prefix of all matches. This is what be written to the screen.
- common_prefix = os.path.commonprefix(self.completion_matches)
-
- # If common_prefix contains a space, then we must add an opening quote to it
- if ' ' in common_prefix:
-
- # Figure out what kind of quote to add
- if '"' in common_prefix:
- quote = "'"
- else:
- quote = '"'
-
- new_completion_token = quote + common_prefix
-
- # Handle a single result
- if len(self.completion_matches) == 1:
- str_to_append = ''
-
- # Add a closing quote if allowed
- if self.allow_closing_quote:
- str_to_append += quote
-
- orig_line = readline.get_line_buffer()
- endidx = readline.get_endidx()
-
- # If we are at the end of the line, then add a space if allowed
- if self.allow_appended_space and endidx == len(orig_line):
- str_to_append += ' '
-
- new_completion_token += str_to_append
-
- # Update the line
- quote_added = True
- self._replace_completion_token(raw_completion_token, new_completion_token)
-
- return quote_added
-
- def _replace_completion_token(self, raw_completion_token, new_completion_token):
- """
- Replaces the token being completed in the readline line buffer which updates the screen
- This is used for things like adding an opening quote for completions with spaces
- :param raw_completion_token: str - the original token being completed as it appears on the command line
- :param new_completion_token: str- the replacement token
- :return: None
- """
- orig_line = readline.get_line_buffer()
- endidx = readline.get_endidx()
-
- starting_index = orig_line[:endidx].rfind(raw_completion_token)
-
- if starting_index != -1:
- # Build the new line
- new_line = orig_line[:starting_index]
- new_line += new_completion_token
- new_line += orig_line[endidx:]
-
- # Calculate the new cursor offset
- len_diff = len(new_completion_token) - len(raw_completion_token)
- new_point = endidx + len_diff
-
- # Replace the line and update the cursor offset
- self._set_readline_line(new_line)
- self._set_readline_point(new_point)
-
- @staticmethod
- def _set_readline_line(new_line):
- """
- Sets the readline line buffer
- :param new_line: str - the new line contents
- """
- if rl_type == RlType.GNU:
- # Byte encode the new line
- if six.PY3:
- encoded_line = bytes(new_line, encoding='utf-8')
- else:
- encoded_line = bytes(new_line)
-
- # Replace the line
- readline_lib.rl_replace_line(encoded_line, 0)
-
- elif rl_type == RlType.PYREADLINE:
- readline.rl.mode.l_buffer.set_line(new_line)
-
- @staticmethod
- def _set_readline_point(new_point):
- """
- Sets the cursor offset in the readline line buffer
- :param new_point: int - the new cursor offset
- """
- if rl_type == RlType.GNU:
- rl_point = ctypes.c_int.in_dll(readline_lib, "rl_point")
- rl_point.value = new_point
-
- elif rl_type == RlType.PYREADLINE:
- readline.rl.mode.l_buffer.point = new_point
-
# ----- Methods which override stuff in cmd -----
def complete(self, text, state):
@@ -2119,21 +2008,30 @@ class Cmd(cmd.Cmd):
self.completion_matches = []
return None
- # readline still performs word breaks after a quote. Therefore something like quoted search
- # text with a space would have resulted in begidx pointing to the middle of the token we
- # we want to complete. Figure out where that token actually begins and save the beginning
- # portion of it that was not part of the text readline gave us. We will remove it from the
- # completions later since readline expects them to start with the original text.
- actual_begidx = line[:endidx].rfind(tokens[-1])
+ # Text we need to remove from completions later
text_to_remove = ''
- if actual_begidx != begidx:
- text_to_remove = line[actual_begidx:begidx]
+ # Get the token being completed with any opening quote preserved
+ raw_completion_token = raw_tokens[-1]
- # Adjust text and where it begins so the completer routines
- # get unbroken search text to complete on.
- text = text_to_remove + text
- begidx = actual_begidx
+ # Check if the token being completed has an opening quote
+ if raw_completion_token and raw_completion_token[0] in QUOTES:
+ unclosed_quote = raw_completion_token[0]
+
+ # readline still performs word breaks after a quote. Therefore something like quoted search
+ # text with a space would have resulted in begidx pointing to the middle of the token we
+ # we want to complete. Figure out where that token actually begins and save the beginning
+ # portion of it that was not part of the text readline gave us. We will remove it from the
+ # completions later since readline expects them to start with the original text.
+ actual_begidx = line[:endidx].rfind(tokens[-1])
+
+ if actual_begidx != begidx:
+ text_to_remove = line[actual_begidx:begidx]
+
+ # Adjust text and where it begins so the completer routines
+ # get unbroken search text to complete on.
+ text = text_to_remove + text
+ begidx = actual_begidx
# Check if a valid command was entered
if command in self.get_all_commands():
@@ -2164,7 +2062,7 @@ class Cmd(cmd.Cmd):
# call the completer function for the current command
self.completion_matches = self._redirect_complete(text, line, begidx, endidx, compfunc)
- if len(self.completion_matches) > 0:
+ if self.completion_matches:
# Eliminate duplicates
matches_set = set(self.completion_matches)
@@ -2173,36 +2071,44 @@ class Cmd(cmd.Cmd):
display_matches_set = set(self.display_matches)
self.display_matches = list(display_matches_set)
- # Get the token being completed as it appears on the command line
- raw_completion_token = raw_tokens[-1]
-
- # Add an opening quote if needed
- if self._handle_completion_token_quote(raw_completion_token):
- # An opening quote was added and the screen was updated. Return no results.
- self.completion_matches = []
- return None
-
- if text_to_remove or shortcut_to_restore:
- # If self.display_matches is empty, then set it to self.completion_matches
- # before we alter them. That way the suggestions will reflect how we parsed
- # the token being completed and not how readline did.
- if len(self.display_matches) == 0:
- self.display_matches = copy.copy(self.completion_matches)
-
- # Check if we need to remove text from the beginning of tab completions
- if text_to_remove:
- self.completion_matches = \
- [m.replace(text_to_remove, '', 1) for m in self.completion_matches]
-
- # Check if we need to restore a shortcut in the tab completions
- # so it doesn't get erased from the command line
- if shortcut_to_restore:
- self.completion_matches = \
- [shortcut_to_restore + match for match in self.completion_matches]
-
- # If the token being completed starts with a quote then we know it has an unclosed quote
- if len(raw_completion_token) > 0 and raw_completion_token[0] in QUOTES:
- unclosed_quote = raw_completion_token[0]
+ # If self.display_matches is empty, then set it to self.completion_matches
+ # before we alter them. That way the suggestions will reflect how we parsed
+ # the token being completed and not how readline did.
+ if not self.display_matches:
+ self.display_matches = copy.copy(self.completion_matches)
+
+ # Check if we need to add an opening quote
+ if not unclosed_quote:
+
+ # Get the common prefix of all matches. This is the actual tab completion.
+ common_prefix = os.path.commonprefix(self.completion_matches)
+
+ # Join all matches into 1 string for ease of searching
+ all_matches_str = ''.join(self.completion_matches)
+
+ # If there is a common_prefix and any of the matches have a space,
+ # then we must add an opening quote to the matches.
+ if common_prefix and ' ' in all_matches_str:
+
+ # Figure out what kind of quote to add
+ if '"' in all_matches_str:
+ quote = "'"
+ else:
+ quote = '"'
+
+ unclosed_quote = quote
+ self.completion_matches = [quote + match for match in self.completion_matches]
+
+ # Check if we need to remove text from the beginning of tab completions
+ elif text_to_remove:
+ self.completion_matches = \
+ [m.replace(text_to_remove, '', 1) for m in self.completion_matches]
+
+ # Check if we need to restore a shortcut in the tab completions
+ # so it doesn't get erased from the command line
+ if shortcut_to_restore:
+ self.completion_matches = \
+ [shortcut_to_restore + match for match in self.completion_matches]
else:
# Complete token against aliases and command names
@@ -2226,7 +2132,7 @@ class Cmd(cmd.Cmd):
self.completion_matches[0] += str_to_append
# Otherwise sort matches
- elif len(self.completion_matches) > 0:
+ elif self.completion_matches:
self.completion_matches.sort()
self.display_matches.sort()
@@ -2871,7 +2777,7 @@ Usage: Usage: alias [name] | [<name> <value>]
alias save_results "print_results > out.txt"
"""
# If no args were given, then print a list of current aliases
- if len(arglist) == 0:
+ if not arglist:
for cur_alias in self.aliases:
self.poutput("alias {} {}".format(cur_alias, self.aliases[cur_alias]))
@@ -2919,7 +2825,7 @@ Usage: Usage: unalias [-a] name [name ...]
Options:
-a remove all alias definitions
"""
- if len(arglist) == 0:
+ if not arglist:
self.do_help('unalias')
if '-a' in arglist:
@@ -3233,7 +3139,7 @@ Usage: Usage: unalias [-a] name [name ...]
# Support expanding ~ in quoted paths
for index, _ in enumerate(tokens):
- if len(tokens[index]) > 0:
+ 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.
first_char = tokens[index][0]