Skip to content

Commit 304bca6

Browse files
committed
parse meta into markdown
1 parent 8920a3c commit 304bca6

File tree

2 files changed

+131
-14
lines changed

2 files changed

+131
-14
lines changed

crates/quarto_markdown_pandoc/src/filters.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
* Copyright (c) 2025 Posit, PBC
44
*/
55

6+
use crate::pandoc::MetaValue;
67
use crate::pandoc::inline::AsInline;
78
use crate::pandoc::meta::Meta;
9+
use crate::pandoc::meta::parse_metadata_strings;
810
use crate::pandoc::meta::rawblock_to_meta;
911
use crate::pandoc::{self, Block, Blocks, Inline, Inlines};
1012

@@ -17,8 +19,10 @@ pub enum FilterReturn<T, U> {
1719

1820
type InlineFilterFn<'a, T> = Box<dyn FnMut(T) -> FilterReturn<T, Inlines> + 'a>;
1921
type BlockFilterFn<'a, T> = Box<dyn FnMut(T) -> FilterReturn<T, Blocks> + 'a>;
22+
type MetaFilterFn<'a> = Box<dyn FnMut(Meta) -> FilterReturn<Meta, Meta> + 'a>;
2023
type InlineFilterField<'a, T> = Option<InlineFilterFn<'a, T>>;
2124
type BlockFilterField<'a, T> = Option<BlockFilterFn<'a, T>>;
25+
type MetaFilterField<'a> = Option<MetaFilterFn<'a>>;
2226

2327
pub struct Filter<'a> {
2428
pub inlines: InlineFilterField<'a, Inlines>,
@@ -65,6 +69,8 @@ pub struct Filter<'a> {
6569
pub header: BlockFilterField<'a, pandoc::Header>,
6670
pub table: BlockFilterField<'a, pandoc::Table>,
6771
pub horizontal_rule: BlockFilterField<'a, pandoc::HorizontalRule>,
72+
73+
pub meta: MetaFilterField<'a>,
6874
}
6975

7076
impl Default for Filter<'static> {
@@ -113,6 +119,8 @@ impl Default for Filter<'static> {
113119
table: None,
114120
horizontal_rule: None,
115121
attr: None,
122+
123+
meta: None,
116124
}
117125
}
118126
}
@@ -910,21 +918,83 @@ pub fn topdown_traverse_blocks(vec: Blocks, filter: &mut Filter) -> Blocks {
910918
}
911919
}
912920

921+
pub fn topdown_traverse_meta_value(value: MetaValue, filter: &mut Filter) -> MetaValue {
922+
match value {
923+
MetaValue::MetaMap(m) => MetaValue::MetaMap(
924+
m.into_iter()
925+
.map(|(k, v)| (k, topdown_traverse_meta_value(v, filter)))
926+
.collect(),
927+
),
928+
MetaValue::MetaList(l) => MetaValue::MetaList(
929+
l.into_iter()
930+
.map(|mv| topdown_traverse_meta_value(mv, filter))
931+
.collect(),
932+
),
933+
MetaValue::MetaBlocks(b) => MetaValue::MetaBlocks(topdown_traverse_blocks(b, filter)),
934+
MetaValue::MetaInlines(i) => MetaValue::MetaInlines(topdown_traverse_inlines(i, filter)),
935+
value => value,
936+
}
937+
}
938+
939+
pub fn topdown_traverse_meta(meta: Meta, filter: &mut Filter) -> Meta {
940+
if let Some(f) = &mut filter.meta {
941+
return match f(meta) {
942+
FilterReturn::FilterResult(new_meta, recurse) => {
943+
if !recurse {
944+
return new_meta;
945+
}
946+
topdown_traverse_meta(new_meta, filter)
947+
}
948+
FilterReturn::Unchanged(m) => {
949+
let meta_value = MetaValue::MetaMap(m);
950+
match topdown_traverse_meta_value(meta_value, filter) {
951+
MetaValue::MetaMap(m) => m,
952+
_ => panic!("Expected MetaMap after filtering meta"),
953+
}
954+
}
955+
};
956+
} else {
957+
return meta
958+
.into_iter()
959+
.map(|(k, v)| (k, topdown_traverse_meta_value(v, filter)))
960+
.collect();
961+
}
962+
}
963+
913964
pub fn topdown_traverse(doc: pandoc::Pandoc, filter: &mut Filter) -> pandoc::Pandoc {
914965
let (real_blocks, meta_blocks): (Vec<Block>, Vec<Block>) = doc
915966
.blocks
916967
.into_iter()
917968
.partition(|b| !matches!(b, Block::RawBlock(rb) if rb.format == "quarto_minus_metadata"));
918969

970+
let mut meta = Meta::default();
971+
let mut meta_from_parses = Meta::default();
919972
meta_blocks.into_iter().for_each(|b| match b {
920973
Block::RawBlock(rb) if rb.format == "quarto_minus_metadata" => {
921-
rawblock_to_meta(rb);
974+
let Some(result) = rawblock_to_meta(rb) else {
975+
return;
976+
};
977+
let meta_map = MetaValue::MetaMap(result);
978+
979+
match parse_metadata_strings(meta_map, &mut meta_from_parses) {
980+
MetaValue::MetaMap(m) => {
981+
for (k, v) in m {
982+
meta.insert(k, v);
983+
}
984+
}
985+
_ => panic!("Expected MetaMap from parse_metadata_strings"),
986+
}
922987
}
923988
_ => {}
924989
});
990+
for (k, v) in meta_from_parses {
991+
if !meta.contains_key(&k) {
992+
meta.insert(k, v);
993+
}
994+
}
925995

926996
pandoc::Pandoc {
927-
meta: Meta::default(),
997+
meta: topdown_traverse_meta(meta, filter),
928998
blocks: topdown_traverse_blocks(real_blocks, filter),
929999
// TODO: handle meta
9301000
}

crates/quarto_markdown_pandoc/src/pandoc/meta.rs

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
* Copyright (c) 2025 Posit, PBC
44
*/
55

6-
use crate::pandoc::RawBlock;
76
use crate::pandoc::block::Blocks;
87
use crate::pandoc::inline::Inlines;
8+
use crate::readers;
9+
use crate::{pandoc::RawBlock, utils::output::VerboseOutput};
910
use std::collections::HashMap;
11+
use std::{io, mem};
1012
use yaml_rust2::parser::{Event, MarkedEventReceiver, Parser};
1113

1214
// Pandoc's MetaValue notably does not support numbers or nulls, so we don't either
@@ -113,18 +115,16 @@ impl MarkedEventReceiver for YamlEventHandler {
113115
self.push_value(MetaValue::MetaList(list));
114116
}
115117
}
116-
Event::Scalar(s, ..) => {
117-
match self.stack.last_mut() {
118-
Some(ContextFrame::Map(_, key_slot @ None)) => {
119-
*key_slot = Some(s.to_string());
120-
}
121-
Some(ContextFrame::Map(_, Some(_))) | Some(ContextFrame::List(_)) => {
122-
let value = self.parse_scalar(&s);
123-
self.push_value(value);
124-
}
125-
_ => {}
118+
Event::Scalar(s, ..) => match self.stack.last_mut() {
119+
Some(ContextFrame::Map(_, key_slot @ None)) => {
120+
*key_slot = Some(s.to_string());
126121
}
127-
}
122+
Some(ContextFrame::Map(_, Some(_))) | Some(ContextFrame::List(_)) => {
123+
let value = self.parse_scalar(&s);
124+
self.push_value(value);
125+
}
126+
_ => {}
127+
},
128128
Event::DocumentEnd | Event::StreamEnd => {}
129129
_ => {}
130130
}
@@ -142,3 +142,50 @@ pub fn rawblock_to_meta(block: RawBlock) -> Option<Meta> {
142142

143143
handler.result
144144
}
145+
146+
pub fn parse_metadata_strings(meta: MetaValue, outer_metadata: &mut Meta) -> MetaValue {
147+
match meta {
148+
MetaValue::MetaString(s) => {
149+
let mut output_stream = VerboseOutput::Sink(io::sink());
150+
let result = readers::qmd::read(s.as_bytes(), &mut output_stream);
151+
match result {
152+
Ok(mut pandoc) => {
153+
for (k, v) in pandoc.meta.into_iter() {
154+
outer_metadata.insert(k, v);
155+
}
156+
// we need to examine pandoc.blocks to see if it's a single paragraph or multiple blocks
157+
// if it's a single paragraph, we can return MetaInlines
158+
if pandoc.blocks.len() == 1 {
159+
let first = &mut pandoc.blocks[0];
160+
match first {
161+
crate::pandoc::Block::Paragraph(p) => {
162+
return MetaValue::MetaInlines(mem::take(&mut p.content));
163+
}
164+
_ => {}
165+
}
166+
}
167+
MetaValue::MetaBlocks(pandoc.blocks)
168+
}
169+
_ => panic!(
170+
"(unimplemented syntax error, this is a bug!) Failed to parse metadata string as markdown: {}",
171+
s
172+
),
173+
}
174+
}
175+
MetaValue::MetaList(list) => {
176+
let parsed_list = list
177+
.into_iter()
178+
.map(|value| parse_metadata_strings(value, outer_metadata))
179+
.collect();
180+
MetaValue::MetaList(parsed_list)
181+
}
182+
MetaValue::MetaMap(map) => {
183+
let parsed_map = map
184+
.into_iter()
185+
.map(|(k, v)| (k, parse_metadata_strings(v, outer_metadata)))
186+
.collect();
187+
MetaValue::MetaMap(parsed_map)
188+
}
189+
other => other,
190+
}
191+
}

0 commit comments

Comments
 (0)