#
mdman/format/
md.rs

    1//! Markdown formatter.
    2
    3use crate::ManMap;
    4use crate::util::unwrap;
    5use anyhow::{Error, bail, format_err};
    6use std::fmt::Write;
    7
    8pub struct MdFormatter {
    9    man_map: ManMap,
    10}
    11
    12impl MdFormatter {
    13    pub fn new(man_map: ManMap) -> MdFormatter {
    14        MdFormatter { man_map }
    15    }
    16}
    17
    18impl MdFormatter {
    19    fn render_html(&self, input: &str) -> Result<String, Error> {
    20        let parser = crate::md_parser(input, None);
    21        let mut html_output: String = String::with_capacity(input.len() * 3 / 2);
    22        pulldown_cmark::html::push_html(&mut html_output, parser.map(|(e, _r)| e));
    23        Ok(html_output)
    24    }
    25}
    26
    27impl super::Formatter for MdFormatter {
    28    fn render(&self, input: &str) -> Result<String, Error> {
    29        Ok(input.replace("\r\n", "\n"))
    30    }
    31
    32    fn render_options_start(&self) -> &'static str {
    33        "<dl>\n"
    34    }
    35
    36    fn render_options_end(&self) -> &'static str {
    37        "</dl>\n"
    38    }
    39
    40    fn render_option(&self, params: &[&str], block: &str, man_name: &str) -> Result<String, Error> {
    41        let mut result = String::new();
    42        fn unwrap_p(t: &str) -> &str {
    43            unwrap(t, "<p>", "</p>")
    44        }
    45
    46        for param in params {
    47            let rendered = self.render_html(param)?;
    48            let no_p = unwrap_p(&rendered);
    49            // split out first term to use as the id.
    50            let first = no_p
    51                .split_whitespace()
    52                .next()
    53                .ok_or_else(|| format_err!("did not expect option `{}` to be empty", param))?;
    54            let no_tags = trim_tags(first);
    55            if no_tags.is_empty() {
    56                bail!("unexpected empty option with no tags `{}`", param);
    57            }
    58            let id = format!("option-{}-{}", man_name, no_tags);
    59            write!(
    60                result,
    61                "<dt class=\"option-term\" id=\"{ID}\">\
    62                <a class=\"option-anchor\" href=\"#{ID}\"></a>{OPTION}</dt>\n",
    63                ID = id,
    64                OPTION = no_p
    65            )?;
    66        }
    67        let rendered_block = self.render_html(block)?;
    68        write!(
    69            result,
    70            "<dd class=\"option-desc\">{}</dd>\n\n",
    71            unwrap_p(&rendered_block)
    72        )?;
    73        Ok(result)
    74    }
    75
    76    fn linkify_man_to_md(&self, name: &str, section: u8) -> Result<String, Error> {
    77        let s = match self.man_map.get(&(name.to_string(), section)) {
    78            Some(link) => format!("[{}({})]({})", name, section, link),
    79            None => format!("[{}({})]({}.html)", name, section, name),
    80        };
    81        Ok(s)
    82    }
    83}
    84
    85fn trim_tags(s: &str) -> String {
    86    // This is a hack. It removes all HTML tags.
    87    let mut in_tag = false;
    88    let mut in_char_ref = false;
    89    s.chars()
    90        .filter(|&ch| match ch {
    91            '<' if in_tag => panic!("unexpected nested tag"),
    92            '&' if in_char_ref => panic!("unexpected nested char ref"),
    93            '<' => {
    94                in_tag = true;
    95                false
    96            }
    97            '&' => {
    98                in_char_ref = true;
    99                false
    100            }
    101            '>' if in_tag => {
    102                in_tag = false;
    103                false
    104            }
    105            ';' if in_char_ref => {
    106                in_char_ref = false;
    107                false
    108            }
    109            _ => !in_tag && !in_char_ref,
    110        })
    111        .collect()
    112}