use owo_colors::{OwoColorize, Stream}; use std::{self, cmp, fmt::Display}; pub fn say(what: A) where A: Display, { eprintln!("{}", what) } pub fn fmt_step( f: &mut std::fmt::Formatter, title: &str, payload: &A, ) -> std::result::Result<(), std::fmt::Error> where A: Display, { write!( f, "{:>13} {}", title .if_supports_color(Stream::Stderr, |s| s.bold()) .if_supports_color(Stream::Stderr, |s| s.purple()), payload.if_supports_color(Stream::Stderr, |s| s.bold()) ) } pub fn ansi_len(s: &str) -> usize { String::from_utf8(strip_ansi_escapes::strip(s).unwrap()) .unwrap() .chars() .count() } pub fn len_longest_line(zero: usize, s: &str) -> usize { s.lines().fold(zero, |max, l| { let n = ansi_len(l); if n > max { n } else { max } }) } pub fn boxed(title: &str, content: &str) -> String { boxed_with(title, content, |s| s.to_string()) } pub fn boxed_with(title: &str, content: &str, border_style: fn(&str) -> String) -> String { let n = len_longest_line(ansi_len(title) + 1, content); let content = content .lines() .map(|line| { format!( "{} {} {}", border_style("│"), pad_right(line.to_string(), n, " "), border_style("│"), ) }) .collect::>() .join("\n"); let top = format!( "{} {}{}", border_style("┍━"), pad_right(format!("{title} "), n, &border_style("━")), border_style("┑"), ); let bottom = format!( "{}{}{}", border_style("┕"), pad_right(String::new(), n + 2, &border_style("━")), border_style("┙") ); format!("{top}\n{content}\n{bottom}") } pub fn open_box( title: &str, content: &str, footer: &str, border_style: fn(&str) -> String, ) -> String { let i = ansi_len(content.lines().collect::>().first().unwrap()); let j = len_longest_line(ansi_len(title) + 1, content); let k = ansi_len(footer); let content = content .lines() .map(|line| format!("{} {line}", border_style("│"),)) .collect::>() .join("\n"); let top = format!( "{} {}", border_style(if footer.is_empty() { "┝━" } else { "┍━" }), pad_right(format!("{title} "), i - 1, &border_style("━")), ); let bottom = if footer.is_empty() { border_style("╽") } else { format!( "{} {}", pad_right( border_style("┕"), if j < k { 0 } else { j + 1 - k }, &border_style("━") ), footer ) }; format!("{top}\n{content}\n{bottom}") } pub fn indent(lines: &str, n: usize) -> String { let tab = pad_left(String::new(), n, " "); lines .lines() .map(|line| format!("{tab}{line}")) .collect::>() .join("\n") } pub fn pad_left(mut text: String, n: usize, delimiter: &str) -> String { let diff = n as i32 - ansi_len(&text) as i32; if diff.is_positive() { for _ in 0..diff { text.insert_str(0, delimiter); } } text } pub fn pad_right(mut text: String, n: usize, delimiter: &str) -> String { let diff = n as i32 - ansi_len(&text) as i32; if diff.is_positive() { for _ in 0..diff { text.push_str(delimiter); } } text } pub fn style_if(styled: bool, s: String, apply_style: fn(String) -> String) -> String { if styled { apply_style(s) } else { s } } pub fn multiline(max_len: usize, s: String) -> Vec { let mut xs = Vec::new(); let mut i = 0; let len = s.len(); loop { let lo = i * max_len; let hi = cmp::min(len - 1, lo + max_len - 1); if lo >= len { break; } let chunk = &s[lo..hi]; xs.push(chunk.to_string()); i += 1; } xs }