vergen_pretty/pretty/
mod.rs1use self::{prefix::Prefix, suffix::Suffix};
10use crate::utils::{has_value, split_key, split_kv};
11use anyhow::Result;
12use bon::Builder;
13#[cfg(feature = "color")]
14use console::Style;
15use std::{collections::BTreeMap, io::Write};
16#[cfg(feature = "trace")]
17use tracing::Level;
18
19pub(crate) mod feature;
20pub(crate) mod prefix;
21pub(crate) mod suffix;
22
23#[cfg_attr(
43 feature = "trace",
44 doc = r"
45# Trace
46
47Generate [`tracing`] output
48
49```
50# use vergen_pretty::{vergen_pretty_env, Pretty};
51#
52# pub fn main() {
53Pretty::builder()
54 .env(vergen_pretty_env!())
55 .build()
56 .trace();
57# }
58```
59"
60)]
61#[cfg_attr(
94 feature = "color",
95 doc = r"
96# Customize Colorized Output
97
98Uses [`Style`](console::Style) to colorize output
99
100```
101# use anyhow::Result;
102# use vergen_pretty::{vergen_pretty_env, Pretty, Style};
103#
104# pub fn main() -> Result<()> {
105let mut stdout = vec![];
106let red_bold = Style::new().bold().red();
107let yellow_bold = Style::new().bold().yellow();
108Pretty::builder()
109 .env(vergen_pretty_env!())
110 .key_style(red_bold)
111 .value_style(yellow_bold)
112 .build()
113 .display(&mut stdout)?;
114# Ok(())
115# }
116```
117"
118)]
119#[derive(Builder, Clone, Debug, PartialEq)]
121pub struct Pretty {
122 #[builder(field)]
123 vars: Vec<(String, String, String)>,
124 #[builder(field)]
125 max_label: usize,
126 #[builder(field)]
127 max_category: usize,
128
129 prefix: Option<Prefix>,
131 env: BTreeMap<&'static str, Option<&'static str>>,
133 filter: Option<Vec<&'static str>>,
135 #[builder(default = true)]
137 category: bool,
138 #[cfg(feature = "color")]
140 key_style: Option<Style>,
141 #[cfg(feature = "color")]
143 value_style: Option<Style>,
144 suffix: Option<Suffix>,
146 #[cfg(feature = "trace")]
148 #[builder(default = Level::INFO)]
149 level: Level,
150 #[cfg(feature = "serde")]
152 #[builder(default = false)]
153 flatten: bool,
154}
155
156impl Pretty {
157 pub fn display<T>(mut self, writer: &mut T) -> Result<()>
164 where
165 T: Write + ?Sized,
166 {
167 self.populate_fmt();
168
169 if let Some(prefix) = &self.prefix {
170 prefix.display(writer)?;
171 }
172
173 for (category, label, value) in &self.vars {
174 let max_label = self.max_label;
175 let max_category = self.max_category;
176 let key = if self.category {
177 format!("{label:>max_label$} ({category:>max_category$})")
178 } else {
179 format!("{label:>max_label$}")
180 };
181 self.inner_display(writer, &key, value)?;
182 }
183
184 if let Some(suffix) = &self.suffix {
185 suffix.display(writer)?;
186 }
187
188 Ok(())
189 }
190
191 fn populate_fmt(&mut self) {
192 let filter_fn = |tuple: &(&'static str, &'static str)| -> bool {
193 let (key, _) = tuple;
194 if let Some(filter) = &self.filter {
195 !filter.contains(key)
196 } else {
197 true
198 }
199 };
200 let vm_iter: Vec<(String, String, String)> = self
201 .env
202 .iter()
203 .filter_map(has_value)
204 .filter(filter_fn)
205 .filter_map(split_key)
206 .filter_map(split_kv)
207 .collect();
208 let max_label = vm_iter
209 .iter()
210 .map(|(_, label, _)| label.len())
211 .max()
212 .map_or_else(|| 16, |x| x);
213 let max_category = vm_iter
214 .iter()
215 .map(|(category, _, _)| category.len())
216 .max()
217 .map_or_else(|| 7, |x| x);
218 self.vars = vm_iter;
219 self.max_label = max_label;
220 self.max_category = max_category;
221 }
222
223 #[cfg(not(feature = "color"))]
224 #[allow(clippy::unused_self)]
225 fn inner_display<T>(&self, writer: &mut T, key: &str, value: &str) -> Result<()>
226 where
227 T: Write + ?Sized,
228 {
229 Ok(writeln!(writer, "{key}: {value}")?)
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::Pretty;
236 use crate::{utils::test_utils::is_empty, vergen_pretty_env};
237 use anyhow::Result;
238 use std::io::Write;
239
240 #[test]
241 #[allow(clippy::clone_on_copy, clippy::redundant_clone)]
242 fn pretty_clone_works() {
243 let map = vergen_pretty_env!();
244 let pretty = Pretty::builder().env(map).build();
245 let another = pretty.clone();
246 assert_eq!(pretty, another);
247 }
248
249 #[test]
250 fn pretty_debug_works() -> Result<()> {
251 let map = vergen_pretty_env!();
252 let pretty = Pretty::builder().env(map).build();
253 let mut buf = vec![];
254 write!(buf, "{pretty:?}")?;
255 assert!(!buf.is_empty());
256 Ok(())
257 }
258
259 #[test]
260 fn default_display_works() -> Result<()> {
261 let mut stdout = vec![];
262 let map = vergen_pretty_env!();
263 let empty = is_empty(&map);
264 let fmt = Pretty::builder().env(map).build();
265 fmt.display(&mut stdout)?;
266 if empty {
267 assert!(stdout.is_empty());
268 } else {
269 assert!(!stdout.is_empty());
270 }
271 Ok(())
272 }
273
274 #[test]
275 fn no_category_works() -> Result<()> {
276 let mut stdout = vec![];
277 let map = vergen_pretty_env!();
278 let empty = is_empty(&map);
279 let fmt = Pretty::builder().env(map).category(false).build();
280 fmt.display(&mut stdout)?;
281 if empty {
282 assert!(stdout.is_empty());
283 } else {
284 assert!(!stdout.is_empty());
285 }
286 Ok(())
287 }
288
289 #[test]
290 fn custom_display_works() -> Result<()> {
291 let mut stdout = vec![];
292 let map = vergen_pretty_env!("vergen-cl");
293 let empty = is_empty(&map);
294 let fmt = Pretty::builder().env(map).build();
295 fmt.display(&mut stdout)?;
296 if empty {
297 assert!(stdout.is_empty());
298 } else {
299 assert!(!stdout.is_empty());
300 }
301 Ok(())
302 }
303}