fix(cli): write machine-readable formats directly to stdout bypassing Rich line-wrapping

The format_output() function returned a string that callers passed to
Rich console.print(), which wraps long lines at terminal width.  This
injected literal newline characters into JSON string values (e.g. in
definition_of_done fields), producing invalid JSON that downstream
parsers could not decode (JSONDecodeError: Invalid control character).

For machine-readable formats (json, yaml, plain), format_output() now
writes the rendered output directly to sys.stdout and returns an empty
string.  This preserves the exact serialization from json.dumps/
yaml.dump without Rich text processing artifacts.

Refs: #746
This commit is contained in:
Luis Mendes
2026-03-13 23:03:43 +00:00
parent 2acb957d83
commit ab911dbdc4
22 changed files with 298 additions and 52 deletions
@@ -211,6 +211,7 @@ def step_set_default_aborted(context):
@when("I print that actor in json format")
def step_print_actor_json(context):
import contextlib
from io import StringIO
from rich.console import Console
@@ -218,7 +219,10 @@ def step_print_actor_json(context):
buf = StringIO()
test_console = Console(file=buf, force_terminal=False)
with patch("cleveragents.cli.commands.actor.console", test_console):
with (
patch("cleveragents.cli.commands.actor.console", test_console),
contextlib.redirect_stdout(buf),
):
_print_actor(context.test_actor, title="Test", fmt="json")
context.print_output = buf.getvalue()
+8 -1
View File
@@ -60,7 +60,14 @@ def _format_data(data: dict[str, Any], fmt: str) -> str:
render_version_rich(data)
return buf.getvalue()
return format_output(data, fmt)
# Machine-readable formats write to sys.stdout; capture it.
from contextlib import redirect_stdout
from io import StringIO
buf = StringIO()
with redirect_stdout(buf):
result = format_output(data, fmt)
return result or buf.getvalue().rstrip("\n")
# ---------------------------------------------------------------------------
+17 -6
View File
@@ -351,6 +351,17 @@ def step_plan_cancel_json(context: Context) -> None:
# ------- Direct formatting module tests -------
def _capture_format_output(data, fmt):
"""Call format_output capturing stdout (machine-readable formats write there)."""
from contextlib import redirect_stdout
from io import StringIO
buf = StringIO()
with redirect_stdout(buf):
result = format_output(data, fmt)
return result or buf.getvalue().rstrip("\n")
@when("I call format_output with a dict and format json")
def step_call_format_json(context: Context) -> None:
data = {
@@ -358,13 +369,13 @@ def step_call_format_json(context: Context) -> None:
"count": 42,
"created_at": datetime(2025, 1, 15).isoformat(),
}
context.format_result = format_output(data, "json")
context.format_result = _capture_format_output(data, "json")
@when("I call format_output with a dict and format yaml")
def step_call_format_yaml(context: Context) -> None:
data = {"name": "test", "count": 42}
context.format_result = format_output(data, "yaml")
context.format_result = _capture_format_output(data, "yaml")
@when("I call format_output with a dict and format plain")
@@ -374,19 +385,19 @@ def step_call_format_plain(context: Context) -> None:
"nested": {"a": 1, "b": 2},
"items": ["x", "y"],
}
context.format_result = format_output(data, "plain")
context.format_result = _capture_format_output(data, "plain")
@when("I call format_output with a dict and format table")
def step_call_format_table(context: Context) -> None:
data = {"name": "test", "count": 42}
context.format_result = format_output(data, "table")
context.format_result = _capture_format_output(data, "table")
@when("I call format_output with a dict and format rich")
def step_call_format_rich(context: Context) -> None:
data = {"name": "test", "count": 42}
context.format_result = format_output(data, "rich")
context.format_result = _capture_format_output(data, "rich")
@when("I call format_output with a list and format plain")
@@ -395,7 +406,7 @@ def step_call_format_list_plain(context: Context) -> None:
{"name": "a", "value": 1},
{"name": "b", "value": 2},
]
context.format_result = format_output(data, "plain")
context.format_result = _capture_format_output(data, "plain")
@when("I call serialize_value with enum and nested data")
@@ -121,6 +121,23 @@ def _mock_wiring_container(context: Any) -> MagicMock:
return mock_container
def _test_format_output(data, format_type):
"""Test-friendly format_output: captures stdout writes and returns the string."""
import sys
from io import StringIO
from cleveragents.cli.formatting import format_output
buf = StringIO()
old = sys.stdout
sys.stdout = buf
try:
result = format_output(data, format_type)
finally:
sys.stdout = old
return result or buf.getvalue().rstrip("\n")
def _run_wired(context: Any, func: Any, *args: Any, **kwargs: Any) -> None:
"""Execute a CLI function with a mocked DI container."""
import typer
@@ -150,6 +167,10 @@ def _run_wired(context: Any, func: Any, *args: Any, **kwargs: Any) -> None:
"cleveragents.cli.commands.project_context.err_console",
test_console,
),
patch(
"cleveragents.cli.commands.project_context.format_output",
_test_format_output,
),
):
try:
func(*args, **kwargs)
+4 -1
View File
@@ -273,6 +273,8 @@ def step_path_resolved(context: Context) -> None:
def _resource_capture_output(func: Any, *args: Any, **kwargs: Any) -> tuple[str, bool]:
"""Run a CLI function capturing its console output and success status."""
import contextlib
buf = StringIO()
console = Console(file=buf, width=200, no_color=True)
@@ -283,7 +285,8 @@ def _resource_capture_output(func: Any, *args: Any, **kwargs: Any) -> tuple[str,
failed = False
try:
func(*args, **kwargs)
with contextlib.redirect_stdout(buf):
func(*args, **kwargs)
except SystemExit:
failed = True
except Exception:
@@ -200,9 +200,15 @@ def step_fmt_data_empty(context: Context) -> None:
@when('I format the data with format type "{fmt}"')
def step_fmt_format(context: Context, fmt: str) -> None:
from contextlib import redirect_stdout
from io import StringIO
from cleveragents.cli.formatting import format_output
context.cov_format_output = format_output(context.cov_format_data, fmt)
buf = StringIO()
with redirect_stdout(buf):
result = format_output(context.cov_format_data, fmt)
context.cov_format_output = result or buf.getvalue().rstrip("\n")
@when('I format the data via format_output_session with format "{fmt}"')
@@ -510,6 +516,8 @@ def step_sess_reset_service(context: Context) -> None:
@when('I invoke session create with format "{fmt}"')
def step_sess_create_fmt(context: Context, fmt: str) -> None:
from contextlib import redirect_stdout
from cleveragents.cli.commands import session as session_mod
buf = StringIO()
@@ -519,7 +527,10 @@ def step_sess_create_fmt(context: Context, fmt: str) -> None:
return_value=context.cov_mock_sess_svc,
):
try:
with patch("typer.echo", side_effect=lambda x: buf.write(str(x))):
with (
patch("typer.echo", side_effect=lambda x: buf.write(str(x))),
redirect_stdout(buf),
):
session_mod.create(fmt=fmt)
except SystemExit:
pass
@@ -192,6 +192,8 @@ def step_errcov_lifecycle_error(context: Context) -> None:
@when('I invoke errcov plan errors in "{fmt}" format')
def step_errcov_invoke_errors(context: Context, fmt: str) -> None:
import contextlib
from cleveragents.cli.commands import plan as plan_mod
svc = MagicMock()
@@ -206,6 +208,7 @@ def step_errcov_invoke_errors(context: Context, fmt: str) -> None:
return_value=svc,
),
patch.object(plan_mod, "console", capture_console),
contextlib.redirect_stdout(buf),
):
plan_mod.plan_errors(plan_id=_PLAN_ID, fmt=fmt)
context.errcov_output = buf.getvalue()
@@ -267,6 +270,7 @@ def step_errcov_invoke_diff(context: Context, fmt: str) -> None:
plan_mod, "_get_apply_service", return_value=context.errcov_apply_svc
),
patch.object(plan_mod, "console", capture_console),
contextlib.redirect_stdout(buf),
):
plan_mod.plan_diff(plan_id=_PLAN_ID, fmt=fmt)
context.errcov_output = buf.getvalue()
@@ -326,6 +330,7 @@ def step_errcov_invoke_artifacts(context: Context, fmt: str) -> None:
plan_mod, "_get_apply_service", return_value=context.errcov_apply_svc
),
patch.object(plan_mod, "console", capture_console),
contextlib.redirect_stdout(buf),
):
plan_mod.plan_artifacts(plan_id=_PLAN_ID, fmt=fmt)
context.errcov_output = buf.getvalue()
+7 -1
View File
@@ -720,8 +720,14 @@ def step_value_error_raised(context: Context) -> None:
@when('I call format_output with a dict and format "{fmt}"')
def step_call_format_output(context: Context, fmt: str) -> None:
from contextlib import redirect_stdout
from io import StringIO
data = {"Name": "test", "Status": "active"}
context.format_result = format_output(data, fmt)
buf = StringIO()
with redirect_stdout(buf):
result = format_output(data, fmt)
context.format_result = result or buf.getvalue().rstrip("\n")
@then("the output contains key-value pairs")
@@ -652,7 +652,13 @@ def step_diff_result_is_json(context: Context) -> None:
@when('pas_cov I call artifacts with format "{fmt}"')
def step_call_artifacts_format(context: Context, fmt: str) -> None:
"""Call artifacts on the service with specified format."""
context.pas_artifacts_result = context.pas_service.artifacts(_PLAN_ID, fmt=fmt)
from contextlib import redirect_stdout
from io import StringIO
buf = StringIO()
with redirect_stdout(buf):
result = context.pas_service.artifacts(_PLAN_ID, fmt=fmt)
context.pas_artifacts_result = result or buf.getvalue().rstrip("\n")
@then('pas_cov the artifacts result should contain "{text}"')
+29 -10
View File
@@ -210,17 +210,23 @@ def step_request_diff_rich(context: Context) -> None:
@when("I request the diff in plain format")
def step_request_diff_plain(context: Context) -> None:
context.diff_output = context.apply_service.diff(context.plan_id, fmt="plain")
context.diff_output = _capture_service_output(
context.apply_service.diff, context.plan_id, fmt="plain"
)
@when("I request the diff in JSON format")
def step_request_diff_json(context: Context) -> None:
context.diff_output = context.apply_service.diff(context.plan_id, fmt="json")
context.diff_output = _capture_service_output(
context.apply_service.diff, context.plan_id, fmt="json"
)
@when("I request the diff in YAML format")
def step_request_diff_yaml(context: Context) -> None:
context.diff_output = context.apply_service.diff(context.plan_id, fmt="yaml")
context.diff_output = _capture_service_output(
context.apply_service.diff, context.plan_id, fmt="yaml"
)
@when("I try to get the diff")
@@ -234,27 +240,40 @@ def step_try_get_diff(context: Context) -> None:
@when("I request the artifacts")
def step_request_artifacts(context: Context) -> None:
context.artifacts_output = context.apply_service.artifacts(context.plan_id)
context.artifacts_output = _capture_service_output(
context.apply_service.artifacts, context.plan_id
)
def _capture_service_output(func, *args, **kwargs):
"""Call a service method capturing stdout (format_output writes there)."""
from contextlib import redirect_stdout
from io import StringIO
buf = StringIO()
with redirect_stdout(buf):
result = func(*args, **kwargs)
return result or buf.getvalue().rstrip("\n")
@when("I request the artifacts in JSON format")
def step_request_artifacts_json(context: Context) -> None:
context.artifacts_output = context.apply_service.artifacts(
context.plan_id, fmt="json"
context.artifacts_output = _capture_service_output(
context.apply_service.artifacts, context.plan_id, fmt="json"
)
@when("I request the artifacts in plain format")
def step_request_artifacts_plain(context: Context) -> None:
context.artifacts_output = context.apply_service.artifacts(
context.plan_id, fmt="plain"
context.artifacts_output = _capture_service_output(
context.apply_service.artifacts, context.plan_id, fmt="plain"
)
@when("I request the artifacts in YAML format")
def step_request_artifacts_yaml(context: Context) -> None:
context.artifacts_output = context.apply_service.artifacts(
context.plan_id, fmt="yaml"
context.artifacts_output = _capture_service_output(
context.apply_service.artifacts, context.plan_id, fmt="yaml"
)
+15 -4
View File
@@ -236,16 +236,27 @@ def step_build_explain_reasoning(context: Context) -> None:
)
def _capture_format_output(data, fmt):
"""Call format_output capturing stdout (machine-readable formats write there)."""
from contextlib import redirect_stdout
from io import StringIO
buf = StringIO()
with redirect_stdout(buf):
result = format_output(data, fmt)
return result or buf.getvalue().rstrip("\n")
@when("I format the explain dict as json")
def step_format_explain_json(context: Context) -> None:
data = _build_explain_dict(context.pe_decision)
context.pe_json_output = format_output(data, "json")
context.pe_json_output = _capture_format_output(data, "json")
@when("I format the explain dict as yaml")
def step_format_explain_yaml(context: Context) -> None:
data = _build_explain_dict(context.pe_decision)
context.pe_yaml_output = format_output(data, "yaml")
context.pe_yaml_output = _capture_format_output(data, "yaml")
# ---------------------------------------------------------------------------
@@ -276,13 +287,13 @@ def step_build_tree_empty(context: Context) -> None:
@when("I format the tree as json")
def step_format_tree_json(context: Context) -> None:
tree = build_decision_tree(context.pe_decisions)
context.pe_tree_json = format_output(tree, "json")
context.pe_tree_json = _capture_format_output(tree, "json")
@when("I format the tree as yaml")
def step_format_tree_yaml(context: Context) -> None:
tree = build_decision_tree(context.pe_decisions)
context.pe_tree_yaml = format_output(tree, "yaml")
context.pe_tree_yaml = _capture_format_output(tree, "yaml")
# ---------------------------------------------------------------------------
+4 -1
View File
@@ -129,6 +129,8 @@ def _unpatch_project_mod() -> None:
def _capture(context: Any, func: Any, *args: Any, **kwargs: Any) -> None:
"""Call a CLI function, capturing console output and success/failure."""
import contextlib
import typer
import cleveragents.cli.commands.project as project_mod
@@ -144,7 +146,8 @@ def _capture(context: Any, func: Any, *args: Any, **kwargs: Any) -> None:
_patch_project_mod(context)
failed = False
try:
func(*args, **kwargs)
with contextlib.redirect_stdout(buf):
func(*args, **kwargs)
except (SystemExit, typer.Exit, typer.Abort):
failed = True
except Exception:
+16 -6
View File
@@ -238,10 +238,22 @@ def step_pcli_create_project_with_invariants(
context.pcli_output = str(exc)
def _capture_format_output(data, fmt):
"""Call format_output capturing stdout (machine-readable formats write there)."""
from contextlib import redirect_stdout
from io import StringIO
from cleveragents.cli.formatting import format_output
buf = StringIO()
with redirect_stdout(buf):
result = format_output(data, fmt)
return result or buf.getvalue().rstrip("\n")
@when('I run project create named "{name}" formatted as "{fmt}"')
def step_pcli_create_project_with_format(context: Any, name: str, fmt: str) -> None:
from cleveragents.cli.commands.project import _project_spec_dict
from cleveragents.cli.formatting import format_output
from cleveragents.domain.models.core.project import (
NamespacedProject,
parse_namespaced_name,
@@ -256,7 +268,7 @@ def step_pcli_create_project_with_format(context: Any, name: str, fmt: str) -> N
)
context.pcli_project_repo.create(proj)
data = _project_spec_dict(proj)
context.pcli_output = format_output(data, fmt)
context.pcli_output = _capture_format_output(data, fmt)
context.pcli_exit_code = 0
except Exception as exc:
context.pcli_exit_code = 1
@@ -364,12 +376,11 @@ def step_pcli_list_projects_invalid_regex(context: Any, regex_str: str) -> None:
@when('I list projects with format "{fmt}"')
def step_pcli_list_projects_fmt(context: Any, fmt: str) -> None:
from cleveragents.cli.commands.project import _project_spec_dict
from cleveragents.cli.formatting import format_output
try:
projects = context.pcli_project_repo.list_projects()
data = [_project_spec_dict(p) for p in projects]
context.pcli_output = format_output(data, fmt)
context.pcli_output = _capture_format_output(data, fmt)
context.pcli_exit_code = 0
except Exception as exc:
context.pcli_exit_code = 1
@@ -401,7 +412,6 @@ def step_pcli_show_project(context: Any, name: str) -> None:
@when('I show project details for "{name}" as "{fmt}"')
def step_pcli_show_project_fmt(context: Any, name: str, fmt: str) -> None:
from cleveragents.cli.commands.project import _project_spec_dict
from cleveragents.cli.formatting import format_output
from cleveragents.infrastructure.database.repositories import (
ProjectNotFoundError,
)
@@ -409,7 +419,7 @@ def step_pcli_show_project_fmt(context: Any, name: str, fmt: str) -> None:
try:
proj = context.pcli_project_repo.get(name)
data = _project_spec_dict(proj)
context.pcli_output = format_output(data, fmt)
context.pcli_output = _capture_format_output(data, fmt)
context.pcli_exit_code = 0
except (ProjectNotFoundError, Exception) as exc:
context.pcli_exit_code = 1
@@ -71,6 +71,7 @@ def _mock_container(context: Any) -> MagicMock:
def _run(context: Any, func: Any, *args: Any, **kwargs: Any) -> None:
import typer
from rich.console import Console as RichConsole
@@ -85,6 +86,21 @@ def _run(context: Any, func: Any, *args: Any, **kwargs: Any) -> None:
soft_wrap=True,
)
def _test_format_output(data, format_type):
import sys as _sys
from io import StringIO as _SIO
from cleveragents.cli.formatting import format_output as _fo
_b = _SIO()
_old = _sys.stdout
_sys.stdout = _b
try:
_r = _fo(data, format_type)
finally:
_sys.stdout = _old
return _r or _b.getvalue().rstrip("\n")
with (
patch(
"cleveragents.application.container.get_container",
@@ -98,6 +114,10 @@ def _run(context: Any, func: Any, *args: Any, **kwargs: Any) -> None:
"cleveragents.cli.commands.project_context.err_console",
test_console,
),
patch(
"cleveragents.cli.commands.project_context.format_output",
_test_format_output,
),
):
try:
func(*args, **kwargs)
@@ -139,6 +139,21 @@ def _run_with_container(context: Any, func: Any, *args: Any, **kwargs: Any) -> N
soft_wrap=True,
)
def _test_format_output(data, format_type):
import sys as _sys
from io import StringIO as _SIO
from cleveragents.cli.formatting import format_output as _fo
_b = _SIO()
_old = _sys.stdout
_sys.stdout = _b
try:
_r = _fo(data, format_type)
finally:
_sys.stdout = _old
return _r or _b.getvalue().rstrip("\n")
with (
patch(
"cleveragents.application.container.get_container",
@@ -152,6 +167,10 @@ def _run_with_container(context: Any, func: Any, *args: Any, **kwargs: Any) -> N
"cleveragents.cli.commands.project_context.err_console",
test_console,
),
patch(
"cleveragents.cli.commands.project_context.format_output",
_test_format_output,
),
):
try:
func(*args, **kwargs)
+4 -1
View File
@@ -34,6 +34,8 @@ def _make_service(context: Context) -> ResourceRegistryService:
def _capture_output(func: Any, *args: Any, **kwargs: Any) -> tuple[str, bool]:
"""Run a CLI function capturing its console output and success status."""
import contextlib
buf = StringIO()
# force_terminal=False and highlight=False prevent Rich from emitting
# ANSI escape codes (bold, syntax highlighting) when FORCE_COLOR is set
@@ -49,7 +51,8 @@ def _capture_output(func: Any, *args: Any, **kwargs: Any) -> tuple[str, bool]:
failed = False
try:
func(*args, **kwargs)
with contextlib.redirect_stdout(buf):
func(*args, **kwargs)
except SystemExit:
failed = True
except Exception:
+4 -1
View File
@@ -38,6 +38,8 @@ def _make_tree_service(context: Context) -> ResourceRegistryService:
def _capture_tree_output(func: Any, *args: Any, **kwargs: Any) -> tuple[str, bool]:
"""Run a CLI function capturing its console output and success status."""
import contextlib
buf = StringIO()
console = Console(file=buf, width=200, no_color=True)
@@ -48,7 +50,8 @@ def _capture_tree_output(func: Any, *args: Any, **kwargs: Any) -> tuple[str, boo
failed = False
try:
func(*args, **kwargs)
with contextlib.redirect_stdout(buf):
func(*args, **kwargs)
except SystemExit:
failed = True
except Exception:
@@ -8,6 +8,7 @@ Targets uncovered lines in src/cleveragents/cli/commands/server.py:
from __future__ import annotations
import contextlib
from io import StringIO
from typing import Any
from unittest.mock import MagicMock, patch
@@ -232,6 +233,7 @@ def step_call_status(context: Context, fmt: str) -> None:
except (ValueError, KeyError):
expected_mode = "disabled"
stdout_buf = StringIO()
with (
patch(
"cleveragents.cli.commands.server._get_config_service",
@@ -249,10 +251,12 @@ def step_call_status(context: Context, fmt: str) -> None:
"cleveragents.cli.commands.server.console",
capture_console,
),
contextlib.redirect_stdout(stdout_buf),
):
server_status(fmt=fmt)
context.echoed_output = echo_buf.getvalue()
echoed = echo_buf.getvalue().strip()
context.echoed_output = echo_buf.getvalue() if echoed else stdout_buf.getvalue()
context.captured_console_output = console_buf.getvalue()
+20 -3
View File
@@ -115,11 +115,28 @@ def test_json_error() -> None:
def test_backward_compat() -> None:
"""format_output still works for all formats."""
"""format_output still works for all formats.
For machine-readable formats (json, yaml, plain) ``format_output``
writes directly to ``sys.stdout`` and returns ``""``. We capture
stdout so the assertion checks the *produced* output rather than
just the return value.
"""
import sys as _sys
from io import StringIO as _SIO
data = {"Name": "test-proj", "Status": "active"}
for fmt in ("json", "yaml", "plain", "table", "rich"):
result = format_output(data, fmt)
assert result, f"format_output({fmt}) returned empty"
buf = _SIO()
old_stdout = _sys.stdout
_sys.stdout = buf
try:
result = format_output(data, fmt)
finally:
_sys.stdout = old_stdout
# Combine: either the return value has content, or stdout was written to
effective = result or buf.getvalue().rstrip("\n")
assert effective, f"format_output({fmt}) produced no output"
print("output-rendering-backward-compat-ok")
+18 -3
View File
@@ -159,12 +159,27 @@ def _diff_output() -> None:
def _artifacts_output() -> None:
"""Test artifacts output."""
"""Test artifacts output.
``format_output`` for machine-readable formats writes directly to
``sys.stdout`` and returns ``""``. We redirect stdout so the JSON
payload is captured for parsing.
"""
from io import StringIO
lifecycle, store, apply_svc = _create_services()
plan_id = _create_plan_with_changeset(lifecycle, store)
output = apply_svc.artifacts(plan_id, fmt="json")
parsed = json.loads(output)
buf = StringIO()
old_stdout = sys.stdout
sys.stdout = buf
try:
output = apply_svc.artifacts(plan_id, fmt="json")
finally:
sys.stdout = old_stdout
# Combine: return value or captured stdout
effective = output or buf.getvalue().strip()
parsed = json.loads(effective)
assert parsed["changeset_id"] == "robot-cs-001"
assert "sandbox-robot-001" in parsed["sandbox_refs"]
assert len(parsed["files_changed"]) == 2
+29
View File
@@ -87,6 +87,31 @@ def _make_mock_container(
return mock_container
def _capturing_format_output(data: Any, format_type: str) -> str:
"""Wrapper around ``format_output`` that captures stdout writes.
``format_output`` for machine-readable formats (json, yaml, plain)
writes directly to ``sys.stdout`` and returns ``""``. This wrapper
redirects ``sys.stdout`` only during the ``format_output`` call so
that the rendered content is returned as a string, allowing the
caller's ``console.print(result)`` to write it to the test's Rich
console buffer. Structlog noise that would otherwise pollute the
captured stdout is excluded because the redirect is scoped tightly.
"""
import sys as _sys
from cleveragents.cli.formatting import format_output as _real_format_output
buf = StringIO()
old = _sys.stdout
_sys.stdout = buf
try:
ret = _real_format_output(data, format_type)
finally:
_sys.stdout = old
return ret or buf.getvalue().rstrip("\n")
def _run_cli_func(
func: Any,
repo: NamespacedProjectRepository,
@@ -123,6 +148,10 @@ def _run_cli_func(
"cleveragents.cli.commands.project_context.err_console",
test_console,
),
patch(
"cleveragents.cli.commands.project_context.format_output",
_capturing_format_output,
),
):
try:
func(**kwargs)
+28 -9
View File
@@ -169,6 +169,12 @@ def format_output(
rendering so that sensitive values are masked unless the
global ``show_secrets`` flag is enabled.
For machine-readable formats (json, yaml, plain) the output is
written directly to ``sys.stdout`` to avoid Rich ``console.print``
line-wrapping which can introduce literal newlines inside JSON
string values. An empty string is returned so the caller's
``console.print(result)`` simply emits a trailing newline.
Parameters
----------
data:
@@ -179,18 +185,31 @@ def format_output(
Returns
-------
str
The rendered string. For ``rich`` format the caller should use
the domain-specific Rich helper instead; this function returns
the JSON representation as a fallback.
The rendered string. For machine-readable formats the output
is written directly to stdout and an empty string is returned.
"""
import sys
safe_data = _redact_data(data)
fmt = format_type.lower()
if fmt == OutputFormat.JSON.value:
return _format_json(safe_data)
if fmt == OutputFormat.YAML.value:
return _format_yaml(safe_data)
if fmt == OutputFormat.PLAIN.value:
return _format_plain(safe_data)
# Machine-readable formats: write directly to stdout to avoid
# Rich console.print() line-wrapping artefacts.
if fmt in (
OutputFormat.JSON.value,
OutputFormat.YAML.value,
OutputFormat.PLAIN.value,
):
if fmt == OutputFormat.JSON.value:
rendered = _format_json(safe_data)
elif fmt == OutputFormat.YAML.value:
rendered = _format_yaml(safe_data)
else:
rendered = _format_plain(safe_data)
sys.stdout.write(rendered + "\n")
sys.stdout.flush()
return ""
if fmt == OutputFormat.TABLE.value:
return _format_table(safe_data)
if fmt == OutputFormat.COLOR.value: