use std::fmt::Write;
#[derive(Debug, Clone)]
pub enum Line {
Comment(String),
Blank,
Field { key: String, value: String },
Stanza { header: String, body: Vec<Line> },
}
pub fn render(lines: &[Line], indent_width: usize) -> String {
let mut out = String::new();
render_at(lines, 0, indent_width, &mut out);
out
}
#[derive(Debug, Clone)]
pub struct Document {
pub lines: Vec<Line>,
pub indent_width: usize,
}
impl Document {
pub fn new(indent_width: usize) -> Self {
Self { lines: Vec::new(), indent_width }
}
pub fn from(indent_width: usize, lines: impl IntoIterator<Item = Line>) -> Self {
Self { lines: lines.into_iter().collect(), indent_width }
}
pub fn push(&mut self, line: Line) -> &mut Self { self.lines.push(line); self }
}
fn render_at(lines: &[Line], depth: usize, iw: usize, out: &mut String) {
let pad = " ".repeat(depth * iw);
for line in lines {
match line {
Line::Comment(c) => {
let _ = writeln!(out, "{pad}-- {c}");
}
Line::Blank => out.push('\n'),
Line::Field { key, value } => {
let _ = writeln!(out, "{pad}{key}: {value}");
}
Line::Stanza { header, body } => {
let _ = writeln!(out, "{pad}{header}");
render_at(body, depth + 1, iw, out);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn field_emits_key_colon_value() {
let out = render(
&[Line::Field { key: "Package".into(), value: "demo".into() }],
2,
);
assert_eq!(out, "Package: demo\n");
}
#[test]
fn stanza_indents_body_by_iw() {
let out = render(
&[Line::Stanza {
header: "library".into(),
body: vec![
Line::Field { key: "exposed-modules".into(), value: "Lib".into() },
Line::Field { key: "build-depends".into(), value: "base".into() },
],
}],
2,
);
assert!(out.starts_with("library\n"));
assert!(out.contains(" exposed-modules: Lib\n"));
assert!(out.contains(" build-depends: base\n"));
}
#[test]
fn blank_line_separates() {
let out = render(
&[
Line::Field { key: "Name".into(), value: "x".into() },
Line::Blank,
Line::Field { key: "Version".into(), value: "1".into() },
],
1,
);
assert_eq!(out, "Name: x\n\nVersion: 1\n");
}
}