Coverage Report

Created: 2026-06-30 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/anstream-1.0.0/src/auto.rs
Line
Count
Source
1
use crate::stream::AsLockedWrite;
2
use crate::stream::RawStream;
3
use crate::ColorChoice;
4
use crate::StripStream;
5
#[cfg(all(windows, feature = "wincon"))]
6
use crate::WinconStream;
7
8
/// [`std::io::Write`] that adapts ANSI escape codes to the underlying `Write`s capabilities
9
///
10
/// This includes
11
/// - Stripping colors for non-terminals
12
/// - Respecting env variables like [NO_COLOR](https://no-color.org/) or [CLICOLOR](https://bixense.com/clicolors/)
13
/// - *(windows)* Falling back to the wincon API where [ENABLE_VIRTUAL_TERMINAL_PROCESSING](https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#output-sequences) is unsupported
14
///
15
/// You can customize auto-detection by calling into
16
/// [anstyle_query](https://docs.rs/anstyle-query/latest/anstyle_query/)
17
/// to get a [`ColorChoice`] and then calling [`AutoStream::new(stream, choice)`].
18
#[derive(Debug)]
19
pub struct AutoStream<S: RawStream> {
20
    inner: StreamInner<S>,
21
}
22
23
#[derive(Debug)]
24
enum StreamInner<S: RawStream> {
25
    PassThrough(S),
26
    Strip(StripStream<S>),
27
    #[cfg(all(windows, feature = "wincon"))]
28
    Wincon(WinconStream<S>),
29
}
30
31
impl<S> AutoStream<S>
32
where
33
    S: RawStream,
34
{
35
    /// Runtime control over styling behavior
36
    ///
37
    /// # Example
38
    ///
39
    /// ```rust
40
    /// # #[cfg(feature = "auto")] {
41
    /// # use std::io::IsTerminal as _;
42
    /// // Like `AutoStream::choice` but without `NO_COLOR`, `CLICOLOR_FORCE`, `CI`
43
    /// fn choice(raw: &dyn anstream::stream::RawStream) -> anstream::ColorChoice {
44
    ///     let choice = anstream::ColorChoice::global();
45
    ///     if choice == anstream::ColorChoice::Auto {
46
    ///         if raw.is_terminal() && anstyle_query::term_supports_color() {
47
    ///             anstream::ColorChoice::Always
48
    ///         } else {
49
    ///             anstream::ColorChoice::Never
50
    ///         }
51
    ///     } else {
52
    ///         choice
53
    ///     }
54
    /// }
55
    ///
56
    /// let stream = std::io::stdout();
57
    /// let choice = choice(&stream);
58
    /// let auto = anstream::AutoStream::new(stream, choice);
59
    /// # }
60
    /// ```
61
    #[inline]
62
0
    pub fn new(raw: S, choice: ColorChoice) -> Self {
63
0
        match choice {
64
            #[cfg(feature = "auto")]
65
0
            ColorChoice::Auto => Self::auto(raw),
66
            #[cfg(not(feature = "auto"))]
67
            ColorChoice::Auto => Self::never(raw),
68
0
            ColorChoice::AlwaysAnsi => Self::always_ansi(raw),
69
0
            ColorChoice::Always => Self::always(raw),
70
0
            ColorChoice::Never => Self::never(raw),
71
        }
72
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock>>::new
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock>>::new
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stderr>>::new
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stdout>>::new
73
74
    /// Auto-adapt for the stream's capabilities
75
    #[cfg(feature = "auto")]
76
    #[inline]
77
0
    pub fn auto(raw: S) -> Self {
78
0
        let choice = Self::choice(&raw);
79
0
        debug_assert_ne!(choice, ColorChoice::Auto);
80
0
        Self::new(raw, choice)
81
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock>>::auto
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock>>::auto
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stderr>>::auto
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stdout>>::auto
82
83
    /// Report the desired choice for the given stream
84
    #[cfg(feature = "auto")]
85
0
    pub fn choice(raw: &S) -> ColorChoice {
86
0
        choice(raw)
87
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock>>::choice
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock>>::choice
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stderr>>::choice
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stdout>>::choice
88
89
    /// Force ANSI escape codes to be passed through as-is, no matter what the inner `Write`
90
    /// supports.
91
    #[inline]
92
0
    pub fn always_ansi(raw: S) -> Self {
93
        #[cfg(feature = "auto")]
94
        {
95
0
            if raw.is_terminal() {
96
0
                let _ = anstyle_query::windows::enable_ansi_colors();
97
0
            }
98
        }
99
0
        Self::always_ansi_(raw)
100
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock>>::always_ansi
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock>>::always_ansi
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stderr>>::always_ansi
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stdout>>::always_ansi
101
102
    #[inline]
103
0
    fn always_ansi_(raw: S) -> Self {
104
0
        let inner = StreamInner::PassThrough(raw);
105
0
        AutoStream { inner }
106
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock>>::always_ansi_
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock>>::always_ansi_
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stderr>>::always_ansi_
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stdout>>::always_ansi_
107
108
    /// Force color, no matter what the inner `Write` supports.
109
    #[inline]
110
0
    pub fn always(raw: S) -> Self {
111
0
        if cfg!(windows) {
112
            #[cfg(feature = "auto")]
113
0
            let use_wincon = raw.is_terminal()
114
0
                && !anstyle_query::windows::enable_ansi_colors().unwrap_or(true)
115
0
                && !anstyle_query::term_supports_ansi_color();
116
            #[cfg(not(feature = "auto"))]
117
            let use_wincon = true;
118
0
            if use_wincon {
119
0
                Self::wincon(raw).unwrap_or_else(|raw| Self::always_ansi_(raw))
120
            } else {
121
0
                Self::always_ansi_(raw)
122
            }
123
        } else {
124
0
            Self::always_ansi(raw)
125
        }
126
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock>>::always
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock>>::always
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stderr>>::always
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stdout>>::always
127
128
    /// Only pass printable data to the inner `Write`.
129
    #[inline]
130
0
    pub fn never(raw: S) -> Self {
131
0
        let inner = StreamInner::Strip(StripStream::new(raw));
132
0
        AutoStream { inner }
133
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock>>::never
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock>>::never
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stderr>>::never
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::Stdout>>::never
134
135
    #[inline]
136
0
    fn wincon(raw: S) -> Result<Self, S> {
137
        #[cfg(all(windows, feature = "wincon"))]
138
        {
139
            Ok(Self {
140
                inner: StreamInner::Wincon(WinconStream::new(raw)),
141
            })
142
        }
143
        #[cfg(not(all(windows, feature = "wincon")))]
144
        {
145
0
            Err(raw)
146
        }
147
0
    }
148
149
    /// Get the wrapped [`RawStream`]
150
    #[inline]
151
0
    pub fn into_inner(self) -> S {
152
0
        match self.inner {
153
0
            StreamInner::PassThrough(w) => w,
154
0
            StreamInner::Strip(w) => w.into_inner(),
155
            #[cfg(all(windows, feature = "wincon"))]
156
            StreamInner::Wincon(w) => w.into_inner(),
157
        }
158
0
    }
159
160
    /// Get the wrapped [`RawStream`]
161
    #[inline]
162
0
    pub fn as_inner(&self) -> &S {
163
0
        match &self.inner {
164
0
            StreamInner::PassThrough(w) => w,
165
0
            StreamInner::Strip(w) => w.as_inner(),
166
            #[cfg(all(windows, feature = "wincon"))]
167
            StreamInner::Wincon(w) => w.as_inner(),
168
        }
169
0
    }
170
171
    /// Returns `true` if the descriptor/handle refers to a terminal/tty.
172
    #[inline]
173
0
    pub fn is_terminal(&self) -> bool {
174
0
        match &self.inner {
175
0
            StreamInner::PassThrough(w) => w.is_terminal(),
176
0
            StreamInner::Strip(w) => w.is_terminal(),
177
            #[cfg(all(windows, feature = "wincon"))]
178
            StreamInner::Wincon(_) => true, // its only ever a terminal
179
        }
180
0
    }
181
182
    /// Prefer [`AutoStream::choice`]
183
    ///
184
    /// This doesn't report what is requested but what is currently active.
185
    #[inline]
186
    #[cfg(feature = "auto")]
187
0
    pub fn current_choice(&self) -> ColorChoice {
188
0
        match &self.inner {
189
0
            StreamInner::PassThrough(_) => ColorChoice::AlwaysAnsi,
190
0
            StreamInner::Strip(_) => ColorChoice::Never,
191
            #[cfg(all(windows, feature = "wincon"))]
192
            StreamInner::Wincon(_) => ColorChoice::Always,
193
        }
194
0
    }
195
}
196
197
#[cfg(feature = "auto")]
198
0
fn choice(raw: &dyn RawStream) -> ColorChoice {
199
0
    let choice = ColorChoice::global();
200
0
    match choice {
201
        ColorChoice::Auto => {
202
0
            let clicolor = anstyle_query::clicolor();
203
0
            let clicolor_enabled = clicolor.unwrap_or(false);
204
0
            let clicolor_disabled = !clicolor.unwrap_or(true);
205
0
            if anstyle_query::no_color() {
206
0
                ColorChoice::Never
207
0
            } else if anstyle_query::clicolor_force() {
208
0
                ColorChoice::Always
209
0
            } else if clicolor_disabled {
210
0
                ColorChoice::Never
211
0
            } else if raw.is_terminal()
212
0
                && (anstyle_query::term_supports_color()
213
0
                    || clicolor_enabled
214
0
                    || anstyle_query::is_ci())
215
            {
216
0
                ColorChoice::Always
217
            } else {
218
0
                ColorChoice::Never
219
            }
220
        }
221
0
        ColorChoice::AlwaysAnsi | ColorChoice::Always | ColorChoice::Never => choice,
222
    }
223
0
}
224
225
impl AutoStream<std::io::Stdout> {
226
    /// Get exclusive access to the `AutoStream`
227
    ///
228
    /// Why?
229
    /// - Faster performance when writing in a loop
230
    /// - Avoid other threads interleaving output with the current thread
231
    #[inline]
232
0
    pub fn lock(self) -> AutoStream<std::io::StdoutLock<'static>> {
233
0
        let inner = match self.inner {
234
0
            StreamInner::PassThrough(w) => StreamInner::PassThrough(w.lock()),
235
0
            StreamInner::Strip(w) => StreamInner::Strip(w.lock()),
236
            #[cfg(all(windows, feature = "wincon"))]
237
            StreamInner::Wincon(w) => StreamInner::Wincon(w.lock()),
238
        };
239
0
        AutoStream { inner }
240
0
    }
241
}
242
243
impl AutoStream<std::io::Stderr> {
244
    /// Get exclusive access to the `AutoStream`
245
    ///
246
    /// Why?
247
    /// - Faster performance when writing in a loop
248
    /// - Avoid other threads interleaving output with the current thread
249
    #[inline]
250
0
    pub fn lock(self) -> AutoStream<std::io::StderrLock<'static>> {
251
0
        let inner = match self.inner {
252
0
            StreamInner::PassThrough(w) => StreamInner::PassThrough(w.lock()),
253
0
            StreamInner::Strip(w) => StreamInner::Strip(w.lock()),
254
            #[cfg(all(windows, feature = "wincon"))]
255
            StreamInner::Wincon(w) => StreamInner::Wincon(w.lock()),
256
        };
257
0
        AutoStream { inner }
258
0
    }
259
}
260
261
impl<S> std::io::Write for AutoStream<S>
262
where
263
    S: RawStream + AsLockedWrite,
264
{
265
    // Must forward all calls to ensure locking happens appropriately
266
    #[inline]
267
0
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
268
0
        match &mut self.inner {
269
0
            StreamInner::PassThrough(w) => w.as_locked_write().write(buf),
270
0
            StreamInner::Strip(w) => w.write(buf),
271
            #[cfg(all(windows, feature = "wincon"))]
272
            StreamInner::Wincon(w) => w.write(buf),
273
        }
274
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock> as std::io::Write>::write
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock> as std::io::Write>::write
Unexecuted instantiation: <anstream::auto::AutoStream<_> as std::io::Write>::write
275
    #[inline]
276
0
    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
277
0
        match &mut self.inner {
278
0
            StreamInner::PassThrough(w) => w.as_locked_write().write_vectored(bufs),
279
0
            StreamInner::Strip(w) => w.write_vectored(bufs),
280
            #[cfg(all(windows, feature = "wincon"))]
281
            StreamInner::Wincon(w) => w.write_vectored(bufs),
282
        }
283
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock> as std::io::Write>::write_vectored
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock> as std::io::Write>::write_vectored
Unexecuted instantiation: <anstream::auto::AutoStream<_> as std::io::Write>::write_vectored
284
    // is_write_vectored: nightly only
285
    #[inline]
286
0
    fn flush(&mut self) -> std::io::Result<()> {
287
0
        match &mut self.inner {
288
0
            StreamInner::PassThrough(w) => w.as_locked_write().flush(),
289
0
            StreamInner::Strip(w) => w.flush(),
290
            #[cfg(all(windows, feature = "wincon"))]
291
            StreamInner::Wincon(w) => w.flush(),
292
        }
293
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock> as std::io::Write>::flush
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock> as std::io::Write>::flush
Unexecuted instantiation: <anstream::auto::AutoStream<_> as std::io::Write>::flush
294
    #[inline]
295
0
    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
296
0
        match &mut self.inner {
297
0
            StreamInner::PassThrough(w) => w.as_locked_write().write_all(buf),
298
0
            StreamInner::Strip(w) => w.write_all(buf),
299
            #[cfg(all(windows, feature = "wincon"))]
300
            StreamInner::Wincon(w) => w.write_all(buf),
301
        }
302
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock> as std::io::Write>::write_all
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock> as std::io::Write>::write_all
Unexecuted instantiation: <anstream::auto::AutoStream<_> as std::io::Write>::write_all
303
    // write_all_vectored: nightly only
304
    #[inline]
305
0
    fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::io::Result<()> {
306
0
        match &mut self.inner {
307
0
            StreamInner::PassThrough(w) => w.as_locked_write().write_fmt(args),
308
0
            StreamInner::Strip(w) => w.write_fmt(args),
309
            #[cfg(all(windows, feature = "wincon"))]
310
            StreamInner::Wincon(w) => w.write_fmt(args),
311
        }
312
0
    }
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StderrLock> as std::io::Write>::write_fmt
Unexecuted instantiation: <anstream::auto::AutoStream<std::io::stdio::StdoutLock> as std::io::Write>::write_fmt
Unexecuted instantiation: <anstream::auto::AutoStream<_> as std::io::Write>::write_fmt
313
}