| 1 | mod buffer; |
| 2 | mod target; |
| 3 | |
| 4 | use self::buffer::BufferWriter; |
| 5 | use std::{io, mem, sync::Mutex}; |
| 6 | |
| 7 | pub(super) use self::buffer::Buffer; |
| 8 | |
| 9 | pub use target::Target; |
| 10 | |
| 11 | /// Whether or not to print styles to the target. |
| 12 | #[allow (clippy::exhaustive_enums)] // By definition don't need more |
| 13 | #[derive (Clone, Copy, Debug, Eq, Hash, PartialEq, Default)] |
| 14 | pub enum WriteStyle { |
| 15 | /// Try to print styles, but don't force the issue. |
| 16 | #[default] |
| 17 | Auto, |
| 18 | /// Try very hard to print styles. |
| 19 | Always, |
| 20 | /// Never print styles. |
| 21 | Never, |
| 22 | } |
| 23 | |
| 24 | #[cfg (feature = "color" )] |
| 25 | impl From<anstream::ColorChoice> for WriteStyle { |
| 26 | fn from(choice: anstream::ColorChoice) -> Self { |
| 27 | match choice { |
| 28 | anstream::ColorChoice::Auto => Self::Auto, |
| 29 | anstream::ColorChoice::Always => Self::Always, |
| 30 | anstream::ColorChoice::AlwaysAnsi => Self::Always, |
| 31 | anstream::ColorChoice::Never => Self::Never, |
| 32 | } |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | #[cfg (feature = "color" )] |
| 37 | impl From<WriteStyle> for anstream::ColorChoice { |
| 38 | fn from(choice: WriteStyle) -> Self { |
| 39 | match choice { |
| 40 | WriteStyle::Auto => anstream::ColorChoice::Auto, |
| 41 | WriteStyle::Always => anstream::ColorChoice::Always, |
| 42 | WriteStyle::Never => anstream::ColorChoice::Never, |
| 43 | } |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | /// A terminal target with color awareness. |
| 48 | #[derive (Debug)] |
| 49 | pub(crate) struct Writer { |
| 50 | inner: BufferWriter, |
| 51 | } |
| 52 | |
| 53 | impl Writer { |
| 54 | pub(crate) fn write_style(&self) -> WriteStyle { |
| 55 | self.inner.write_style() |
| 56 | } |
| 57 | |
| 58 | pub(super) fn buffer(&self) -> Buffer { |
| 59 | self.inner.buffer() |
| 60 | } |
| 61 | |
| 62 | pub(super) fn print(&self, buf: &Buffer) -> io::Result<()> { |
| 63 | self.inner.print(buf) |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | /// A builder for a terminal writer. |
| 68 | /// |
| 69 | /// The target and style choice can be configured before building. |
| 70 | #[derive (Debug)] |
| 71 | pub(crate) struct Builder { |
| 72 | target: Target, |
| 73 | write_style: WriteStyle, |
| 74 | is_test: bool, |
| 75 | built: bool, |
| 76 | } |
| 77 | |
| 78 | impl Builder { |
| 79 | /// Initialize the writer builder with defaults. |
| 80 | pub(crate) fn new() -> Self { |
| 81 | Builder { |
| 82 | target: Default::default(), |
| 83 | write_style: Default::default(), |
| 84 | is_test: false, |
| 85 | built: false, |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | /// Set the target to write to. |
| 90 | pub(crate) fn target(&mut self, target: Target) -> &mut Self { |
| 91 | self.target = target; |
| 92 | self |
| 93 | } |
| 94 | |
| 95 | /// Parses a style choice string. |
| 96 | /// |
| 97 | /// See the [Disabling colors] section for more details. |
| 98 | /// |
| 99 | /// [Disabling colors]: ../index.html#disabling-colors |
| 100 | pub(crate) fn parse_write_style(&mut self, write_style: &str) -> &mut Self { |
| 101 | self.write_style(parse_write_style(write_style)) |
| 102 | } |
| 103 | |
| 104 | /// Whether or not to print style characters when writing. |
| 105 | pub(crate) fn write_style(&mut self, write_style: WriteStyle) -> &mut Self { |
| 106 | self.write_style = write_style; |
| 107 | self |
| 108 | } |
| 109 | |
| 110 | /// Whether or not to capture logs for `cargo test`. |
| 111 | #[allow (clippy::wrong_self_convention)] |
| 112 | pub(crate) fn is_test(&mut self, is_test: bool) -> &mut Self { |
| 113 | self.is_test = is_test; |
| 114 | self |
| 115 | } |
| 116 | |
| 117 | /// Build a terminal writer. |
| 118 | pub(crate) fn build(&mut self) -> Writer { |
| 119 | assert!(!self.built, "attempt to re-use consumed builder" ); |
| 120 | self.built = true; |
| 121 | |
| 122 | let color_choice = self.write_style; |
| 123 | #[cfg (feature = "auto-color" )] |
| 124 | let color_choice = if color_choice == WriteStyle::Auto { |
| 125 | match &self.target { |
| 126 | Target::Stdout => anstream::AutoStream::choice(&io::stdout()).into(), |
| 127 | Target::Stderr => anstream::AutoStream::choice(&io::stderr()).into(), |
| 128 | Target::Pipe(_) => color_choice, |
| 129 | } |
| 130 | } else { |
| 131 | color_choice |
| 132 | }; |
| 133 | let color_choice = if color_choice == WriteStyle::Auto { |
| 134 | WriteStyle::Never |
| 135 | } else { |
| 136 | color_choice |
| 137 | }; |
| 138 | |
| 139 | let writer = match mem::take(&mut self.target) { |
| 140 | Target::Stdout => BufferWriter::stdout(self.is_test, color_choice), |
| 141 | Target::Stderr => BufferWriter::stderr(self.is_test, color_choice), |
| 142 | Target::Pipe(pipe) => BufferWriter::pipe(Box::new(Mutex::new(pipe)), color_choice), |
| 143 | }; |
| 144 | |
| 145 | Writer { inner: writer } |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | impl Default for Builder { |
| 150 | fn default() -> Self { |
| 151 | Builder::new() |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | fn parse_write_style(spec: &str) -> WriteStyle { |
| 156 | match spec { |
| 157 | "auto" => WriteStyle::Auto, |
| 158 | "always" => WriteStyle::Always, |
| 159 | "never" => WriteStyle::Never, |
| 160 | _ => Default::default(), |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | #[cfg (test)] |
| 165 | mod tests { |
| 166 | use super::*; |
| 167 | |
| 168 | #[test ] |
| 169 | fn parse_write_style_valid() { |
| 170 | let inputs = vec![ |
| 171 | ("auto" , WriteStyle::Auto), |
| 172 | ("always" , WriteStyle::Always), |
| 173 | ("never" , WriteStyle::Never), |
| 174 | ]; |
| 175 | |
| 176 | for (input, expected) in inputs { |
| 177 | assert_eq!(expected, parse_write_style(input)); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | #[test ] |
| 182 | fn parse_write_style_invalid() { |
| 183 | let inputs = vec!["" , "true" , "false" , "NEVER!!" ]; |
| 184 | |
| 185 | for input in inputs { |
| 186 | assert_eq!(WriteStyle::Auto, parse_write_style(input)); |
| 187 | } |
| 188 | } |
| 189 | } |
| 190 | |