Skip to content

Commit

Permalink
Refactored _perform_completion() to remove need for a cast() call
Browse files Browse the repository at this point in the history
Changed CommandResult.stderr to a str instead of Optional[str]
  • Loading branch information
kmvanbrunt authored and anselor committed Apr 2, 2021
1 parent 48d26a3 commit 9e62a0a
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 26 deletions.
32 changes: 17 additions & 15 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1856,12 +1856,10 @@ def _perform_completion(
ArgparseCompleter,
)

unclosed_quote = ''
command: Optional[str] = None

# If custom_settings is None, then we are completing a command's arguments
# If custom_settings is None, then we are completing a command's argument.
# Parse the command line to get the command token.
command = ''
if custom_settings is None:
# Parse the command line
statement = self.statement_parser.parse_command_only(line)
command = statement.command

Expand Down Expand Up @@ -1891,7 +1889,7 @@ def _perform_completion(
if not tokens: # pragma: no cover
return

# Determine the completer function to use
# Determine the completer function to use for the command's argument
if custom_settings is None:
# Check if a macro was entered
if command in self.macros:
Expand Down Expand Up @@ -1923,7 +1921,7 @@ def _perform_completion(
# Not a recognized macro or command
else:
# Check if this command should be run as a shell command
if self.default_to_shell and command in utils.get_exes_in_path(cast(str, command)):
if self.default_to_shell and command in utils.get_exes_in_path(command):
completer_func = self.path_complete
else:
completer_func = self.completedefault # type: ignore[assignment]
Expand All @@ -1941,11 +1939,15 @@ def _perform_completion(
# Get the token being completed with any opening quote preserved
raw_completion_token = raw_tokens[-1]

# Used for adding quotes to the completion token
completion_token_quote = ''

# Check if the token being completed has an opening quote
if raw_completion_token and raw_completion_token[0] in constants.QUOTES:

# Since the token is still being completed, we know the opening quote is unclosed
unclosed_quote = raw_completion_token[0]
# Since the token is still being completed, we know the opening quote is unclosed.
# Save the quote so we can add a matching closing quote later.
completion_token_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
Expand Down Expand Up @@ -1981,7 +1983,7 @@ def _perform_completion(
self.display_matches = copy.copy(self.completion_matches)

# Check if we need to add an opening quote
if not unclosed_quote:
if not completion_token_quote:

add_quote = False

Expand All @@ -2004,19 +2006,19 @@ def _perform_completion(
if add_quote:
# Figure out what kind of quote to add and save it as the unclosed_quote
if any('"' in match for match in self.completion_matches):
unclosed_quote = "'"
completion_token_quote = "'"
else:
unclosed_quote = '"'
completion_token_quote = '"'

self.completion_matches = [unclosed_quote + match for match in self.completion_matches]
self.completion_matches = [completion_token_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 = [match.replace(text_to_remove, '', 1) for match in self.completion_matches]

# If we have one result, then add a closing quote if needed and allowed
if len(self.completion_matches) == 1 and self.allow_closing_quote and unclosed_quote:
self.completion_matches[0] += unclosed_quote
if len(self.completion_matches) == 1 and self.allow_closing_quote and completion_token_quote:
self.completion_matches[0] += completion_token_quote

def complete( # type: ignore[override]
self, text: str, state: int, custom_settings: Optional[utils.CustomCompletionSettings] = None
Expand Down
7 changes: 3 additions & 4 deletions cmd2/py_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ class CommandResult(NamedTuple):
:stdout: str - output captured from stdout while this command is executing
:stderr: str - output captured from stderr while this command is executing
None if no error captured.
:stop: bool - return value of onecmd_plus_hooks after it runs the given
command line.
:data: possible data populated by the command.
Expand Down Expand Up @@ -66,7 +65,7 @@ class CommandResult(NamedTuple):
"""

stdout: str = ''
stderr: Optional[str] = None
stderr: str = ''
stop: bool = False
data: Any = None

Expand Down Expand Up @@ -132,10 +131,10 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul
self._cmd2_app.stdout = cast(IO[str], copy_cmd_stdout.inner_stream)
self.stop = stop or self.stop

# Save the output. If stderr is empty, set it to None.
# Save the result
result = CommandResult(
stdout=copy_cmd_stdout.getvalue(),
stderr=copy_stderr.getvalue() if copy_stderr.getvalue() else None,
stderr=copy_stderr.getvalue(),
stop=stop,
data=self._cmd2_app.last_result,
)
Expand Down
10 changes: 3 additions & 7 deletions tests_isolated/test_commandset/test_commandset.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,28 +257,24 @@ def test_commandset_decorators(command_sets_app):
assert 'extra1' in result.data['unknown']
assert 'extra2' in result.data['unknown']
assert result.data['arg1'] == 'juice'
assert result.stderr is None
assert not result.stderr

result = command_sets_app.app_cmd('durian juice extra1 extra2')
assert len(result.data['args']) == 3
assert 'juice' in result.data['args']
assert 'extra1' in result.data['args']
assert 'extra2' in result.data['args']
assert result.stderr is None
assert not result.stderr

result = command_sets_app.app_cmd('durian')
assert len(result.data['args']) == 0
assert result.stderr is None
assert not result.stderr

result = command_sets_app.app_cmd('elderberry')
assert result.stderr is not None
assert len(result.stderr) > 0
assert 'arguments are required' in result.stderr
assert result.data is None

result = command_sets_app.app_cmd('elderberry a b')
assert result.stderr is not None
assert len(result.stderr) > 0
assert 'unrecognized arguments' in result.stderr
assert result.data is None

Expand Down

0 comments on commit 9e62a0a

Please sign in to comment.