1use crate::DocumentationWriter;
2use std::borrow::Cow;
3use std::io::Write;
4use std::{io, mem};
5
6pub 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 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 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" – ")?;
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}