modcli/output/
style.rs

1use crossterm::style::{Attribute, Color, Stylize};
2
3#[derive(Clone)]
4pub struct StyledPart {
5    pub text: String,
6    pub fg: Option<Color>,
7    pub bg: Option<Color>,
8    pub styles: Vec<Attribute>,
9    pub fill_bg: bool,
10}
11
12impl StyledPart {
13    pub fn render(&self, default_fg: Option<Color>, default_bg: Option<Color>) -> String {
14        let styled = self.text.clone();
15        let mut content = styled.clone();
16
17        if let Some(color) = self.fg.or(default_fg) {
18            content = content.with(color).to_string();
19        }
20
21        if let Some(bg) = self.bg.or(default_bg) {
22            content = content.on(bg).to_string();
23        }
24
25        for attr in &self.styles {
26            content = apply_attr(content, *attr);
27        }
28
29        content
30    }
31}
32
33fn apply_attr(text: String, attr: Attribute) -> String {
34    match attr {
35        Attribute::Bold => text.bold().to_string(),
36        Attribute::Italic => text.italic().to_string(),
37        Attribute::Underlined => text.underlined().to_string(),
38        Attribute::CrossedOut => text.crossed_out().to_string(),
39        Attribute::SlowBlink => text.slow_blink().to_string(),
40        _ => text,
41    }
42}
43
44pub struct OutputBuilder {
45    parts: Vec<StyledPart>,
46    default_fg: Option<Color>,
47    default_bg: Option<Color>,
48}
49
50impl OutputBuilder {
51    pub fn new() -> Self {
52        Self {
53            parts: vec![],
54            default_fg: None,
55            default_bg: None,
56        }
57    }
58
59    pub fn base(self) -> BaseStyleBuilder {
60        BaseStyleBuilder {
61            builder: self,
62        }
63    }
64
65    pub fn part(self, text: &str) -> StyledPartBuilder {
66        StyledPartBuilder {
67            parent: self,
68            current: StyledPart {
69                text: text.to_string(),
70                fg: None,
71                bg: None,
72                styles: vec![],
73                fill_bg: false,
74            },
75        }
76    }
77
78    pub fn get(mut self) -> String {
79        let output = self
80            .parts
81            .iter()
82            .map(|p| p.render(self.default_fg, self.default_bg))
83            .collect::<Vec<_>>()
84            .join("");
85        self.clear();
86        output
87    }
88
89    pub fn copy(&self) -> String {
90        self.parts
91            .iter()
92            .map(|p| p.render(self.default_fg, self.default_bg))
93            .collect::<Vec<_>>()
94            .join("")
95    }
96
97    pub fn clear(&mut self) {
98        self.parts.clear();
99    }
100
101    pub fn add_part(&mut self, part: StyledPart) {
102        self.parts.push(part);
103    }
104}
105
106pub struct BaseStyleBuilder {
107    builder: OutputBuilder,
108}
109
110impl BaseStyleBuilder {
111    pub fn color(mut self, color: Color) -> Self {
112        self.builder.default_fg = Some(color);
113        self
114    }
115
116    pub fn background(mut self, color: Color) -> Self {
117        self.builder.default_bg = Some(color);
118        self
119    }
120
121    pub fn done(self) -> OutputBuilder {
122        self.builder
123    }
124}
125pub struct StyledPartBuilder {
126    parent: OutputBuilder,
127    current: StyledPart,
128}
129
130impl StyledPartBuilder {
131    pub fn new(parent: OutputBuilder) -> Self {
132        StyledPartBuilder {
133            parent,
134            current: StyledPart {
135                text: String::new(),
136                fg: None,
137                bg: None,
138                styles: vec![],
139                fill_bg: false,
140            },
141        }
142    }
143
144
145    pub fn part(self, text: &str) -> Self {
146        let mut builder = self.parent;
147        builder.add_part(self.current); // store last part
148        StyledPartBuilder {
149            parent: builder,
150            current: StyledPart {
151                text: text.to_string(),
152                fg: None,
153                bg: None,
154                styles: vec![],
155                fill_bg: false,
156            },
157        }
158    }
159
160    pub fn color(mut self, color: Color) -> Self {
161        self.current.fg = Some(color);
162        self
163    }
164
165    pub fn background(mut self, color: Color) -> Self {
166        self.current.bg = Some(color);
167        self
168    }
169
170    pub fn bold(mut self) -> Self {
171        self.current.styles.push(Attribute::Bold);
172        self
173    }
174
175    pub fn italic(mut self) -> Self {
176        self.current.styles.push(Attribute::Italic);
177        self
178    }
179
180    pub fn underline(mut self) -> Self {
181        self.current.styles.push(Attribute::Underlined);
182        self
183    }
184
185    pub fn strike(mut self) -> Self {
186        self.current.styles.push(Attribute::CrossedOut);
187        self
188    }
189
190    pub fn blink(mut self) -> Self {
191        self.current.styles.push(Attribute::SlowBlink);
192        self
193    }
194
195    pub fn space(mut self) -> Self {
196        self.current.text.push(' ');
197        self
198    }
199
200    pub fn none(mut self) -> Self {
201        self.current.styles.clear();
202        self
203    }
204
205    pub fn fill_bg(mut self) -> Self {
206        self.current.fill_bg = true;
207        self
208    }
209
210    pub fn end(self) -> OutputBuilder {
211        let mut builder = self.parent;
212        builder.add_part(self.current);
213        builder
214    }
215
216    pub fn get(self) -> String {
217        let builder = self.end();
218        builder.get()
219    }
220}
221
222pub fn build() -> OutputBuilder {
223    OutputBuilder::new()
224}