marco_core/intelligence/markdown/
blocks.rs1use super::ast::{is_block_kind, Document, Node, NodeKind};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum BlockCategory {
10 Heading,
12 Paragraph,
14 CodeBlock,
16 ThematicBreak,
18 List,
20 ListItem,
22 DefinitionList,
24 DefinitionTerm,
26 DefinitionDescription,
28 TaskCheckbox,
30 Blockquote,
32 Admonition,
34 TabGroup,
36 TabItem,
38 SliderDeck,
40 Slide,
42 Table,
44 TableRow,
46 TableCell,
48 HtmlBlock,
50 FootnoteDefinition,
52 MermaidDiagram,
54}
55
56pub fn classify_block_kind(kind: &NodeKind) -> Option<BlockCategory> {
58 match kind {
59 NodeKind::Heading { .. } => Some(BlockCategory::Heading),
60 NodeKind::Paragraph => Some(BlockCategory::Paragraph),
61 NodeKind::CodeBlock { .. } => Some(BlockCategory::CodeBlock),
62 NodeKind::ThematicBreak => Some(BlockCategory::ThematicBreak),
63 NodeKind::List { .. } => Some(BlockCategory::List),
64 NodeKind::ListItem => Some(BlockCategory::ListItem),
65 NodeKind::DefinitionList => Some(BlockCategory::DefinitionList),
66 NodeKind::DefinitionTerm => Some(BlockCategory::DefinitionTerm),
67 NodeKind::DefinitionDescription => Some(BlockCategory::DefinitionDescription),
68 NodeKind::TaskCheckbox { .. } => Some(BlockCategory::TaskCheckbox),
69 NodeKind::Blockquote => Some(BlockCategory::Blockquote),
70 NodeKind::Admonition { .. } => Some(BlockCategory::Admonition),
71 NodeKind::TabGroup => Some(BlockCategory::TabGroup),
72 NodeKind::TabItem { .. } => Some(BlockCategory::TabItem),
73 NodeKind::SliderDeck { .. } => Some(BlockCategory::SliderDeck),
74 NodeKind::Slide { .. } => Some(BlockCategory::Slide),
75 NodeKind::Table { .. } => Some(BlockCategory::Table),
76 NodeKind::TableRow { .. } => Some(BlockCategory::TableRow),
77 NodeKind::TableCell { .. } => Some(BlockCategory::TableCell),
78 NodeKind::HtmlBlock { .. } => Some(BlockCategory::HtmlBlock),
79 NodeKind::FootnoteDefinition { .. } => Some(BlockCategory::FootnoteDefinition),
80 NodeKind::MermaidDiagram { .. } => Some(BlockCategory::MermaidDiagram),
81 _ => None,
82 }
83}
84
85pub fn is_block_node(node: &Node) -> bool {
87 is_block_kind(&node.kind)
88}
89
90pub fn top_level_blocks(document: &Document) -> impl Iterator<Item = &Node> {
92 document.children.iter().filter(|node| is_block_node(node))
93}
94
95pub fn collect_block_nodes<'a>(nodes: &'a [Node], out: &mut Vec<&'a Node>) {
97 for node in nodes {
98 if is_block_node(node) {
99 out.push(node);
100 }
101 if !node.children.is_empty() {
102 collect_block_nodes(&node.children, out);
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn smoke_test_classify_block_kind_basic() {
113 assert_eq!(
114 classify_block_kind(&NodeKind::Paragraph),
115 Some(BlockCategory::Paragraph)
116 );
117 assert_eq!(classify_block_kind(&NodeKind::Text("x".to_string())), None);
118 }
119
120 #[test]
121 fn smoke_test_collect_block_nodes_recursive() {
122 let doc = Document {
123 children: vec![Node {
124 kind: NodeKind::Paragraph,
125 span: None,
126 children: vec![Node {
127 kind: NodeKind::Link {
128 url: "https://example.com".to_string(),
129 title: None,
130 },
131 span: None,
132 children: vec![],
133 }],
134 }],
135 ..Default::default()
136 };
137
138 let mut blocks = Vec::new();
139 collect_block_nodes(&doc.children, &mut blocks);
140
141 assert_eq!(blocks.len(), 1);
142 assert!(matches!(blocks[0].kind, NodeKind::Paragraph));
143 }
144}