mdman/format/
md.rs

1//! Markdown formatter.
2
3use crate::util::unwrap;
4use crate::ManMap;
5use anyhow::{bail, format_err, Error};
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}