parse_ansi/
lib.rs

1//! Parse ANSI escape codes (colors, underlines, etc.)
2//!
3//! ```rust
4//! extern crate parse_ansi;
5//!
6//! assert_eq!(
7//!     parse_ansi::ANSI_REGEX.replace_all(
8//!         b"Hello, \x1b[42mworld\x1b[0m!",
9//!         b"" as &[u8],
10//!     ),
11//!     b"Hello, world!" as &[u8],
12//! );
13//! ```
14
15#[macro_use]
16extern crate lazy_static;
17extern crate regex;
18
19#[cfg(test)]
20extern crate itertools;
21
22use regex::bytes::{Matches, Regex};
23
24// Inspired by https://github.com/nodejs/node/blob/641d4a4159aaa96eece8356e03ec6c7248ae3e73/lib/internal/readline.js#L9
25pub const ANSI_RE: &str =
26    r"[\x1b\x9b]\[[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]";
27
28lazy_static! {
29    /// A `Regex` that matches ANSI escape codes.
30    ///
31    /// ```rust
32    /// # use parse_ansi::ANSI_REGEX;
33    /// assert_eq!(
34    ///     ANSI_REGEX.replace_all(b"foo \x1b[42mbar\x1b[0m baz", b"" as &[u8]),
35    ///     b"foo bar baz" as &[u8],
36    /// );
37    pub static ref ANSI_REGEX: Regex = Regex::new(ANSI_RE).unwrap();
38}
39
40/// Parses ANSI escape codes from the given text, returning an `Iterator<Item = Match>`.
41///
42/// ```rust
43/// # use parse_ansi::parse_bytes;
44/// let ansi_text = b"Hello, \x1b[31;4mworld\x1b[0m!";
45/// let parsed: Vec<_> = parse_bytes(ansi_text)
46///     .map(|m| (m.start(), m.end()))
47///     .collect();
48/// assert_eq!(
49///     parsed,
50///     vec![(7, 14), (19, 23)],
51/// );
52/// ```
53pub fn parse_bytes(text: &[u8]) -> Matches {
54    ANSI_REGEX.find_iter(text)
55}
56
57#[cfg(test)]
58mod tests {
59    use super::parse_bytes;
60    use itertools::zip_eq;
61
62    fn test_parse(text: &[u8], expected: &[(usize, usize, &[u8])]) {
63        for (match_, expected_match) in zip_eq(parse_bytes(text), expected.iter()) {
64            assert_eq!(
65                &(match_.start(), match_.end(), match_.as_bytes()),
66                expected_match,
67            );
68        }
69    }
70
71    #[test]
72    fn red_underline() {
73        test_parse(
74            b"before \x1b[31;4mred underline\x1b[0m after",
75            &[(7, 14, b"\x1b[31;4m"), (27, 31, b"\x1b[0m")],
76        );
77    }
78}