doc-writer 0.2.0

Generate documentation in multiple formats.
Documentation
#![allow(clippy::comparison_to_empty)]
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]

//! Generate documentation in multiple formats using the [`DocumentationWriter`] trait.
//! Implementations of which can be found in the [`render`] module for [man pages][crate::render::ManWriter] and [Markdown files][crate::render::MarkdownWriter].
//!
//! Generated contents look like this:
//! ## `grep.md`
#![doc = include_str!("../examples/grep.md")]
//!
//! ## `man grep.1`
//! ```man
#![doc = include_str!("../examples/grep.1.txt")]
//! ```

mod man;
mod markdown;
#[cfg(feature = "parse-markdown")]
mod markdown_ext;

#[cfg(feature = "parse-markdown")]
pub use markdown_ext::*;

/// Implementations of [`DocumentationWriter`] for different file formats.
pub mod render {
    pub use crate::man::*;
    pub use crate::markdown::*;
}

use std::borrow::Cow;
use std::error::Error;

/// A way to format documentation.
///
/// The order in which this trait's methods can be called is outlined below.
/// Any other order is undefined behaviour.
///
/// ```text
/// (set_title | set_subtitle | set_license)*
/// usage?
/// (start_description text*)?
/// custom_section*
/// (start_options text* (option text*)*)?
/// custom_section*
/// (start_environment text* (variable text*)*)?
/// (custom_section | (start_enum text* (variant text*)*))*
/// finish
/// ```
/// with
/// ```text
/// custom_section := start_section text*
/// text := plain | paragraph_break | emphasis | bold | link
/// ```
///
/// - `a*`: `a` is repeated 0 or more times
/// - `a?`: `a` is optional
/// - `a | b`: either `a` or `b` is called
/// - `a := b`: `a` is a shorthand for `b`
/// - `a`: If `a` was not defined using the syntax above then `DocumentationWriter::a` is called
///
/// [`MarkdownParseExt::write_markdown`] can be used instead of `text` at any point to convert
/// markdown comments into the documentation format if the `parse-markdown` feature is enabled.
///
/// # Examples
/// ```
/// # use doc_writer::DocumentationWriter;
/// # use doc_writer::render::*;
/// #
/// let mut man_string = Vec::new();
/// let mut md_string = Vec::new();
///
/// generate(ManWriter::new(&mut man_string).with_section("1")).unwrap();
/// generate(MarkdownWriter::new(&mut md_string)).unwrap();
///
/// fn generate<W: DocumentationWriter>(mut w: W) -> Result<(), W::Error> {
///     w.set_title("grep".into());
///     w.set_subtitle("print lines that match patterns".into());
///     w.set_license(
///         r#"Copyright 1998-2000, 2002, 2005-2020 Free Software Foundation, Inc.
///
/// This is free software; see the source for copying conditions.
/// There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."#
///             .into(),
///     );
///
///     w.usage("grep [OPTION...] PATTERNS [FILE...]")?;
///
///     w.start_description()?;
///     w.plain("grep searches for PATTERNS in each FILE.\nPATTERNS is one or more patterns separated by newline characters.")?;
///     w.paragraph_break()?;
///     w.plain("A FILE of “-” stands for standard input.")?;
///
///     w.start_options()?;
///     w.option("--help", "")?;
///     w.plain("Output a usage message and exit.")?;
///
///     w.option("--color", "auto")?;
///     w.plain("Display color on the terminal")?;
///
///     w.start_section("Regular Expressions")?;
///     w.plain("A regular expression is a pattern that describes a set of strings.")?;
///     w.paragraph_break()?;
///     w.plain("Perl-compatible regular expressions give additional functionality, and are documented in ")?;
///     w.link("", "man:pcresyntax(3)")?;
///
///     w.start_environment()?;
///     w.variable("GREP_COLOR", "auto")?;
///     w.plain("Display color on the terminal")?;
///
///     w.finish()
/// }
///
/// assert_eq!(&String::from_utf8(md_string).unwrap(), include_str!("../examples/grep.md").trim());
/// assert_eq!(&String::from_utf8(man_string).unwrap(), include_str!("../examples/grep.1").trim());
/// ```
///
/// ## `grep.md`
#[doc = include_str!("../examples/grep.md")]
///
/// ## `man grep.1`
/// ```man
#[doc = include_str!("../examples/grep.1.txt")]
/// ```
pub trait DocumentationWriter {
    /// The error type returned by this writer.
    type Error: Error;

    /// Set the title of this document.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.set_title("grep".into());
    /// ```
    fn set_title(&mut self, title: Cow<'static, str>);

    /// Set the subtitle of this document.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.set_title("grep".into());
    /// w.set_subtitle("print lines that match patterns".into());
    /// ```
    fn set_subtitle(&mut self, subtitle: Cow<'static, str>);

    /// Set the license of this man page and/or the code it covers.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.set_license(r"Copyright 1998-2000, 2002, 2005-2020 Free Software Foundation, Inc.
    ///
    /// This is free software; see the source for copying conditions.
    /// There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.".into())
    /// ```
    fn set_license(&mut self, license: Cow<'static, str>);

    /// Emit usage information.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.usage("grep [OPTION...] PATTERNS [FILE...]")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn usage(&mut self, usage: &str) -> Result<(), Self::Error>;

    /// Start the description section.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_description()?;
    /// w.plain("grep searches for PATTERNS in each FILE.\nPATTERNS is one or more patterns separated by newline characters.")?;
    /// w.paragraph_break()?;
    /// w.plain("A FILE of “-” stands for standard input.")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn start_description(&mut self) -> Result<(), Self::Error>;

    /// Start a custom section.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_section("Regular Expressions")?;
    /// w.plain("A regular expression is a pattern that describes a set of strings.")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn start_section(&mut self, name: &str) -> Result<(), Self::Error>;

    /// Emit unformatted text.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_description()?;
    /// w.plain("grep searches for PATTERNS in each FILE.")?;
    /// w.plain("PATTERNS is one or more patterns separated by newline characters.")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn plain(&mut self, s: &str) -> Result<(), Self::Error>;

    /// Emit a paragraph break.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_description()?;
    /// w.plain("grep searches for PATTERNS in each FILE.\nPATTERNS is one or more patterns separated by newline characters.")?;
    /// w.paragraph_break()?;
    /// w.plain("A FILE of “-” stands for standard input.")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn paragraph_break(&mut self) -> Result<(), Self::Error>;

    /// Emit emphasized (italicized) text.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_description()?;
    /// w.emphasis("grep")?;
    /// w.plain("searches for PATTERNS in each FILE.\nPATTERNS is one or more patterns separated by newline characters.")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn emphasis(&mut self, text: &str) -> Result<(), Self::Error>;

    /// Emit strong (bold) text.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_description()?;
    /// w.strong("NOTE:")?;
    /// w.plain("quite important information")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn strong(&mut self, text: &str) -> Result<(), Self::Error>;

    /// Emit a hyperlink.
    ///
    /// If `text == ""`, `to` is used as the displayed text.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_description()?;
    /// w.plain("Perl-compatible regular expressions give additional functionality, and are documented in ")?;
    /// w.link("", "man:pcresyntax(3)")?;
    /// w.plain("and can be tested using online services like")?;
    /// w.link("regex101", "https://regex101.com")?;
    /// w.plain(".")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn link(&mut self, text: &str, to: &str) -> Result<(), Self::Error>;

    /// Start the options section.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_options()?;
    /// w.plain("These options are available:")?;
    ///
    /// w.option("--help", "")?;
    /// w.plain("Output a usage message and exit.")?;
    ///
    /// w.option("--color", "auto")?;
    /// w.plain("Display color on the terminal")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn start_options(&mut self) -> Result<(), Self::Error>;

    /// Emit an option.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_options()?;
    /// w.plain("These options are available:")?;
    ///
    /// w.option("--help", "")?;
    /// w.plain("Output a usage message and exit.")?;
    ///
    /// w.option("--color", "auto")?;
    /// w.plain("Display color on the terminal")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn option(&mut self, name: &str, default: &str) -> Result<(), Self::Error>;

    /// Start the environment section.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_environment()?;
    /// w.variable("GREP_COLOR", "auto")?;
    /// w.plain("Display color on the terminal")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn start_environment(&mut self) -> Result<(), Self::Error>;

    /// Emit an environment variable.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_environment()?;
    /// w.variable("GREP_COLOR", "auto")?;
    /// w.plain("Display color on the terminal")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn variable(&mut self, name: &str, default: &str) -> Result<(), Self::Error>;

    /// Start a custom enum section.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_enum("COLOR")?;
    /// w.variant("never")?;
    /// w.plain("Never output any colors to stdout.")?;
    /// w.variant("always")?;
    /// w.plain("Always output colors to stdout.")?;
    /// w.variant("auto")?;
    /// w.plain("Only output colors to stdout if a tty is attached.")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn start_enum(&mut self, name: &str) -> Result<(), Self::Error>;

    /// Emit a enum variant.
    ///
    /// # Examples
    /// ```
    /// # use doc_writer::render::MarkdownWriter;
    /// # use doc_writer::DocumentationWriter;
    /// # let mut w = MarkdownWriter::new(Vec::<u8>::new());
    /// w.start_enum("COLOR")?;
    /// w.variant("never")?;
    /// w.plain("Never output any colors to stdout.")?;
    /// w.variant("always")?;
    /// w.plain("Always output colors to stdout.")?;
    /// w.variant("auto")?;
    /// w.plain("Only output colors to stdout if a tty is attached.")?;
    /// # Ok::<(), std::io::Error>(())
    /// ```
    fn variant(&mut self, name: &str) -> Result<(), Self::Error>;

    /// Finish the document by writing a footer.
    ///
    /// This function must always be called.
    fn finish(self) -> Result<(), Self::Error>;
}