markdown_ppp/ast_transform/
transformer.rs

1//! Transformer pattern for AST modifications
2//!
3//! This module provides the Transformer trait for modifying AST nodes in place.
4//! Unlike the visitor pattern which is read-only, transformers consume and
5//! return modified AST nodes.
6//!
7//! # Example
8//!
9//! ```rust
10//! use markdown_ppp::ast::*;
11//! use markdown_ppp::ast_transform::{Transformer, TransformWith};
12//!
13//! struct UppercaseTransformer;
14//!
15//! impl Transformer for UppercaseTransformer {
16//!     fn transform_inline(&mut self, inline: Inline) -> Inline {
17//!         match inline {
18//!             Inline::Text(text) => Inline::Text(text.to_uppercase()),
19//!             other => self.walk_transform_inline(other),
20//!         }
21//!     }
22//! }
23//!
24//! let doc = Document {
25//!     blocks: vec![Block::Paragraph(vec![Inline::Text("hello".to_string())])],
26//! };
27//!
28//! let result = doc.transform_with(&mut UppercaseTransformer);
29//! ```
30
31use crate::ast::*;
32
33/// Transformer trait for modifying AST nodes
34///
35/// Provides default implementations that recursively transform child nodes.
36/// Override specific methods to implement custom transformation logic.
37///
38/// # Example
39///
40/// ```rust
41/// use markdown_ppp::ast::*;
42/// use markdown_ppp::ast_transform::Transformer;
43///
44/// struct UppercaseTransformer;
45///
46/// impl Transformer for UppercaseTransformer {
47///     fn transform_inline(&mut self, inline: Inline) -> Inline {
48///         match inline {
49///             Inline::Text(text) => Inline::Text(text.to_uppercase()),
50///             other => self.walk_transform_inline(other),
51///         }
52///     }
53/// }
54/// ```
55pub trait Transformer {
56    /// Transform a document node
57    fn transform_document(&mut self, doc: Document) -> Document {
58        self.walk_transform_document(doc)
59    }
60
61    /// Transform a block node
62    fn transform_block(&mut self, block: Block) -> Block {
63        self.walk_transform_block(block)
64    }
65
66    /// Transform an inline node
67    fn transform_inline(&mut self, inline: Inline) -> Inline {
68        self.walk_transform_inline(inline)
69    }
70
71    /// Default transformation for document
72    fn walk_transform_document(&mut self, mut doc: Document) -> Document {
73        doc.blocks = doc
74            .blocks
75            .into_iter()
76            .map(|block| self.transform_block(block))
77            .collect();
78        doc
79    }
80
81    /// Default transformation for block nodes
82    fn walk_transform_block(&mut self, block: Block) -> Block {
83        match block {
84            Block::Paragraph(inlines) => Block::Paragraph(
85                inlines
86                    .into_iter()
87                    .map(|inline| self.transform_inline(inline))
88                    .collect(),
89            ),
90            Block::Heading(mut heading) => {
91                heading.content = heading
92                    .content
93                    .into_iter()
94                    .map(|inline| self.transform_inline(inline))
95                    .collect();
96                Block::Heading(heading)
97            }
98            Block::BlockQuote(blocks) => Block::BlockQuote(
99                blocks
100                    .into_iter()
101                    .map(|block| self.transform_block(block))
102                    .collect(),
103            ),
104            Block::List(mut list) => {
105                list.items = list
106                    .items
107                    .into_iter()
108                    .map(|mut item| {
109                        item.blocks = item
110                            .blocks
111                            .into_iter()
112                            .map(|block| self.transform_block(block))
113                            .collect();
114                        item
115                    })
116                    .collect();
117                Block::List(list)
118            }
119            Block::Table(mut table) => {
120                table.rows = table
121                    .rows
122                    .into_iter()
123                    .map(|row| {
124                        row.into_iter()
125                            .map(|cell| {
126                                cell.into_iter()
127                                    .map(|inline| self.transform_inline(inline))
128                                    .collect()
129                            })
130                            .collect()
131                    })
132                    .collect();
133                Block::Table(table)
134            }
135            Block::FootnoteDefinition(mut footnote) => {
136                footnote.blocks = footnote
137                    .blocks
138                    .into_iter()
139                    .map(|block| self.transform_block(block))
140                    .collect();
141                Block::FootnoteDefinition(footnote)
142            }
143            Block::GitHubAlert(mut alert) => {
144                alert.blocks = alert
145                    .blocks
146                    .into_iter()
147                    .map(|block| self.transform_block(block))
148                    .collect();
149                Block::GitHubAlert(alert)
150            }
151            Block::Definition(mut def) => {
152                def.label = def
153                    .label
154                    .into_iter()
155                    .map(|inline| self.transform_inline(inline))
156                    .collect();
157                Block::Definition(def)
158            }
159            // Terminal nodes - no transformation needed
160            other => other,
161        }
162    }
163
164    /// Default transformation for inline nodes
165    fn walk_transform_inline(&mut self, inline: Inline) -> Inline {
166        match inline {
167            Inline::Emphasis(inlines) => Inline::Emphasis(
168                inlines
169                    .into_iter()
170                    .map(|inline| self.transform_inline(inline))
171                    .collect(),
172            ),
173            Inline::Strong(inlines) => Inline::Strong(
174                inlines
175                    .into_iter()
176                    .map(|inline| self.transform_inline(inline))
177                    .collect(),
178            ),
179            Inline::Strikethrough(inlines) => Inline::Strikethrough(
180                inlines
181                    .into_iter()
182                    .map(|inline| self.transform_inline(inline))
183                    .collect(),
184            ),
185            Inline::Link(mut link) => {
186                link.children = link
187                    .children
188                    .into_iter()
189                    .map(|inline| self.transform_inline(inline))
190                    .collect();
191                Inline::Link(link)
192            }
193            Inline::LinkReference(mut link_ref) => {
194                link_ref.label = link_ref
195                    .label
196                    .into_iter()
197                    .map(|inline| self.transform_inline(inline))
198                    .collect();
199                link_ref.text = link_ref
200                    .text
201                    .into_iter()
202                    .map(|inline| self.transform_inline(inline))
203                    .collect();
204                Inline::LinkReference(link_ref)
205            }
206            // Terminal nodes - no transformation needed
207            other => other,
208        }
209    }
210}
211
212/// Extension trait for transforming documents
213pub trait TransformWith {
214    /// Apply a transformer to this AST node
215    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self;
216}
217
218impl TransformWith for Document {
219    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self {
220        transformer.transform_document(self)
221    }
222}
223
224impl TransformWith for Block {
225    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self {
226        transformer.transform_block(self)
227    }
228}
229
230impl TransformWith for Inline {
231    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self {
232        transformer.transform_inline(self)
233    }
234}
235
236/// Composite transformer that applies multiple transformers in sequence
237pub struct CompositeTransformer {
238    transformers: Vec<Box<dyn Transformer>>,
239}
240
241impl CompositeTransformer {
242    /// Create a new composite transformer
243    pub fn new() -> Self {
244        Self {
245            transformers: Vec::new(),
246        }
247    }
248
249    /// Add a transformer to the sequence
250    pub fn add_transformer<T: Transformer + 'static>(mut self, transformer: T) -> Self {
251        self.transformers.push(Box::new(transformer));
252        self
253    }
254}
255
256impl Default for CompositeTransformer {
257    fn default() -> Self {
258        Self::new()
259    }
260}
261
262impl Transformer for CompositeTransformer {
263    fn transform_document(&mut self, mut doc: Document) -> Document {
264        for transformer in &mut self.transformers {
265            doc = transformer.transform_document(doc);
266        }
267        doc
268    }
269
270    fn transform_block(&mut self, mut block: Block) -> Block {
271        for transformer in &mut self.transformers {
272            block = transformer.transform_block(block);
273        }
274        block
275    }
276
277    fn transform_inline(&mut self, mut inline: Inline) -> Inline {
278        for transformer in &mut self.transformers {
279            inline = transformer.transform_inline(inline);
280        }
281        inline
282    }
283}