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}