Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,20 @@ shell, and the option for a persistent bottom bar that can display realtime stat
- `CompletionItem.descriptive_data` is now called `CompletionItem.table_row`.
- `Cmd.default_sort_key` moved to `utils.DEFAULT_STR_SORT_KEY`.
- Moved completion state data, which previously resided in `Cmd`, into other classes.
1. `Cmd.matches_sorted` -> `Completions.is_sorted` and `Choices.is_sorted`
1. `Cmd.completion_hint` -> `Completions.completion_hint`
1. `Cmd.formatted_completions` -> `Completions.completion_table`
1. `Cmd.matches_delimited` -> `Completions.is_delimited`
1. `Cmd.allow_appended_space/allow_closing_quote` -> `Completions.allow_finalization`
- `Cmd.matches_sorted` -> `Completions.is_sorted` and `Choices.is_sorted`
- `Cmd.completion_hint` -> `Completions.completion_hint`
- `Cmd.formatted_completions` -> `Completions.completion_table`
- `Cmd.matches_delimited` -> `Completions.is_delimited`
- `Cmd.allow_appended_space/allow_closing_quote` -> `Completions.allow_finalization`
- Removed `flag_based_complete` and `index_based_complete` functions since their functionality
is already provided in arpgarse-based completion.
- Changed `Statement.multiline_command` from a string to a bool.
- Made `Statement.arg_list` a property which generates the list on-demand.
- Renamed `Statement.output` to `Statement.redirector`.
- Renamed `Statement.output_to` to `Statement.redirect_to`.
- Removed `Statement.pipe_to` since it can be handled by `Statement.redirector` and
`Statement.redirect_to`.
- Changed `StatementParser.parse_command_only()` to return a `PartialStatement` object.
- Enhancements
- New `cmd2.Cmd` parameters
- **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These
Expand Down
62 changes: 28 additions & 34 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ def _(event: Any) -> None: # pragma: no cover
if os.path.exists(startup_script):
script_cmd = f"run_script {su.quote(startup_script)}"
if silence_startup_script:
script_cmd += f" {constants.REDIRECTION_OUTPUT} {os.devnull}"
script_cmd += f" {constants.REDIRECTION_OVERWRITE} {os.devnull}"
self._startup_commands.append(script_cmd)

# Transcript files to run instead of interactive command loop
Expand Down Expand Up @@ -2140,7 +2140,7 @@ def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, com

if prior_token == constants.REDIRECTION_PIPE:
do_shell_completion = True
elif in_pipe or prior_token in (constants.REDIRECTION_OUTPUT, constants.REDIRECTION_APPEND):
elif in_pipe or prior_token in (constants.REDIRECTION_OVERWRITE, constants.REDIRECTION_APPEND):
do_path_completion = True

prior_token = cur_token
Expand Down Expand Up @@ -2190,14 +2190,14 @@ def _perform_completion(
# Parse the command line to get the command token.
command = ''
if custom_settings is None:
statement = self.statement_parser.parse_command_only(line)
command = statement.command
partial_statement = self.statement_parser.parse_command_only(line)
command = partial_statement.command

# Malformed command line (e.g. quoted command token)
if not command:
return Completions()

expanded_line = statement.command_and_args
expanded_line = partial_statement.command_and_args

if not expanded_line[-1:].isspace():
# Unquoted trailing whitespace gets stripped by parse_command_only().
Expand Down Expand Up @@ -2642,8 +2642,8 @@ def parseline(self, line: str) -> tuple[str, str, str]:
:param line: line read by prompt-toolkit
:return: tuple containing (command, args, line)
"""
statement = self.statement_parser.parse_command_only(line)
return statement.command, statement.args, statement.command_and_args
partial_statement = self.statement_parser.parse_command_only(line)
return partial_statement.command, partial_statement.args, partial_statement.command_and_args

def onecmd_plus_hooks(
self,
Expand Down Expand Up @@ -2853,8 +2853,8 @@ def _complete_statement(self, line: str) -> Statement:
except Cmd2ShlexError:
# we have an unclosed quotation mark, let's parse only the command
# and see if it's a multiline
statement = self.statement_parser.parse_command_only(line)
if not statement.multiline_command:
partial_statement = self.statement_parser.parse_command_only(line)
if not partial_statement.multiline_command:
# not a multiline command, so raise the exception
raise

Expand Down Expand Up @@ -2907,8 +2907,7 @@ def _input_line_to_statement(self, line: str) -> Statement:
# Make sure all input has been read and convert it to a Statement
statement = self._complete_statement(line)

# If this is the first loop iteration, save the original line and stop
# combining multiline history entries in the remaining iterations.
# If this is the first loop iteration, save the original line
if orig_line is None:
orig_line = statement.raw

Expand All @@ -2922,22 +2921,14 @@ def _input_line_to_statement(self, line: str) -> Statement:
else:
break

# This will be true when a macro was used
# If a macro was expanded, the 'statement' now contains the expanded text.
# We need to swap the 'raw' attribute back to the string the user typed
# so history shows the original line.
if orig_line != statement.raw:
# Build a Statement that contains the resolved macro line
# but the originally typed line for its raw member.
statement = Statement(
statement.args,
raw=orig_line,
command=statement.command,
arg_list=statement.arg_list,
multiline_command=statement.multiline_command,
terminator=statement.terminator,
suffix=statement.suffix,
pipe_to=statement.pipe_to,
output=statement.output,
output_to=statement.output_to,
)
statement_dict = statement.to_dict()
statement_dict["raw"] = orig_line
statement = Statement.from_dict(statement_dict)

return statement

def _resolve_macro(self, statement: Statement) -> str | None:
Expand Down Expand Up @@ -3004,7 +2995,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
# Don't return since we set some state variables at the end of the function
pass

elif statement.pipe_to:
elif statement.redirector == constants.REDIRECTION_PIPE:
# Create a pipe with read and write sides
read_fd, write_fd = os.pipe()

Expand All @@ -3028,7 +3019,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:

# For any stream that is a StdSim, we will use a pipe so we can capture its output
proc = subprocess.Popen( # noqa: S602
statement.pipe_to,
statement.redirect_to,
stdin=subproc_stdin,
stdout=subprocess.PIPE if isinstance(self.stdout, utils.StdSim) else self.stdout, # type: ignore[unreachable]
stderr=subprocess.PIPE if isinstance(sys.stderr, utils.StdSim) else sys.stderr,
Expand All @@ -3055,14 +3046,14 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
if stdouts_match:
sys.stdout = self.stdout

elif statement.output:
if statement.output_to:
elif statement.redirector in (constants.REDIRECTION_OVERWRITE, constants.REDIRECTION_APPEND):
if statement.redirect_to:
# redirecting to a file
# statement.output can only contain REDIRECTION_APPEND or REDIRECTION_OUTPUT
mode = 'a' if statement.output == constants.REDIRECTION_APPEND else 'w'
mode = 'a' if statement.redirector == constants.REDIRECTION_APPEND else 'w'
try:
# Use line buffering
new_stdout = cast(TextIO, open(su.strip_quotes(statement.output_to), mode=mode, buffering=1)) # noqa: SIM115
new_stdout = cast(TextIO, open(su.strip_quotes(statement.redirect_to), mode=mode, buffering=1)) # noqa: SIM115
except OSError as ex:
raise RedirectionError('Failed to redirect output') from ex

Expand Down Expand Up @@ -3093,7 +3084,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
if stdouts_match:
sys.stdout = self.stdout

if statement.output == constants.REDIRECTION_APPEND:
if statement.redirector == constants.REDIRECTION_APPEND:
self.stdout.write(current_paste_buffer)
self.stdout.flush()

Expand All @@ -3111,7 +3102,10 @@ def _restore_output(self, statement: Statement, saved_redir_state: utils.Redirec
"""
if saved_redir_state.redirecting:
# If we redirected output to the clipboard
if statement.output and not statement.output_to:
if (
statement.redirector in (constants.REDIRECTION_OVERWRITE, constants.REDIRECTION_APPEND)
and not statement.redirect_to
):
self.stdout.seek(0)
write_to_paste_buffer(self.stdout.read())

Expand Down
6 changes: 3 additions & 3 deletions cmd2/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
# Used for command parsing, output redirection, completion, and word breaks. Do not change.
QUOTES = ['"', "'"]
REDIRECTION_PIPE = '|'
REDIRECTION_OUTPUT = '>'
REDIRECTION_OVERWRITE = '>'
REDIRECTION_APPEND = '>>'
REDIRECTION_CHARS = [REDIRECTION_PIPE, REDIRECTION_OUTPUT]
REDIRECTION_TOKENS = [REDIRECTION_PIPE, REDIRECTION_OUTPUT, REDIRECTION_APPEND]
REDIRECTION_CHARS = [REDIRECTION_PIPE, REDIRECTION_OVERWRITE]
REDIRECTION_TOKENS = [REDIRECTION_PIPE, REDIRECTION_OVERWRITE, REDIRECTION_APPEND]
COMMENT_CHAR = '#'
MULTILINE_TERMINATOR = ';'

Expand Down
2 changes: 1 addition & 1 deletion cmd2/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ class to gain access to the historical record.
"""

# Used in JSON dictionaries
_history_version = '1.0.0'
_history_version = '4.0.0'
_history_version_field = 'history_version'
_history_items_field = 'history_items'

Expand Down
Loading
Loading