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 Default for OutputBuilder {
51    fn default() -> Self {
52        Self::new()
53    }
54}
55
56impl OutputBuilder {
57    pub fn new() -> Self {
58        Self {
59            parts: vec![],
60            default_fg: None,
61            default_bg: None,
62        }
63    }
64
65    pub fn base(self) -> BaseStyleBuilder {
66        BaseStyleBuilder { builder: self }
67    }
68
69    pub fn part(self, text: &str) -> StyledPartBuilder {
70        StyledPartBuilder {
71            parent: self,
72            current: StyledPart {
73                text: text.to_string(),
74                fg: None,
75                bg: None,
76                styles: vec![],
77                fill_bg: false,
78            },
79        }
80    }
81
82    pub fn get(mut self) -> String {
83        let output = self
84            .parts
85            .iter()
86            .map(|p| p.render(self.default_fg, self.default_bg))
87            .collect::<Vec<_>>()
88            .join("");
89        self.clear();
90        output
91    }
92
93    pub fn copy(&self) -> String {
94        self.parts
95            .iter()
96            .map(|p| p.render(self.default_fg, self.default_bg))
97            .collect::<Vec<_>>()
98            .join("")
99    }
100
101    pub fn clear(&mut self) {
102        self.parts.clear();
103    }
104
105    pub fn add_part(&mut self, part: StyledPart) {
106        self.parts.push(part);
107    }
108}
109
110pub struct BaseStyleBuilder {
111    builder: OutputBuilder,
112}
113
114impl BaseStyleBuilder {
115    pub fn color(mut self, color: Color) -> Self {
116        self.builder.default_fg = Some(color);
117        self
118    }
119
120    pub fn background(mut self, color: Color) -> Self {
121        self.builder.default_bg = Some(color);
122        self
123    }
124
125    pub fn done(self) -> OutputBuilder {
126        self.builder
127    }
128}
129pub struct StyledPartBuilder {
130    parent: OutputBuilder,
131    current: StyledPart,
132}
133
134impl StyledPartBuilder {
135    pub fn new(parent: OutputBuilder) -> Self {
136        StyledPartBuilder {
137            parent,
138            current: StyledPart {
139                text: String::new(),
140                fg: None,
141                bg: None,
142                styles: vec![],
143                fill_bg: false,
144            },
145        }
146    }
147
148    pub fn part(self, text: &str) -> Self {
149        let mut builder = self.parent;
150        builder.add_part(self.current); // store last part
151        StyledPartBuilder {
152            parent: builder,
153            current: StyledPart {
154                text: text.to_string(),
155                fg: None,
156                bg: None,
157                styles: vec![],
158                fill_bg: false,
159            },
160        }
161    }
162
163    pub fn color(mut self, color: Color) -> Self {
164        self.current.fg = Some(color);
165        self
166    }
167
168    pub fn background(mut self, color: Color) -> Self {
169        self.current.bg = Some(color);
170        self
171    }
172
173    pub fn bold(mut self) -> Self {
174        self.current.styles.push(Attribute::Bold);
175        self
176    }
177
178    pub fn italic(mut self) -> Self {
179        self.current.styles.push(Attribute::Italic);
180        self
181    }
182
183    pub fn underline(mut self) -> Self {
184        self.current.styles.push(Attribute::Underlined);
185        self
186    }
187
188    pub fn strike(mut self) -> Self {
189        self.current.styles.push(Attribute::CrossedOut);
190        self
191    }
192
193    pub fn blink(mut self) -> Self {
194        self.current.styles.push(Attribute::SlowBlink);
195        self
196    }
197
198    pub fn space(mut self) -> Self {
199        self.current.text.push(' ');
200        self
201    }
202
203    pub fn none(mut self) -> Self {
204        self.current.styles.clear();
205        self
206    }
207
208    pub fn fill_bg(mut self) -> Self {
209        self.current.fill_bg = true;
210        self
211    }
212
213    pub fn end(self) -> OutputBuilder {
214        let mut builder = self.parent;
215        builder.add_part(self.current);
216        builder
217    }
218
219    pub fn get(self) -> String {
220        let builder = self.end();
221        builder.get()
222    }
223}
224
225pub fn build() -> OutputBuilder {
226    OutputBuilder::new()
227}