1#![doc = include_str!("../README.md")]
2
3use std::borrow::Cow;
4
5use vt_push_parser::event::VTEvent;
6use vt_push_parser::{VT_PARSER_INTEREST_NONE, VTPushParser};
7
8pub fn strip_ansi_string(s: &str) -> Cow<'_, str> {
11 let mut output = Cow::Borrowed(s);
12 let mut parser = VTPushParser::new_with_interest::<VT_PARSER_INTEREST_NONE>();
13 let mut has_text = false;
14 parser.feed_with(s.as_bytes(), |event: VTEvent| {
15 if let VTEvent::Raw(text) = event {
16 has_text = true;
17 if text.len() == s.len() {
18 return;
19 }
20 let output = match &mut output {
21 Cow::Borrowed(_) => {
22 output = Cow::Owned(String::with_capacity(s.len()));
23 let Cow::Owned(s) = &mut output else {
24 unreachable!()
25 };
26 s
27 }
28 Cow::Owned(s) => s,
29 };
30 output.push_str(String::from_utf8_lossy(text).as_ref());
31 }
32 });
33 if has_text { output } else { Cow::Borrowed("") }
34}
35
36pub fn strip_ansi_bytes(s: &[u8]) -> Cow<'_, [u8]> {
39 let mut output = Cow::Borrowed(s);
40 let mut parser = VTPushParser::new_with_interest::<VT_PARSER_INTEREST_NONE>();
41 let mut has_text = false;
42 parser.feed_with(s, |event: VTEvent| {
43 if let VTEvent::Raw(text) = event {
44 has_text = true;
45 if text.len() == s.len() {
46 return;
47 }
48 let output = match &mut output {
49 Cow::Borrowed(_) => {
50 output = Cow::Owned(Vec::with_capacity(s.len()));
51 let Cow::Owned(s) = &mut output else {
52 unreachable!()
53 };
54 s
55 }
56 Cow::Owned(s) => s,
57 };
58 output.extend_from_slice(text);
59 }
60 });
61 if has_text { output } else { Cow::Borrowed(b"") }
62}
63
64pub fn strip_ansi_bytes_callback(s: &[u8], mut cb: impl FnMut(&[u8])) {
67 let mut parser = VTPushParser::new_with_interest::<VT_PARSER_INTEREST_NONE>();
68 parser.feed_with(s, |event: VTEvent| {
69 if let VTEvent::Raw(text) = event {
70 cb(text)
71 }
72 });
73}
74
75pub struct StreamingStripper {
78 parser: VTPushParser<VT_PARSER_INTEREST_NONE>,
79}
80
81impl Default for StreamingStripper {
82 fn default() -> Self {
83 Self::new()
84 }
85}
86
87impl StreamingStripper {
88 pub const fn new() -> Self {
89 Self {
90 parser: VTPushParser::new_with_interest::<VT_PARSER_INTEREST_NONE>(),
91 }
92 }
93
94 pub fn feed(&mut self, s: &[u8], cb: &mut impl FnMut(&[u8])) {
97 self.parser.feed_with(s, &mut |event: VTEvent| {
98 if let VTEvent::Raw(text) = event {
99 cb(text)
100 }
101 });
102 }
103}
104
105pub struct Writer<W: std::io::Write> {
111 writer: W,
112 ansi_strip: StreamingStripper,
113}
114
115impl<W: std::io::Write> Writer<W> {
116 pub fn new(writer: W) -> Self {
117 Self {
118 writer,
119 ansi_strip: StreamingStripper::new(),
120 }
121 }
122
123 pub fn into_inner(self) -> W {
124 self.writer
125 }
126}
127
128impl<W: std::io::Write> std::io::Write for Writer<W> {
129 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
130 self.write_all(buf)?;
131 Ok(buf.len())
132 }
133
134 fn flush(&mut self) -> std::io::Result<()> {
135 self.writer.flush()
136 }
137
138 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
139 let mut error = None;
140 self.ansi_strip.feed(buf, &mut |text| {
141 if error.is_none() {
142 _ = self.writer.write_all(text).map_err(|e| {
143 error = Some(e);
144 });
145 }
146 });
147 if let Some(error) = error.take() {
148 Err(error)
149 } else {
150 Ok(())
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use std::io::Write;
158
159 use super::*;
160
161 #[test]
162 fn test_strip_ansi_string() {
163 let input = "Hello, world!\x1b[31mHello, world!\x1b[0m";
164 let output = strip_ansi_string(input);
165 assert_eq!(output, "Hello, world!Hello, world!");
166 }
167
168 #[test]
169 fn test_strip_ansi_as_is() {
170 let input = b"Hello, world!";
171 let output = strip_ansi_bytes(input);
172 assert_eq!(output, b"Hello, world!".as_slice());
173 assert!(matches!(output, Cow::Borrowed(_)));
174 }
175
176 #[test]
177 fn test_writer() {
178 let mut writer = Writer::new(Vec::new());
179 writer
180 .write_all(b"Hello, world!\x1b[31mHello, world!\x1b[0m")
181 .unwrap();
182 assert_eq!(writer.into_inner(), b"Hello, world!Hello, world!");
183 }
184
185 #[test]
186 fn test_only_ansi() {
187 let mut writer = Writer::new(Vec::new());
188 writer
189 .write_all("\x1b[31m\x1b[1m\x1b[0m".as_bytes())
190 .unwrap();
191 assert_eq!(writer.into_inner(), b"");
192
193 assert_eq!(strip_ansi_string("\x1b[31m\x1b[1m\x1b[0m"), "");
194 assert_eq!(
195 strip_ansi_bytes(b"\x1b[31m\x1b[1m\x1b[0m"),
196 Cow::Borrowed(b"")
197 );
198 }
199}