yansi/
paint.rs

1use core::fmt;
2
3#[cfg(all(feature = "alloc", not(feature = "std")))]
4use alloc::{string::{String, ToString}, borrow::Cow};
5
6#[cfg(feature = "std")]
7use std::borrow::Cow;
8
9use crate::{Color, Attribute, Quirk, Style, Condition};
10
11/// An arbitrary value with a [`Style`] applied to it.
12///
13/// A `Painted` can be directly formatted. This results in the internal
14/// [`value`](Self::value) being formatted as specified and ANSI code styling
15/// sequences corresponding to [`style`](Self::style) being prefixed and
16/// suffixed as necessary. Both the global and local [`Condition`] affects
17/// whether styling sequences are actually emitted: both must evaluated to true.
18/// Otherwise, no styling sequences are emitted.
19///
20/// ```rust
21/// use yansi::{Paint, Condition};
22///
23/// println!("Hello, {}!", "world".red().underline().blink());
24/// // > Hello, world! # world is red, underlined, and blinking
25///
26/// let v = format!("{}", "world".red().underline().blink());
27/// assert_eq!(v, "\u{1b}[4;5;31mworld\u{1b}[0m");
28/// println!("{}", v); // > world # world is red, underlined, and blinking
29///
30/// let v = format!("{}", "world".red().underline().blink().whenever(Condition::NEVER));
31/// assert_eq!(v, "world");
32/// ```
33#[derive(Copy, Clone)]
34pub struct Painted<T> {
35    /// The value to be styled.
36    pub value: T,
37    /// The style to apply.
38    pub style: Style,
39}
40
41/// A trait to apply styling to any value. Implemented for all types.
42///
43/// Because this trait is implemented for all types, you can use its methods on
44/// any type. With the exception of one constructor method, [`Paint::new()`],
45/// all methods are called with method syntax:
46///
47/// ```rust
48/// use yansi::Paint;
49///
50/// "hello".green(); // calls `Paint::<&'static str>::green()`.
51/// "hello".strike(); // calls `Paint::<&'static str>::strike()`.
52/// 1.on_red(); // calls `Paint::<i32>::red()`.
53/// 1.blink(); // calls `Paint::<i32>::blink()`.
54/// ```
55///
56/// ### Chaining
57///
58/// All methods return a [`Painted`] whose methods are exactly those of `Paint`.
59/// This means you can chain `Paint` method calls:
60///
61/// ```rust
62/// use yansi::Paint;
63///
64/// "hello".green().strike(); // calls `Paint::green()` then `Painted::strike()`.
65/// 1.on_red().blink(); // calls `Paint::red()` + `Painted::blink()`.
66/// ```
67///
68/// ### Borrow vs. Owned Receiver
69///
70/// The returned [`Painted`] type contains a borrow to the receiver:
71///
72/// ```rust
73/// use yansi::{Paint, Painted};
74///
75/// let v: Painted<&i32> = 1.red();
76/// ```
77///
78/// This is nearly always what you want. In the exceedingly rare case that you
79/// _do_ want `Painted` to own its value, use [`Paint::new()`] or the equivalent
80/// [`Painted::new()`]:
81///
82/// ```rust
83/// use yansi::{Paint, Painted};
84///
85/// let v: Painted<i32> = Paint::new(1);
86/// let v: Painted<i32> = Painted::new(1);
87/// ```
88///
89/// ### Further Details
90///
91/// See the [crate level docs](crate#usage) for more details and examples.
92pub trait Paint {
93    /// Create a new [`Painted`] with a default [`Style`].
94    ///
95    /// # Example
96    ///
97    /// ```rust
98    /// use yansi::Paint;
99    ///
100    /// let painted = Paint::new("hello");
101    /// assert_eq!(painted.style, yansi::Style::new());
102    /// ```
103    #[inline(always)]
104    fn new(self) -> Painted<Self> where Self: Sized {
105        Painted::new(self)
106    }
107
108    #[doc(hidden)]
109    #[inline(always)]
110    fn apply(&self, a: crate::style::Application) -> Painted<&Self> {
111        Painted::new(self).apply(a)
112    }
113
114    /// Apply a style wholesale to `self`. Any previous style is replaced.
115    ///
116    /// # Example
117    ///
118    /// ```rust
119    /// use yansi::{Paint, Style, Color::*};
120    ///
121    /// static DEBUG: Style = Black.bold().on_yellow();
122    ///
123    /// let painted = "hello".paint(DEBUG);
124    /// ```
125    #[inline(always)]
126    fn paint<S: Into<Style>>(&self, style: S) -> Painted<&Self> {
127        Painted { value: self, style: style.into() }
128    }
129
130    properties!(signature(&Self) -> Painted<&Self>);
131}
132
133#[allow(rustdoc::broken_intra_doc_links)]
134impl<T: ?Sized> Paint for T {
135    properties!(constructor(&Self) -> Painted<&Self>);
136}
137
138impl<T> Painted<T> {
139    /// Create a new [`Painted`] with a default [`Style`].
140    ///
141    /// # Example
142    ///
143    /// ```rust
144    /// use yansi::Painted;
145    ///
146    /// let painted = Painted::new("hello");
147    /// assert_eq!(painted.style, yansi::Style::new());
148    /// ```
149    #[inline(always)]
150    pub const fn new(value: T) -> Painted<T> {
151        Painted { value, style: Style::new() }
152    }
153
154    #[inline(always)]
155    const fn apply(mut self, a: crate::style::Application) -> Self {
156        self.style = self.style.apply(a);
157        self
158    }
159
160    #[inline]
161    pub(crate) fn enabled(&self) -> bool {
162        crate::is_enabled() && self.style.condition.map_or(true, |c| c())
163    }
164
165    properties!([pub const] constructor(Self) -> Self);
166}
167
168impl<T> Painted<T> {
169    pub(crate) fn color_fmt_value(
170        &self,
171        fmt: &dyn Fn(&T, &mut fmt::Formatter) -> fmt::Result,
172        f: &mut fmt::Formatter,
173    ) -> fmt::Result {
174        self.style.fmt_prefix(f)?;
175        fmt(&self.value, f)?;
176        self.style.fmt_suffix(f)
177    }
178
179    #[cfg(feature = "alloc")]
180    pub(crate) fn reset_fmt_args(
181        &self,
182        fmt: &dyn Fn(&T, &mut fmt::Formatter) -> fmt::Result,
183        f: &mut fmt::Formatter,
184        args: &fmt::Arguments<'_>,
185    ) -> fmt::Result {
186        // A tiny state machine to find escape sequences.
187        enum State { Searching, Open, }
188        let mut state = State::Searching;
189        let escape = |c: char| {
190            match state {
191                State::Searching if c == '\x1B' => { state = State::Open; true }
192                State::Open if c == 'm' => { state = State::Searching; true }
193                State::Searching => false,
194                State::Open => true,
195            }
196        };
197
198        // Only replace when the string contains styling.
199        let string = args.as_str()
200            .map(|string| Cow::Borrowed(string))
201            .unwrap_or_else(|| Cow::Owned(args.to_string()));
202
203        if string.contains('\x1B') {
204            f.write_str(&string.replace(escape, ""))
205        } else {
206            fmt(&self.value, f)
207        }
208    }
209
210    #[cfg(feature = "alloc")]
211    pub(crate) fn color_wrap_fmt_args(
212        &self,
213        fmt: &dyn Fn(&T, &mut fmt::Formatter) -> fmt::Result,
214        f: &mut fmt::Formatter,
215        args: &fmt::Arguments<'_>,
216    ) -> fmt::Result {
217        // Only replace when the string contains styling.
218        let string = args.as_str()
219            .map(|string| Cow::Borrowed(string))
220            .unwrap_or_else(|| Cow::Owned(args.to_string()));
221
222        if !string.contains('\x1B') {
223            return self.color_fmt_value(fmt, f);
224        }
225
226        // Compute the prefix for the style with a reset in front.
227        let mut prefix = String::new();
228        prefix.push_str("\x1B[0m");
229        self.style.fmt_prefix(&mut prefix)?;
230
231        // Write out formatted string, replacing resets with computed prefix.
232        self.style.fmt_prefix(f)?;
233        write!(f, "{}", string.replace("\x1B[0m", &prefix))?;
234        self.style.fmt_suffix(f)
235    }
236
237    pub(crate) fn fmt_args(
238        &self,
239        fmt: &dyn Fn(&T, &mut fmt::Formatter) -> fmt::Result,
240        f: &mut fmt::Formatter,
241        _args: fmt::Arguments<'_>,
242    ) -> fmt::Result {
243        let enabled = self.enabled();
244        let masked = self.style.quirks.contains(Quirk::Mask);
245
246        #[cfg(not(feature = "alloc"))]
247        match (enabled, masked) {
248            (true, _) => self.color_fmt_value(fmt, f),
249            (false, false) => fmt(&self.value, f),
250            (false, true) => Ok(()),
251        }
252
253        #[cfg(feature = "alloc")]
254        match (enabled, masked, self.style.quirks.contains(Quirk::Wrap)) {
255            (true, _, true) => self.color_wrap_fmt_args(fmt, f, &_args),
256            (true, _, false) => self.color_fmt_value(fmt, f),
257            (false, false, true) => self.reset_fmt_args(fmt, f, &_args),
258            (false, false, false) => fmt(&self.value, f),
259            (false, true, _) => Ok(()),
260        }
261    }
262}
263
264impl_fmt_traits!(<T> Painted<T> => self.value (T));
265
266impl<T> From<Painted<T>> for Style {
267    fn from(painted: Painted<T>) -> Self {
268        painted.style
269    }
270}