strip-ansi-escapes
Strips ANSI escape sequences from byte streams so colored terminal output can be logged or shown as plain text.
Repository Health
Technical Analysis
strip-ansi-escapes is a small Rust crate that removes ANSI escape sequences from byte sequences, turning color- and cursor-control-laden terminal output into plain text. It exposes a strip function that takes any byte slice and returns a Vec<u8> with escapes removed, plus a strip_str convenience function for UTF-8 strings.
For streaming use cases, the crate provides a Writer<W> type that wraps any std::io::Write implementation and strips escape sequences from data as it is written, buffering output line by line via an internal LineWriter. This makes it straightforward to pipe colored subprocess output (e.g. from cargo build) into a log file or non-terminal destination without hand-parsing ANSI codes.
What You Get
- strip() function - Takes any
AsRef<[u8]>byte slice and returns aVec<u8>with all ANSI escape sequences removed. - strip_str() helper - Convenience wrapper for UTF-8
&strinput/output so callers don’t have to convert to and from bytes manually. - Writer<W> adapter - A
std::io::Writeimplementation that strips escapes from data as it’s written, letting you pipe streaming output directly into a file or buffer. - vte-based parsing - Escape sequence detection is delegated to the
vtecrate’s real ANSI parser state machine, the same crate used by terminal emulators like Alacritty.
Common Use Cases
- Logging colored CLI output - Capture a subprocess’s colorized stdout/stderr and write clean, escape-free text to a log file.
- Terminal output in test assertions - Strip ANSI codes from captured terminal output before comparing it to plain-text expectations in tests.
- Non-terminal display of terminal output - Show program output in a UI, email, or report that can’t render ANSI escape sequences.
- Piping build tool output - Clean up compiler or build tool output (e.g. cargo, npm) before further text processing or storage.
Under The Hood
Architecture
The crate is a single src/lib.rs file with a clear three-layer data flow: the public API (strip, strip_str, Writer<W>) hands bytes to a private Performer<W> struct, which implements the vte::Perform trait and holds a std::io::LineWriter<W>; a vte::Parser drives that Performer via advance(), calling print() for plain characters and execute() for control bytes like newlines. This keeps parsing (delegated entirely to vte) cleanly separated from output buffering (the LineWriter), with the generic W: Write bound as the only injection point — swapping the underlying writer (Vec<u8>, Stdout, a file) requires no changes to the stripping logic itself.
Tech Stack
The crate has exactly one runtime dependency, vte 0.14 (with default features disabled), the same VT100/ANSI parser used by terminal emulators such as Alacritty, plus doc-comment 0.3 as a dev-dependency to doctest the README’s examples against src/lib.rs. There is no build tooling beyond Cargo itself and no async runtime, network, or filesystem dependency — the crate operates purely on in-memory byte streams and anything implementing std::io::Write.
Code Quality
Unit tests in src/lib.rs cover simple stripping, newline handling, and multi-line escaped build output; doctests embedded in the public API’s doc comments and in the README (via doc_comment::doctest!) are exercised through cfg(doctest). CI (.github/workflows/rust.yml) runs cargo check, cargo test, cargo fmt --check, and cargo clippy on every push and PR, enforcing both correctness and style. Error handling propagates io::Result through Writer::write, capturing any Performer write error and returning it on the next call; the top-level strip() function calls .expect() only on the documented invariant that writing to an in-memory Vec<u8> cannot fail.
API Design
The public surface is intentionally tiny — two functions and one generic type — covering both one-shot (strip, strip_str) and streaming (Writer<W>) use cases with idiomatic Rust naming and a Writer that behaves exactly like any other std::io::Write implementor, so there’s effectively zero boilerplate to start using it. It leans on the well-exercised vte parser rather than inventing its own ANSI-matching logic, trading novelty for correctness and reliability.