doc_writer/lib.rs
1#![allow(clippy::comparison_to_empty)]
2#![warn(missing_docs)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5//! Generate documentation in multiple formats using the [`DocumentationWriter`] trait.
6//! Implementations of which can be found in the [`render`] module for [man pages][crate::render::ManWriter] and [Markdown files][crate::render::MarkdownWriter].
7//!
8//! Generated contents look like this:
9//! ## `grep.md`
10#![doc = include_str!("../examples/grep.md")]
11//!
12//! ## `man grep.1`
13//! ```man
14#![doc = include_str!("../examples/grep.1.txt")]
15//! ```
16
17mod man;
18mod markdown;
19#[cfg(feature = "parse-markdown")]
20mod markdown_ext;
21
22#[cfg(feature = "parse-markdown")]
23pub use markdown_ext::*;
24
25/// Implementations of [`DocumentationWriter`] for different file formats.
26pub mod render {
27 pub use crate::man::*;
28 pub use crate::markdown::*;
29}
30
31use std::borrow::Cow;
32use std::error::Error;
33
34/// A way to format documentation.
35///
36/// The order in which this trait's methods can be called is outlined below.
37/// Any other order is undefined behaviour.
38///
39/// ```text
40/// (set_title | set_subtitle | set_license)*
41/// usage?
42/// (start_description text*)?
43/// custom_section*
44/// (start_options text* (option text*)*)?
45/// custom_section*
46/// (start_environment text* (variable text*)*)?
47/// (custom_section | (start_enum text* (variant text*)*))*
48/// finish
49/// ```
50/// with
51/// ```text
52/// custom_section := start_section text*
53/// text := plain | paragraph_break | emphasis | bold | link
54/// ```
55///
56/// - `a*`: `a` is repeated 0 or more times
57/// - `a?`: `a` is optional
58/// - `a | b`: either `a` or `b` is called
59/// - `a := b`: `a` is a shorthand for `b`
60/// - `a`: If `a` was not defined using the syntax above then `DocumentationWriter::a` is called
61///
62/// [`MarkdownParseExt::write_markdown`] can be used instead of `text` at any point to convert
63/// markdown comments into the documentation format if the `parse-markdown` feature is enabled.
64///
65/// # Examples
66/// ```
67/// # use doc_writer::DocumentationWriter;
68/// # use doc_writer::render::*;
69/// #
70/// let mut man_string = Vec::new();
71/// let mut md_string = Vec::new();
72///
73/// generate(ManWriter::new(&mut man_string).with_section("1")).unwrap();
74/// generate(MarkdownWriter::new(&mut md_string)).unwrap();
75///
76/// fn generate<W: DocumentationWriter>(mut w: W) -> Result<(), W::Error> {
77/// w.set_title("grep".into());
78/// w.set_subtitle("print lines that match patterns".into());
79/// w.set_license(
80/// r#"Copyright 1998-2000, 2002, 2005-2020 Free Software Foundation, Inc.
81///
82/// This is free software; see the source for copying conditions.
83/// There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."#
84/// .into(),
85/// );
86///
87/// w.usage("grep [OPTION...] PATTERNS [FILE...]")?;
88///
89/// w.start_description()?;
90/// w.plain("grep searches for PATTERNS in each FILE.\nPATTERNS is one or more patterns separated by newline characters.")?;
91/// w.paragraph_break()?;
92/// w.plain("A FILE of “-” stands for standard input.")?;
93///
94/// w.start_options()?;
95/// w.option("--help", "")?;
96/// w.plain("Output a usage message and exit.")?;
97///
98/// w.option("--color", "auto")?;
99/// w.plain("Display color on the terminal")?;
100///
101/// w.start_section("Regular Expressions")?;
102/// w.plain("A regular expression is a pattern that describes a set of strings.")?;
103/// w.paragraph_break()?;
104/// w.plain("Perl-compatible regular expressions give additional functionality, and are documented in ")?;
105/// w.link("", "man:pcresyntax(3)")?;
106///
107/// w.start_environment()?;
108/// w.variable("GREP_COLOR", "auto")?;
109/// w.plain("Display color on the terminal")?;
110///
111/// w.finish()
112/// }
113///
114/// assert_eq!(&String::from_utf8(md_string).unwrap(), include_str!("../examples/grep.md").trim());
115/// assert_eq!(&String::from_utf8(man_string).unwrap(), include_str!("../examples/grep.1").trim());
116/// ```
117///
118/// ## `grep.md`
119#[doc = include_str!("../examples/grep.md")]
120///
121/// ## `man grep.1`
122/// ```man
123#[doc = include_str!("../examples/grep.1.txt")]
124/// ```
125pub trait DocumentationWriter {
126 /// The error type returned by this writer.
127 type Error: Error;
128
129 /// Set the title of this document.
130 ///
131 /// # Examples
132 /// ```
133 /// # use doc_writer::render::MarkdownWriter;
134 /// # use doc_writer::DocumentationWriter;
135 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
136 /// w.set_title("grep".into());
137 /// ```
138 fn set_title(&mut self, title: Cow<'static, str>);
139
140 /// Set the subtitle of this document.
141 ///
142 /// # Examples
143 /// ```
144 /// # use doc_writer::render::MarkdownWriter;
145 /// # use doc_writer::DocumentationWriter;
146 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
147 /// w.set_title("grep".into());
148 /// w.set_subtitle("print lines that match patterns".into());
149 /// ```
150 fn set_subtitle(&mut self, subtitle: Cow<'static, str>);
151
152 /// Set the license of this man page and/or the code it covers.
153 ///
154 /// # Examples
155 /// ```
156 /// # use doc_writer::render::MarkdownWriter;
157 /// # use doc_writer::DocumentationWriter;
158 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
159 /// w.set_license(r"Copyright 1998-2000, 2002, 2005-2020 Free Software Foundation, Inc.
160 ///
161 /// This is free software; see the source for copying conditions.
162 /// There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.".into())
163 /// ```
164 fn set_license(&mut self, license: Cow<'static, str>);
165
166 /// Emit usage information.
167 ///
168 /// # Examples
169 /// ```
170 /// # use doc_writer::render::MarkdownWriter;
171 /// # use doc_writer::DocumentationWriter;
172 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
173 /// w.usage("grep [OPTION...] PATTERNS [FILE...]")?;
174 /// # Ok::<(), std::io::Error>(())
175 /// ```
176 fn usage(&mut self, usage: &str) -> Result<(), Self::Error>;
177
178 /// Start the description section.
179 ///
180 /// # Examples
181 /// ```
182 /// # use doc_writer::render::MarkdownWriter;
183 /// # use doc_writer::DocumentationWriter;
184 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
185 /// w.start_description()?;
186 /// w.plain("grep searches for PATTERNS in each FILE.\nPATTERNS is one or more patterns separated by newline characters.")?;
187 /// w.paragraph_break()?;
188 /// w.plain("A FILE of “-” stands for standard input.")?;
189 /// # Ok::<(), std::io::Error>(())
190 /// ```
191 fn start_description(&mut self) -> Result<(), Self::Error>;
192
193 /// Start a custom section.
194 ///
195 /// # Examples
196 /// ```
197 /// # use doc_writer::render::MarkdownWriter;
198 /// # use doc_writer::DocumentationWriter;
199 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
200 /// w.start_section("Regular Expressions")?;
201 /// w.plain("A regular expression is a pattern that describes a set of strings.")?;
202 /// # Ok::<(), std::io::Error>(())
203 /// ```
204 fn start_section(&mut self, name: &str) -> Result<(), Self::Error>;
205
206 /// Emit unformatted text.
207 ///
208 /// # Examples
209 /// ```
210 /// # use doc_writer::render::MarkdownWriter;
211 /// # use doc_writer::DocumentationWriter;
212 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
213 /// w.start_description()?;
214 /// w.plain("grep searches for PATTERNS in each FILE.")?;
215 /// w.plain("PATTERNS is one or more patterns separated by newline characters.")?;
216 /// # Ok::<(), std::io::Error>(())
217 /// ```
218 fn plain(&mut self, s: &str) -> Result<(), Self::Error>;
219
220 /// Emit a paragraph break.
221 ///
222 /// # Examples
223 /// ```
224 /// # use doc_writer::render::MarkdownWriter;
225 /// # use doc_writer::DocumentationWriter;
226 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
227 /// w.start_description()?;
228 /// w.plain("grep searches for PATTERNS in each FILE.\nPATTERNS is one or more patterns separated by newline characters.")?;
229 /// w.paragraph_break()?;
230 /// w.plain("A FILE of “-” stands for standard input.")?;
231 /// # Ok::<(), std::io::Error>(())
232 /// ```
233 fn paragraph_break(&mut self) -> Result<(), Self::Error>;
234
235 /// Emit emphasized (italicized) text.
236 ///
237 /// # Examples
238 /// ```
239 /// # use doc_writer::render::MarkdownWriter;
240 /// # use doc_writer::DocumentationWriter;
241 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
242 /// w.start_description()?;
243 /// w.emphasis("grep")?;
244 /// w.plain("searches for PATTERNS in each FILE.\nPATTERNS is one or more patterns separated by newline characters.")?;
245 /// # Ok::<(), std::io::Error>(())
246 /// ```
247 fn emphasis(&mut self, text: &str) -> Result<(), Self::Error>;
248
249 /// Emit strong (bold) text.
250 ///
251 /// # Examples
252 /// ```
253 /// # use doc_writer::render::MarkdownWriter;
254 /// # use doc_writer::DocumentationWriter;
255 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
256 /// w.start_description()?;
257 /// w.strong("NOTE:")?;
258 /// w.plain("quite important information")?;
259 /// # Ok::<(), std::io::Error>(())
260 /// ```
261 fn strong(&mut self, text: &str) -> Result<(), Self::Error>;
262
263 /// Emit a hyperlink.
264 ///
265 /// If `text == ""`, `to` is used as the displayed text.
266 ///
267 /// # Examples
268 /// ```
269 /// # use doc_writer::render::MarkdownWriter;
270 /// # use doc_writer::DocumentationWriter;
271 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
272 /// w.start_description()?;
273 /// w.plain("Perl-compatible regular expressions give additional functionality, and are documented in ")?;
274 /// w.link("", "man:pcresyntax(3)")?;
275 /// w.plain("and can be tested using online services like")?;
276 /// w.link("regex101", "https://regex101.com")?;
277 /// w.plain(".")?;
278 /// # Ok::<(), std::io::Error>(())
279 /// ```
280 fn link(&mut self, text: &str, to: &str) -> Result<(), Self::Error>;
281
282 /// Start the options section.
283 ///
284 /// # Examples
285 /// ```
286 /// # use doc_writer::render::MarkdownWriter;
287 /// # use doc_writer::DocumentationWriter;
288 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
289 /// w.start_options()?;
290 /// w.plain("These options are available:")?;
291 ///
292 /// w.option("--help", "")?;
293 /// w.plain("Output a usage message and exit.")?;
294 ///
295 /// w.option("--color", "auto")?;
296 /// w.plain("Display color on the terminal")?;
297 /// # Ok::<(), std::io::Error>(())
298 /// ```
299 fn start_options(&mut self) -> Result<(), Self::Error>;
300
301 /// Emit an option.
302 ///
303 /// # Examples
304 /// ```
305 /// # use doc_writer::render::MarkdownWriter;
306 /// # use doc_writer::DocumentationWriter;
307 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
308 /// w.start_options()?;
309 /// w.plain("These options are available:")?;
310 ///
311 /// w.option("--help", "")?;
312 /// w.plain("Output a usage message and exit.")?;
313 ///
314 /// w.option("--color", "auto")?;
315 /// w.plain("Display color on the terminal")?;
316 /// # Ok::<(), std::io::Error>(())
317 /// ```
318 fn option(&mut self, name: &str, default: &str) -> Result<(), Self::Error>;
319
320 /// Start the environment section.
321 ///
322 /// # Examples
323 /// ```
324 /// # use doc_writer::render::MarkdownWriter;
325 /// # use doc_writer::DocumentationWriter;
326 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
327 /// w.start_environment()?;
328 /// w.variable("GREP_COLOR", "auto")?;
329 /// w.plain("Display color on the terminal")?;
330 /// # Ok::<(), std::io::Error>(())
331 /// ```
332 fn start_environment(&mut self) -> Result<(), Self::Error>;
333
334 /// Emit an environment variable.
335 ///
336 /// # Examples
337 /// ```
338 /// # use doc_writer::render::MarkdownWriter;
339 /// # use doc_writer::DocumentationWriter;
340 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
341 /// w.start_environment()?;
342 /// w.variable("GREP_COLOR", "auto")?;
343 /// w.plain("Display color on the terminal")?;
344 /// # Ok::<(), std::io::Error>(())
345 /// ```
346 fn variable(&mut self, name: &str, default: &str) -> Result<(), Self::Error>;
347
348 /// Start a custom enum section.
349 ///
350 /// # Examples
351 /// ```
352 /// # use doc_writer::render::MarkdownWriter;
353 /// # use doc_writer::DocumentationWriter;
354 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
355 /// w.start_enum("COLOR")?;
356 /// w.variant("never")?;
357 /// w.plain("Never output any colors to stdout.")?;
358 /// w.variant("always")?;
359 /// w.plain("Always output colors to stdout.")?;
360 /// w.variant("auto")?;
361 /// w.plain("Only output colors to stdout if a tty is attached.")?;
362 /// # Ok::<(), std::io::Error>(())
363 /// ```
364 fn start_enum(&mut self, name: &str) -> Result<(), Self::Error>;
365
366 /// Emit a enum variant.
367 ///
368 /// # Examples
369 /// ```
370 /// # use doc_writer::render::MarkdownWriter;
371 /// # use doc_writer::DocumentationWriter;
372 /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
373 /// w.start_enum("COLOR")?;
374 /// w.variant("never")?;
375 /// w.plain("Never output any colors to stdout.")?;
376 /// w.variant("always")?;
377 /// w.plain("Always output colors to stdout.")?;
378 /// w.variant("auto")?;
379 /// w.plain("Only output colors to stdout if a tty is attached.")?;
380 /// # Ok::<(), std::io::Error>(())
381 /// ```
382 fn variant(&mut self, name: &str) -> Result<(), Self::Error>;
383
384 /// Finish the document by writing a footer.
385 ///
386 /// This function must always be called.
387 fn finish(self) -> Result<(), Self::Error>;
388}