use bstr::ByteSlice;
use owo_colors::Style;
pub fn highlight_end(slice: &[u8]) -> usize {
let mut iter = slice.find_iter(b"\n");
match iter.next() {
Some(_) => {
match iter.next() {
Some(second) => second,
None => slice.len(),
}
}
None => slice.len(),
}
}
#[derive(Debug, Default, Clone)]
pub(super) struct Styles {
pub(super) is_colorized: bool,
pub(super) count: Style,
pub(super) pass: Style,
pub(super) retry: Style,
pub(super) fail: Style,
pub(super) skip: Style,
pub(super) script_id: Style,
pub(super) list_styles: crate::list::Styles,
pub(super) run_id_prefix: Style,
pub(super) run_id_rest: Style,
}
impl Styles {
pub(super) fn colorize(&mut self) {
self.is_colorized = true;
self.count = Style::new().bold();
self.pass = Style::new().green().bold();
self.retry = Style::new().magenta().bold();
self.fail = Style::new().red().bold();
self.skip = Style::new().yellow().bold();
self.script_id = Style::new().blue().bold();
self.list_styles.colorize();
self.run_id_prefix = Style::new().bold().purple();
self.run_id_rest = Style::new().bright_black();
}
}
pub(crate) fn print_lines_in_chunks(
text: &str,
max_chunk_bytes: usize,
mut callback: impl FnMut(&str),
) {
let mut remaining = text;
while !remaining.is_empty() {
let max = remaining.floor_char_boundary(max_chunk_bytes);
let last_newline = remaining[..max].rfind('\n');
if let Some(index) = last_newline {
callback(&remaining[..=index]);
remaining = &remaining[index + 1..];
} else {
let next_newline = remaining[max..].find('\n');
if let Some(index) = next_newline {
callback(&remaining[..=index + max]);
remaining = &remaining[index + max + 1..];
} else {
callback(remaining);
remaining = "";
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_highlight_end() {
let tests: &[(&str, usize)] = &[
("", 0),
("\n", 1),
("foo", 3),
("foo\n", 4),
("foo\nbar", 7),
("foo\nbar\n", 7),
("foo\nbar\nbaz", 7),
("foo\nbar\nbaz\n", 7),
("\nfoo\nbar\nbaz", 4),
];
for (input, output) in tests {
assert_eq!(
highlight_end(input.as_bytes()),
*output,
"for input {input:?}"
);
}
}
#[test]
fn test_print_lines_in_chunks() {
let tests: &[(&str, &str, usize, &[&str])] = &[
("empty string", "", 1024, &[]),
("single line no newline", "hello", 1024, &["hello"]),
("single line with newline", "hello\n", 1024, &["hello\n"]),
(
"multiple lines small",
"line1\nline2\nline3\n",
1024,
&["line1\nline2\nline3\n"],
),
(
"breaks at newline",
"line1\nline2\nline3\n",
10,
&["line1\n", "line2\n", "line3\n"],
),
(
"multiple lines per chunk",
"line1\nline2\nline3\n",
13,
&["line1\nline2\n", "line3\n"],
),
(
"long line with newline",
&format!("{}\n", "a".repeat(2000)),
1024,
&[&format!("{}\n", "a".repeat(2000))],
),
(
"long line no newline",
&"a".repeat(2000),
1024,
&[&"a".repeat(2000)],
),
("exact boundary", "123456789\n", 10, &["123456789\n"]),
(
"newline at boundary",
"12345678\nabcdefgh\n",
10,
&["12345678\n", "abcdefgh\n"],
),
(
"utf8 emoji",
"hello๐\nworld\n",
10,
&["hello๐\n", "world\n"],
),
(
"utf8 near boundary",
"1234๐\nabcd\n",
7,
&["1234๐\n", "abcd\n"],
),
(
"consecutive newlines",
"line1\n\n\nline2\n",
10,
&["line1\n\n\n", "line2\n"],
),
(
"no trailing newline",
"line1\nline2\nline3",
10,
&["line1\n", "line2\n", "line3"],
),
(
"mixed lengths",
"short\nvery_long_line_that_exceeds_chunk_size\nmedium_line\nok\n",
20,
&[
"short\n",
"very_long_line_that_exceeds_chunk_size\n",
"medium_line\nok\n",
],
),
];
for (description, input, chunk_size, expected) in tests {
let mut chunks = Vec::new();
print_lines_in_chunks(input, *chunk_size, |chunk| chunks.push(chunk.to_string()));
assert_eq!(
chunks,
expected.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
"test case: {}",
description
);
}
}
}