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); 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}