use std::fmt;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Fields<'a> {
pub rows: Vec<(&'a str, String)>,
}
impl<'a> Fields<'a> {
pub fn push(&mut self, label: &'a str, value: impl fmt::Display) {
self.rows.push((label, value.to_string()));
}
}
impl fmt::Display for Fields<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let width = self.rows.iter().map(|(label, _)| label.chars().count());
let width = width.max().unwrap_or_default();
let indent = width + 3;
for (label, value) in &self.rows {
let mut lines = value.lines();
match lines.next().filter(|line| !line.is_empty()) {
Some(first) => {
let pad = width - label.chars().count() + 2;
writeln!(f, "{label}:{:pad$}{first}", "")?;
}
None => writeln!(f, "{label}:")?,
}
for line in lines {
match line {
"" => writeln!(f)?,
line => writeln!(f, "{:indent$}{line}", "")?,
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::fields::*;
#[test]
fn values_align_past_the_longest_label() {
let mut fields = Fields::default();
fields.push("Type", "Bearer");
fields.push("Refresh token", "yes");
fields.push("Scopes", "mail\ncontacts");
fields.push("Note", "");
let expected = "\
Type: Bearer
Refresh token: yes
Scopes: mail
contacts
Note:
";
assert_eq!(fields.to_string(), expected);
}
#[test]
fn an_empty_record_prints_nothing() {
assert_eq!(Fields::default().to_string(), "");
}
}