pub(super) fn fill(name: &str, value: &str, width: usize, indent: &str) -> Vec<String> {
let mut words = value.split_whitespace();
let Some(first) = words.next() else {
return vec![format!("{name}:")];
};
let mut lines = Vec::new();
let head = format!("{name}: ");
let mut current = if head.chars().count() + first.chars().count() <= width {
format!("{head}{first}")
} else {
lines.push(format!("{name}:"));
format!("{indent}{first}")
};
for word in words {
if current.chars().count() + 1 + word.chars().count() <= width {
current.push(' ');
current.push_str(word);
} else {
lines.push(std::mem::take(&mut current));
current = format!("{indent}{word}");
}
}
lines.push(current);
lines
}
#[cfg(test)]
mod tests {
use super::*;
const INDENT: &str = " ";
#[test]
fn a_short_value_stays_on_the_key_line() {
assert_eq!(fill("Title", "A Thing", 80, INDENT), vec!["Title: A Thing"]);
}
#[test]
fn an_empty_value_leaves_a_bare_key() {
assert_eq!(fill("Encoding", "", 80, INDENT), vec!["Encoding:"]);
assert_eq!(fill("Encoding", " ", 80, INDENT), vec!["Encoding:"]);
}
#[test]
fn continuation_lines_are_indented() {
assert_eq!(
fill("Description", "alpha beta gamma delta", 25, INDENT),
vec!["Description: alpha beta", " gamma delta"]
);
}
#[test]
fn an_oversized_first_word_leaves_the_key_alone() {
let url = "https://example.com/a-very-long-path-indeed";
assert_eq!(
fill("URL", url, 20, INDENT),
vec!["URL:".to_string(), format!("{INDENT}{url}")]
);
}
#[test]
fn width_counts_characters_not_bytes() {
assert_eq!(
fill("Title", "ünïcöde word", 18, INDENT),
vec!["Title: ünïcöde", " word"]
);
}
#[test]
fn the_budget_is_inclusive() {
assert_eq!(fill("Title", "abcd", 11, INDENT), vec!["Title: abcd"]);
assert_eq!(
fill("Title", "abcd", 10, INDENT),
vec!["Title:", " abcd"]
);
}
}