Skip to main content

rushdown_meta/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4extern crate alloc;
5
6use alloc::boxed::Box;
7use alloc::format;
8use alloc::rc::Rc;
9use alloc::string::String;
10use alloc::string::ToString;
11use alloc::vec::Vec;
12use core::cell::RefCell;
13use core::result::Result as CoreResult;
14use rushdown::ast::Table;
15use rushdown::ast::TableBody;
16use rushdown::ast::TableCell;
17use rushdown::ast::TableHeader;
18use rushdown::ast::TableRow;
19use rushdown::parser::ParserOptions;
20
21use rushdown::{
22    as_kind_data_mut, as_type_data, as_type_data_mut,
23    ast::{Arena, CodeBlock, CodeBlockType, Meta, NodeRef, Text, TextQualifier},
24    context::{ContextKey, ContextKeyRegistry, NodeRefValue},
25    parser::{
26        self, AnyAstTransformer, AnyBlockParser, AstTransformer, BlockParser, NoParserOptions,
27        Parser, ParserExtension, ParserExtensionFn, PRIORITY_SETTEXT_HEADING,
28    },
29    text::{self, Reader},
30    util::StringMap,
31};
32
33// Parser {{{
34
35const META_NODE: &str = "rushdown-meta-n";
36
37/// Options for the meta parser.
38#[derive(Debug, Clone, Default)]
39pub struct MetaParserOptions {
40    /// Convert the meta data to a table node.
41    pub table: bool,
42}
43
44impl ParserOptions for MetaParserOptions {}
45
46#[derive(Debug)]
47struct MetaParser {
48    meta_node: ContextKey<NodeRefValue>,
49}
50
51impl MetaParser {
52    /// Returns a new [`MetaParser`].
53    pub fn new(reg: Rc<RefCell<ContextKeyRegistry>>) -> Self {
54        let meta_node = reg.borrow_mut().get_or_create::<NodeRefValue>(META_NODE);
55        Self { meta_node }
56    }
57}
58
59impl BlockParser for MetaParser {
60    fn trigger(&self) -> &[u8] {
61        b"-"
62    }
63
64    fn open(
65        &self,
66        arena: &mut Arena,
67        _parent_ref: NodeRef,
68        reader: &mut text::BasicReader,
69        ctx: &mut parser::Context,
70    ) -> Option<(NodeRef, parser::State)> {
71        let (line, _) = reader.position();
72        if line != 0 {
73            return None;
74        }
75        let (line, _) = reader.peek_line_bytes()?;
76        if !line.starts_with(b"---") {
77            return None;
78        }
79        reader.advance_to_eol();
80        let node_ref = arena.new_node(CodeBlock::new(CodeBlockType::Fenced, None));
81        ctx.insert(self.meta_node, node_ref);
82        Some((node_ref, parser::State::NO_CHILDREN))
83    }
84
85    fn cont(
86        &self,
87        arena: &mut Arena,
88        node_ref: NodeRef,
89        reader: &mut text::BasicReader,
90        _ctx: &mut parser::Context,
91    ) -> Option<parser::State> {
92        let (line, seg) = reader.peek_line_bytes()?;
93        if line.starts_with(b"---") {
94            reader.advance_to_eol();
95            return None;
96        }
97        as_type_data_mut!(arena, node_ref, Block).append_source_line(seg);
98        Some(parser::State::NO_CHILDREN)
99    }
100
101    fn close(
102        &self,
103        _arena: &mut Arena,
104        _node_ref: NodeRef,
105        _reader: &mut text::BasicReader,
106        _ctx: &mut parser::Context,
107    ) {
108    }
109
110    fn can_interrupt_paragraph(&self) -> bool {
111        true
112    }
113}
114
115impl From<MetaParser> for AnyBlockParser {
116    fn from(p: MetaParser) -> Self {
117        AnyBlockParser::Extension(Box::new(p))
118    }
119}
120
121#[derive(Debug)]
122struct MetaAstTransformer {
123    meta_node: ContextKey<NodeRefValue>,
124    options: MetaParserOptions,
125}
126
127impl MetaAstTransformer {
128    /// Returns a new [`MetaAstTransformer`].
129    pub fn new(reg: Rc<RefCell<ContextKeyRegistry>>, options: MetaParserOptions) -> Self {
130        let meta_node = reg.borrow_mut().get_or_create::<NodeRefValue>(META_NODE);
131        Self { meta_node, options }
132    }
133}
134
135impl AstTransformer for MetaAstTransformer {
136    fn transform(
137        &self,
138        arena: &mut Arena,
139        doc_ref: NodeRef,
140        reader: &mut text::BasicReader,
141        ctx: &mut parser::Context,
142    ) {
143        let Some(meta_ref) = ctx.get(self.meta_node) else {
144            return;
145        };
146        let mut yaml_data = String::new();
147
148        for line in as_type_data!(arena, *meta_ref, Block).source() {
149            yaml_data.push_str(&line.str(reader.source()));
150        }
151        meta_ref.delete(arena);
152        match parse_yaml(&yaml_data) {
153            Ok(meta) => {
154                if let Meta::Mapping(map) = meta {
155                    let m = map.clone();
156                    for (key, value) in map {
157                        as_kind_data_mut!(arena, doc_ref, Document)
158                            .metadata_mut()
159                            .insert(key, value);
160                    }
161                    if self.options.table {
162                        let table_ref = arena.new_node(Table::new());
163                        let header_ref = arena.new_node(TableHeader::new());
164                        let header_row_ref = arena.new_node(TableRow::new());
165                        for (key, _) in m.iter() {
166                            let cell_ref = arena.new_node(TableCell::default());
167                            let text_ref = arena
168                                .new_node(Text::with_qualifiers(key.clone(), TextQualifier::CODE));
169                            cell_ref.append_child(arena, text_ref);
170                            header_row_ref.append_child(arena, cell_ref);
171                        }
172                        header_ref.append_child(arena, header_row_ref);
173                        table_ref.append_child(arena, header_ref);
174
175                        let body_ref = arena.new_node(TableBody::new());
176                        table_ref.append_child(arena, body_ref);
177                        let body_row_ref = arena.new_node(TableRow::new());
178                        for (_, value) in m {
179                            let cell_ref = arena.new_node(TableCell::default());
180                            let text_ref = arena.new_node(Text::new(format_meta(&value)));
181                            cell_ref.append_child(arena, text_ref);
182                            body_row_ref.append_child(arena, cell_ref);
183                        }
184                        body_ref.append_child(arena, body_row_ref);
185                        if let Some(first) = arena[doc_ref].first_child() {
186                            doc_ref.insert_before(arena, first, table_ref);
187                        } else {
188                            doc_ref.append_child(arena, table_ref);
189                        }
190                    }
191                } else {
192                    let error_msg = "<!-- YAML metadata must be a mapping -->\n".to_string();
193                    let error_ref =
194                        arena.new_node(Text::with_qualifiers(error_msg, TextQualifier::CODE));
195                    if let Some(first) = arena[doc_ref].first_child() {
196                        doc_ref.insert_before(arena, first, error_ref);
197                    } else {
198                        doc_ref.append_child(arena, error_ref);
199                    }
200                }
201            }
202            Err(e) => {
203                let error_msg = format!("<!-- Error parsing YAML metadata: {} -->\n", e);
204                let error_ref =
205                    arena.new_node(Text::with_qualifiers(error_msg, TextQualifier::CODE));
206                if let Some(first) = arena[doc_ref].first_child() {
207                    doc_ref.insert_before(arena, first, error_ref);
208                } else {
209                    doc_ref.append_child(arena, error_ref);
210                }
211            }
212        }
213    }
214}
215
216impl From<MetaAstTransformer> for AnyAstTransformer {
217    fn from(t: MetaAstTransformer) -> Self {
218        AnyAstTransformer::Extension(Box::new(t))
219    }
220}
221
222fn format_meta(meta: &Meta) -> String {
223    match meta {
224        Meta::Null => "null".to_string(),
225        Meta::Bool(b) => b.to_string(),
226        Meta::Int(i) => i.to_string(),
227        Meta::Float(f) => f.to_string(),
228        Meta::String(s) => s.clone(),
229        Meta::Sequence(seq) => {
230            let items: Vec<String> = seq.iter().map(format_meta).collect();
231            format!("[{}]", items.join(", "))
232        }
233        Meta::Mapping(map) => {
234            let items: Vec<String> = map
235                .iter()
236                .map(|(k, v)| format!("{}: {}", k, format_meta(v)))
237                .collect();
238            format!("{{{}}}", items.join(", "))
239        }
240    }
241}
242
243// }}}
244
245// Extension {{{
246
247/// Returns a parser extension that parses metas.
248pub fn meta_parser_extension(options: MetaParserOptions) -> impl ParserExtension {
249    ParserExtensionFn::new(|p: &mut Parser| {
250        p.add_block_parser(
251            MetaParser::new,
252            NoParserOptions,
253            PRIORITY_SETTEXT_HEADING - 100,
254        );
255        p.add_ast_transformer(MetaAstTransformer::new, options, 0);
256    })
257}
258
259/*
260/// Returns a renderer extension that renders metas in HTML.
261pub fn meta_html_renderer_extension<'cb, W>(
262    options: impl Into<FootnoteHtmlRendererOptions>,
263) -> impl RendererExtension<'cb, W>
264where
265    W: TextWrite + 'cb,
266{
267    RendererExtensionFn::new(move |r: &mut Renderer<'cb, W>| {
268        let options = options.into();
269        r.add_post_render_hook(FootnotePostRenderHook::new, options.clone(), 500);
270        r.add_node_renderer(FootnoteDefinitionHtmlRenderer::new, options.clone());
271        r.add_node_renderer(FootnoteReferenceHtmlRenderer::new, options);
272    })
273}
274*/
275
276// }}}
277
278// YAML {{{
279
280fn to_meta<R: yaml_peg::repr::Repr>(node: &yaml_peg::Node<R>) -> Meta {
281    match node.yaml() {
282        yaml_peg::Yaml::Null => Meta::Null,
283        yaml_peg::Yaml::Bool(b) => Meta::Bool(*b),
284        yaml_peg::Yaml::Int(s) => Meta::Int(s.parse().unwrap_or(0)),
285        yaml_peg::Yaml::Float(s) => Meta::Float(s.parse().unwrap_or(0.0)),
286        yaml_peg::Yaml::Str(s) => Meta::String(s.clone()),
287        yaml_peg::Yaml::Seq(seq) => Meta::Sequence(seq.iter().map(|n| to_meta(n)).collect()),
288        yaml_peg::Yaml::Map(map) => {
289            let mut result = StringMap::with_capacity(map.len());
290            for (k, v) in map.iter() {
291                if let yaml_peg::Yaml::Str(key) = k.yaml() {
292                    result.insert(key.clone(), to_meta(v));
293                }
294            }
295            Meta::Mapping(result)
296        }
297        yaml_peg::Yaml::Alias(_) => Meta::Null, // Aliases are not supported in this
298                                                // implementation
299    }
300}
301
302fn parse_yaml(input: &str) -> CoreResult<Meta, String> {
303    let doc = yaml_peg::parser::parse::<yaml_peg::repr::RcRepr>(input)
304        .map_err(|e| format!("YAML parsing error: {:?}", e))?;
305    if !doc.is_empty() {
306        Ok(to_meta(&doc[0]))
307    } else {
308        Err("YAML document is empty".to_string())
309    }
310}
311
312// }}} YAML