use self::{prefix::Prefix, suffix::Suffix};
use crate::utils::{has_value, split_key, split_kv};
use anyhow::Result;
use bon::Builder;
#[cfg(feature = "color")]
use console::Style;
use std::{collections::BTreeMap, io::Write};
#[cfg(feature = "trace")]
use tracing::Level;
pub(crate) mod feature;
pub(crate) mod prefix;
pub(crate) mod suffix;
#[cfg_attr(
feature = "trace",
doc = r"
# Trace
Generate [`tracing`] output
```
# use vergen_pretty::{vergen_pretty_env, Pretty};
#
# pub fn main() {
Pretty::builder()
.env(vergen_pretty_env!())
.build()
.trace();
# }
```
"
)]
#[cfg_attr(
feature = "color",
doc = r"
# Customize Colorized Output
Uses [`Style`](console::Style) to colorize output
```
# use anyhow::Result;
# use vergen_pretty::{vergen_pretty_env, Pretty, Style};
#
# pub fn main() -> Result<()> {
let mut stdout = vec![];
let red_bold = Style::new().bold().red();
let yellow_bold = Style::new().bold().yellow();
Pretty::builder()
.env(vergen_pretty_env!())
.key_style(red_bold)
.value_style(yellow_bold)
.build()
.display(&mut stdout)?;
# Ok(())
# }
```
"
)]
#[derive(Builder, Clone, Debug, PartialEq)]
pub struct Pretty {
#[builder(field)]
vars: Vec<(String, String, String)>,
#[builder(field)]
max_label: usize,
#[builder(field)]
max_category: usize,
prefix: Option<Prefix>,
env: BTreeMap<&'static str, Option<&'static str>>,
filter: Option<Vec<&'static str>>,
#[builder(default = true)]
category: bool,
#[cfg(feature = "color")]
key_style: Option<Style>,
#[cfg(feature = "color")]
value_style: Option<Style>,
suffix: Option<Suffix>,
#[cfg(feature = "trace")]
#[builder(default = Level::INFO)]
level: Level,
#[cfg(feature = "serde")]
#[builder(default = false)]
flatten: bool,
}
impl Pretty {
pub fn display<T>(mut self, writer: &mut T) -> Result<()>
where
T: Write + ?Sized,
{
self.populate_fmt();
if let Some(prefix) = &self.prefix {
prefix.display(writer)?;
}
for (category, label, value) in &self.vars {
let max_label = self.max_label;
let max_category = self.max_category;
let key = if self.category {
format!("{label:>max_label$} ({category:>max_category$})")
} else {
format!("{label:>max_label$}")
};
self.inner_display(writer, &key, value)?;
}
if let Some(suffix) = &self.suffix {
suffix.display(writer)?;
}
Ok(())
}
fn populate_fmt(&mut self) {
let filter_fn = |tuple: &(&'static str, &'static str)| -> bool {
let (key, _) = tuple;
if let Some(filter) = &self.filter {
!filter.contains(key)
} else {
true
}
};
let vm_iter: Vec<(String, String, String)> = self
.env
.iter()
.filter_map(has_value)
.filter(filter_fn)
.filter_map(split_key)
.filter_map(split_kv)
.collect();
let max_label = vm_iter
.iter()
.map(|(_, label, _)| label.len())
.max()
.unwrap_or(16);
let max_category = vm_iter
.iter()
.map(|(category, _, _)| category.len())
.max()
.unwrap_or(7);
self.vars = vm_iter;
self.max_label = max_label;
self.max_category = max_category;
}
#[cfg(not(feature = "color"))]
#[allow(clippy::unused_self)]
fn inner_display<T>(&self, writer: &mut T, key: &str, value: &str) -> Result<()>
where
T: Write + ?Sized,
{
Ok(writeln!(writer, "{key}: {value}")?)
}
}
#[cfg(test)]
mod tests {
use super::Pretty;
use crate::{utils::test_utils::is_empty, vergen_pretty_env};
use anyhow::Result;
use std::io::Write;
#[test]
#[allow(clippy::clone_on_copy, clippy::redundant_clone)]
fn pretty_clone_works() {
let map = vergen_pretty_env!();
let pretty = Pretty::builder().env(map).build();
let another = pretty.clone();
assert_eq!(pretty, another);
}
#[test]
fn pretty_debug_works() -> Result<()> {
let map = vergen_pretty_env!();
let pretty = Pretty::builder().env(map).build();
let mut buf = vec![];
write!(buf, "{pretty:?}")?;
assert!(!buf.is_empty());
Ok(())
}
#[test]
fn default_display_works() -> Result<()> {
let mut stdout = vec![];
let map = vergen_pretty_env!();
let empty = is_empty(&map);
let fmt = Pretty::builder().env(map).build();
fmt.display(&mut stdout)?;
if empty {
assert!(stdout.is_empty());
} else {
assert!(!stdout.is_empty());
}
Ok(())
}
#[test]
fn no_category_works() -> Result<()> {
let mut stdout = vec![];
let map = vergen_pretty_env!();
let empty = is_empty(&map);
let fmt = Pretty::builder().env(map).category(false).build();
fmt.display(&mut stdout)?;
if empty {
assert!(stdout.is_empty());
} else {
assert!(!stdout.is_empty());
}
Ok(())
}
#[test]
fn custom_display_works() -> Result<()> {
let mut stdout = vec![];
let map = vergen_pretty_env!("vergen-cl");
let empty = is_empty(&map);
let fmt = Pretty::builder().env(map).build();
fmt.display(&mut stdout)?;
if empty {
assert!(stdout.is_empty());
} else {
assert!(!stdout.is_empty());
}
Ok(())
}
}