xpct/core/format/
formatter_color.rs

1#![cfg(feature = "color")]
2
3use std::fmt;
4use std::io::{self, IsTerminal};
5
6use super::strings::{indent_segments, OutputSegment};
7use super::{Format, OutputStyle};
8
9// Disable colors and text styles if stderr is not a tty.
10fn check_disable_color() {
11    if !io::stderr().is_terminal() {
12        colored::control::set_override(false)
13    }
14}
15
16/// Configuration for formatting with [`Format`].
17///
18/// This value is passed to [`Format::fmt`] and is used to set various options to configure how the
19/// value will be formatted.
20///
21/// [`Format::fmt`]: crate::core::Format::fmt
22#[derive(Debug)]
23pub struct Formatter {
24    prev: Vec<OutputSegment>,
25    current: OutputSegment,
26}
27
28impl Formatter {
29    fn new() -> Self {
30        Self {
31            prev: Vec::new(),
32            current: Default::default(),
33        }
34    }
35
36    fn into_segments(self) -> Vec<OutputSegment> {
37        let mut segments = self.prev;
38        segments.push(self.current);
39        segments
40    }
41
42    fn push_segments(&mut self, segments: Vec<OutputSegment>) {
43        let new_current = OutputSegment {
44            buf: String::new(),
45            style: self.current.style.clone(),
46        };
47
48        self.prev
49            .push(std::mem::replace(&mut self.current, new_current));
50        self.prev.extend(segments);
51    }
52
53    /// Write a string to the output.
54    pub fn write_str(&mut self, s: impl AsRef<str>) {
55        self.current.buf.push_str(s.as_ref());
56    }
57
58    /// Write a `char` to the output.
59    pub fn write_char(&mut self, c: char) {
60        self.current.buf.push(c);
61    }
62
63    /// Pass some pre-formatted output through to the output.
64    ///
65    /// This method is often used when writing formatters for matchers which compose other
66    /// matchers, such as [`not`] or [`each`]. It's used by formatters such as [`FailureFormat`]
67    /// and [`SomeFailuresFormat`].
68    ///
69    /// [`not`]: crate::not
70    /// [`each`]: crate::each
71    /// [`FailureFormat`]: crate::format::FailureFormat
72    /// [`SomeFailuresFormat`]: crate::format::SomeFailuresFormat
73    pub fn write_fmt(&mut self, output: impl Into<FormattedOutput>) {
74        let formatted = output.into();
75        self.push_segments(formatted.segments)
76    }
77
78    fn indented_inner(
79        &mut self,
80        prefix: impl AsRef<str>,
81        hanging: bool,
82        func: impl FnOnce(&mut Formatter) -> crate::Result<()>,
83    ) -> crate::Result<()> {
84        let mut formatter = Self::new();
85        formatter.current.style = self.current.style.clone();
86
87        func(&mut formatter)?;
88
89        let segments = formatter.into_segments();
90        let output = FormattedOutput { segments };
91
92        let indented = output.indented_inner(prefix.as_ref(), hanging);
93        self.push_segments(indented.segments);
94
95        Ok(())
96    }
97
98    /// Write some indented text to the output.
99    ///
100    /// Anything written to the [`Formatter`] passed to `func` is indented by `prefix`.
101    ///
102    /// To indent with whitespace, you can use [`whitespace`].
103    ///
104    /// The current [`style`] is inherited by the [`Formatter`] passed to `func`.
105    ///
106    /// [`whitespace`]: crate::core::whitespace
107    /// [`style`]: crate::core::Formatter::style
108    pub fn indented(
109        &mut self,
110        prefix: impl AsRef<str>,
111        func: impl FnOnce(&mut Formatter) -> crate::Result<()>,
112    ) -> crate::Result<()> {
113        self.indented_inner(prefix, false, func)
114    }
115
116    /// Write some indented text to the output with a hanging indent.
117    ///
118    /// This is the same as [`indented`], but generates a hanging indent.
119    ///
120    /// [`indented`]: crate::core::Formatter::indented
121    pub fn indented_hanging(
122        &mut self,
123        prefix: impl AsRef<str>,
124        func: impl FnOnce(&mut Formatter) -> crate::Result<()>,
125    ) -> crate::Result<()> {
126        self.indented_inner(prefix, true, func)
127    }
128
129    /// Get the current [`OutputStyle`].
130    pub fn style(&self) -> &OutputStyle {
131        &self.current.style
132    }
133
134    /// Set the current [`OutputStyle`].
135    ///
136    /// This is used to configure colors and text styles in the output. Output formatting is
137    /// stripped out when stderr is not a tty.
138    pub fn set_style(&mut self, style: OutputStyle) {
139        self.prev.push(std::mem::replace(
140            &mut self.current,
141            OutputSegment {
142                buf: String::new(),
143                style,
144            },
145        ));
146    }
147
148    /// Reset the current colors and text styles to their defaults.
149    pub fn reset_style(&mut self) {
150        self.set_style(Default::default());
151    }
152}
153
154/// A value that has been formatted with [`Format`].
155///
156/// Formatting a value with [`Format`] returns this opaque type rather than a string, since we need
157/// to encapsulate the colors and text styles information in a cross-platform way. While ANSI escape
158/// codes can be included in a string, other platforms (such as Windows) have their own mechanisms
159/// for including colors and text styles in stdout/stderr.
160#[derive(Debug)]
161pub struct FormattedOutput {
162    segments: Vec<OutputSegment>,
163}
164
165impl FormattedOutput {
166    /// Create a new [`FormattedOutput`] by formatting `value` with `format`.
167    pub fn new<Value, Fmt>(value: Value, format: Fmt) -> crate::Result<Self>
168    where
169        Fmt: Format<Value = Value>,
170    {
171        let mut formatter = Formatter::new();
172        format.fmt(&mut formatter, value)?;
173        Ok(Self {
174            segments: formatter.into_segments(),
175        })
176    }
177
178    fn indented_inner(self, prefix: impl AsRef<str>, hanging: bool) -> Self {
179        if prefix.as_ref().is_empty() {
180            return self;
181        }
182
183        Self {
184            segments: indent_segments(self.segments, prefix.as_ref(), hanging),
185        }
186    }
187
188    /// Return a new [`FormattedOutput`] which has been indented by `prefix`.
189    ///
190    /// To indent with whitespace, you can use [`whitespace`].
191    ///
192    /// [`whitespace`]: crate::core::whitespace
193    pub fn indented(self, prefix: impl AsRef<str>) -> Self {
194        self.indented_inner(prefix, false)
195    }
196
197    /// Return a new [`FormattedOutput`] which has been indented by `prefix` with a hanging indent.
198    ///
199    /// This is the same as [`indented`], but generates a hanging indent.
200    ///
201    /// [`indented`]: crate::core::FormattedOutput::indented
202    pub fn indented_hanging(self, prefix: impl AsRef<str>) -> Self {
203        self.indented_inner(prefix, true)
204    }
205
206    /// Panic with this output as the error message.
207    ///
208    /// This does not print colors or text styles when the [`NO_COLOR`](https://no-color.org/)
209    /// environment variable is set or when stderr is not a tty.
210    pub fn fail(&self) -> ! {
211        // See CONTRIBUTING.md for an explanation of why we do this.
212        if cfg!(debug_screenshot) {
213            println!("{}", self);
214            std::process::exit(0);
215        } else {
216            check_disable_color();
217            panic!("\n{}\n", self);
218        }
219    }
220}
221
222impl fmt::Display for FormattedOutput {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        for segment in &self.segments {
225            f.write_fmt(format_args!("{}", segment.style.apply(&segment.buf)))?;
226        }
227
228        Ok(())
229    }
230}