doc_writer/
markdown.rs

1use crate::DocumentationWriter;
2use std::borrow::Cow;
3use std::io::Write;
4use std::{io, mem};
5
6/// A [`DocumentationWriter`] that generates markdown documents.
7///
8/// # Examples
9/// See [`DocumentationWriter`].
10pub struct MarkdownWriter<W: Write> {
11    writer: W,
12
13    title: Cow<'static, str>,
14    subtitle: Cow<'static, str>,
15    license: Cow<'static, str>,
16
17    wrote_header: bool,
18}
19
20impl<W: Write> MarkdownWriter<W> {
21    /// Create a new instance.
22    pub fn new(w: W) -> Self {
23        Self {
24            writer: w,
25            title: "".into(),
26            subtitle: "".into(),
27            license: "".into(),
28            wrote_header: false,
29        }
30    }
31}
32
33impl<W: Write> DocumentationWriter for MarkdownWriter<W> {
34    type Error = io::Error;
35
36    fn set_title(&mut self, title: Cow<'static, str>) {
37        self.title = title;
38    }
39
40    fn set_subtitle(&mut self, subtitle: Cow<'static, str>) {
41        self.subtitle = subtitle;
42    }
43
44    fn set_license(&mut self, license: Cow<'static, str>) {
45        self.license = license;
46    }
47
48    fn usage(&mut self, usage: &str) -> Result<(), Self::Error> {
49        self.write_header_once()?;
50        self.write_raw(b"usage: ")?;
51        self.write_code_literal(usage)?;
52        self.write_raw(b"\n")?;
53        Ok(())
54    }
55
56    fn start_description(&mut self) -> Result<(), Self::Error> {
57        self.write_header_once()?;
58        self.write_raw(b"\n\n")?;
59        Ok(())
60    }
61
62    fn start_section(&mut self, name: &str) -> Result<(), Self::Error> {
63        self.write_header_once()?;
64        self.write_raw(b"\n\n## ")?;
65        self.write_escaped(name)?;
66        self.write_raw(b"\n")?;
67        Ok(())
68    }
69
70    fn plain(&mut self, s: &str) -> Result<(), Self::Error> {
71        self.write_escaped(s.trim())?;
72        self.write_raw(b"\n")
73    }
74
75    fn paragraph_break(&mut self) -> Result<(), Self::Error> {
76        self.write_raw(b"\n\n")?;
77        Ok(())
78    }
79
80    fn emphasis(&mut self, text: &str) -> Result<(), Self::Error> {
81        self.write_raw(b"*")?;
82        self.write_escaped(text)?;
83        self.write_raw(b"*\n")?;
84        Ok(())
85    }
86
87    fn strong(&mut self, text: &str) -> Result<(), Self::Error> {
88        self.write_raw(b"**")?;
89        self.write_escaped(text)?;
90        self.write_raw(b"**\n")?;
91        Ok(())
92    }
93
94    fn link(&mut self, text: &str, to: &str) -> Result<(), Self::Error> {
95        if let Some(man) = to.strip_prefix("man:") {
96            let paren_index = man.find('(').unwrap_or(man.len());
97            self.write_raw(b"**")?;
98            self.write_escaped(&man[..paren_index])?;
99            self.write_raw(b"**")?;
100            self.write_escaped(&man[paren_index..])?;
101            return Ok(());
102        }
103        if text == "" {
104            self.write_raw(b"<")?;
105            self.write_raw(
106                to.replace(" ", "%20")
107                    .replace("(", "%28")
108                    .replace(")", "%29")
109                    .replace("<", "%3c")
110                    .replace(">", "%3e")
111                    .as_bytes(),
112            )?;
113            self.write_raw(b">")?;
114        } else {
115            self.write_raw(b"[")?;
116            self.write_escaped(text)?;
117            self.write_raw(b"](")?;
118            self.write_raw(
119                to.replace(" ", "%20")
120                    .replace("(", "%28")
121                    .replace(")", "%29")
122                    .as_bytes(),
123            )?;
124            self.write_raw(b")")?;
125        }
126        Ok(())
127    }
128
129    fn start_options(&mut self) -> Result<(), Self::Error> {
130        self.write_header_once()?;
131        self.write_raw(b"\n\n## Options\n")?;
132        Ok(())
133    }
134
135    fn option(&mut self, name: &str, default: &str) -> Result<(), Self::Error> {
136        self.write_raw(b"- ")?;
137        if default == "" {
138            self.write_code_literal(name)?;
139        } else {
140            self.write_code_literal(format!("{}={}", name, default))?;
141        }
142        self.write_raw(b": ")?;
143        // TODO support multiple paragraphs using indents
144        Ok(())
145    }
146
147    fn start_environment(&mut self) -> Result<(), Self::Error> {
148        self.write_header_once()?;
149        self.write_raw(b"\n\n## Environment\n")?;
150        Ok(())
151    }
152
153    fn variable(&mut self, name: &str, default: &str) -> Result<(), Self::Error> {
154        self.option(name, default)
155    }
156
157    fn start_enum(&mut self, name: &str) -> Result<(), Self::Error> {
158        self.write_header_once()?;
159        self.write_raw(b"\n\n## ")?;
160        self.write_escaped(name)?;
161        self.write_raw(b"\n")?;
162        Ok(())
163    }
164
165    fn variant(&mut self, name: &str) -> Result<(), Self::Error> {
166        self.option(name, "")
167    }
168
169    fn finish(mut self) -> Result<(), Self::Error> {
170        self.write_header_once()?;
171        if self.license != "" {
172            self.write_raw(b"\n\n## License\n")?;
173            let license = mem::take(&mut self.license);
174            self.write_escaped(license.replace("\n", "  \n"))?;
175        }
176        Ok(())
177    }
178}
179
180impl<W: Write> MarkdownWriter<W> {
181    fn write_header_once(&mut self) -> Result<(), io::Error> {
182        if self.wrote_header {
183            return Ok(());
184        }
185        self.wrote_header = true;
186
187        if !self.title.is_empty() {
188            self.write_raw(b"# ")?;
189            let title = mem::take(&mut self.title);
190            self.write_escaped(title)?;
191            if !self.subtitle.is_empty() {
192                self.write_raw(b" &ndash; ")?;
193                let subtitle = mem::take(&mut self.subtitle);
194                self.write_escaped(subtitle)?;
195            }
196        }
197        self.write_raw(b"\n\n")?;
198
199        Ok(())
200    }
201
202    fn write_raw(&mut self, s: &[u8]) -> Result<(), io::Error> {
203        self.writer.write_all(s)
204    }
205
206    fn write_escaped<'a>(&mut self, s: impl Into<Cow<'a, str>>) -> Result<(), io::Error> {
207        self.writer.write_all(
208            s.into()
209                .replace('*', "\\*")
210                .replace('_', "\\_")
211                .replace('<', "\\<")
212                .replace('[', "\\[")
213                .replace('`', "\\`")
214                .replace('#', "\\#")
215                .replace('&', "\\&")
216                .replace('.', "\\.")
217                .as_bytes(),
218        )
219    }
220
221    fn write_code_literal<'a>(&mut self, s: impl Into<Cow<'a, str>>) -> Result<(), io::Error> {
222        let s = s.into();
223        let mut backticks = "`".to_owned();
224        loop {
225            if !s.contains(&backticks) {
226                self.write_raw(backticks.as_bytes())?;
227                self.write_raw(s.as_bytes())?;
228                self.write_raw(backticks.as_bytes())?;
229                break;
230            } else {
231                backticks.push('`');
232            }
233        }
234        Ok(())
235    }
236}