use std::fmt::Write;
#[derive(Debug, Clone)]
pub enum Line {
Comment(String),
Blank,
Directive { keyword: String, args: Vec<String> },
Group { keyword: String, inner: Vec<Line> },
}
pub fn render(lines: &[Line]) -> String {
let mut out = String::new();
render_at(lines, 0, &mut out);
out
}
#[derive(Debug, Clone, Default)]
pub struct Lines(pub Vec<Line>);
impl Lines {
pub fn new() -> Self { Self::default() }
pub fn push(&mut self, line: Line) -> &mut Self { self.0.push(line); self }
pub fn extend(&mut self, lines: impl IntoIterator<Item = Line>) -> &mut Self {
self.0.extend(lines);
self
}
pub fn from(items: impl IntoIterator<Item = Line>) -> Self {
Self(items.into_iter().collect())
}
}
fn render_at(lines: &[Line], depth: usize, out: &mut String) {
let indent = "\t".repeat(depth);
for line in lines {
match line {
Line::Comment(c) => {
let _ = writeln!(out, "{indent}// {c}");
}
Line::Blank => out.push('\n'),
Line::Directive { keyword, args } => {
let _ = write!(out, "{indent}{keyword}");
for a in args {
out.push(' ');
out.push_str(a);
}
out.push('\n');
}
Line::Group { keyword, inner } => {
let _ = writeln!(out, "{indent}{keyword} (");
render_at(inner, depth + 1, out);
let _ = writeln!(out, "{indent})");
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_directive() {
let out = render(&[Line::Directive {
keyword: "module".to_string(),
args: vec!["github.com/pleme-io/demo".to_string()],
}]);
assert_eq!(out, "module github.com/pleme-io/demo\n");
}
#[test]
fn group_renders_with_tab_indent() {
let out = render(&[Line::Group {
keyword: "require".to_string(),
inner: vec![
Line::Directive {
keyword: "github.com/x/y".to_string(),
args: vec!["v1.2.3".to_string()],
},
Line::Directive {
keyword: "github.com/a/b".to_string(),
args: vec!["v0.1.0".to_string()],
},
],
}]);
assert!(out.contains("require (\n"));
assert!(out.contains("\tgithub.com/x/y v1.2.3\n"));
assert!(out.ends_with(")\n"));
}
#[test]
fn blank_separates_stanzas() {
let out = render(&[
Line::Directive { keyword: "module".to_string(), args: vec!["m".to_string()] },
Line::Blank,
Line::Directive { keyword: "go".to_string(), args: vec!["1.22".to_string()] },
]);
assert_eq!(out, "module m\n\ngo 1.22\n");
}
}