|
| 1 | +// Copyright 2023 Google LLC |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +//! Utilities to style matcher explanation message with ANSI formatting. |
| 16 | +
|
| 17 | +use std::fmt::Display; |
| 18 | +use std::io::IsTerminal; |
| 19 | + |
| 20 | + |
| 21 | +/// Environment variable controlling the usage of ansi color in difference |
| 22 | +/// summary. |
| 23 | +pub(crate) const NO_COLOR_VAR: &str = "GTEST_RUST_NO_COLOR"; |
| 24 | + |
| 25 | +// Use ANSI code to enable styling on the summary lines. |
| 26 | +// |
| 27 | +// See https://en.wikipedia.org/wiki/ANSI_escape_code. |
| 28 | +pub(crate) struct LineStyle { |
| 29 | + ansi_prefix: &'static str, |
| 30 | + ansi_suffix: &'static str, |
| 31 | + highlighted_prefix: &'static str, |
| 32 | + header: char, |
| 33 | +} |
| 34 | + |
| 35 | +impl LineStyle { |
| 36 | + // Font in red and bold |
| 37 | + pub(crate) fn extra_actual_style() -> Self { |
| 38 | + Self { |
| 39 | + ansi_prefix: "\x1B[1;31m", |
| 40 | + ansi_suffix: "\x1B[0m", |
| 41 | + highlighted_prefix: "", |
| 42 | + header: '-', |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + // Font in green and bold |
| 47 | + pub(crate) fn extra_expected_style() -> Self { |
| 48 | + Self { |
| 49 | + ansi_prefix: "\x1B[1;32m", |
| 50 | + ansi_suffix: "\x1B[0m", |
| 51 | + highlighted_prefix: "", |
| 52 | + header: '+', |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + // Font in italic |
| 57 | + pub(crate) fn comment_style() -> Self { |
| 58 | + Self { ansi_prefix: "\x1B[3m", ansi_suffix: "\x1B[0m", highlighted_prefix: "", header: ' ' } |
| 59 | + } |
| 60 | + |
| 61 | + // No ansi styling |
| 62 | + pub(crate) fn unchanged_style() -> Self { |
| 63 | + Self { ansi_prefix: "", ansi_suffix: "", highlighted_prefix: "", header: ' ' } |
| 64 | + } |
| 65 | + |
| 66 | + pub(crate) fn style(self, line: &str) -> impl Display + '_ { |
| 67 | + StyledLine { style: self, line } |
| 68 | + } |
| 69 | + |
| 70 | + pub(crate) fn style_highlighted(self, line: HighlightedString) -> impl Display { |
| 71 | + StyledLineWithHighlight { style: self, line } |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | +struct StyledLine<'a> { |
| 76 | + style: LineStyle, |
| 77 | + line: &'a str, |
| 78 | +} |
| 79 | + |
| 80 | +impl<'a> Display for StyledLine<'a> { |
| 81 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 82 | + if stdout_supports_color() { |
| 83 | + write!( |
| 84 | + f, |
| 85 | + "{}{}{}{}", |
| 86 | + self.style.header, self.style.ansi_prefix, self.line, self.style.ansi_suffix |
| 87 | + ) |
| 88 | + } else { |
| 89 | + write!(f, "{}{}", self.style.header, self.line) |
| 90 | + } |
| 91 | + } |
| 92 | +} |
| 93 | +struct StyledLineWithHighlight { |
| 94 | + style: LineStyle, |
| 95 | + line: HighlightedString, |
| 96 | +} |
| 97 | + |
| 98 | +impl Display for StyledLineWithHighlight { |
| 99 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 100 | + write!(f, "{}", self.style.header)?; |
| 101 | + for (piece, highlight) in &self.line.0 { |
| 102 | + if ! stdout_supports_color() { |
| 103 | + write!(f, "{}", piece)?; |
| 104 | + } else if *highlight { |
| 105 | + write!(f, "{}{}{}", self.style.highlighted_prefix, piece, self.style.ansi_suffix)?; |
| 106 | + } else { |
| 107 | + write!(f, "{}{}{}", self.style.ansi_prefix, piece, self.style.ansi_suffix)?; |
| 108 | + } |
| 109 | + } |
| 110 | + Ok(()) |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +pub(crate) struct HighlightedString(Vec<(String, bool)>); |
| 115 | + |
| 116 | +impl HighlightedString { |
| 117 | + pub(crate) fn new() -> Self { |
| 118 | + Self(vec![]) |
| 119 | + } |
| 120 | + |
| 121 | + pub(crate) fn push_highlighted(&mut self, c: char) { |
| 122 | + match self.0.last_mut() { |
| 123 | + Some((s, true)) => s.push(c), |
| 124 | + _ => self.0.push((c.to_string(), true)), |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + pub(crate) fn push(&mut self, c: char) { |
| 129 | + match self.0.last_mut() { |
| 130 | + Some((s, false)) => s.push(c), |
| 131 | + _ => self.0.push((c.to_string(), false)), |
| 132 | + } |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | +#[rustversion::since(1.70)] |
| 139 | +fn stdout_supports_color() -> bool { |
| 140 | + match (is_env_var_set("NO_COLOR"), is_env_var_set("FORCE_COLOR")) { |
| 141 | + (true, _) => false, |
| 142 | + (false, true) => true, |
| 143 | + (false, false) => std::io::stdout().is_terminal(), |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +#[rustversion::not(since(1.70))] |
| 148 | +fn stdout_supports_color() -> bool { |
| 149 | + is_env_var_set("FORCE_COLOR") |
| 150 | +} |
| 151 | + |
| 152 | +fn is_env_var_set(var: &'static str) -> bool { |
| 153 | + std::env::var(var).map(|s| !s.is_empty()).unwrap_or(false) |
| 154 | +} |
0 commit comments