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}