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    // ——————————————————————————————————————————————————————————————————————————
289    // Expandable transformation methods (1-to-many)
290    // ——————————————————————————————————————————————————————————————————————————
291
292    /// Transform a document with possibility to expand into multiple documents
293    ///
294    /// Default implementation delegates to the regular transform_document method
295    fn expand_document(&mut self, doc: Document) -> Vec<Document> {
296        vec![self.transform_document(doc)]
297    }
298
299    /// Transform a block with possibility to expand into multiple blocks
300    ///
301    /// This enables scenarios like:
302    /// - Split one paragraph into paragraph + list
303    /// - Expand one block into multiple blocks
304    /// - Transform one block based on content patterns
305    ///
306    /// Default implementation delegates to the regular transform_block method
307    fn expand_block(&mut self, block: Block) -> Vec<Block> {
308        vec![self.transform_block(block)]
309    }
310
311    /// Transform an inline with possibility to expand into multiple inlines
312    ///
313    /// This enables scenarios like:
314    /// - Split text into multiple text nodes
315    /// - Transform one inline element into several elements
316    /// - Expand abbreviations or macros
317    ///
318    /// Default implementation delegates to the regular transform_inline method
319    fn expand_inline(&mut self, inline: Inline) -> Vec<Inline> {
320        vec![self.transform_inline(inline)]
321    }
322
323    /// Transform a table cell with possibility to expand into multiple cells
324    fn expand_table_cell(&mut self, cell: TableCell) -> Vec<TableCell> {
325        vec![self.transform_table_cell(cell)]
326    }
327
328    /// Transform a list item with possibility to expand into multiple items
329    fn expand_list_item(&mut self, item: ListItem) -> Vec<ListItem> {
330        vec![self.transform_list_item(item)]
331    }
332
333    /// Transform a table row with possibility to expand into multiple rows
334    fn expand_table_row(&mut self, row: TableRow) -> Vec<TableRow> {
335        vec![self.transform_table_row(row)]
336    }
337
338    /// Transform a heading with possibility to expand into multiple headings
339    fn expand_heading(&mut self, heading: Heading) -> Vec<Heading> {
340        vec![self.transform_heading(heading)]
341    }
342
343    /// Transform a link with possibility to expand into multiple links
344    fn expand_link(&mut self, link: Link) -> Vec<Link> {
345        vec![self.transform_link(link)]
346    }
347
348    /// Transform an image with possibility to expand into multiple images
349    fn expand_image(&mut self, image: Image) -> Vec<Image> {
350        vec![self.transform_image(image)]
351    }
352
353    /// Transform a code block with possibility to expand into multiple code blocks
354    fn expand_code_block(&mut self, code_block: CodeBlock) -> Vec<CodeBlock> {
355        vec![self.transform_code_block(code_block)]
356    }
357
358    /// Transform text with possibility to expand into multiple text strings
359    fn expand_text(&mut self, text: String) -> Vec<String> {
360        vec![self.transform_text(text)]
361    }
362
363    /// Transform a footnote definition with possibility to expand into multiple definitions
364    fn expand_footnote_definition(
365        &mut self,
366        footnote: FootnoteDefinition,
367    ) -> Vec<FootnoteDefinition> {
368        vec![self.transform_footnote_definition(footnote)]
369    }
370
371    /// Transform a GitHub alert with possibility to expand into multiple alerts
372    fn expand_github_alert(&mut self, alert: GitHubAlert) -> Vec<GitHubAlert> {
373        vec![self.transform_github_alert(alert)]
374    }
375
376    // ——————————————————————————————————————————————————————————————————————————
377    // Walk methods for expandable transformations
378    // ——————————————————————————————————————————————————————————————————————————
379
380    /// Walk document with expandable transformations
381    ///
382    /// Use this when you want to apply expandable transformations with the default
383    /// behavior of using expand_* methods for child nodes.
384    fn walk_expand_document(&mut self, mut doc: Document) -> Vec<Document> {
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    /// Walk block with expandable transformations
394    ///
395    /// Override this method to implement custom expandable block transformations.
396    /// By default, delegates to transform_block (1-to-1 transformation) but processes
397    /// child nodes with expandable transformations.
398    fn walk_expand_block(&mut self, block: Block) -> Vec<Block> {
399        match block {
400            Block::Paragraph(inlines) => {
401                let expanded_inlines: Vec<Inline> = inlines
402                    .into_iter()
403                    .flat_map(|inline| self.walk_expand_inline(inline))
404                    .collect();
405                vec![Block::Paragraph(expanded_inlines)]
406            }
407            Block::Heading(mut heading) => {
408                heading.content = heading
409                    .content
410                    .into_iter()
411                    .flat_map(|inline| self.walk_expand_inline(inline))
412                    .collect();
413                vec![Block::Heading(heading)]
414            }
415            Block::BlockQuote(blocks) => {
416                let expanded_blocks: Vec<Block> = blocks
417                    .into_iter()
418                    .flat_map(|block| self.walk_expand_block(block))
419                    .collect();
420                vec![Block::BlockQuote(expanded_blocks)]
421            }
422            Block::List(mut list) => {
423                list.items = list
424                    .items
425                    .into_iter()
426                    .flat_map(|item| self.walk_expand_list_item(item))
427                    .collect();
428                vec![Block::List(list)]
429            }
430            Block::Table(mut table) => {
431                table.rows = table
432                    .rows
433                    .into_iter()
434                    .flat_map(|row| self.walk_expand_table_row(row))
435                    .collect();
436                vec![Block::Table(table)]
437            }
438            Block::FootnoteDefinition(mut footnote) => {
439                footnote.blocks = footnote
440                    .blocks
441                    .into_iter()
442                    .flat_map(|block| self.walk_expand_block(block))
443                    .collect();
444                vec![Block::FootnoteDefinition(footnote)]
445            }
446            Block::GitHubAlert(mut alert) => {
447                alert.blocks = alert
448                    .blocks
449                    .into_iter()
450                    .flat_map(|block| self.walk_expand_block(block))
451                    .collect();
452                vec![Block::GitHubAlert(alert)]
453            }
454            Block::Definition(mut def) => {
455                def.label = def
456                    .label
457                    .into_iter()
458                    .flat_map(|inline| self.walk_expand_inline(inline))
459                    .collect();
460                vec![Block::Definition(def)]
461            }
462            // Terminal nodes - no transformation needed
463            other => vec![self.transform_block(other)],
464        }
465    }
466
467    /// Walk inline with expandable transformations
468    ///
469    /// Override this method to implement custom expandable inline transformations.
470    /// By default, delegates to transform_inline (1-to-1 transformation).
471    fn walk_expand_inline(&mut self, inline: Inline) -> Vec<Inline> {
472        vec![self.transform_inline(inline)]
473    }
474
475    /// Walk table cell with expandable transformations
476    fn walk_expand_table_cell(&mut self, cell: TableCell) -> Vec<TableCell> {
477        let expanded_cell = cell
478            .into_iter()
479            .flat_map(|inline| self.expand_inline(inline))
480            .collect();
481        vec![expanded_cell]
482    }
483
484    /// Walk list item with expandable transformations
485    fn walk_expand_list_item(&mut self, mut item: ListItem) -> Vec<ListItem> {
486        item.blocks = item
487            .blocks
488            .into_iter()
489            .flat_map(|block| self.expand_block(block))
490            .collect();
491        vec![item]
492    }
493
494    /// Walk table row with expandable transformations
495    fn walk_expand_table_row(&mut self, row: TableRow) -> Vec<TableRow> {
496        let expanded_row = row
497            .into_iter()
498            .flat_map(|cell| self.expand_table_cell(cell))
499            .collect();
500        vec![expanded_row]
501    }
502
503    /// Walk heading with expandable transformations
504    fn walk_expand_heading(&mut self, mut heading: Heading) -> Vec<Heading> {
505        heading.content = heading
506            .content
507            .into_iter()
508            .flat_map(|inline| self.expand_inline(inline))
509            .collect();
510        vec![heading]
511    }
512
513    /// Walk link with expandable transformations
514    fn walk_expand_link(&mut self, mut link: Link) -> Vec<Link> {
515        link.children = link
516            .children
517            .into_iter()
518            .flat_map(|inline| self.expand_inline(inline))
519            .collect();
520        vec![link]
521    }
522
523    /// Walk image with expandable transformations
524    fn walk_expand_image(&mut self, image: Image) -> Vec<Image> {
525        // Images are terminal nodes
526        vec![image]
527    }
528
529    /// Walk code block with expandable transformations
530    fn walk_expand_code_block(&mut self, code_block: CodeBlock) -> Vec<CodeBlock> {
531        // Code blocks are terminal nodes
532        vec![code_block]
533    }
534
535    /// Walk text with expandable transformations
536    fn walk_expand_text(&mut self, text: String) -> Vec<String> {
537        // Text is a terminal node
538        vec![text]
539    }
540
541    /// Walk footnote definition with expandable transformations
542    fn walk_expand_footnote_definition(
543        &mut self,
544        mut footnote: FootnoteDefinition,
545    ) -> Vec<FootnoteDefinition> {
546        footnote.blocks = footnote
547            .blocks
548            .into_iter()
549            .flat_map(|block| self.expand_block(block))
550            .collect();
551        vec![footnote]
552    }
553
554    /// Walk GitHub alert with expandable transformations
555    fn walk_expand_github_alert(&mut self, mut alert: GitHubAlert) -> Vec<GitHubAlert> {
556        alert.blocks = alert
557            .blocks
558            .into_iter()
559            .flat_map(|block| self.expand_block(block))
560            .collect();
561        vec![alert]
562    }
563
564    /// Default transformation for footnote definitions
565    fn walk_transform_footnote_definition(
566        &mut self,
567        mut footnote: FootnoteDefinition,
568    ) -> FootnoteDefinition {
569        footnote.blocks = footnote
570            .blocks
571            .into_iter()
572            .map(|block| self.transform_block(block))
573            .collect();
574        footnote
575    }
576
577    /// Default transformation for GitHub alerts
578    fn walk_transform_github_alert(&mut self, mut alert: GitHubAlert) -> GitHubAlert {
579        alert.blocks = alert
580            .blocks
581            .into_iter()
582            .map(|block| self.transform_block(block))
583            .collect();
584        alert
585    }
586}
587
588/// Extension trait for transforming documents
589pub trait TransformWith {
590    /// Apply a transformer to this AST node
591    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self;
592}
593
594impl TransformWith for Document {
595    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self {
596        transformer.transform_document(self)
597    }
598}
599
600impl TransformWith for Block {
601    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self {
602        transformer.transform_block(self)
603    }
604}
605
606impl TransformWith for Inline {
607    fn transform_with<T: Transformer>(self, transformer: &mut T) -> Self {
608        transformer.transform_inline(self)
609    }
610}
611
612/// Extension trait for expandable transformations
613pub trait ExpandWith {
614    /// Apply an expandable transformer to this AST node, returning multiple nodes
615    fn expand_with<T: Transformer>(self, transformer: &mut T) -> Vec<Self>
616    where
617        Self: Sized;
618}
619
620impl ExpandWith for Document {
621    fn expand_with<T: Transformer>(self, transformer: &mut T) -> Vec<Self> {
622        transformer.walk_expand_document(self)
623    }
624}
625
626impl ExpandWith for Block {
627    fn expand_with<T: Transformer>(self, transformer: &mut T) -> Vec<Self> {
628        transformer.walk_expand_block(self)
629    }
630}
631
632impl ExpandWith for Inline {
633    fn expand_with<T: Transformer>(self, transformer: &mut T) -> Vec<Self> {
634        transformer.walk_expand_inline(self)
635    }
636}
637
638/// Composite transformer that applies multiple transformers in sequence
639pub struct CompositeTransformer {
640    transformers: Vec<Box<dyn Transformer>>,
641}
642
643impl CompositeTransformer {
644    /// Create a new composite transformer
645    pub fn new() -> Self {
646        Self {
647            transformers: Vec::new(),
648        }
649    }
650
651    /// Add a transformer to the sequence
652    pub fn add_transformer<T: Transformer + 'static>(mut self, transformer: T) -> Self {
653        self.transformers.push(Box::new(transformer));
654        self
655    }
656}
657
658impl Default for CompositeTransformer {
659    fn default() -> Self {
660        Self::new()
661    }
662}
663
664impl Transformer for CompositeTransformer {
665    fn transform_document(&mut self, mut doc: Document) -> Document {
666        for transformer in &mut self.transformers {
667            doc = transformer.transform_document(doc);
668        }
669        doc
670    }
671
672    fn transform_block(&mut self, mut block: Block) -> Block {
673        for transformer in &mut self.transformers {
674            block = transformer.transform_block(block);
675        }
676        block
677    }
678
679    fn transform_inline(&mut self, mut inline: Inline) -> Inline {
680        for transformer in &mut self.transformers {
681            inline = transformer.transform_inline(inline);
682        }
683        inline
684    }
685}