Skip to content

Commit 54a3968

Browse files
authored
Feature/attr tracking (#78)
* fix metadata tracking bug * attribute source tracking * full source attribute tracking; bugfix for tracking infostring source info
1 parent 33fb5fe commit 54a3968

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+7004
-118
lines changed

crates/quarto-markdown-pandoc/src/filters.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,21 @@ impl_inline_filterable_terminal!(
291291
Math,
292292
RawInline,
293293
Shortcode,
294-
NoteReference,
295-
Attr
294+
NoteReference
296295
);
297296

297+
// Attr is special because it has two fields (Attr, AttrSourceInfo)
298+
// We need a custom impl that preserves attr_source
299+
// However, filters don't actually work on Attr values directly,
300+
// so this is just a placeholder that should never be called
301+
impl InlineFilterableStructure for (pandoc::Attr, crate::pandoc::attr::AttrSourceInfo) {
302+
fn filter_structure(self, _: &mut Filter) -> Inline {
303+
// Note: This should not be called in practice because Attr inlines
304+
// are stripped during postprocessing before filters run
305+
Inline::Attr(self.0, self.1)
306+
}
307+
}
308+
298309
macro_rules! impl_inline_filterable_simple {
299310
($($variant:ident),*) => {
300311
$(
@@ -350,6 +361,7 @@ impl InlineFilterableStructure for pandoc::Cite {
350361
mode: cit.mode,
351362
note_num: cit.note_num,
352363
hash: cit.hash,
364+
id_source: cit.id_source,
353365
})
354366
.collect(),
355367
content: topdown_traverse_inlines(self.content, filter),
@@ -641,8 +653,22 @@ pub fn topdown_traverse_inline(inline: Inline, filter: &mut Filter) -> Inlines {
641653
Inline::NoteReference(note_ref) => {
642654
handle_inline_filter!(NoteReference, note_ref, note_reference, filter)
643655
}
644-
Inline::Attr(attr) => {
645-
handle_inline_filter!(Attr, attr, attr, filter)
656+
Inline::Attr(attr, attr_source) => {
657+
// Special handling for Attr since it has two fields and filters don't actually work on Attr tuples
658+
// Attr inlines should be stripped during postprocessing before filters run
659+
// So this branch should rarely be hit
660+
if let Some(f) = &mut filter.inline {
661+
let inline = Inline::Attr(attr, attr_source);
662+
match f(inline.clone()) {
663+
FilterReturn::Unchanged(_) => vec![inline],
664+
FilterReturn::FilterResult(result, _should_recurse) => result,
665+
}
666+
} else {
667+
vec![traverse_inline_structure(
668+
Inline::Attr(attr, attr_source),
669+
filter,
670+
)]
671+
}
646672
}
647673
Inline::Insert(ins) => {
648674
handle_inline_filter!(Insert, ins, insert, filter)
@@ -827,6 +853,7 @@ fn traverse_inline_nonterminal(inline: Inline, filter: &mut Filter) -> Inline {
827853
mode: cit.mode,
828854
note_num: cit.note_num,
829855
hash: cit.hash,
856+
id_source: cit.id_source,
830857
})
831858
.collect(),
832859
content: topdown_traverse_inlines(c.content, filter),
@@ -837,12 +864,16 @@ fn traverse_inline_nonterminal(inline: Inline, filter: &mut Filter) -> Inline {
837864
target: l.target,
838865
content: topdown_traverse_inlines(l.content, filter),
839866
source_info: l.source_info,
867+
attr_source: l.attr_source,
868+
target_source: l.target_source,
840869
}),
841870
Inline::Image(i) => Inline::Image(crate::pandoc::Image {
842871
attr: i.attr,
843872
target: i.target,
844873
content: topdown_traverse_inlines(i.content, filter),
845874
source_info: i.source_info,
875+
attr_source: i.attr_source,
876+
target_source: i.target_source,
846877
}),
847878
Inline::Note(note) => Inline::Note(crate::pandoc::Note {
848879
content: topdown_traverse_blocks(note.content, filter),
@@ -852,6 +883,7 @@ fn traverse_inline_nonterminal(inline: Inline, filter: &mut Filter) -> Inline {
852883
attr: span.attr,
853884
content: topdown_traverse_inlines(span.content, filter),
854885
source_info: span.source_info,
886+
attr_source: span.attr_source,
855887
}),
856888
_ => panic!("Unsupported inline type: {:?}", inline),
857889
}
@@ -870,7 +902,7 @@ pub fn traverse_inline_structure(inline: Inline, filter: &mut Filter) -> Inline
870902
// extensions
871903
Inline::Shortcode(_) => inline,
872904
Inline::NoteReference(_) => inline,
873-
Inline::Attr(_) => inline,
905+
Inline::Attr(_, _) => inline,
874906
_ => traverse_inline_nonterminal(inline, filter),
875907
}
876908
}
@@ -893,6 +925,7 @@ fn traverse_caption(
893925
long: caption
894926
.long
895927
.map(|long| topdown_traverse_blocks(long, filter)),
928+
source_info: caption.source_info,
896929
}
897930
}
898931

crates/quarto-markdown-pandoc/src/pandoc/attr.rs

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

6+
use quarto_source_map::SourceInfo;
7+
use serde::{Deserialize, Serialize};
68
use std::collections::HashMap;
79

810
pub fn empty_attr() -> Attr {
@@ -14,3 +16,50 @@ pub type Attr = (String, Vec<String>, HashMap<String, String>);
1416
pub fn is_empty_attr(attr: &Attr) -> bool {
1517
attr.0.is_empty() && attr.1.is_empty() && attr.2.is_empty()
1618
}
19+
20+
/// Source location information for Attr components.
21+
///
22+
/// Attr is a tuple: (id: String, classes: Vec<String>, attributes: HashMap<String, String>)
23+
/// This struct tracks source locations for each component:
24+
/// - id: Source location of the id string (None if id is empty "")
25+
/// - classes: Source locations for each class string
26+
/// - attributes: Source locations for each key-value pair (both key and value)
27+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28+
pub struct AttrSourceInfo {
29+
pub id: Option<SourceInfo>,
30+
pub classes: Vec<Option<SourceInfo>>,
31+
pub attributes: Vec<(Option<SourceInfo>, Option<SourceInfo>)>,
32+
}
33+
34+
impl AttrSourceInfo {
35+
/// Creates an empty AttrSourceInfo with no source tracking.
36+
pub fn empty() -> Self {
37+
AttrSourceInfo {
38+
id: None,
39+
classes: Vec::new(),
40+
attributes: Vec::new(),
41+
}
42+
}
43+
}
44+
45+
/// Source location information for Target components.
46+
///
47+
/// Target is a tuple: (url: String, title: String)
48+
/// This struct tracks source locations for each component:
49+
/// - url: Source location of the URL string (None if url is empty "")
50+
/// - title: Source location of the title string (None if title is empty "")
51+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52+
pub struct TargetSourceInfo {
53+
pub url: Option<SourceInfo>,
54+
pub title: Option<SourceInfo>,
55+
}
56+
57+
impl TargetSourceInfo {
58+
/// Creates an empty TargetSourceInfo with no source tracking.
59+
pub fn empty() -> Self {
60+
TargetSourceInfo {
61+
url: None,
62+
title: None,
63+
}
64+
}
65+
}

crates/quarto-markdown-pandoc/src/pandoc/block.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
use crate::pandoc::MetaValueWithSourceInfo;
7-
use crate::pandoc::attr::Attr;
7+
use crate::pandoc::attr::{Attr, AttrSourceInfo};
88
use crate::pandoc::caption::Caption;
99
use crate::pandoc::inline::Inlines;
1010
use crate::pandoc::list::ListAttributes;
@@ -59,6 +59,7 @@ pub struct CodeBlock {
5959
pub attr: Attr,
6060
pub text: String,
6161
pub source_info: quarto_source_map::SourceInfo,
62+
pub attr_source: AttrSourceInfo,
6263
}
6364

6465
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -99,6 +100,7 @@ pub struct Header {
99100
pub attr: Attr,
100101
pub content: Inlines,
101102
pub source_info: quarto_source_map::SourceInfo,
103+
pub attr_source: AttrSourceInfo,
102104
}
103105

104106
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -112,13 +114,15 @@ pub struct Figure {
112114
pub caption: Caption,
113115
pub content: Blocks,
114116
pub source_info: quarto_source_map::SourceInfo,
117+
pub attr_source: AttrSourceInfo,
115118
}
116119

117120
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118121
pub struct Div {
119122
pub attr: Attr,
120123
pub content: Blocks,
121124
pub source_info: quarto_source_map::SourceInfo,
125+
pub attr_source: AttrSourceInfo,
122126
}
123127

124128
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

crates/quarto-markdown-pandoc/src/pandoc/caption.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ use serde::{Deserialize, Serialize};
1111
pub struct Caption {
1212
pub short: Option<Inlines>,
1313
pub long: Option<Blocks>,
14+
pub source_info: quarto_source_map::SourceInfo,
1415
}

0 commit comments

Comments
 (0)