Skip to main content

weimo_wdf/
cli.rs

1use crate::model::*;
2
3const ANSI_BOLD: &str = "\x1b[1m";
4const ANSI_OFF: &str = "\x1b[22m";
5
6/// Renders a [`WdfDocument`] as plain terminal text.
7///
8/// Headings keep their `#` prefix, list items use `-` / `N.`, quotes are
9/// prefixed with `>`, and no escape codes are emitted — suitable for logs,
10/// previews and piped output.
11pub fn render_plain(document: &WdfDocument) -> String {
12    render(document, false)
13}
14
15/// Like [`render_plain`], but wraps bold runs and heading lines in ANSI
16/// bold escape codes for interactive terminals.
17pub fn render_ansi(document: &WdfDocument) -> String {
18    render(document, true)
19}
20
21fn inline(content: &[WdfText], ansi: bool) -> String {
22    content
23        .iter()
24        .map(|text| {
25            if ansi && text.is_bold() {
26                format!("{ANSI_BOLD}{}{ANSI_OFF}", text.text)
27            } else {
28                text.text.clone()
29            }
30        })
31        .collect()
32}
33fn lines(value: String, prefix: &str) -> String {
34    value
35        .split('\n')
36        .map(|line| {
37            if line.is_empty() {
38                prefix.trim_end().to_string()
39            } else {
40                format!("{prefix}{line}")
41            }
42        })
43        .collect::<Vec<_>>()
44        .join("\n")
45}
46fn block(block: &WdfBlock, ansi: bool) -> String {
47    match block {
48        WdfBlock::Paragraph { content } => inline(content, ansi),
49        WdfBlock::Heading { attrs, content } => format!(
50            "{}{}",
51            "#".repeat(attrs.level as usize) + " ",
52            if ansi {
53                format!("{ANSI_BOLD}{}{ANSI_OFF}", inline(content, true))
54            } else {
55                inline(content, false)
56            }
57        ),
58        WdfBlock::Blockquote { content } => content
59            .iter()
60            .map(|p| lines(inline(&p.content, ansi), "> "))
61            .collect::<Vec<_>>()
62            .join("\n"),
63        WdfBlock::BulletList { content } => content
64            .iter()
65            .map(|i| lines(inline(&i.content[0].content, ansi), "- "))
66            .collect::<Vec<_>>()
67            .join("\n"),
68        WdfBlock::OrderedList { attrs, content } => content
69            .iter()
70            .enumerate()
71            .map(|(index, i)| {
72                lines(
73                    inline(&i.content[0].content, ansi),
74                    &format!("{}. ", attrs.as_ref().map_or(1, |a| a.start) + index as u32),
75                )
76            })
77            .collect::<Vec<_>>()
78            .join("\n"),
79    }
80}
81fn render(document: &WdfDocument, ansi: bool) -> String {
82    document
83        .root
84        .content
85        .iter()
86        .map(|b| block(b, ansi))
87        .collect::<Vec<_>>()
88        .join("\n\n")
89        + "\n"
90}