markdown_ppp/ast_transform/
generic_transformer.rs

1//! Generic transformer support for AST nodes with user data
2//!
3//! This module provides transformer traits that work with the generic AST types
4//! that support user-defined data. This allows for powerful transformations
5//! while preserving or modifying user data attached to AST nodes.
6//!
7//! # Example
8//!
9//! ```rust
10//! use markdown_ppp::ast::generic::*;
11//! use markdown_ppp::ast_transform::{GenericTransformer, GenericExpandWith};
12//!
13//! #[derive(Debug, Clone, PartialEq, Default)]
14//! struct NodeId(u32);
15//!
16//! struct IdAssigner {
17//!     next_id: u32,
18//! }
19//!
20//! impl GenericTransformer<NodeId> for IdAssigner {
21//!     fn expand_block(&mut self, block: Block<NodeId>) -> Vec<Block<NodeId>> {
22//!         // Assign new ID and potentially split block
23//!         match block {
24//!             Block::Paragraph { content, .. } if content.len() > 5 => {
25//!                 // Split long paragraphs into two
26//!                 let mid = content.len() / 2;
27//!                 let (first_half, second_half) = content.split_at(mid);
28//!                 vec![
29//!                     Block::Paragraph {
30//!                         content: first_half.to_vec(),
31//!                         user_data: NodeId(self.next_id)
32//!                     },
33//!                     Block::Paragraph {
34//!                         content: second_half.to_vec(),
35//!                         user_data: NodeId(self.next_id + 1)
36//!                     },
37//!                 ]
38//!             }
39//!             other => vec![self.walk_transform_block(other)]
40//!         }
41//!     }
42//! }
43//! ```
44
45use crate::ast::generic::*;
46
47/// Generic transformer trait for AST nodes with user data
48///
49/// This trait provides the same transformation capabilities as the regular
50/// Transformer trait, but works with generic AST nodes that contain user data.
51pub trait GenericTransformer<T> {
52    /// Transform a document with user data
53    fn transform_document(&mut self, doc: Document<T>) -> Document<T> {
54        self.walk_transform_document(doc)
55    }
56
57    /// Transform a block with user data
58    fn transform_block(&mut self, block: Block<T>) -> Block<T> {
59        self.walk_transform_block(block)
60    }
61
62    /// Transform an inline with user data
63    fn transform_inline(&mut self, inline: Inline<T>) -> Inline<T> {
64        self.walk_transform_inline(inline)
65    }
66
67    /// Transform a table cell with user data
68    fn transform_table_cell(&mut self, cell: TableCell<T>) -> TableCell<T> {
69        self.walk_transform_table_cell(cell)
70    }
71
72    /// Transform a list item with user data
73    fn transform_list_item(&mut self, item: ListItem<T>) -> ListItem<T> {
74        self.walk_transform_list_item(item)
75    }
76
77    /// Transform a table row with user data
78    fn transform_table_row(&mut self, row: TableRow<T>) -> TableRow<T> {
79        self.walk_transform_table_row(row)
80    }
81
82    /// Transform a heading with user data
83    fn transform_heading(&mut self, heading: Heading<T>) -> Heading<T> {
84        self.walk_transform_heading(heading)
85    }
86
87    /// Transform a link with user data
88    fn transform_link(&mut self, link: Link<T>) -> Link<T> {
89        self.walk_transform_link(link)
90    }
91
92    /// Transform an image with user data
93    fn transform_image(&mut self, image: Image<T>) -> Image<T> {
94        self.walk_transform_image(image)
95    }
96
97    /// Transform a code block with user data
98    fn transform_code_block(&mut self, code_block: CodeBlock<T>) -> CodeBlock<T> {
99        self.walk_transform_code_block(code_block)
100    }
101
102    /// Transform a footnote definition with user data
103    fn transform_footnote_definition(
104        &mut self,
105        footnote: FootnoteDefinition<T>,
106    ) -> FootnoteDefinition<T> {
107        self.walk_transform_footnote_definition(footnote)
108    }
109
110    /// Transform a GitHub alert with user data
111    fn transform_github_alert(&mut self, alert: GitHubAlertNode<T>) -> GitHubAlertNode<T> {
112        self.walk_transform_github_alert(alert)
113    }
114
115    // ——————————————————————————————————————————————————————————————————————————
116    // Expandable transformation methods (1-to-many) for generic types
117    // ——————————————————————————————————————————————————————————————————————————
118
119    /// Transform a document with possibility to expand into multiple documents
120    fn expand_document(&mut self, doc: Document<T>) -> Vec<Document<T>> {
121        vec![self.transform_document(doc)]
122    }
123
124    /// Transform a block with possibility to expand into multiple blocks
125    fn expand_block(&mut self, block: Block<T>) -> Vec<Block<T>> {
126        vec![self.transform_block(block)]
127    }
128
129    /// Transform an inline with possibility to expand into multiple inlines
130    fn expand_inline(&mut self, inline: Inline<T>) -> Vec<Inline<T>> {
131        vec![self.transform_inline(inline)]
132    }
133
134    /// Transform a table cell with possibility to expand into multiple cells
135    fn expand_table_cell(&mut self, cell: TableCell<T>) -> Vec<TableCell<T>> {
136        vec![self.transform_table_cell(cell)]
137    }
138
139    /// Transform a list item with possibility to expand into multiple items
140    fn expand_list_item(&mut self, item: ListItem<T>) -> Vec<ListItem<T>> {
141        vec![self.transform_list_item(item)]
142    }
143
144    /// Transform a table row with possibility to expand into multiple rows
145    fn expand_table_row(&mut self, row: TableRow<T>) -> Vec<TableRow<T>> {
146        vec![self.transform_table_row(row)]
147    }
148
149    /// Transform a heading with possibility to expand into multiple headings
150    fn expand_heading(&mut self, heading: Heading<T>) -> Vec<Heading<T>> {
151        vec![self.transform_heading(heading)]
152    }
153
154    /// Transform a link with possibility to expand into multiple links
155    fn expand_link(&mut self, link: Link<T>) -> Vec<Link<T>> {
156        vec![self.transform_link(link)]
157    }
158
159    /// Transform an image with possibility to expand into multiple images
160    fn expand_image(&mut self, image: Image<T>) -> Vec<Image<T>> {
161        vec![self.transform_image(image)]
162    }
163
164    /// Transform a code block with possibility to expand into multiple code blocks
165    fn expand_code_block(&mut self, code_block: CodeBlock<T>) -> Vec<CodeBlock<T>> {
166        vec![self.transform_code_block(code_block)]
167    }
168
169    /// Transform a footnote definition with possibility to expand into multiple definitions
170    fn expand_footnote_definition(
171        &mut self,
172        footnote: FootnoteDefinition<T>,
173    ) -> Vec<FootnoteDefinition<T>> {
174        vec![self.transform_footnote_definition(footnote)]
175    }
176
177    /// Transform a GitHub alert with possibility to expand into multiple alerts
178    fn expand_github_alert(&mut self, alert: GitHubAlertNode<T>) -> Vec<GitHubAlertNode<T>> {
179        vec![self.transform_github_alert(alert)]
180    }
181
182    // ——————————————————————————————————————————————————————————————————————————
183    // Default implementations for transformations
184    // ——————————————————————————————————————————————————————————————————————————
185
186    /// Default transformation for document with user data
187    fn walk_transform_document(&mut self, mut doc: Document<T>) -> Document<T> {
188        doc.blocks = doc
189            .blocks
190            .into_iter()
191            .map(|block| self.transform_block(block))
192            .collect();
193        doc
194    }
195
196    /// Default transformation for block nodes with user data
197    fn walk_transform_block(&mut self, block: Block<T>) -> Block<T> {
198        match block {
199            Block::Paragraph { content, user_data } => Block::Paragraph {
200                content: content
201                    .into_iter()
202                    .map(|inline| self.transform_inline(inline))
203                    .collect(),
204                user_data,
205            },
206            Block::Heading(heading) => Block::Heading(self.transform_heading(heading)),
207            Block::BlockQuote { blocks, user_data } => Block::BlockQuote {
208                blocks: blocks
209                    .into_iter()
210                    .map(|block| self.transform_block(block))
211                    .collect(),
212                user_data,
213            },
214            Block::List(list) => Block::List(self.transform_list_item_container(list)),
215            Block::Table(table) => Block::Table(self.transform_table(table)),
216            Block::FootnoteDefinition(footnote) => {
217                Block::FootnoteDefinition(self.transform_footnote_definition(footnote))
218            }
219            Block::GitHubAlert(alert) => Block::GitHubAlert(self.transform_github_alert(alert)),
220            Block::Definition(mut def) => {
221                def.label = def
222                    .label
223                    .into_iter()
224                    .map(|inline| self.transform_inline(inline))
225                    .collect();
226                Block::Definition(def)
227            }
228            Block::CodeBlock(code_block) => Block::CodeBlock(self.transform_code_block(code_block)),
229            // Terminal nodes - no transformation needed
230            other => other,
231        }
232    }
233
234    /// Default transformation for inline nodes with user data
235    fn walk_transform_inline(&mut self, inline: Inline<T>) -> Inline<T> {
236        match inline {
237            Inline::Emphasis { content, user_data } => Inline::Emphasis {
238                content: content
239                    .into_iter()
240                    .map(|inline| self.transform_inline(inline))
241                    .collect(),
242                user_data,
243            },
244            Inline::Strong { content, user_data } => Inline::Strong {
245                content: content
246                    .into_iter()
247                    .map(|inline| self.transform_inline(inline))
248                    .collect(),
249                user_data,
250            },
251            Inline::Strikethrough { content, user_data } => Inline::Strikethrough {
252                content: content
253                    .into_iter()
254                    .map(|inline| self.transform_inline(inline))
255                    .collect(),
256                user_data,
257            },
258            Inline::Link(link) => Inline::Link(self.transform_link(link)),
259            Inline::LinkReference(mut link_ref) => {
260                link_ref.label = link_ref
261                    .label
262                    .into_iter()
263                    .map(|inline| self.transform_inline(inline))
264                    .collect();
265                link_ref.text = link_ref
266                    .text
267                    .into_iter()
268                    .map(|inline| self.transform_inline(inline))
269                    .collect();
270                Inline::LinkReference(link_ref)
271            }
272            Inline::Image(image) => Inline::Image(self.transform_image(image)),
273            // Terminal nodes - no transformation needed
274            other => other,
275        }
276    }
277
278    /// Default transformation for table cells with user data
279    fn walk_transform_table_cell(&mut self, cell: TableCell<T>) -> TableCell<T> {
280        cell.into_iter()
281            .map(|inline| self.transform_inline(inline))
282            .collect()
283    }
284
285    /// Default transformation for list items with user data
286    fn walk_transform_list_item(&mut self, mut item: ListItem<T>) -> ListItem<T> {
287        item.blocks = item
288            .blocks
289            .into_iter()
290            .map(|block| self.transform_block(block))
291            .collect();
292        item
293    }
294
295    /// Default transformation for table rows with user data
296    fn walk_transform_table_row(&mut self, row: TableRow<T>) -> TableRow<T> {
297        row.into_iter()
298            .map(|cell| self.transform_table_cell(cell))
299            .collect()
300    }
301
302    /// Default transformation for headings with user data
303    fn walk_transform_heading(&mut self, mut heading: Heading<T>) -> Heading<T> {
304        heading.content = heading
305            .content
306            .into_iter()
307            .map(|inline| self.transform_inline(inline))
308            .collect();
309        heading
310    }
311
312    /// Default transformation for links with user data
313    fn walk_transform_link(&mut self, mut link: Link<T>) -> Link<T> {
314        link.children = link
315            .children
316            .into_iter()
317            .map(|inline| self.transform_inline(inline))
318            .collect();
319        link
320    }
321
322    /// Default transformation for images with user data
323    fn walk_transform_image(&mut self, image: Image<T>) -> Image<T> {
324        // Images are terminal nodes
325        image
326    }
327
328    /// Default transformation for code blocks with user data
329    fn walk_transform_code_block(&mut self, code_block: CodeBlock<T>) -> CodeBlock<T> {
330        // Code blocks are terminal nodes
331        code_block
332    }
333
334    /// Default transformation for footnote definitions with user data
335    fn walk_transform_footnote_definition(
336        &mut self,
337        mut footnote: FootnoteDefinition<T>,
338    ) -> FootnoteDefinition<T> {
339        footnote.blocks = footnote
340            .blocks
341            .into_iter()
342            .map(|block| self.transform_block(block))
343            .collect();
344        footnote
345    }
346
347    /// Default transformation for GitHub alerts with user data
348    fn walk_transform_github_alert(&mut self, mut alert: GitHubAlertNode<T>) -> GitHubAlertNode<T> {
349        alert.blocks = alert
350            .blocks
351            .into_iter()
352            .map(|block| self.transform_block(block))
353            .collect();
354        alert
355    }
356
357    // Helper methods for composite structures
358
359    /// Helper to transform list container
360    fn transform_list_item_container(&mut self, mut list: List<T>) -> List<T> {
361        list.items = list
362            .items
363            .into_iter()
364            .map(|item| self.transform_list_item(item))
365            .collect();
366        list
367    }
368
369    /// Helper to transform table
370    fn transform_table(&mut self, mut table: Table<T>) -> Table<T> {
371        table.rows = table
372            .rows
373            .into_iter()
374            .map(|row| self.transform_table_row(row))
375            .collect();
376        table
377    }
378
379    // ——————————————————————————————————————————————————————————————————————————
380    // Walk methods for expandable transformations with user data
381    // ——————————————————————————————————————————————————————————————————————————
382
383    /// Default expandable transformation for document using flat_map
384    fn walk_expand_document(&mut self, mut doc: Document<T>) -> Vec<Document<T>> {
385        doc.blocks = doc
386            .blocks
387            .into_iter()
388            .flat_map(|block| self.walk_expand_block(block))
389            .collect();
390        vec![doc]
391    }
392
393    /// Default expandable transformation for block nodes using flat_map
394    fn walk_expand_block(&mut self, block: Block<T>) -> Vec<Block<T>> {
395        let transformed_block = match block {
396            Block::Paragraph { content, user_data } => Block::Paragraph {
397                content: content
398                    .into_iter()
399                    .flat_map(|inline| self.walk_expand_inline(inline))
400                    .collect(),
401                user_data,
402            },
403            Block::Heading(heading) => {
404                let expanded_headings = self.expand_heading(heading);
405                return expanded_headings.into_iter().map(Block::Heading).collect();
406            }
407            Block::BlockQuote { blocks, user_data } => Block::BlockQuote {
408                blocks: blocks
409                    .into_iter()
410                    .flat_map(|block| self.walk_expand_block(block))
411                    .collect(),
412                user_data,
413            },
414            Block::List(list) => {
415                let expanded_list = self.expand_list_container(list);
416                return expanded_list.into_iter().map(Block::List).collect();
417            }
418            Block::Table(table) => {
419                let expanded_table = self.expand_table_container(table);
420                return expanded_table.into_iter().map(Block::Table).collect();
421            }
422            Block::FootnoteDefinition(footnote) => {
423                let expanded_footnotes = self.expand_footnote_definition(footnote);
424                return expanded_footnotes
425                    .into_iter()
426                    .map(Block::FootnoteDefinition)
427                    .collect();
428            }
429            Block::GitHubAlert(alert) => {
430                let expanded_alerts = self.expand_github_alert(alert);
431                return expanded_alerts
432                    .into_iter()
433                    .map(Block::GitHubAlert)
434                    .collect();
435            }
436            Block::Definition(mut def) => {
437                def.label = def
438                    .label
439                    .into_iter()
440                    .flat_map(|inline| self.walk_expand_inline(inline))
441                    .collect();
442                Block::Definition(def)
443            }
444            Block::CodeBlock(code_block) => {
445                let expanded_code_blocks = self.expand_code_block(code_block);
446                return expanded_code_blocks
447                    .into_iter()
448                    .map(Block::CodeBlock)
449                    .collect();
450            }
451            // Terminal nodes - no transformation needed
452            other => other,
453        };
454        vec![transformed_block]
455    }
456
457    /// Default expandable transformation for inline nodes using flat_map
458    fn walk_expand_inline(&mut self, inline: Inline<T>) -> Vec<Inline<T>> {
459        let transformed_inline = match inline {
460            Inline::Emphasis { content, user_data } => Inline::Emphasis {
461                content: content
462                    .into_iter()
463                    .flat_map(|inline| self.walk_expand_inline(inline))
464                    .collect(),
465                user_data,
466            },
467            Inline::Strong { content, user_data } => Inline::Strong {
468                content: content
469                    .into_iter()
470                    .flat_map(|inline| self.walk_expand_inline(inline))
471                    .collect(),
472                user_data,
473            },
474            Inline::Strikethrough { content, user_data } => Inline::Strikethrough {
475                content: content
476                    .into_iter()
477                    .flat_map(|inline| self.walk_expand_inline(inline))
478                    .collect(),
479                user_data,
480            },
481            Inline::Link(link) => {
482                let expanded_links = self.expand_link(link);
483                return expanded_links.into_iter().map(Inline::Link).collect();
484            }
485            Inline::LinkReference(mut link_ref) => {
486                link_ref.label = link_ref
487                    .label
488                    .into_iter()
489                    .flat_map(|inline| self.walk_expand_inline(inline))
490                    .collect();
491                link_ref.text = link_ref
492                    .text
493                    .into_iter()
494                    .flat_map(|inline| self.walk_expand_inline(inline))
495                    .collect();
496                Inline::LinkReference(link_ref)
497            }
498            Inline::Image(image) => {
499                let expanded_images = self.expand_image(image);
500                return expanded_images.into_iter().map(Inline::Image).collect();
501            }
502            // Terminal nodes - no transformation needed
503            other => other,
504        };
505        vec![transformed_inline]
506    }
507
508    // Helper methods for expandable transformations
509
510    /// Helper to expand list container
511    fn expand_list_container(&mut self, mut list: List<T>) -> Vec<List<T>> {
512        list.items = list
513            .items
514            .into_iter()
515            .flat_map(|item| self.expand_list_item(item))
516            .collect();
517        vec![list]
518    }
519
520    /// Helper to expand table container
521    fn expand_table_container(&mut self, mut table: Table<T>) -> Vec<Table<T>> {
522        table.rows = table
523            .rows
524            .into_iter()
525            .flat_map(|row| self.expand_table_row(row))
526            .collect();
527        vec![table]
528    }
529}
530
531/// Extension trait for generic transformations
532pub trait GenericTransformWith<T> {
533    /// Apply a generic transformer to this AST node
534    fn transform_with<Tr: GenericTransformer<T>>(self, transformer: &mut Tr) -> Self;
535}
536
537impl<T> GenericTransformWith<T> for Document<T> {
538    fn transform_with<Tr: GenericTransformer<T>>(self, transformer: &mut Tr) -> Self {
539        transformer.transform_document(self)
540    }
541}
542
543impl<T> GenericTransformWith<T> for Block<T> {
544    fn transform_with<Tr: GenericTransformer<T>>(self, transformer: &mut Tr) -> Self {
545        transformer.transform_block(self)
546    }
547}
548
549impl<T> GenericTransformWith<T> for Inline<T> {
550    fn transform_with<Tr: GenericTransformer<T>>(self, transformer: &mut Tr) -> Self {
551        transformer.transform_inline(self)
552    }
553}
554
555/// Extension trait for generic expandable transformations
556pub trait GenericExpandWith<T> {
557    /// Apply a generic expandable transformer to this AST node, returning multiple nodes
558    fn expand_with<Tr: GenericTransformer<T>>(self, transformer: &mut Tr) -> Vec<Self>
559    where
560        Self: Sized;
561}
562
563impl<T> GenericExpandWith<T> for Document<T> {
564    fn expand_with<Tr: GenericTransformer<T>>(self, transformer: &mut Tr) -> Vec<Self> {
565        transformer.walk_expand_document(self)
566    }
567}
568
569impl<T> GenericExpandWith<T> for Block<T> {
570    fn expand_with<Tr: GenericTransformer<T>>(self, transformer: &mut Tr) -> Vec<Self> {
571        transformer.walk_expand_block(self)
572    }
573}
574
575impl<T> GenericExpandWith<T> for Inline<T> {
576    fn expand_with<Tr: GenericTransformer<T>>(self, transformer: &mut Tr) -> Vec<Self> {
577        transformer.walk_expand_inline(self)
578    }
579}