Skip to main content

wowlab_common/markdown/
doc.rs

1use super::inline::quote;
2
3const H2_LEVEL: u8 = 2;
4
5const H3_LEVEL: u8 = 3;
6
7/// Builder for multi-line markdown documents.
8#[derive(Debug)]
9pub struct Doc(Vec<Box<str>>);
10
11impl Doc {
12    #[must_use]
13    pub fn new() -> Self {
14        Self(Vec::new())
15    }
16
17    #[must_use]
18    pub fn line(mut self, text: &str) -> Self {
19        self.0.push(text.into());
20
21        self
22    }
23
24    #[must_use]
25    pub fn blank(mut self) -> Self {
26        self.0.push(Box::from(""));
27
28        self
29    }
30
31    #[must_use]
32    pub fn heading(mut self, level: u8, text: &str) -> Self {
33        self.0
34            .push(format!("{} {text}", "#".repeat(level as usize)).into_boxed_str());
35
36        self
37    }
38
39    #[must_use]
40    pub fn h1(self, text: &str) -> Self {
41        self.heading(1, text)
42    }
43
44    #[must_use]
45    pub fn h2(self, text: &str) -> Self {
46        self.heading(H2_LEVEL, text)
47    }
48
49    #[must_use]
50    pub fn h3(self, text: &str) -> Self {
51        self.heading(H3_LEVEL, text)
52    }
53
54    #[must_use]
55    pub fn numbered(mut self, n: usize, text: &str) -> Self {
56        self.0.push(format!("{n}. {text}").into_boxed_str());
57
58        self
59    }
60
61    #[must_use]
62    pub fn bullet(mut self, text: &str) -> Self {
63        self.0.push(format!("- {text}").into_boxed_str());
64
65        self
66    }
67
68    /// Render a bullet with a bold key and plain value: `- **key:** value`.
69    #[must_use]
70    pub fn kv_bullet(mut self, key: &str, value: &str) -> Self {
71        self.0
72            .push(format!("- **{key}:** {value}").into_boxed_str());
73
74        self
75    }
76
77    /// Render a bullet with an inline-code term and description: `` - `term` — desc ``.
78    #[must_use]
79    pub fn def(mut self, term: &str, desc: &str) -> Self {
80        self.0.push(format!("- `{term}` — {desc}").into_boxed_str());
81
82        self
83    }
84
85    /// Render `` - `term` (detail) — desc ``.
86    #[must_use]
87    pub fn def_detail(mut self, term: &str, detail: &str, desc: &str) -> Self {
88        self.0
89            .push(format!("- `{term}` ({detail}) — {desc}").into_boxed_str());
90
91        self
92    }
93
94    /// Render a blockquote (each line prefixed with `>`).
95    // #t(fn: rust_recursive_fn) calls free fn quote(), not self - false positive
96    #[must_use]
97    pub fn quote(self, text: &str) -> Self {
98        self.raw(quote(text))
99    }
100
101    #[must_use]
102    pub fn code_block(mut self, language: &str, content: &str) -> Self {
103        if language.is_empty() {
104            self.0.push(format!("```\n{content}\n```").into_boxed_str());
105        } else {
106            self.0
107                .push(format!("```{language}\n{content}\n```").into_boxed_str());
108        }
109
110        self
111    }
112
113    #[must_use]
114    pub fn raw(mut self, text: String) -> Self {
115        self.0.push(text.into_boxed_str());
116
117        self
118    }
119
120    #[must_use]
121    pub fn build(self) -> String {
122        self.0.join("\n")
123    }
124}
125
126impl Default for Doc {
127    fn default() -> Self {
128        Self::new()
129    }
130}