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    /// Transform a table cell
72    fn transform_table_cell(&mut self, cell: TableCell) -> TableCell {
73        self.walk_transform_table_cell(cell)
74    }
75
76    /// Transform a list item
77    fn transform_list_item(&mut self, item: ListItem) -> ListItem {
78        self.walk_transform_list_item(item)
79    }
80
81    /// Transform a table row
82    fn transform_table_row(&mut self, row: TableRow) -> TableRow {
83        self.walk_transform_table_row(row)
84    }
85
86    /// Transform a heading
87    fn transform_heading(&mut self, heading: Heading) -> Heading {
88        self.walk_transform_heading(heading)
89    }
90
91    /// Transform a link
92    fn transform_link(&mut self, link: Link) -> Link {
93        self.walk_transform_link(link)
94    }
95
96    /// Transform an image
97    fn transform_image(&mut self, image: Image) -> Image {
98        self.walk_transform_image(image)
99    }
100
101    /// Transform a code block
102    fn transform_code_block(&mut self, code_block: CodeBlock) -> CodeBlock {
103        self.walk_transform_code_block(code_block)
104    }
105
106    /// Transform text content
107    fn transform_text(&mut self, text: String) -> String {
108        self.walk_transform_text(text)
109    }
110
111    /// Transform a footnote definition
112    fn transform_footnote_definition(
113        &mut self,
114        footnote: FootnoteDefinition,
115    ) -> FootnoteDefinition {
116        self.walk_transform_footnote_definition(footnote)
117    }
118
119    /// Transform a GitHub alert
120    fn transform_github_alert(&mut self, alert: GitHubAlert) -> GitHubAlert {
121        self.walk_transform_github_alert(alert)
122    }
123
124    /// Default transformation for document
125    fn walk_transform_document(&mut self, mut doc: Document) -> Document {
126        doc.blocks = doc
127            .blocks
128            .into_iter()
129            .map(|block| self.transform_block(block))
130            .collect();
131        doc
132    }
133
134    /// Default transformation for block nodes
135    fn walk_transform_block(&mut self, block: Block) -> Block {
136        match block {
137            Block::Paragraph(inlines) => Block::Paragraph(
138                inlines
139                    .into_iter()
140                    .map(|inline| self.transform_inline(inline))
141                    .collect(),
142            ),
143            Block::Heading(heading) => Block::Heading(self.transform_heading(heading)),
144            Block::BlockQuote(blocks) => Block::BlockQuote(
145                blocks
146                    .into_iter()
147                    .map(|block| self.transform_block(block))
148                    .collect(),
149            ),
150            Block::List(mut list) => {
151                list.items = list
152                    .items
153                    .into_iter()
154                    .map(|item| self.transform_list_item(item))
155                    .collect();
156                Block::List(list)
157            }
158            Block::Table(mut table) => {
159                table.rows = table
160                    .rows
161                    .into_iter()
162                    .map(|row| self.transform_table_row(row))
163                    .collect();
164                Block::Table(table)
165            }
166            Block::FootnoteDefinition(footnote) => {
167                Block::FootnoteDefinition(self.transform_footnote_definition(footnote))
168            }
169            Block::GitHubAlert(alert) => Block::GitHubAlert(self.transform_github_alert(alert)),
170            Block::Definition(mut def) => {
171                def.label = def
172                    .label
173                    .into_iter()
174                    .map(|inline| self.transform_inline(inline))
175                    .collect();
176                Block::Definition(def)
177            }
178            Block::CodeBlock(code_block) => Block::CodeBlock(self.transform_code_block(code_block)),
179            // Terminal nodes - no transformation needed
180            other => other,
181        }
182    }
183
184    /// Default transformation for inline nodes
185    fn walk_transform_inline(&mut self, inline: Inline) -> Inline {
186        match inline {
187            Inline::Emphasis(inlines) => Inline::Emphasis(
188                inlines
189                    .into_iter()
190                    .map(|inline| self.transform_inline(inline))
191                    .collect(),
192            ),
193            Inline::Strong(inlines) => Inline::Strong(
194                inlines
195                    .into_iter()
196                    .map(|inline| self.transform_inline(inline))
197                    .collect(),
198            ),
199            Inline::Strikethrough(inlines) => Inline::Strikethrough(
200                inlines
201                    .into_iter()
202                    .map(|inline| self.transform_inline(inline))
203                    .collect(),
204            ),
205            Inline::Link(link) => Inline::Link(self.transform_link(link)),
206            Inline::LinkReference(mut link_ref) => {
207                link_ref.label = link_ref
208                    .label
209                    .into_iter()
210                    .map(|inline| self.transform_inline(inline))
211                    .collect();
212                link_ref.text = link_ref
213                    .text
214                    .into_iter()
215                    .map(|inline| self.transform_inline(inline))
216                    .collect();
217                Inline::LinkReference(link_ref)
218            }
219            Inline::Image(image) => Inline::Image(self.transform_image(image)),
220            Inline::Text(text) => Inline::Text(self.transform_text(text)),
221            // Terminal nodes - no transformation needed
222            other => other,
223        }
224    }
225
226    /// Default transformation for table cells
227    fn walk_transform_table_cell(&mut self, cell: TableCell) -> TableCell {
228        cell.into_iter()
229            .map(|inline| self.transform_inline(inline))
230            .collect()
231    }
232
233    /// Default transformation for list items
234    fn walk_transform_list_item(&mut self, mut item: ListItem) -> ListItem {
235        item.blocks = item
236            .blocks
237            .into_iter()
238            .map(|block| self.transform_block(block))
239            .collect();
240        item
241    }
242
243    /// Default transformation for table rows
244    fn walk_transform_table_row(&mut self, row: TableRow) -> TableRow {
245        row.into_iter()
246            .map(|cell| self.transform_table_cell(cell))
247            .collect()
248    }
249
250    /// Default transformation for headings
251    fn walk_transform_heading(&mut self, mut heading: Heading) -> Heading {
252        heading.content = heading
253            .content
254            .into_iter()
255            .map(|inline| self.transform_inline(inline))
256            .collect();
257        heading
258    }
259
260    /// Default transformation for links
261    fn walk_transform_link(&mut self, mut link: Link) -> Link {
262        link.children = link
263            .children
264            .into_iter()
265            .map(|inline| self.transform_inline(inline))
266            .collect();
267        link
268    }
269
270    /// Default transformation for images
271    fn walk_transform_image(&mut self, image: Image) -> Image {
272        // Images are terminal nodes
273        image
274    }
275
276    /// Default transformation for code blocks
277    fn walk_transform_code_block(&mut self, code_block: CodeBlock) -> CodeBlock {
278        // Code blocks are terminal nodes
279        code_block
280    }
281
282    /// Default transformation for text
283    fn walk_transform_text(&mut self, text: String) -> String {
284        // Text is a terminal node
285        text
286    }
287
288    /// Default transformation for footnote definitions
289    fn walk_transform_footnote_definition(
290        &mut self,
291        mut footnote: FootnoteDefinition,
292    ) -> FootnoteDefinition {
293        footnote.blocks = footnote
294            .blocks
295            .into_iter()
296            .map(|block| self.transform_block(block))
297            .collect();
298        footnote
299    }
300
301    /// Default transformation for GitHub alerts
302    fn walk_transform_github_alert(&mut self, mut alert: GitHubAlert) -> GitHubAlert {
303        alert.blocks = alert
304            .blocks
305            .into_iter()
306            .map(|block| self.transform_block(block))
307            .collect();
308        alert
309    }
310}
311
312/// Extension trait for transforming documents
313pub trait TransformWith {
314    /// Apply a transformer to this AST node
315    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self;
316}
317
318impl TransformWith for Document {
319    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self {
320        transformer.transform_document(self)
321    }
322}
323
324impl TransformWith for Block {
325    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self {
326        transformer.transform_block(self)
327    }
328}
329
330impl TransformWith for Inline {
331    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self {
332        transformer.transform_inline(self)
333    }
334}
335
336/// Composite transformer that applies multiple transformers in sequence
337pub struct CompositeTransformer {
338    transformers: Vec<Box<dyn Transformer>>,
339}
340
341impl CompositeTransformer {
342    /// Create a new composite transformer
343    pub fn new() -> Self {
344        Self {
345            transformers: Vec::new(),
346        }
347    }
348
349    /// Add a transformer to the sequence
350    pub fn add_transformer<T: Transformer + 'static>(mut self, transformer: T) -> Self {
351        self.transformers.push(Box::new(transformer));
352        self
353    }
354}
355
356impl Default for CompositeTransformer {
357    fn default() -> Self {
358        Self::new()
359    }
360}
361
362impl Transformer for CompositeTransformer {
363    fn transform_document(&mut self, mut doc: Document) -> Document {
364        for transformer in &mut self.transformers {
365            doc = transformer.transform_document(doc);
366        }
367        doc
368    }
369
370    fn transform_block(&mut self, mut block: Block) -> Block {
371        for transformer in &mut self.transformers {
372            block = transformer.transform_block(block);
373        }
374        block
375    }
376
377    fn transform_inline(&mut self, mut inline: Inline) -> Inline {
378        for transformer in &mut self.transformers {
379            inline = transformer.transform_inline(inline);
380        }
381        inline
382    }
383}