Skip to main content

tiny_table/table/
style.rs

1use crate::color::Color;
2
3use super::ANSI_RESET;
4
5/// A styling action to apply to text (color, formatting, etc.).
6///
7/// These actions are accumulated in cells and columns, and applied when rendering.
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum StyleAction {
10    /// Set the foreground color.
11    Color(Color),
12    /// Set the background color.
13    OnColor(Color),
14    /// Clear all formatting.
15    Clear,
16    /// Reset formatting back to normal.
17    Normal,
18    /// Make the text bold.
19    Bold,
20    /// Make the text dimmed.
21    Dimmed,
22    /// Make the text italic.
23    Italic,
24    /// Underline the text.
25    Underline,
26    /// Blink the text.
27    Blink,
28    /// Reverse foreground and background colors.
29    Reversed,
30    /// Hide the text.
31    Hidden,
32    /// Strike the text through.
33    Strikethrough,
34}
35
36/// Macro to implement styling methods on a type.
37///
38/// This macro generates all the styling methods (color, bold, italic, etc.) for a given type.
39/// The type must have a way to accumulate [`StyleAction`]s.
40///
41/// # Parameters
42///
43/// - `$ty`: The type to implement styling methods for.
44/// - `$push`: A closure that takes the type and a `StyleAction`, and returns the modified type.
45///
46/// # Example
47///
48/// ```ignore
49/// impl_style_methods!(Cell, |mut cell: Cell, action| {
50///     cell.styles.push(action);
51///     cell
52/// });
53/// ```
54#[macro_export]
55macro_rules! impl_style_methods {
56    ($ty:ty, $push:expr) => {
57        impl $ty {
58            fn push_style(self, action: $crate::table::style::StyleAction) -> Self {
59                ($push)(self, action)
60            }
61
62            /// Apply a foreground color.
63            pub fn color(self, color: $crate::color::Color) -> Self {
64                self.push_style($crate::table::style::StyleAction::Color(color))
65            }
66
67            /// Apply a background color.
68            pub fn on_color(self, color: $crate::color::Color) -> Self {
69                self.push_style($crate::table::style::StyleAction::OnColor(color))
70            }
71
72            /// Clear all formatting.
73            pub fn clear(self) -> Self {
74                self.push_style($crate::table::style::StyleAction::Clear)
75            }
76
77            /// Reset formatting back to normal.
78            pub fn normal(self) -> Self {
79                self.push_style($crate::table::style::StyleAction::Normal)
80            }
81
82            /// Make the text bold.
83            pub fn bold(self) -> Self {
84                self.push_style($crate::table::style::StyleAction::Bold)
85            }
86
87            /// Make the text dimmed.
88            pub fn dimmed(self) -> Self {
89                self.push_style($crate::table::style::StyleAction::Dimmed)
90            }
91
92            /// Make the text italic.
93            pub fn italic(self) -> Self {
94                self.push_style($crate::table::style::StyleAction::Italic)
95            }
96
97            /// Underline the text.
98            pub fn underline(self) -> Self {
99                self.push_style($crate::table::style::StyleAction::Underline)
100            }
101
102            /// Blink the text.
103            pub fn blink(self) -> Self {
104                self.push_style($crate::table::style::StyleAction::Blink)
105            }
106
107            /// Reverse foreground and background colors.
108            pub fn reversed(self) -> Self {
109                self.push_style($crate::table::style::StyleAction::Reversed)
110            }
111
112            /// Hide the text.
113            pub fn hidden(self) -> Self {
114                self.push_style($crate::table::style::StyleAction::Hidden)
115            }
116
117            /// Strike the text through.
118            pub fn strikethrough(self) -> Self {
119                self.push_style($crate::table::style::StyleAction::Strikethrough)
120            }
121
122            /// Use a black foreground color.
123            pub fn black(self) -> Self {
124                self.color($crate::color::Color::Black)
125            }
126
127            /// Use a red foreground color.
128            pub fn red(self) -> Self {
129                self.color($crate::color::Color::Red)
130            }
131
132            /// Use a green foreground color.
133            pub fn green(self) -> Self {
134                self.color($crate::color::Color::Green)
135            }
136
137            /// Use a yellow foreground color.
138            pub fn yellow(self) -> Self {
139                self.color($crate::color::Color::Yellow)
140            }
141
142            /// Use a blue foreground color.
143            pub fn blue(self) -> Self {
144                self.color($crate::color::Color::Blue)
145            }
146
147            /// Use a magenta foreground color.
148            pub fn magenta(self) -> Self {
149                self.color($crate::color::Color::Magenta)
150            }
151
152            /// Use a cyan foreground color.
153            pub fn cyan(self) -> Self {
154                self.color($crate::color::Color::Cyan)
155            }
156
157            /// Use a white foreground color.
158            pub fn white(self) -> Self {
159                self.color($crate::color::Color::White)
160            }
161
162            /// Use a bright black foreground color.
163            pub fn bright_black(self) -> Self {
164                self.color($crate::color::Color::BrightBlack)
165            }
166
167            /// Use a bright red foreground color.
168            pub fn bright_red(self) -> Self {
169                self.color($crate::color::Color::BrightRed)
170            }
171
172            /// Use a bright green foreground color.
173            pub fn bright_green(self) -> Self {
174                self.color($crate::color::Color::BrightGreen)
175            }
176
177            /// Use a bright yellow foreground color.
178            pub fn bright_yellow(self) -> Self {
179                self.color($crate::color::Color::BrightYellow)
180            }
181
182            /// Use a bright blue foreground color.
183            pub fn bright_blue(self) -> Self {
184                self.color($crate::color::Color::BrightBlue)
185            }
186
187            /// Use a bright magenta foreground color.
188            pub fn bright_magenta(self) -> Self {
189                self.color($crate::color::Color::BrightMagenta)
190            }
191
192            /// Use a bright cyan foreground color.
193            pub fn bright_cyan(self) -> Self {
194                self.color($crate::color::Color::BrightCyan)
195            }
196
197            /// Use a bright white foreground color.
198            pub fn bright_white(self) -> Self {
199                self.color($crate::color::Color::BrightWhite)
200            }
201
202            /// Use a purple foreground color.
203            pub fn purple(self) -> Self {
204                self.color($crate::color::Color::Magenta)
205            }
206
207            /// Use a bright purple foreground color.
208            pub fn bright_purple(self) -> Self {
209                self.color($crate::color::Color::BrightMagenta)
210            }
211
212            /// Use an ANSI 8-bit foreground color.
213            pub fn ansi_color(self, color: impl Into<u8>) -> Self {
214                self.push_style($crate::table::style::StyleAction::Color(
215                    $crate::color::Color::AnsiColor(color.into()),
216                ))
217            }
218
219            /// Use a truecolor foreground color.
220            pub fn truecolor(self, red: u8, green: u8, blue: u8) -> Self {
221                self.push_style($crate::table::style::StyleAction::Color(
222                    $crate::color::Color::TrueColor {
223                        r: red,
224                        g: green,
225                        b: blue,
226                    },
227                ))
228            }
229
230            /// Use a black background color.
231            pub fn on_black(self) -> Self {
232                self.push_style($crate::table::style::StyleAction::OnColor(
233                    $crate::color::Color::Black,
234                ))
235            }
236
237            /// Use a red background color.
238            pub fn on_red(self) -> Self {
239                self.push_style($crate::table::style::StyleAction::OnColor(
240                    $crate::color::Color::Red,
241                ))
242            }
243
244            /// Use a green background color.
245            pub fn on_green(self) -> Self {
246                self.push_style($crate::table::style::StyleAction::OnColor(
247                    $crate::color::Color::Green,
248                ))
249            }
250
251            /// Use a yellow background color.
252            pub fn on_yellow(self) -> Self {
253                self.push_style($crate::table::style::StyleAction::OnColor(
254                    $crate::color::Color::Yellow,
255                ))
256            }
257
258            /// Use a blue background color.
259            pub fn on_blue(self) -> Self {
260                self.push_style($crate::table::style::StyleAction::OnColor(
261                    $crate::color::Color::Blue,
262                ))
263            }
264
265            /// Use a magenta background color.
266            pub fn on_magenta(self) -> Self {
267                self.push_style($crate::table::style::StyleAction::OnColor(
268                    $crate::color::Color::Magenta,
269                ))
270            }
271
272            /// Use a cyan background color.
273            pub fn on_cyan(self) -> Self {
274                self.push_style($crate::table::style::StyleAction::OnColor(
275                    $crate::color::Color::Cyan,
276                ))
277            }
278
279            /// Use a white background color.
280            pub fn on_white(self) -> Self {
281                self.push_style($crate::table::style::StyleAction::OnColor(
282                    $crate::color::Color::White,
283                ))
284            }
285
286            /// Use a bright black background color.
287            pub fn on_bright_black(self) -> Self {
288                self.push_style($crate::table::style::StyleAction::OnColor(
289                    $crate::color::Color::BrightBlack,
290                ))
291            }
292
293            /// Use a bright red background color.
294            pub fn on_bright_red(self) -> Self {
295                self.push_style($crate::table::style::StyleAction::OnColor(
296                    $crate::color::Color::BrightRed,
297                ))
298            }
299
300            /// Use a bright green background color.
301            pub fn on_bright_green(self) -> Self {
302                self.push_style($crate::table::style::StyleAction::OnColor(
303                    $crate::color::Color::BrightGreen,
304                ))
305            }
306
307            /// Use a bright yellow background color.
308            pub fn on_bright_yellow(self) -> Self {
309                self.push_style($crate::table::style::StyleAction::OnColor(
310                    $crate::color::Color::BrightYellow,
311                ))
312            }
313
314            /// Use a bright blue background color.
315            pub fn on_bright_blue(self) -> Self {
316                self.push_style($crate::table::style::StyleAction::OnColor(
317                    $crate::color::Color::BrightBlue,
318                ))
319            }
320
321            /// Use a bright magenta background color.
322            pub fn on_bright_magenta(self) -> Self {
323                self.push_style($crate::table::style::StyleAction::OnColor(
324                    $crate::color::Color::BrightMagenta,
325                ))
326            }
327
328            /// Use a bright cyan background color.
329            pub fn on_bright_cyan(self) -> Self {
330                self.push_style($crate::table::style::StyleAction::OnColor(
331                    $crate::color::Color::BrightCyan,
332                ))
333            }
334
335            /// Use a bright white background color.
336            pub fn on_bright_white(self) -> Self {
337                self.push_style($crate::table::style::StyleAction::OnColor(
338                    $crate::color::Color::BrightWhite,
339                ))
340            }
341
342            /// Use an ANSI 8-bit background color.
343            pub fn custom_color(self, color: impl Into<$crate::color::CustomColor>) -> Self {
344                let color = color.into();
345                self.push_style($crate::table::style::StyleAction::Color(
346                    $crate::color::Color::TrueColor {
347                        r: color.r,
348                        g: color.g,
349                        b: color.b,
350                    },
351                ))
352            }
353
354            /// Use a custom truecolor background color.
355            pub fn on_custom_color(self, color: impl Into<$crate::color::CustomColor>) -> Self {
356                let color = color.into();
357                self.push_style($crate::table::style::StyleAction::OnColor(
358                    $crate::color::Color::TrueColor {
359                        r: color.r,
360                        g: color.g,
361                        b: color.b,
362                    },
363                ))
364            }
365
366            /// Use an ANSI 8-bit background color.
367            pub fn on_ansi_color(self, color: impl Into<u8>) -> Self {
368                self.push_style($crate::table::style::StyleAction::OnColor(
369                    $crate::color::Color::AnsiColor(color.into()),
370                ))
371            }
372
373            /// Use a truecolor background color.
374            pub fn on_truecolor(self, red: u8, green: u8, blue: u8) -> Self {
375                self.push_style($crate::table::style::StyleAction::OnColor(
376                    $crate::color::Color::TrueColor {
377                        r: red,
378                        g: green,
379                        b: blue,
380                    },
381                ))
382            }
383        }
384    };
385}
386
387/// Apply a sequence of style actions to content, wrapping it in ANSI escape codes.
388///
389/// If the content or actions are empty, the content is returned unchanged.
390/// Otherwise, the content is wrapped with the appropriate ANSI codes and a reset sequence.
391///
392/// # Arguments
393///
394/// - `content`: The text to style.
395/// - `actions`: The styling actions to apply.
396///
397/// # Returns
398///
399/// The styled content with ANSI escape codes, or the original content if no styling is needed.
400pub fn apply_style_actions(content: &str, actions: &[StyleAction]) -> String {
401    if content.is_empty() || actions.is_empty() {
402        return content.to_string();
403    }
404
405    let mut prefix = String::new();
406    let mut has_style = false;
407
408    for action in actions {
409        match action {
410            StyleAction::Color(color) => {
411                prefix.push_str(&color.ansi_fg());
412                has_style = true;
413            }
414            StyleAction::OnColor(color) => {
415                prefix.push_str(&color.ansi_bg());
416                has_style = true;
417            }
418            StyleAction::Clear | StyleAction::Normal => {
419                prefix.clear();
420                has_style = false;
421            }
422            StyleAction::Bold => {
423                prefix.push_str("\x1b[1m");
424                has_style = true;
425            }
426            StyleAction::Dimmed => {
427                prefix.push_str("\x1b[2m");
428                has_style = true;
429            }
430            StyleAction::Italic => {
431                prefix.push_str("\x1b[3m");
432                has_style = true;
433            }
434            StyleAction::Underline => {
435                prefix.push_str("\x1b[4m");
436                has_style = true;
437            }
438            StyleAction::Blink => {
439                prefix.push_str("\x1b[5m");
440                has_style = true;
441            }
442            StyleAction::Reversed => {
443                prefix.push_str("\x1b[7m");
444                has_style = true;
445            }
446            StyleAction::Hidden => {
447                prefix.push_str("\x1b[8m");
448                has_style = true;
449            }
450            StyleAction::Strikethrough => {
451                prefix.push_str("\x1b[9m");
452                has_style = true;
453            }
454        }
455    }
456
457    if !has_style {
458        return content.to_string();
459    }
460
461    format!("{}{}{}", prefix, content, ANSI_RESET)
462}