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
33const META_NODE: &str = "rushdown-meta-n";
36
37#[derive(Debug, Clone, Default)]
39pub struct MetaParserOptions {
40 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 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_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 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).lines() {
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
243pub 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
259fn 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, }
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