1use crate::DocumentationWriter;
2use std::borrow::Cow;
3use std::io::Write;
4use std::{io, mem};
5
6macro_rules! setter {
9 ($(#[$doc:meta])* $var:ident, $with:ident, $set:ident, $typ:ty) => {
10 $(#[$doc])*
11 #[doc=concat!("[", stringify!($set), "](Self::", stringify!($set), ")")]
14 #[inline]
15 pub fn $with(mut self, $var: $typ) -> Self {
16 self.$var = $var;
17 self
18 }
19
20 $(#[$doc])*
21 #[doc=concat!("[", stringify!($with), "](Self::", stringify!($with), ")")]
24 #[inline]
25 pub fn set_section(&mut self, $var: $typ) {
26 self.$var = $var;
27 }
28 };
29 ($var:ident, $with:ident, $set:ident) => {
30 setter!($var, $with, $set, Cow<'static, str>);
31 };
32}
33
34pub struct ManWriter<W: Write> {
39 writer: W,
40
41 section: &'static str,
42 title: Cow<'static, str>,
43 subtitle: Cow<'static, str>,
44 license: Cow<'static, str>,
45
46 wrote_header: bool,
47 see_also: Vec<(String, String)>,
48}
49
50impl<W: Write> ManWriter<W> {
51 pub fn new(w: W) -> Self {
53 Self {
54 writer: w,
55 section: "",
56 title: "".into(),
57 subtitle: "".into(),
58 license: "".into(),
59 wrote_header: false,
60 see_also: vec![],
61 }
62 }
63
64 setter!(
65 section,
79 with_section,
80 set_section,
81 &'static str
82 );
83}
84
85impl<W: Write> DocumentationWriter for ManWriter<W> {
86 type Error = io::Error;
87
88 fn set_title(&mut self, title: Cow<'static, str>) {
89 self.title = title;
90 }
91
92 fn set_subtitle(&mut self, subtitle: Cow<'static, str>) {
93 self.subtitle = subtitle;
94 }
95
96 fn set_license(&mut self, license: Cow<'static, str>) {
97 self.license = license;
98 }
99
100 fn usage(&mut self, usage: &str) -> Result<(), Self::Error> {
101 self.write_header_once()?;
102 self.write_raw(b".SH \"SYNOPSIS\"\n")?;
103 let mut args_iter = usage.splitn(2, ' ');
104 if let Some(command) = args_iter.next() {
105 self.emphasis(command)?;
106 if let Some(args) = args_iter.next() {
107 self.write_escaped_para(args)?;
108 self.write_raw(b"\n")?;
109 }
110 }
111 Ok(())
112 }
113
114 fn start_description(&mut self) -> Result<(), Self::Error> {
115 self.write_header_once()?;
116 self.write_raw(b".SH \"DESCRIPTION\"\n")
117 }
118
119 fn start_section(&mut self, name: &str) -> Result<(), Self::Error> {
120 self.write_header_once()?;
121 self.write_raw(b".SH ")?;
122 self.write_escaped_str(name.to_uppercase())?;
123 self.write_raw(b"\n")
124 }
125
126 fn plain(&mut self, s: &str) -> Result<(), Self::Error> {
127 self.write_escaped_para(s.trim())?;
128 self.write_raw(b"\n")
129 }
130
131 fn paragraph_break(&mut self) -> Result<(), Self::Error> {
132 self.write_raw(b".P\n")
133 }
134
135 fn emphasis(&mut self, text: &str) -> Result<(), Self::Error> {
136 self.write_raw(b".I ")?;
137 self.write_escaped_para(text)?;
138 self.write_raw(b"\n")
139 }
140
141 fn strong(&mut self, text: &str) -> Result<(), Self::Error> {
142 self.write_raw(b".B ")?;
143 self.write_escaped_para(text)?;
144 self.write_raw(b"\n")
145 }
146
147 fn link(&mut self, text: &str, to: &str) -> Result<(), Self::Error> {
148 self.see_also.push((text.to_owned(), to.to_owned()));
149 if let Some(man) = to.strip_prefix("man:") {
150 if text != "" {
151 self.plain(text)?;
152 self.write_raw(b" (")?;
153 }
154 self.write_raw(b".BR ")?;
155 self.write_escaped_para(man.replace("(", " ("))?;
156 if text != "" {
157 self.write_raw(b" )")?;
158 }
159 self.write_raw(b"\n")?;
160 } else if let Some(mail) = to.strip_prefix("mailto:") {
161 self.write_raw(b".MT ")?;
162 self.write_escaped_para(mail)?;
163 self.write_raw(b"\n")?;
164 if text != "" {
165 self.write_escaped_para(text)?;
166 } else {
167 self.write_escaped_para(mail)?;
168 }
169 self.write_raw(b"\n.ME\n")?;
170 } else {
171 self.write_raw(b".UR ")?;
172 self.write_escaped_para(to)?;
173 self.write_raw(b"\n")?;
174 if text != "" {
175 self.write_escaped_para(text)?;
176 } else {
177 self.write_escaped_para(to)?;
178 }
179 self.write_raw(b"\n.UE\n")?;
180 }
181 Ok(())
182 }
183
184 fn start_options(&mut self) -> Result<(), Self::Error> {
185 self.write_header_once()?;
186 self.write_raw(b".SH \"OPTIONS\"\n")
187 }
188
189 fn option(&mut self, name: &str, default: &str) -> Result<(), Self::Error> {
190 self.write_raw(br#".IP "\fI"#)?;
191 self.write_escaped_para(name)?;
192 if default != "" {
193 self.write_raw(b"=")?;
194 self.write_escaped_para(default)?;
195 }
196 self.write_raw(b"\\fP\" 10\n")
197 }
198
199 fn start_environment(&mut self) -> Result<(), Self::Error> {
200 self.write_header_once()?;
201 self.write_raw(b".SH \"ENVIRONMENT\"\n")
202 }
203
204 fn variable(&mut self, name: &str, default: &str) -> Result<(), Self::Error> {
205 self.option(name, default)
206 }
207
208 fn start_enum(&mut self, name: &str) -> Result<(), Self::Error> {
209 self.write_header_once()?;
210 self.write_raw(b".SH ")?;
211 self.write_escaped_str(name)?;
212 self.write_raw(b"\n")
213 }
214
215 fn variant(&mut self, name: &str) -> Result<(), Self::Error> {
216 self.option(name, "")
217 }
218
219 fn finish(mut self) -> Result<(), Self::Error> {
220 self.write_header_once()?;
221 if !self.see_also.is_empty() {
222 self.write_raw(b".SH \"SEE ALSO\"\n")?;
223 let see_also = mem::take(&mut self.see_also);
224 for also in see_also {
225 self.link(&also.0, &also.1)?;
226 }
227 }
228
229 if self.license != "" {
230 self.write_raw(b"\n.SH \"COPYRIGHT\"")?;
231 let license = mem::take(&mut self.license);
232 let mut lines = license.split('\n').peekable();
233 while let Some(line) = lines.next() {
234 self.write_raw(b"\n")?;
235 self.write_escaped_para(line)?;
236 match lines.peek() {
237 Some(&"") => {
238 self.write_raw(b"\n.P")?;
239 lines.next();
240 }
241 Some(_) => self.write_raw(b"\n.br")?,
242 None => {
243 lines.next();
244 }
245 }
246 }
247 }
248 Ok(())
249 }
250}
251
252impl<W: Write> ManWriter<W> {
253 fn write_header_once(&mut self) -> Result<(), io::Error> {
254 if self.wrote_header {
255 return Ok(());
256 }
257 self.wrote_header = true;
258
259 if self.title != "" {
260 let title = mem::take(&mut self.title);
261
262 self.write_raw(b".TH ")?;
263 self.write_escaped_str(title.to_uppercase())?;
264 if self.section != "" {
265 self.write_raw(b" ")?;
266 self.write_escaped_str(self.section)?;
267 } else {
268 self.write_raw(b" \"1\"")?;
269 }
270 self.write_raw(b"\n")?;
271
272 self.write_raw(b".SH \"NAME\"\n")?;
273 self.write_escaped_para(title)?;
274
275 if self.subtitle != "" {
276 self.write_raw(b"\n\\(em ")?;
277 let subtitle = mem::take(&mut self.subtitle);
278 self.write_escaped_para(subtitle)?;
279 }
280 self.write_raw(b"\n")?;
281 }
282
283 Ok(())
284 }
285
286 fn write_raw(&mut self, s: &[u8]) -> Result<(), io::Error> {
287 self.writer.write_all(s)
288 }
289
290 fn write_escaped_para<'a>(&mut self, s: impl Into<Cow<'a, str>>) -> Result<(), io::Error> {
291 let s = s.into();
292 if s.starts_with(&['.', '\''][..]) {
293 self.write_raw(b"\\&")?;
294 }
295 self.writer
296 .write_all(s.replace('"', "\\(dq").replace('\\', "\\e").as_bytes())
297 }
298
299 fn write_escaped_str<'a>(&mut self, s: impl Into<Cow<'a, str>>) -> Result<(), io::Error> {
300 self.write_raw(b"\"")?;
301 self.write_escaped_para(s.into())?;
302 self.write_raw(b"\"")
303 }
304}