diagnostic/style/
paint.rs

1use crate::style::style::Property;
2use std::fmt;
3/// A structure encapsulating an item and styling.
4///
5/// See the [crate level documentation](./) for usage information.
6///
7/// # Method Glossary
8///
9/// The `Paint` structure exposes many methods for convenience.
10///
11/// ### Unstyled Constructors
12///
13/// Return a new `Paint` structure with no or default styling applied.
14///
15///   * [`Paint::new(item: T)`](Paint::new())
16///   * [`Paint::default(item: T)`](Paint::default())
17///   * [`Paint::masked(item: T)`](Paint::masked())
18///   * [`Paint::wrapping(item: T)`](Paint::wrapping())
19///
20/// ### Foreground Color Constructors
21///
22/// Return a new `Paint` structure with a foreground color applied.
23///
24///   * [`Paint::rgb(r: u8, g: u8, b: u8, item: T)`](Paint::rgb())
25///   * [`Paint::fixed(color: u8, item: T)`](Paint::fixed())
26///   * [`Paint::black(item: T)`](Paint::black())
27///   * [`Paint::red(item: T)`](Paint::red())
28///   * [`Paint::green(item: T)`](Paint::green())
29///   * [`Paint::yellow(item: T)`](Paint::yellow())
30///   * [`Paint::blue(item: T)`](Paint::blue())
31///   * [`Paint::magenta(item: T)`](Paint::magenta())
32///   * [`Paint::cyan(item: T)`](Paint::cyan())
33///   * [`Paint::white(item: T)`](Paint::white())
34///
35/// ### Getters
36///
37/// Return information about the `Paint` structure.
38///
39///   * [`paint.style()`](Paint::style())
40///   * [`paint.inner()`](Paint::inner())
41///
42/// ### Setters
43///
44/// Set a style property on a given `Paint` structure.
45///
46///   * [`paint.with_style(style: Style)`](Paint::with_style())
47///   * [`paint.mask()`](Paint::mask())
48///   * [`paint.wrap()`](Paint::wrap())
49///   * [`paint.fg(color: Color)`](Paint::fg())
50///   * [`paint.bg(color: Color)`](Paint::bg())
51///   * [`paint.bold()`](Paint::bold())
52///   * [`paint.dimmed()`](Paint::dimmed())
53///   * [`paint.italic()`](Paint::italic())
54///   * [`paint.underline()`](Paint::underline())
55///   * [`paint.blink()`](Paint::blink())
56///   * [`paint.invert()`](Paint::invert())
57///   * [`paint.hidden()`](Paint::hidden())
58///   * [`paint.strikethrough()`](Paint::strikethrough())
59///
60/// These methods can be chained:
61///
62/// ```rust
63/// use diagnostic::Paint;
64///
65/// Paint::new("hi").underline().invert().italic().dimmed().bold();
66/// ```
67///
68/// ### Global Methods
69///
70/// Modify or observe the global behavior of painting.
71///
72///   * [`Paint::enable()`](Paint::enable())
73///   * [`Paint::disable()`](Paint::disable())
74///   * [`Paint::is_enabled()`](Paint::is_enabled())
75///   * [`Paint::enable_windows_ascii()`](Paint::enable_windows_ascii())
76#[derive(Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
77pub struct Paint<T> {
78    item: T,
79    style: Style,
80}
81
82macro_rules! constructors_for {
83    ($T:ty, $($name:ident: $color:ident),*) => ($(
84        #[doc = concat!(
85            "Constructs a new `Paint` structure encapsulating `item` with the foreground color\n",
86            "set to ", stringify!($name), ".\n",
87            "```rust\n",
88            "use diagnostic::Paint;\n",
89            "\n",
90            "println!(\"This is going to be ", stringify!($name),
91                ": {}\", Paint::", stringify!($name), "(\"yay!\"));\n",
92            "```\n"
93        )]
94        #[inline]
95        pub fn $name(item: $T) -> Paint<$T> {
96            Paint::new(item).fg(Color::$color)
97        }
98    )*)
99}
100
101impl<T> Paint<T> {
102    /// Constructs a new `Paint` structure encapsulating `item` with no set
103    /// styling.
104    ///
105    /// ```rust
106    /// use diagnostic::Paint;
107    ///
108    /// assert_eq!(Paint::new("hello!").to_string(), "hello!".to_string());
109    /// ```
110    #[inline]
111    pub fn new(item: T) -> Paint<T> {
112        Paint { item, style: Style::default() }
113    }
114
115    /// Constructs a new `Paint` structure encapsulating `item` with the active
116    /// terminal's default foreground and background.
117    ///
118    /// ```rust
119    /// use diagnostic::Paint;
120    ///
121    /// println!("This is going to use {}!", Paint::default("default colors"));
122    /// ```
123    #[inline]
124    pub fn default(item: T) -> Paint<T> {
125        Paint::new(item).fg(Color::Default).bg(Color::Default)
126    }
127
128    /// Constructs a new _masked_ `Paint` structure encapsulating `item` with
129    /// no set styling.
130    ///
131    /// A masked `Paint` is not written out when painting is disabled during
132    /// `Display` or `Debug` invocations. When painting is enabled, masking has
133    /// no effect.
134    ///
135    /// ```rust
136    /// use diagnostic::Paint;
137    ///
138    /// // The emoji won't be printed when coloring is disabled.
139    /// println!("{}Sprout!", Paint::masked("🌱 "));
140    /// ```
141    #[inline]
142    pub fn masked(item: T) -> Paint<T> {
143        Paint::new(item).mask()
144    }
145
146    /// Constructs a new _wrapping_ `Paint` structure encapsulating `item` with
147    /// default styling.
148    ///
149    /// A wrapping `Paint` converts all color resets written out by the internal
150    /// value to the styling of itself. This allows for seamless color wrapping
151    /// of other colored text.
152    ///
153    /// # Performance
154    ///
155    /// In order to wrap an internal value, the internal value must first be
156    /// written out to a local buffer and examined. As a result, displaying a
157    /// wrapped value is likely to result in a heap allocation and copy.
158    ///
159    /// # Example
160    ///
161    /// ```rust
162    /// use diagnostic::{Color, Paint};
163    ///
164    /// let inner = format!("{} and {}", Paint::red("Stop"), Paint::green("Go"));
165    ///
166    /// // 'Hey!' will be unstyled, "Stop" will be red, "and" will be blue, and
167    /// // "Go" will be green. Without a wrapping `Paint`, "and" would be
168    /// // unstyled.
169    /// println!("Hey! {}", Paint::wrapping(inner).fg(Color::Blue));
170    /// ```
171    #[inline]
172    pub fn wrapping(item: T) -> Paint<T> {
173        Paint::new(item).wrap()
174    }
175
176    /// Constructs a new `Paint` structure encapsulating `item` with the
177    /// foreground color set to the RGB color `r`, `g`, `b`.
178    ///
179    /// ```rust
180    /// use diagnostic::Paint;
181    ///
182    /// println!("This is going to be funky: {}", Paint::rgb(70, 130, 122, "hi!"));
183    /// ```
184    #[inline]
185    pub fn rgb(r: u8, g: u8, b: u8, item: T) -> Paint<T> {
186        Paint::new(item).fg(Color::RGB(r, g, b))
187    }
188
189    /// Constructs a new `Paint` structure encapsulating `item` with the
190    /// foreground color set to the fixed 8-bit color `color`.
191    ///
192    /// ```rust
193    /// use diagnostic::Paint;
194    ///
195    /// println!("This is going to be funky: {}", Paint::fixed(100, "hi!"));
196    /// ```
197    #[inline]
198    pub fn fixed(color: u8, item: T) -> Paint<T> {
199        Paint::new(item).fg(Color::Fixed(color))
200    }
201
202    constructors_for!(T, black: Black, red: Red, green: Green, yellow: Yellow,
203        blue: Blue, magenta: Magenta, cyan: Cyan, white: White);
204
205    /// Retrieves the style currently set on `self`.
206    ///
207    /// ```rust
208    /// use diagnostic::{Color, Paint, Style};
209    ///
210    /// let alert = Style::new(Color::Red).bold().underline();
211    /// let painted = Paint::red("hi").bold().underline();
212    ///
213    /// assert_eq!(alert, painted.style());
214    /// ```
215    #[inline]
216    pub fn style(&self) -> Style {
217        self.style
218    }
219
220    /// Retrieves a borrow to the inner item.
221    ///
222    /// ```rust
223    /// use diagnostic::Paint;
224    ///
225    /// let x = Paint::red("Hello, world!");
226    /// assert_eq!(*x.inner(), "Hello, world!");
227    /// ```
228    #[inline]
229    pub fn inner(&self) -> &T {
230        &self.item
231    }
232
233    /// Sets the style of `self` to `style`.
234    ///
235    /// Any styling currently set on `self` is lost. Prefer to use the
236    /// [`style.paint()`](Style::paint()) method to create a `Paint` struct from
237    /// `Style`.
238    ///
239    /// ```rust
240    /// use diagnostic::{Color, Paint, Style};
241    ///
242    /// let s = Style::new(Color::Red).bold().underline();
243    ///
244    /// // Using this method.
245    /// println!("Alert: {}", Paint::new("This thing happened!").with_style(s));
246    ///
247    /// // Using the `style.paint()` method.
248    /// println!("Alert: {}", s.paint("This thing happened!"));
249    /// ```
250    #[inline]
251    pub fn with_style(mut self, style: Style) -> Paint<T> {
252        self.style = style;
253        self
254    }
255
256    /// Masks `self`.
257    ///
258    /// A masked `Paint` is not written out when painting is disabled during
259    /// `Display` or `Debug` invocations. When painting is enabled, masking has
260    /// no effect.
261    ///
262    /// ```rust
263    /// use diagnostic::Paint;
264    ///
265    /// // "Whoops! " will only print when coloring is enabled.
266    /// println!("{}Something happened.", Paint::red("Whoops! ").mask());
267    /// ```
268    #[inline]
269    pub fn mask(mut self) -> Paint<T> {
270        self.style.masked = true;
271        self
272    }
273
274    /// Makes `self` a _wrapping_ `Paint`.
275    ///
276    /// A wrapping `Paint` converts all color resets written out by the internal
277    /// value to the styling of itself. This allows for seamless color wrapping
278    /// of other colored text.
279    ///
280    /// # Performance
281    ///
282    /// In order to wrap an internal value, the internal value must first be
283    /// written out to a local buffer and examined. As a result, displaying a
284    /// wrapped value is likely to result in a heap allocation and copy.
285    ///
286    /// # Example
287    ///
288    /// ```rust
289    /// use diagnostic::{Color, Paint};
290    ///
291    /// let inner = format!("{} and {}", Paint::red("Stop"), Paint::green("Go"));
292    ///
293    /// // 'Hey!' will be unstyled, "Stop" will be red, "and" will be blue, and
294    /// // "Go" will be green. Without a wrapping `Paint`, "and" would be
295    /// // unstyled.
296    /// println!("Hey! {}", Paint::blue(inner).wrap());
297    /// ```
298    #[inline]
299    pub fn wrap(mut self) -> Paint<T> {
300        self.style.wrap = true;
301        self
302    }
303
304    /// Sets the foreground to `color`.
305    ///
306    /// ```rust
307    /// use diagnostic::{Color::Red, Paint};
308    ///
309    /// println!("Red foreground: {}", Paint::new("hi!").fg(Red));
310    /// ```
311    #[inline]
312    pub fn fg(mut self, color: Color) -> Paint<T> {
313        self.style.foreground = color;
314        self
315    }
316
317    /// Sets the background to `color`.
318    ///
319    /// ```rust
320    /// use diagnostic::{Color::Yellow, Paint};
321    ///
322    /// println!("Yellow background: {}", Paint::new("hi!").bg(Yellow));
323    /// ```
324    #[inline]
325    pub fn bg(mut self, color: Color) -> Paint<T> {
326        self.style.background = color;
327        self
328    }
329
330    style_builder_for!(Paint<T>, |paint| paint.style.properties,
331                       bold: BOLD, dimmed: DIMMED, italic: ITALIC,
332                       underline: UNDERLINE, blink: BLINK, invert: INVERT,
333                       hidden: HIDDEN, strikethrough: STRIKETHROUGH);
334}
335
336macro_rules! impl_fmt_trait {
337    ($trait:ident, $fmt:expr) => {
338        impl<T: fmt::$trait> fmt::$trait for Paint<T> {
339            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
340                if Paint::is_enabled() && self.style.wrap {
341                    let mut prefix = String::new();
342                    prefix.push_str("\x1B[0m");
343                    self.style.fmt_prefix(&mut prefix)?;
344
345                    self.style.fmt_prefix(f)?;
346                    let item = format!($fmt, self.item).replace("\x1B[0m", &prefix);
347                    fmt::$trait::fmt(&item, f)?;
348                    self.style.fmt_suffix(f)
349                }
350                else if Paint::is_enabled() {
351                    self.style.fmt_prefix(f)?;
352                    fmt::$trait::fmt(&self.item, f)?;
353                    self.style.fmt_suffix(f)
354                }
355                else if !self.style.masked {
356                    fmt::$trait::fmt(&self.item, f)
357                }
358                else {
359                    Ok(())
360                }
361            }
362        }
363    };
364}
365
366impl_fmt_trait!(Display, "{}");
367impl_fmt_trait!(Debug, "{:?}");
368
369use crate::{Color, Style};
370use std::sync::atomic::{AtomicBool, Ordering};
371
372static ENABLED: AtomicBool = AtomicBool::new(true);
373
374impl Paint<()> {
375    /// Disables coloring globally.
376    ///
377    /// # Example
378    ///
379    /// ```rust
380    /// use diagnostic::Paint;
381    ///
382    /// // With coloring enabled, ANSI color codes are emitted.
383    /// assert_ne!(Paint::green("go").to_string(), "go".to_string());
384    ///
385    /// // With coloring disabled, ANSI color codes are _not_ emitted.
386    /// Paint::disable();
387    /// assert_eq!(Paint::green("go").to_string(), "go".to_string());
388    /// ```
389    pub fn disable() {
390        ENABLED.store(false, Ordering::Release);
391    }
392
393    /// Enables coloring globally. Coloring is enabled by default, so this
394    /// method should only be called to _re_ enable coloring.
395    ///
396    /// # Example
397    ///
398    /// ```rust
399    /// use diagnostic::Paint;
400    ///
401    /// // With coloring disabled, ANSI color codes are _not_ emitted.
402    /// Paint::disable();
403    /// assert_eq!(Paint::green("go").to_string(), "go".to_string());
404    ///
405    /// // Reenabling causes color code to be emitted.
406    /// Paint::enable();
407    /// assert_ne!(Paint::green("go").to_string(), "go".to_string());
408    /// ```
409    pub fn enable() {
410        ENABLED.store(true, Ordering::Release);
411    }
412
413    /// Returns `true` if coloring is enabled and `false` otherwise. Coloring is
414    /// enabled by default but can be enabled and disabled on-the-fly with the
415    /// [`Paint::enable()`] and [`Paint::disable()`] methods.
416    ///
417    /// [`Paint::disable()`]: struct.Paint.html#method.disable
418    /// [`Paint::enable()`]: struct.Paint.html#method.disable
419    ///
420    /// # Example
421    ///
422    /// ```rust
423    /// use diagnostic::Paint;
424    ///
425    /// // Coloring is enabled by default.
426    /// assert!(Paint::is_enabled());
427    ///
428    /// // Disable it with `Paint::disable()`.
429    /// Paint::disable();
430    /// assert!(!Paint::is_enabled());
431    ///
432    /// // Reenable with `Paint::enable()`.
433    /// Paint::enable();
434    /// assert!(Paint::is_enabled());
435    /// ```
436    pub fn is_enabled() -> bool {
437        ENABLED.load(Ordering::Acquire)
438    }
439}