Skip to content

Commit 910f2eb

Browse files
authored
Statement and StatementParser updates (#1579)
Statement Updates - 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. StatementParser Updates - Changed StatementParser.parse_command_only() to return a PartialStatement object.
1 parent c2d29d4 commit 910f2eb

File tree

8 files changed

+335
-398
lines changed

8 files changed

+335
-398
lines changed

CHANGELOG.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,20 @@ shell, and the option for a persistent bottom bar that can display realtime stat
3333
- `CompletionItem.descriptive_data` is now called `CompletionItem.table_row`.
3434
- `Cmd.default_sort_key` moved to `utils.DEFAULT_STR_SORT_KEY`.
3535
- Moved completion state data, which previously resided in `Cmd`, into other classes.
36-
1. `Cmd.matches_sorted` -> `Completions.is_sorted` and `Choices.is_sorted`
37-
1. `Cmd.completion_hint` -> `Completions.completion_hint`
38-
1. `Cmd.formatted_completions` -> `Completions.completion_table`
39-
1. `Cmd.matches_delimited` -> `Completions.is_delimited`
40-
1. `Cmd.allow_appended_space/allow_closing_quote` -> `Completions.allow_finalization`
36+
- `Cmd.matches_sorted` -> `Completions.is_sorted` and `Choices.is_sorted`
37+
- `Cmd.completion_hint` -> `Completions.completion_hint`
38+
- `Cmd.formatted_completions` -> `Completions.completion_table`
39+
- `Cmd.matches_delimited` -> `Completions.is_delimited`
40+
- `Cmd.allow_appended_space/allow_closing_quote` -> `Completions.allow_finalization`
4141
- Removed `flag_based_complete` and `index_based_complete` functions since their functionality
4242
is already provided in arpgarse-based completion.
43+
- Changed `Statement.multiline_command` from a string to a bool.
44+
- Made `Statement.arg_list` a property which generates the list on-demand.
45+
- Renamed `Statement.output` to `Statement.redirector`.
46+
- Renamed `Statement.output_to` to `Statement.redirect_to`.
47+
- Removed `Statement.pipe_to` since it can be handled by `Statement.redirector` and
48+
`Statement.redirect_to`.
49+
- Changed `StatementParser.parse_command_only()` to return a `PartialStatement` object.
4350
- Enhancements
4451
- New `cmd2.Cmd` parameters
4552
- **auto_suggest**: (boolean) if `True`, provide fish shell style auto-suggestions. These

cmd2/cmd2.py

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ def _(event: Any) -> None: # pragma: no cover
595595
if os.path.exists(startup_script):
596596
script_cmd = f"run_script {su.quote(startup_script)}"
597597
if silence_startup_script:
598-
script_cmd += f" {constants.REDIRECTION_OUTPUT} {os.devnull}"
598+
script_cmd += f" {constants.REDIRECTION_OVERWRITE} {os.devnull}"
599599
self._startup_commands.append(script_cmd)
600600

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

21412141
if prior_token == constants.REDIRECTION_PIPE:
21422142
do_shell_completion = True
2143-
elif in_pipe or prior_token in (constants.REDIRECTION_OUTPUT, constants.REDIRECTION_APPEND):
2143+
elif in_pipe or prior_token in (constants.REDIRECTION_OVERWRITE, constants.REDIRECTION_APPEND):
21442144
do_path_completion = True
21452145

21462146
prior_token = cur_token
@@ -2190,14 +2190,14 @@ def _perform_completion(
21902190
# Parse the command line to get the command token.
21912191
command = ''
21922192
if custom_settings is None:
2193-
statement = self.statement_parser.parse_command_only(line)
2194-
command = statement.command
2193+
partial_statement = self.statement_parser.parse_command_only(line)
2194+
command = partial_statement.command
21952195

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

2200-
expanded_line = statement.command_and_args
2200+
expanded_line = partial_statement.command_and_args
22012201

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

26482648
def onecmd_plus_hooks(
26492649
self,
@@ -2853,8 +2853,8 @@ def _complete_statement(self, line: str) -> Statement:
28532853
except Cmd2ShlexError:
28542854
# we have an unclosed quotation mark, let's parse only the command
28552855
# and see if it's a multiline
2856-
statement = self.statement_parser.parse_command_only(line)
2857-
if not statement.multiline_command:
2856+
partial_statement = self.statement_parser.parse_command_only(line)
2857+
if not partial_statement.multiline_command:
28582858
# not a multiline command, so raise the exception
28592859
raise
28602860

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

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

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

2925-
# This will be true when a macro was used
2924+
# If a macro was expanded, the 'statement' now contains the expanded text.
2925+
# We need to swap the 'raw' attribute back to the string the user typed
2926+
# so history shows the original line.
29262927
if orig_line != statement.raw:
2927-
# Build a Statement that contains the resolved macro line
2928-
# but the originally typed line for its raw member.
2929-
statement = Statement(
2930-
statement.args,
2931-
raw=orig_line,
2932-
command=statement.command,
2933-
arg_list=statement.arg_list,
2934-
multiline_command=statement.multiline_command,
2935-
terminator=statement.terminator,
2936-
suffix=statement.suffix,
2937-
pipe_to=statement.pipe_to,
2938-
output=statement.output,
2939-
output_to=statement.output_to,
2940-
)
2928+
statement_dict = statement.to_dict()
2929+
statement_dict["raw"] = orig_line
2930+
statement = Statement.from_dict(statement_dict)
2931+
29412932
return statement
29422933

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

3007-
elif statement.pipe_to:
2998+
elif statement.redirector == constants.REDIRECTION_PIPE:
30082999
# Create a pipe with read and write sides
30093000
read_fd, write_fd = os.pipe()
30103001

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

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

3058-
elif statement.output:
3059-
if statement.output_to:
3049+
elif statement.redirector in (constants.REDIRECTION_OVERWRITE, constants.REDIRECTION_APPEND):
3050+
if statement.redirect_to:
30603051
# redirecting to a file
30613052
# statement.output can only contain REDIRECTION_APPEND or REDIRECTION_OUTPUT
3062-
mode = 'a' if statement.output == constants.REDIRECTION_APPEND else 'w'
3053+
mode = 'a' if statement.redirector == constants.REDIRECTION_APPEND else 'w'
30633054
try:
30643055
# Use line buffering
3065-
new_stdout = cast(TextIO, open(su.strip_quotes(statement.output_to), mode=mode, buffering=1)) # noqa: SIM115
3056+
new_stdout = cast(TextIO, open(su.strip_quotes(statement.redirect_to), mode=mode, buffering=1)) # noqa: SIM115
30663057
except OSError as ex:
30673058
raise RedirectionError('Failed to redirect output') from ex
30683059

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

3096-
if statement.output == constants.REDIRECTION_APPEND:
3087+
if statement.redirector == constants.REDIRECTION_APPEND:
30973088
self.stdout.write(current_paste_buffer)
30983089
self.stdout.flush()
30993090

@@ -3111,7 +3102,10 @@ def _restore_output(self, statement: Statement, saved_redir_state: utils.Redirec
31113102
"""
31123103
if saved_redir_state.redirecting:
31133104
# If we redirected output to the clipboard
3114-
if statement.output and not statement.output_to:
3105+
if (
3106+
statement.redirector in (constants.REDIRECTION_OVERWRITE, constants.REDIRECTION_APPEND)
3107+
and not statement.redirect_to
3108+
):
31153109
self.stdout.seek(0)
31163110
write_to_paste_buffer(self.stdout.read())
31173111

cmd2/constants.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
# Used for command parsing, output redirection, completion, and word breaks. Do not change.
99
QUOTES = ['"', "'"]
1010
REDIRECTION_PIPE = '|'
11-
REDIRECTION_OUTPUT = '>'
11+
REDIRECTION_OVERWRITE = '>'
1212
REDIRECTION_APPEND = '>>'
13-
REDIRECTION_CHARS = [REDIRECTION_PIPE, REDIRECTION_OUTPUT]
14-
REDIRECTION_TOKENS = [REDIRECTION_PIPE, REDIRECTION_OUTPUT, REDIRECTION_APPEND]
13+
REDIRECTION_CHARS = [REDIRECTION_PIPE, REDIRECTION_OVERWRITE]
14+
REDIRECTION_TOKENS = [REDIRECTION_PIPE, REDIRECTION_OVERWRITE, REDIRECTION_APPEND]
1515
COMMENT_CHAR = '#'
1616
MULTILINE_TERMINATOR = ';'
1717

cmd2/history.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ class to gain access to the historical record.
146146
"""
147147

148148
# Used in JSON dictionaries
149-
_history_version = '1.0.0'
149+
_history_version = '4.0.0'
150150
_history_version_field = 'history_version'
151151
_history_items_field = 'history_items'
152152

0 commit comments

Comments
 (0)