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::HtmlBlock;
15use rushdown::ast::Table;
16use rushdown::ast::TableBody;
17use rushdown::ast::TableCell;
18use rushdown::ast::TableHeader;
19use rushdown::ast::TableRow;
20use rushdown::parser::ParserOptions;
21
22use rushdown::{
23 as_kind_data_mut, as_type_data, as_type_data_mut,
24 ast::{Arena, CodeBlock, CodeBlockKind, Meta, NodeRef, Text},
25 context::{ContextKey, ContextKeyRegistry, NodeRefValue},
26 parser::{
27 self, AnyAstTransformer, AnyBlockParser, AstTransformer, BlockParser, NoParserOptions,
28 Parser, ParserExtension, ParserExtensionFn, PRIORITY_SETTEXT_HEADING,
29 },
30 text::{self, Reader},
31 util::StringMap,
32};
33
34const META_NODE: &str = "rushdown-meta-n";
37
38#[derive(Debug, Clone, Default)]
40pub struct MetaParserOptions {
41 pub table: bool,
43}
44
45impl ParserOptions for MetaParserOptions {}
46
47#[derive(Debug)]
48struct MetaParser {
49 meta_node: ContextKey<NodeRefValue>,
50}
51
52impl MetaParser {
53 pub fn new(reg: Rc<RefCell<ContextKeyRegistry>>) -> Self {
55 let meta_node = reg.borrow_mut().get_or_create::<NodeRefValue>(META_NODE);
56 Self { meta_node }
57 }
58}
59
60impl BlockParser for MetaParser {
61 fn trigger(&self) -> &[u8] {
62 b"-"
63 }
64
65 fn open(
66 &self,
67 arena: &mut Arena,
68 _parent_ref: NodeRef,
69 reader: &mut text::BasicReader,
70 ctx: &mut parser::Context,
71 ) -> Option<(NodeRef, parser::State)> {
72 let (line, _) = reader.position();
73 if line != 0 {
74 return None;
75 }
76 let (line, _) = reader.peek_line_bytes()?;
77 if !line.starts_with(b"---") {
78 return None;
79 }
80 reader.advance_to_eol();
81 let node_ref = arena.new_node(CodeBlock::new(CodeBlockKind::Fenced, None));
82 ctx.insert(self.meta_node, node_ref);
83 Some((node_ref, parser::State::NO_CHILDREN))
84 }
85
86 fn cont(
87 &self,
88 arena: &mut Arena,
89 node_ref: NodeRef,
90 reader: &mut text::BasicReader,
91 _ctx: &mut parser::Context,
92 ) -> Option<parser::State> {
93 let (line, seg) = reader.peek_line_bytes()?;
94 if line.starts_with(b"---") {
95 reader.advance_to_eol();
96 return None;
97 }
98 as_type_data_mut!(arena, node_ref, Block).append_source_line(seg);
99 Some(parser::State::NO_CHILDREN)
100 }
101
102 fn close(
103 &self,
104 _arena: &mut Arena,
105 _node_ref: NodeRef,
106 _reader: &mut text::BasicReader,
107 _ctx: &mut parser::Context,
108 ) {
109 }
110
111 fn can_interrupt_paragraph(&self) -> bool {
112 true
113 }
114}
115
116impl From<MetaParser> for AnyBlockParser {
117 fn from(p: MetaParser) -> Self {
118 AnyBlockParser::Extension(Box::new(p))
119 }
120}
121
122#[derive(Debug)]
123struct MetaAstTransformer {
124 meta_node: ContextKey<NodeRefValue>,
125 options: MetaParserOptions,
126}
127
128impl MetaAstTransformer {
129 pub fn new(reg: Rc<RefCell<ContextKeyRegistry>>, options: MetaParserOptions) -> Self {
131 let meta_node = reg.borrow_mut().get_or_create::<NodeRefValue>(META_NODE);
132 Self { meta_node, options }
133 }
134}
135
136impl AstTransformer for MetaAstTransformer {
137 fn transform(
138 &self,
139 arena: &mut Arena,
140 doc_ref: NodeRef,
141 reader: &mut text::BasicReader,
142 ctx: &mut parser::Context,
143 ) {
144 let Some(meta_ref) = ctx.get(self.meta_node) else {
145 return;
146 };
147 let mut yaml_data = String::new();
148
149 for line in as_type_data!(arena, *meta_ref, Block).source() {
150 yaml_data.push_str(&line.str(reader.source()));
151 }
152 meta_ref.delete(arena);
153 match parse_yaml(&yaml_data) {
154 Ok(meta) => {
155 if let Meta::Mapping(map) = meta {
156 let m = map.clone();
157 for (key, value) in map {
158 as_kind_data_mut!(arena, doc_ref, Document)
159 .metadata_mut()
160 .insert(key, value);
161 }
162 if self.options.table {
163 let table_ref = arena.new_node(Table::new());
164 let header_ref = arena.new_node(TableHeader::new());
165 let header_row_ref = arena.new_node(TableRow::new());
166 for (key, _) in m.iter() {
167 let cell_ref = arena.new_node(TableCell::default());
168 let text_ref = arena.new_node(Text::new(key.clone()));
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 mut error_data = HtmlBlock::new(rushdown::ast::HtmlBlockKind::Kind2);
193 error_data.set_value("<!-- YAML metadata must be a mapping -->\n".to_string());
194 let error_ref = arena.new_node(error_data);
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 mut error_data = HtmlBlock::new(rushdown::ast::HtmlBlockKind::Kind2);
204 error_data.set_value(
205 format!("<!-- Error parsing YAML metadata: {} -->\n", e).to_string(),
206 );
207 let error_ref = arena.new_node(error_data);
208 if let Some(first) = arena[doc_ref].first_child() {
209 doc_ref.insert_before(arena, first, error_ref);
210 } else {
211 doc_ref.append_child(arena, error_ref);
212 }
213 }
214 }
215 }
216}
217
218impl From<MetaAstTransformer> for AnyAstTransformer {
219 fn from(t: MetaAstTransformer) -> Self {
220 AnyAstTransformer::Extension(Box::new(t))
221 }
222}
223
224fn format_meta(meta: &Meta) -> String {
225 match meta {
226 Meta::Null => "null".to_string(),
227 Meta::Bool(b) => b.to_string(),
228 Meta::Int(i) => i.to_string(),
229 Meta::Float(f) => f.to_string(),
230 Meta::String(s) => s.clone(),
231 Meta::Sequence(seq) => {
232 let items: Vec<String> = seq.iter().map(format_meta).collect();
233 format!("[{}]", items.join(", "))
234 }
235 Meta::Mapping(map) => {
236 let items: Vec<String> = map
237 .iter()
238 .map(|(k, v)| format!("{}: {}", k, format_meta(v)))
239 .collect();
240 format!("{{{}}}", items.join(", "))
241 }
242 }
243}
244
245pub fn meta_parser_extension(options: impl Into<MetaParserOptions>) -> impl ParserExtension {
251 ParserExtensionFn::new(|p: &mut Parser| {
252 p.add_block_parser(
253 MetaParser::new,
254 NoParserOptions,
255 PRIORITY_SETTEXT_HEADING - 100,
256 );
257 p.add_ast_transformer(MetaAstTransformer::new, options.into(), 0);
258 })
259}
260
261fn to_meta<R: yaml_peg::repr::Repr>(node: &yaml_peg::Node<R>) -> Meta {
266 match node.yaml() {
267 yaml_peg::Yaml::Null => Meta::Null,
268 yaml_peg::Yaml::Bool(b) => Meta::Bool(*b),
269 yaml_peg::Yaml::Int(s) => Meta::Int(s.parse().unwrap_or(0)),
270 yaml_peg::Yaml::Float(s) => Meta::Float(s.parse().unwrap_or(0.0)),
271 yaml_peg::Yaml::Str(s) => Meta::String(s.clone()),
272 yaml_peg::Yaml::Seq(seq) => Meta::Sequence(seq.iter().map(|n| to_meta(n)).collect()),
273 yaml_peg::Yaml::Map(map) => {
274 let mut result = StringMap::with_capacity(map.len());
275 for (k, v) in map.iter() {
276 if let yaml_peg::Yaml::Str(key) = k.yaml() {
277 result.insert(key.clone(), to_meta(v));
278 }
279 }
280 Meta::Mapping(result)
281 }
282 yaml_peg::Yaml::Alias(_) => Meta::Null, }
285}
286
287fn parse_yaml(input: &str) -> CoreResult<Meta, String> {
288 let doc = yaml_peg::parser::parse::<yaml_peg::repr::RcRepr>(input)
289 .map_err(|e| format!("YAML parsing error: {:?}", e))?;
290 if !doc.is_empty() {
291 Ok(to_meta(&doc[0]))
292 } else {
293 Err("YAML document is empty".to_string())
294 }
295}
296
297