1use 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 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}\">{no_p}</a></dt>\n",
63 )?;
64 }
65 let rendered_block = self.render_html(block)?;
66 write!(
67 result,
68 "<dd class=\"option-desc\">{rendered_block}</dd>\n\n",
69 )?;
70 Ok(result)
71 }
72
73 fn linkify_man_to_md(&self, name: &str, section: u8) -> Result<String, Error> {
74 let s = match self.man_map.get(&(name.to_string(), section)) {
75 Some(link) => format!("[{}({})]({})", name, section, link),
76 None => format!("[{}({})]({}.html)", name, section, name),
77 };
78 Ok(s)
79 }
80}
81
82fn trim_tags(s: &str) -> String {
83 let mut in_tag = false;
85 let mut in_char_ref = false;
86 s.chars()
87 .filter(|&ch| match ch {
88 '<' if in_tag => panic!("unexpected nested tag"),
89 '&' if in_char_ref => panic!("unexpected nested char ref"),
90 '<' => {
91 in_tag = true;
92 false
93 }
94 '&' => {
95 in_char_ref = true;
96 false
97 }
98 '>' if in_tag => {
99 in_tag = false;
100 false
101 }
102 ';' if in_char_ref => {
103 in_char_ref = false;
104 false
105 }
106 _ => !in_tag && !in_char_ref,
107 })
108 .collect()
109}