1use bstr::ByteSlice;
5use owo_colors::Style;
6
7pub fn highlight_end(slice: &[u8]) -> usize {
11 let mut iter = slice.find_iter(b"\n");
13 match iter.next() {
14 Some(_) => {
15 match iter.next() {
16 Some(second) => second,
17 None => slice.len(),
19 }
20 }
21 None => slice.len(),
23 }
24}
25
26#[derive(Debug, Default, Clone)]
27pub(super) struct Styles {
28 pub(super) is_colorized: bool,
29 pub(super) count: Style,
30 pub(super) pass: Style,
31 pub(super) retry: Style,
32 pub(super) fail: Style,
33 pub(super) skip: Style,
34 pub(super) script_id: Style,
35 pub(super) list_styles: crate::list::Styles,
36 pub(super) run_id_prefix: Style,
38 pub(super) run_id_rest: Style,
40}
41
42impl Styles {
43 pub(super) fn colorize(&mut self) {
44 self.is_colorized = true;
45 self.count = Style::new().bold();
46 self.pass = Style::new().green().bold();
47 self.retry = Style::new().magenta().bold();
48 self.fail = Style::new().red().bold();
49 self.skip = Style::new().yellow().bold();
50 self.script_id = Style::new().blue().bold();
51 self.list_styles.colorize();
52 self.run_id_prefix = Style::new().bold().purple();
53 self.run_id_rest = Style::new().bright_black();
54 }
55}
56
57pub(crate) fn print_lines_in_chunks(
71 text: &str,
72 max_chunk_bytes: usize,
73 mut callback: impl FnMut(&str),
74) {
75 let mut remaining = text;
76 while !remaining.is_empty() {
77 let max = remaining.floor_char_boundary(max_chunk_bytes);
80
81 let last_newline = remaining[..max].rfind('\n');
83
84 if let Some(index) = last_newline {
85 callback(&remaining[..=index]);
87 remaining = &remaining[index + 1..];
88 } else {
89 let next_newline = remaining[max..].find('\n');
91
92 if let Some(index) = next_newline {
93 callback(&remaining[..=index + max]);
95 remaining = &remaining[index + max + 1..];
96 } else {
97 callback(remaining);
99 remaining = "";
100 }
101 }
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_highlight_end() {
111 let tests: &[(&str, usize)] = &[
112 ("", 0),
113 ("\n", 1),
114 ("foo", 3),
115 ("foo\n", 4),
116 ("foo\nbar", 7),
117 ("foo\nbar\n", 7),
118 ("foo\nbar\nbaz", 7),
119 ("foo\nbar\nbaz\n", 7),
120 ("\nfoo\nbar\nbaz", 4),
121 ];
122
123 for (input, output) in tests {
124 assert_eq!(
125 highlight_end(input.as_bytes()),
126 *output,
127 "for input {input:?}"
128 );
129 }
130 }
131
132 #[test]
133 fn test_print_lines_in_chunks() {
134 let tests: &[(&str, &str, usize, &[&str])] = &[
135 ("empty string", "", 1024, &[]),
137 ("single line no newline", "hello", 1024, &["hello"]),
138 ("single line with newline", "hello\n", 1024, &["hello\n"]),
139 (
140 "multiple lines small",
141 "line1\nline2\nline3\n",
142 1024,
143 &["line1\nline2\nline3\n"],
144 ),
145 (
146 "breaks at newline",
147 "line1\nline2\nline3\n",
148 10,
149 &["line1\n", "line2\n", "line3\n"],
150 ),
151 (
152 "multiple lines per chunk",
153 "line1\nline2\nline3\n",
154 13,
155 &["line1\nline2\n", "line3\n"],
156 ),
157 (
158 "long line with newline",
159 &format!("{}\n", "a".repeat(2000)),
160 1024,
161 &[&format!("{}\n", "a".repeat(2000))],
162 ),
163 (
164 "long line no newline",
165 &"a".repeat(2000),
166 1024,
167 &[&"a".repeat(2000)],
168 ),
169 ("exact boundary", "123456789\n", 10, &["123456789\n"]),
170 (
171 "newline at boundary",
172 "12345678\nabcdefgh\n",
173 10,
174 &["12345678\n", "abcdefgh\n"],
175 ),
176 (
177 "utf8 emoji",
178 "hello๐\nworld\n",
179 10,
180 &["hello๐\n", "world\n"],
181 ),
182 (
183 "utf8 near boundary",
184 "1234๐\nabcd\n",
185 7,
186 &["1234๐\n", "abcd\n"],
187 ),
188 (
189 "consecutive newlines",
190 "line1\n\n\nline2\n",
191 10,
192 &["line1\n\n\n", "line2\n"],
193 ),
194 (
195 "no trailing newline",
196 "line1\nline2\nline3",
197 10,
198 &["line1\n", "line2\n", "line3"],
199 ),
200 (
201 "mixed lengths",
202 "short\nvery_long_line_that_exceeds_chunk_size\nmedium_line\nok\n",
203 20,
204 &[
205 "short\n",
206 "very_long_line_that_exceeds_chunk_size\n",
207 "medium_line\nok\n",
208 ],
209 ),
210 ];
211
212 for (description, input, chunk_size, expected) in tests {
213 let mut chunks = Vec::new();
214 print_lines_in_chunks(input, *chunk_size, |chunk| chunks.push(chunk.to_string()));
215 assert_eq!(
216 chunks,
217 expected.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
218 "test case: {}",
219 description
220 );
221 }
222 }
223}