Skip to content

Commit df1db8a

Browse files
committed
Added interactive pipe mode.
1 parent c488828 commit df1db8a

File tree

2 files changed

+46
-11
lines changed

2 files changed

+46
-11
lines changed

cmd2/cmd2.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,12 @@ def __init__(
392392
self.default_to_shell = False # Attempt to run unrecognized commands as shell commands
393393
self.allow_redirection = allow_redirection # Security setting to prevent redirection of stdout
394394

395+
# If True, cmd2 treats redirected input (pipes/files) as an interactive session.
396+
# It will display the prompt before reading each line to synchronize with
397+
# automation tools (like Pexpect) and will skip echoing the input to prevent
398+
# duplicate prompts in the output.
399+
self.interactive_pipe = False
400+
395401
# Attributes which ARE dynamically settable via the set command at runtime
396402
self.always_show_hint = False
397403
self.debug = False
@@ -3218,18 +3224,25 @@ def _read_raw_input(
32183224
return session.prompt(prompt, completer=completer, **prompt_kwargs)
32193225

32203226
# We're not at a terminal, so we're likely reading from a file or a pipe.
3221-
# We wait for a line of data before we print anything.
3227+
prompt_obj = prompt() if callable(prompt) else prompt
3228+
prompt_str = prompt_obj.value if isinstance(prompt_obj, ANSI) else prompt_obj
3229+
3230+
# If this is an interactive pipe, then display the prompt first
3231+
if self.interactive_pipe:
3232+
self.poutput(prompt_str, end='')
3233+
self.stdout.flush()
3234+
3235+
# Wait for the next line of input
32223236
line = self.stdin.readline()
32233237

32243238
# If the stream is empty, we've reached the end of the input.
32253239
if not line:
32263240
raise EOFError
32273241

3228-
# If echo is on, we want the output to look like a session transcript.
3229-
# Print the prompt and the command before the results.
3230-
if self.echo:
3231-
prompt_obj = prompt() if callable(prompt) else prompt
3232-
prompt_str = prompt_obj.value if isinstance(prompt_obj, ANSI) else prompt_obj
3242+
# If not interactive and echo is on, we want the output to simulate a
3243+
# live session. Print the prompt and the command so they appear in the
3244+
# output stream before the results.
3245+
if not self.interactive_pipe and self.echo:
32333246
end = "" if line.endswith('\n') else "\n"
32343247

32353248
self.poutput(f'{prompt_str}{line}', end=end)

tests/test_cmd2.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1918,20 +1918,42 @@ def test_read_raw_input_tty(base_app: cmd2.Cmd) -> None:
19181918
assert result == "foo"
19191919

19201920

1921-
def test_read_raw_input_pipe() -> None:
1921+
def test_read_raw_input_interactive_pipe(capsys) -> None:
1922+
prompt = "prompt> "
19221923
app = cmd2.Cmd(stdin=io.StringIO("input from pipe\n"))
1923-
result = app._read_raw_input("prompt> ", app.session, DummyCompleter())
1924+
app.interactive_pipe = True
1925+
result = app._read_raw_input(prompt, app.session, DummyCompleter())
19241926
assert result == "input from pipe"
19251927

1928+
# In interactive mode, _read_raw_input() prints the prompt.
1929+
captured = capsys.readouterr()
1930+
assert captured.out == prompt
1931+
1932+
1933+
def test_read_raw_input_non_interactive_pipe_echo_off(capsys) -> None:
1934+
prompt = "prompt> "
1935+
app = cmd2.Cmd(stdin=io.StringIO("input from pipe\n"))
1936+
app.interactive_pipe = False
1937+
app.echo = False
1938+
result = app._read_raw_input(prompt, app.session, DummyCompleter())
1939+
assert result == "input from pipe"
1940+
1941+
# When not echoing in non-interactive mode, _read_raw_input() prints nothing.
1942+
captured = capsys.readouterr()
1943+
assert not captured.out
1944+
19261945

1927-
def test_read_raw_input_pipe_echo(capsys) -> None:
1946+
def test_read_raw_input_non_interactive_pipe_echo_on(capsys) -> None:
1947+
prompt = "prompt> "
19281948
app = cmd2.Cmd(stdin=io.StringIO("input from pipe\n"))
1949+
app.interactive_pipe = False
19291950
app.echo = True
1930-
result = app._read_raw_input("prompt> ", app.session, DummyCompleter())
1951+
result = app._read_raw_input(prompt, app.session, DummyCompleter())
19311952
assert result == "input from pipe"
19321953

1954+
# When echoing in non-interactive mode, _read_raw_input() prints the prompt and input text.
19331955
captured = capsys.readouterr()
1934-
assert "prompt> input from pipe" in captured.out
1956+
assert f"{prompt}input from pipe\n" == captured.out
19351957

19361958

19371959
def test_read_raw_input_eof() -> None:

0 commit comments

Comments
 (0)