//! A crate for stripping ANSI escape sequences from byte sequences. //! //! This can be used to take output from a program that includes escape sequences and write //! it somewhere that does not easily support them, such as a log file. //! //! The simplest interface provided is the [`strip`] function, which takes a byte slice and returns //! a `Vec` of bytes with escape sequences removed. For writing bytes directly to a writer, you //! may prefer using the [`Writer`] struct, which implements `Write` and strips escape sequences //! as they are written. //! //! [`strip`]: fn.strip.html //! [`Writer`]: struct.Writer.html //! //! # Example //! //! ``` //! use std::io::{self, Write}; //! //! # fn foo() -> io::Result<()> { //! let bytes_with_colors = b"\x1b[32mfoo\x1b[m bar"; //! let plain_bytes = strip_ansi_escapes::strip(&bytes_with_colors)?; //! io::stdout().write_all(&plain_bytes)?; //! # Ok(()) //! # } //! ``` extern crate vte; use std::io::{self, Cursor, IntoInnerError, LineWriter, Write}; use vte::{Parser, Perform}; /// `Writer` wraps an underlying type that implements `Write`, stripping ANSI escape sequences /// from bytes written to it before passing them to the underlying writer. /// /// # Example /// ``` /// use std::io::{self, Write}; /// use strip_ansi_escapes::Writer; /// /// # fn foo() -> io::Result<()> { /// let bytes_with_colors = b"\x1b[32mfoo\x1b[m bar"; /// let mut writer = Writer::new(io::stdout()); /// // Only `foo bar` will be written to stdout /// writer.write_all(bytes_with_colors)?; /// # Ok(()) /// # } /// ``` pub struct Writer where W: Write, { performer: Performer, parser: Parser, } /// Strip ANSI escapes from `data` and return the remaining bytes as a `Vec`. /// /// See [the module documentation][mod] for an example. /// /// [mod]: index.html pub fn strip(data: T) -> io::Result> where T: AsRef<[u8]>, { let c = Cursor::new(Vec::new()); let mut writer = Writer::new(c); writer.write_all(data.as_ref())?; Ok(writer.into_inner()?.into_inner()) } struct Performer where W: Write, { writer: LineWriter, err: Option, } impl Writer where W: Write, { /// Create a new `Writer` that writes to `inner`. pub fn new(inner: W) -> Writer { Writer { performer: Performer { writer: LineWriter::new(inner), err: None, }, parser: Parser::new(), } } /// Unwraps this `Writer`, returning the underlying writer. /// /// The internal buffer is written out before returning the writer, which /// may produce an [`IntoInnerError`]. /// /// [IntoInnerError]: https://doc.rust-lang.org/std/io/struct.IntoInnerError.html pub fn into_inner(self) -> Result>> { self.performer.into_inner() } } impl Write for Writer where W: Write, { fn write(&mut self, buf: &[u8]) -> io::Result { for b in buf.iter() { self.parser.advance(&mut self.performer, *b) } match self.performer.err.take() { Some(e) => Err(e), None => Ok(buf.len()), } } fn flush(&mut self) -> io::Result<()> { self.performer.flush() } } impl Performer where W: Write, { pub fn flush(&mut self) -> io::Result<()> { self.writer.flush() } pub fn into_inner(self) -> Result>> { self.writer.into_inner() } } impl Perform for Performer where W: Write, { fn print(&mut self, c: char) { // Just print bytes to the inner writer. self.err = write!(self.writer, "{}", c).err(); } fn execute(&mut self, byte: u8) { // We only care about executing linefeeds. if byte == b'\n' { self.err = writeln!(self.writer).err(); } } } #[cfg(test)] mod tests { use super::*; use std::env; use std::env::consts::EXE_EXTENSION; use std::path::Path; use std::process::Command; #[test] fn readme_test() { let rustdoc = Path::new("rustdoc").with_extension(EXE_EXTENSION); let readme = Path::new(file!()) .parent() .unwrap() .parent() .unwrap() .join("README.md"); let exe = env::current_exe().unwrap(); let outdir = exe.parent().unwrap(); let mut cmd = Command::new(rustdoc); cmd.args(&["--verbose", "--test", "-L"]) .arg(&outdir) .arg(&readme); println!("{:?}", cmd); let result = cmd .spawn() .expect("Failed to spawn process") .wait() .expect("Failed to run process"); assert!( result.success(), "Failed to run rustdoc tests on README.md!" ); } fn assert_parsed(input: &[u8], expected: &[u8]) { let bytes = strip(input).expect("Failed to strip escapes"); assert_eq!(bytes, expected); } #[test] fn test_simple() { assert_parsed(b"\x1b[m\x1b[m\x1b[32m\x1b[1m Finished\x1b[m dev [unoptimized + debuginfo] target(s) in 0.0 secs", b" Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs"); } #[test] fn test_newlines() { assert_parsed(b"foo\nbar\n", b"foo\nbar\n"); } #[test] fn test_escapes_newlines() { assert_parsed(b"\x1b[m\x1b[m\x1b[32m\x1b[1m Compiling\x1b[m utf8parse v0.1.0 \x1b[m\x1b[m\x1b[32m\x1b[1m Compiling\x1b[m vte v0.3.2 \x1b[m\x1b[m\x1b[32m\x1b[1m Compiling\x1b[m strip-ansi-escapes v0.1.0-pre (file:///build/strip-ansi-escapes) \x1b[m\x1b[m\x1b[32m\x1b[1m Finished\x1b[m dev [unoptimized + debuginfo] target(s) in 0.66 secs ", b" Compiling utf8parse v0.1.0 Compiling vte v0.3.2 Compiling strip-ansi-escapes v0.1.0-pre (file:///build/strip-ansi-escapes) Finished dev [unoptimized + debuginfo] target(s) in 0.66 secs "); } }