sable-markdown/src/render/mod.rs@main
raw
1mod block;
2mod highlighter;
3mod index;
4mod inline;
5mod tests;
6mod util;
7
8use std::collections::HashMap;
9
10use pretty::{Arena, DocBuilder};
11
12use crate::ast::{Document, Inline, LinkDefinition};
13
14pub trait UrlRewriter {
15 fn rewrite(&self, path: &str, target: Option<&str>) -> String;
19}
20
21pub struct Config<'a> {
22 pub(crate) width: usize,
23 pub(crate) link_rewriter: Option<Box<dyn UrlRewriter + 'a>>,
24 pub(crate) wikilink_rewriter: Option<Box<dyn UrlRewriter + 'a>>,
25}
26
27impl Default for Config<'_> {
28 fn default() -> Self {
29 Self {
30 width: 80,
31 link_rewriter: None,
32 wikilink_rewriter: None,
33 }
34 }
35}
36
37impl<'a> Config<'a> {
38 #[must_use]
39 pub fn with_width(self, width: usize) -> Self {
40 Self { width, ..self }
41 }
42
43 #[must_use]
44 pub fn with_link_rewriter<R: UrlRewriter + 'a>(self, url_rewriter: R) -> Self {
45 Self {
46 link_rewriter: Some(Box::new(url_rewriter)),
47 ..self
48 }
49 }
50
51 #[must_use]
52 pub fn with_wikilink_rewriter<R: UrlRewriter + 'a>(self, url_rewriter: R) -> Self {
53 Self {
54 wikilink_rewriter: Some(Box::new(url_rewriter)),
55 ..self
56 }
57 }
58
59 pub(crate) fn rewrite_link(&self, path: &str, target: Option<&str>) -> String {
60 if let Some(rewriter) = &self.link_rewriter {
61 return rewriter.rewrite(path, target);
62 }
63
64 path.to_owned()
65 }
66
67 pub(crate) fn rewrite_wikilink(&self, path: &str, target: Option<&str>) -> String {
68 if let Some(rewriter) = &self.wikilink_rewriter {
69 return rewriter.rewrite(path, target);
70 }
71
72 path.to_owned()
73 }
74}
75
76pub(crate) struct Context {
77 footnote_index: HashMap<String, usize>,
79 link_definitions: HashMap<Vec<Inline>, LinkDefinition>,
81}
82
83impl Context {
84 pub(crate) fn new(ast: &Document) -> Self {
85 let (footnote_index, link_definitions) = index::get_indicies(ast);
86 Self {
87 footnote_index,
88 link_definitions,
89 }
90 }
91
92 pub(crate) fn get_footnote_index(&self, label: &str) -> Option<&usize> {
93 self.footnote_index.get(label)
94 }
95
96 pub(crate) fn get_link_definition(&self, label: &Vec<Inline>) -> Option<&LinkDefinition> {
97 self.link_definitions.get(label)
98 }
99}
100
101#[must_use]
103pub fn render_html(ast: &Document, config: &Config<'_>) -> String {
104 let mut buf = Vec::new();
105
106 {
107 let width = config.width;
108
109 let context = Context::new(ast);
110 let arena = Arena::new();
111 ast.to_doc(config, &context, &arena)
112 .render(width, &mut buf)
113 .unwrap();
114 }
115
116 String::from_utf8(buf).unwrap()
117}
118
119trait ToDoc<'a> {
120 fn to_doc(
121 &self,
122 config: &'a Config<'a>,
123 context: &'a Context,
124 arena: &'a Arena<'a>,
125 ) -> DocBuilder<'a, Arena<'a>, ()>;
126}
127
128impl<'a> ToDoc<'a> for Document {
129 fn to_doc(
130 &self,
131 config: &'a Config<'a>,
132 context: &'a Context,
133 arena: &'a Arena<'a>,
134 ) -> DocBuilder<'a, Arena<'a>, ()> {
135 self.blocks.to_doc(config, context, arena)
136 }
137}
138