markdown_ppp/ast_specialized/
utilities.rs

1//! Utility functions for working with specialized AST types
2//!
3//! This module provides helper functions and utilities for common operations
4//! with specialized AST types.
5//!
6//! # Example
7//!
8//! ```rust
9//! use markdown_ppp::ast_specialized::utilities::id_utils;
10//! use markdown_ppp::ast::{Document, Block, Heading, HeadingKind, Inline};
11//!
12//! // Create a regular document
13//! let doc = Document {
14//!     blocks: vec![
15//!         Block::Heading(Heading {
16//!             kind: HeadingKind::Atx(1),
17//!             content: vec![Inline::Text("Title".to_string())],
18//!         })
19//!     ],
20//! };
21//!
22//! // Add IDs to all elements
23//! let doc_with_ids = id_utils::add_ids_to_document(doc);
24//! println!("Document ID: {}", doc_with_ids.user_data.id());
25//! ```
26
27use super::element_id::IdGenerator;
28use super::type_aliases::with_ids;
29use super::ElementId;
30
31/// Utility functions for adding IDs to AST nodes
32pub mod id_utils {
33    use super::*;
34    use crate::ast::convert::WithData;
35    use crate::ast::generic;
36    use crate::ast::map_data_visitor::{map_user_data, MapDataVisitor};
37
38    /// Add sequential IDs to all nodes in a document
39    pub fn add_ids_to_document(doc: crate::ast::Document) -> with_ids::Document {
40        let doc_with_unit: generic::Document<()> = doc.with_default_data();
41        let mut id_gen = IdGenerator::new();
42        map_user_data(doc_with_unit, |_| id_gen.generate())
43    }
44
45    /// Add sequential IDs starting from a specific value
46    pub fn add_ids_from(doc: crate::ast::Document, start_id: u64) -> with_ids::Document {
47        let doc_with_unit: generic::Document<()> = doc.with_default_data();
48        let mut id_gen = IdGenerator::starting_from(start_id);
49        map_user_data(doc_with_unit, |_| id_gen.generate())
50    }
51
52    /// Custom visitor for adding IDs with more control
53    pub struct IdAssignmentVisitor {
54        id_gen: IdGenerator,
55    }
56
57    impl IdAssignmentVisitor {
58        pub fn new() -> Self {
59            Self {
60                id_gen: IdGenerator::new(),
61            }
62        }
63
64        pub fn starting_from(start_id: u64) -> Self {
65            Self {
66                id_gen: IdGenerator::starting_from(start_id),
67            }
68        }
69    }
70
71    impl Default for IdAssignmentVisitor {
72        fn default() -> Self {
73            Self::new()
74        }
75    }
76
77    impl<T> MapDataVisitor<T, ElementId> for IdAssignmentVisitor {
78        fn map_data(&mut self, _data: T) -> ElementId {
79            self.id_gen.generate()
80        }
81    }
82
83    /// Add IDs to any generic document
84    pub fn add_ids_to_generic_document<T>(doc: generic::Document<T>) -> with_ids::Document {
85        let mut visitor = IdAssignmentVisitor::new();
86        visitor.visit_document(doc)
87    }
88
89    #[cfg(test)]
90    mod tests {
91        use super::*;
92        use crate::ast::{Block, Heading, HeadingKind, Inline};
93
94        #[test]
95        fn test_add_ids_to_document() {
96            let doc = crate::ast::Document {
97                blocks: vec![Block::Heading(Heading {
98                    kind: HeadingKind::Atx(1),
99                    content: vec![Inline::Text("Test".to_string())],
100                })],
101            };
102
103            let doc_with_ids = add_ids_to_document(doc);
104
105            // Should have ID assigned to document
106            assert!(doc_with_ids.user_data.id() > 0);
107
108            // Should have IDs assigned to all blocks
109            assert_eq!(doc_with_ids.blocks.len(), 1);
110            match &doc_with_ids.blocks[0] {
111                generic::Block::Heading(h) => {
112                    assert!(h.user_data.id() > 0);
113                    assert_eq!(h.content.len(), 1);
114                    match &h.content[0] {
115                        generic::Inline::Text { user_data, .. } => {
116                            assert!(user_data.id() > 0);
117                        }
118                        _ => panic!("Expected text inline"),
119                    }
120                }
121                _ => panic!("Expected heading"),
122            }
123        }
124
125        #[test]
126        fn test_add_ids_from() {
127            let doc = crate::ast::Document {
128                blocks: vec![Block::Heading(Heading {
129                    kind: HeadingKind::Atx(1),
130                    content: vec![Inline::Text("Test".to_string())],
131                })],
132            };
133
134            let doc_with_ids = add_ids_from(doc, 100);
135
136            // Should start from the specified ID
137            assert!(doc_with_ids.user_data.id() >= 100);
138        }
139
140        #[test]
141        fn test_id_assignment_visitor() {
142            let mut visitor = IdAssignmentVisitor::starting_from(50);
143            let id1 = visitor.map_data(());
144            let id2 = visitor.map_data(());
145
146            assert_eq!(id1.id(), 50);
147            assert_eq!(id2.id(), 51);
148        }
149    }
150}