1use crate::model::*;
2
3const ANSI_BOLD: &str = "\x1b[1m";
4const ANSI_OFF: &str = "\x1b[22m";
5
6pub fn render_plain(document: &WdfDocument) -> String {
12 render(document, false)
13}
14
15pub 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}