From 61a77ac3cef60565d5056f50b687edab7fa4531f Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Tue, 2 Jul 2024 09:30:06 +0200 Subject: [PATCH] Callable alias: fixed capturing stdout in case of redirect (#5527) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation Closes #5512. The issue was introduced in #4445. It's needed to add checking the redirect case. ### Before ```xsh cd /tmp @aliases.register('a') def _a(): print("1-hello") echo 2-hello a > o.txt # 2-hello cat o.txt # 1-hello ``` ### After ```xsh cd /tmp @aliases.register('a') def _a(): print("1-hello") echo 2-hello a > o.txt cat o.txt # 1-hello # 2-hello ``` ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- news/callias_capture_redirect.rst | 23 +++++++++++++++++++++++ tests/procs/test_specs.py | 19 +++++++++++++++++++ xonsh/procs/specs.py | 27 +++++++++++++++++++-------- 3 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 news/callias_capture_redirect.rst diff --git a/news/callias_capture_redirect.rst b/news/callias_capture_redirect.rst new file mode 100644 index 000000000..243972f3e --- /dev/null +++ b/news/callias_capture_redirect.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Callable alias: fixed capturing stdout in case of redirect e.g. `a > file` (#5527). + +**Security:** + +* diff --git a/tests/procs/test_specs.py b/tests/procs/test_specs.py index d3ee5f1c3..95178a047 100644 --- a/tests/procs/test_specs.py +++ b/tests/procs/test_specs.py @@ -150,6 +150,25 @@ def test_capture_always( assert exp in capfd.readouterr().out +@skip_if_on_windows +@pytest.mark.flaky(reruns=3, reruns_delay=2) +def test_callias_captured_redirect(xonsh_session, tmpdir): + @xonsh_session.aliases.register("a") + def _a(a, i, o, e): + print("print_stdout") + xonsh_session.subproc_captured_stdout(["echo", "cap_stdout"]) + xonsh_session.subproc_captured_object(["echo", "cap_object"]) + xonsh_session.subproc_captured_hiddenobject(["echo", "hiddenobject"]) + xonsh_session.subproc_uncaptured(["echo", "uncaptured"]) + print("print_error", file=e) + + f = tmpdir / "capture.txt" + cmd = (["a", (">", str(f))],) + specs = cmds_to_specs(cmd, captured="hiddenobject") + _run_command_pipeline(specs, cmd).end() + assert f.read_text(encoding="utf-8") == "print_stdout\nhiddenobject\n" + + @skip_if_on_windows @pytest.mark.parametrize("captured", ["stdout", "object"]) @pytest.mark.parametrize("interactive", [True, False]) diff --git a/xonsh/procs/specs.py b/xonsh/procs/specs.py index 0530fb037..d16c6d3ce 100644 --- a/xonsh/procs/specs.py +++ b/xonsh/procs/specs.py @@ -1021,19 +1021,30 @@ def cmds_to_specs(cmds, captured=False, envs=None): # Apply boundary conditions if not XSH.env.get("XONSH_CAPTURE_ALWAYS"): - # Make sure sub-specs are always captured. - # I.e. ![some_alias | grep x] $(some_alias) - specs_to_capture = specs if captured in STDOUT_CAPTURE_KINDS else specs[:-1] - for spec in specs_to_capture: - if spec.env is None: - spec.env = {"XONSH_CAPTURE_ALWAYS": True} - else: - spec.env.setdefault("XONSH_CAPTURE_ALWAYS", True) + # Make sure sub-specs are always captured in case: + # `![some_alias | grep x]`, `$(some_alias)`, `some_alias > file`. + last = spec + is_redirected_stdout = bool(last.stdout) + specs_to_capture = ( + specs + if captured in STDOUT_CAPTURE_KINDS or is_redirected_stdout + else specs[:-1] + ) + _set_specs_capture_always(specs_to_capture) _update_last_spec(specs[-1]) return specs +def _set_specs_capture_always(specs_to_capture): + """Set XONSH_CAPTURE_ALWAYS for all specs.""" + for spec in specs_to_capture: + if spec.env is None: + spec.env = {"XONSH_CAPTURE_ALWAYS": True} + else: + spec.env.setdefault("XONSH_CAPTURE_ALWAYS", True) + + def _shell_set_title(cmds): if XSH.env.get("XONSH_INTERACTIVE") and XSH.shell is not None: # context manager updates the command information that gets