Skip to content

feat(filters): global layers pipeline and levels (v1: decorative only)#2297

Open
aeppling wants to merge 6 commits into
developfrom
feat/filter-pipeline-v1
Open

feat(filters): global layers pipeline and levels (v1: decorative only)#2297
aeppling wants to merge 6 commits into
developfrom
feat/filter-pipeline-v1

Conversation

@aeppling
Copy link
Copy Markdown
Contributor

@aeppling aeppling commented Jun 6, 2026

Summary

  • Add a centralized, layered output pipeline (core::pipeline), applied in runner before each command's own filter — for both captured and streamed commands. First layer: decorative (strip ANSI, collapse blank runs, drop box-drawing at high), user-tunable via [levels].decorative or RTK_DECORATIVE (light/reasonable/high).

  • Add a global fallback pipeline: commands with no cmds/ handler and no TOML filter now stream through the pipeline too, TTY-gated (terminal → passthrough, piped → filter), with a built-in + user-extensible ([levels].exclude) exclude list so raw-output commands (cat, head, base64, …) stay byte-exact. Routing order unchanged: cmds → TOML → fallback.

  • Remove now-redundant per-command ANSI stripping for commands the pipeline covers.

Scope: applies to all runner-routed commands (captured + streamed) and the fallback.

Test plan

  • cargo fmt --all && cargo clippy --all-targets && cargo test , 2078 passed, 0 failed; clippy clean
  • Manual testing: rtk <command> output inspected
    • rtk printf '…\x1b…\n\n\n…' → ANSI stripped, blank runs collapsed; RTK_DECORATIVE=light preserves blanks
    • rtk cat <file> → raw passthrough (excluded); [levels].exclude=["printf"] → printf raw
    • rtk df → TOML filter path unaffected (RTK_NO_TOML=1 falls through to the pipeline)
    • exit codes propagate: false → 1, unknown command → 127

aeppling added 6 commits June 6, 2026 15:00
Introduce a generic filter pipeline routed centrally in core::runner so
every command flows through the enabled layers before its own filter, which
always runs last.

- core::pipeline: `Layers` (per-command, code-level policy), `Pipeline` with
  `run` (captured) and `stream` (streaming) modes, and the decorative layer
  (light/reasonable/high) shared by both via `decorative_line`.
- Levels resolved once and cached (env RTK_DECORATIVE > [levels] config >
  default) to protect the <10ms startup budget.
- runner applies the pipeline for captured (run_filtered/_with_exit) and
  streamed (run_streamed) paths, gated by RunOptions.layers (default all on).
- config: new [levels] section (decorative).
- mypy routes through the pipeline centrally (inline strip_ansi removed).
…ers)

Separate concerns so adding a layer is a new file, not bloat in one:
- pipeline/mod.rs   — Pipeline, Layers, run()/stream() (layer-agnostic)
- pipeline/levels.rs — Levels + cached env/config resolution
- pipeline/decorative.rs — the decorative layer + its streaming adapter

No behaviour change; pure code movement.
Commands with no cmds/ handler and no TOML filter previously passed through
unfiltered. They now stream live through the global pipeline. Routing order is
unchanged: cmds -> TOML -> global fallback pipeline; it fires only when
neither matched.

- Streams (not captures): forwards output live, never hangs on
  never-terminating commands; layers apply per line via Pipeline::stream.
- Interactivity is decided by TTY, not a command list: terminal stdout passes
  through, piped stdout streams through the pipeline.
- runner streamed mode honors RunOptions.inherit_stdin (for piped input).
- core::stream::Identity: a no-op StreamFilter (the empty layer set's inner).
These filters route through runner, so the decorative pipeline layer already
strips ANSI before their custom filter runs; their own strip_ansi was a runtime
no-op. Removed it.

- next_cmd, rake_cmd: drop strip_ansi (rake's ANSI test fixture de-ANSIfied,
  since ANSI handling is now the pipeline's job).
- glab_cmd: drop the strip_ansi step; GitLab-specific section/bare-ANSI regex
  cleanup stays.

Commands that bypass runner (dotnet/binlog, gt, playwright) keep their own
stripping — they are not covered by the central pipeline.
Raw-output commands (cat, head, tail, base64, xxd, hexdump, od, strings, dd)
must stay byte-exact, so they bypass the global fallback pipeline and pass
through untouched. Users extend the list via [levels].exclude in config (same
location as the layer levels).

Also fix LevelsConfig parsing: decorative now has a serde default, so a partial
[levels] table (e.g. only exclude) no longer drops the whole config.
- src/core/pipeline/README.md: technical flow — layers vs levels vs custom,
  run/stream modes, where it is wired (runner), global fallback + exclude,
  level resolution, how to add a layer.
- TECHNICAL.md: generic pipeline in filter execution + updated fallback flow.
- configuration.md: [levels] (decorative + exclude), RTK_DECORATIVE env var,
  and a user-facing "Filter levels" section.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant