use std::fmt;
use super::DisplayTree;
pub struct AsTree<'a, T: DisplayTree> {
tree: &'a T,
style: Style,
}
impl<'a, T: DisplayTree> AsTree<'a, T> {
pub fn new(tree: &'a T) -> Self {
Self {
tree,
style: Style::default(),
}
}
pub fn with_style(tree: &'a T, style: Style) -> Self {
Self { tree, style }
}
}
impl<'a, T: DisplayTree> StyleBuilder for AsTree<'a, T> {
fn style_mut(&mut self) -> &mut Style {
&mut self.style
}
}
impl<'a, T: DisplayTree> fmt::Display for AsTree<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.tree.fmt(f, self.style)
}
}
#[derive(Clone, Copy)]
pub struct Style {
pub char_set: CharSet,
pub indentation: u32,
pub leaf_style: TextStyle,
pub branch_style: TextStyle,
}
impl StyleBuilder for Style {
fn style_mut(&mut self) -> &mut Style {
self
}
}
impl Default for Style {
fn default() -> Self {
Self {
char_set: CharSet::SINGLE_LINE,
indentation: 2,
leaf_style: TextStyle::default(),
branch_style: TextStyle::default(),
}
}
}
#[derive(Clone, Copy, Default)]
pub struct TextStyle {
pub text_color: Option<Color>,
pub background_color: Option<Color>,
pub is_bold: bool,
pub is_faint: bool,
pub is_italic: bool,
pub is_underlined: bool,
pub is_strikethrough: bool,
}
impl TextStyle {
pub fn apply(&self, string: &str) -> String {
use std::borrow::Cow;
let mut ansi_codes: Vec<Cow<str>> = Vec::new();
if let Some(text_color) = self.text_color {
ansi_codes.push(match text_color {
Color::Black => "30".into(),
Color::Red => "31".into(),
Color::Green => "32".into(),
Color::Yellow => "33".into(),
Color::Blue => "34".into(),
Color::Magenta => "35".into(),
Color::Cyan => "36".into(),
Color::White => "37".into(),
Color::Rgb(r, g, b) => format!("38;2;{r};{g};{b}").into(),
})
}
if let Some(background_color) = self.background_color {
ansi_codes.push(match background_color {
Color::Black => "40".into(),
Color::Red => "41".into(),
Color::Green => "42".into(),
Color::Yellow => "43".into(),
Color::Blue => "44".into(),
Color::Magenta => "45".into(),
Color::Cyan => "46".into(),
Color::White => "47".into(),
Color::Rgb(r, g, b) => format!("48;2;{r};{g};{b}").into(),
})
}
if self.is_bold {
ansi_codes.push("1".into())
}
if self.is_faint {
ansi_codes.push("2".into())
}
if self.is_italic {
ansi_codes.push("3".into())
}
if self.is_underlined {
ansi_codes.push("4".into())
}
if self.is_strikethrough {
ansi_codes.push("9".into())
}
if !ansi_codes.is_empty() {
let escape_sequences = ansi_codes
.into_iter()
.map(|code| format!("\x1b[{code}m"))
.collect::<String>();
format!("{escape_sequences}{string}\x1b[0m")
} else {
string.to_owned()
}
}
}
#[derive(Clone, Copy)]
pub enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
Rgb(u8, u8, u8),
}
#[derive(Clone, Copy)]
pub struct CharSet {
pub horizontal: char,
pub vertical: char,
pub connector: char,
pub end_connector: char,
}
impl CharSet {
pub const SINGLE_LINE: Self = Self {
horizontal: '─',
vertical: '│',
connector: '├',
end_connector: '└',
};
pub const SINGLE_LINE_BOLD: Self = Self {
horizontal: '━',
vertical: '┃',
connector: '┣',
end_connector: '┗',
};
pub const SINGLE_LINE_CURVED: Self = Self {
horizontal: '─',
vertical: '│',
connector: '├',
end_connector: '╰',
};
pub const DOUBLE_LINE: Self = Self {
horizontal: '═',
vertical: '║',
connector: '╠',
end_connector: '╚',
};
pub const ASCII: Self = Self {
horizontal: '-',
vertical: '|',
connector: '|',
end_connector: '`',
};
}
pub trait StyleBuilder: Sized {
#[doc(hidden)]
fn style_mut(&mut self) -> &mut Style;
fn char_set(mut self, char_set: CharSet) -> Self {
self.style_mut().char_set = char_set;
self
}
fn indentation(mut self, indentation: u32) -> Self {
self.style_mut().indentation = indentation;
self
}
fn leaf_style(mut self, style: TextStyle) -> Self {
self.style_mut().leaf_style = style;
self
}
fn branch_style(mut self, style: TextStyle) -> Self {
self.style_mut().branch_style = style;
self
}
fn leaf_color(mut self, color: Color) -> Self {
self.style_mut().leaf_style.text_color = Some(color);
self
}
fn leaf_background_color(mut self, color: Color) -> Self {
self.style_mut().leaf_style.background_color = Some(color);
self
}
fn bold_leaves(mut self) -> Self {
self.style_mut().leaf_style.is_bold = true;
self
}
fn faint_leaves(mut self) -> Self {
self.style_mut().leaf_style.is_faint = true;
self
}
fn italic_leaves(mut self) -> Self {
self.style_mut().leaf_style.is_italic = true;
self
}
fn underlined_leaves(mut self) -> Self {
self.style_mut().leaf_style.is_underlined = true;
self
}
fn strikethrough_leaves(mut self) -> Self {
self.style_mut().leaf_style.is_strikethrough = true;
self
}
fn branch_color(mut self, color: Color) -> Self {
self.style_mut().branch_style.text_color = Some(color);
self
}
fn branch_background_color(mut self, color: Color) -> Self {
self.style_mut().branch_style.background_color = Some(color);
self
}
fn bold_branches(mut self) -> Self {
self.style_mut().branch_style.is_bold = true;
self
}
fn faint_branches(mut self) -> Self {
self.style_mut().branch_style.is_faint = true;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain() {
let style = TextStyle::default();
assert_eq!(style.apply("text"), "text")
}
#[test]
fn text_color() {
let style = TextStyle {
text_color: Some(Color::Red),
..TextStyle::default()
};
assert_eq!(style.apply("text"), "\x1b[31mtext\x1b[0m")
}
#[test]
fn background_color() {
let style = TextStyle {
background_color: Some(Color::Red),
..TextStyle::default()
};
assert_eq!(style.apply("text"), "\x1b[41mtext\x1b[0m")
}
#[test]
fn bold() {
let style = TextStyle {
is_bold: true,
..TextStyle::default()
};
assert_eq!(style.apply("text"), "\x1b[1mtext\x1b[0m")
}
#[test]
fn faint() {
let style = TextStyle {
is_faint: true,
..TextStyle::default()
};
assert_eq!(style.apply("text"), "\x1b[2mtext\x1b[0m")
}
#[test]
fn italic() {
let style = TextStyle {
is_italic: true,
..TextStyle::default()
};
assert_eq!(style.apply("text"), "\x1b[3mtext\x1b[0m")
}
#[test]
fn underline() {
let style = TextStyle {
is_underlined: true,
..TextStyle::default()
};
assert_eq!(style.apply("text"), "\x1b[4mtext\x1b[0m")
}
#[test]
fn strikethrough() {
let style = TextStyle {
is_strikethrough: true,
..TextStyle::default()
};
assert_eq!(style.apply("text"), "\x1b[9mtext\x1b[0m")
}
}