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}