markdown_ppp/ast_transform/
convenience.rs

1//! Convenience methods for common AST transformations
2//!
3//! This module provides high-level convenience methods for common transformation
4//! tasks. These methods are implemented as extension traits on AST types, making
5//! them easy to use in a fluent style.
6//!
7//! # Example
8//!
9//! ```rust
10//! use markdown_ppp::ast::*;
11//! use markdown_ppp::ast_transform::{Transform, FilterTransform};
12//!
13//! let doc = Document {
14//!     blocks: vec![Block::Paragraph(vec![Inline::Text("  hello  ".to_string())])],
15//! };
16//!
17//! let result = doc
18//!     .transform_text(|text| text.trim().to_string())
19//!     .normalize_whitespace()
20//!     .remove_empty_text();
21//! ```
22
23use super::transformer::Transformer;
24use crate::ast::*;
25
26/// High-level transformation methods for common use cases
27pub trait Transform {
28    /// Transform all text elements with a function
29    ///
30    /// # Example
31    ///
32    /// ```rust
33    /// use markdown_ppp::ast::*;
34    /// use markdown_ppp::ast_transform::Transform;
35    ///
36    /// let doc = Document {
37    ///     blocks: vec![Block::Paragraph(vec![Inline::Text("hello".to_string())])],
38    /// };
39    /// let result = doc.transform_text(|text| text.to_uppercase());
40    /// ```
41    fn transform_text<F>(self, f: F) -> Self
42    where
43        F: Fn(String) -> String;
44
45    /// Transform all image URLs with a function
46    ///
47    /// # Example
48    ///
49    /// ```rust
50    /// use markdown_ppp::ast::*;
51    /// use markdown_ppp::ast_transform::Transform;
52    ///
53    /// let doc = Document {
54    ///     blocks: vec![Block::Paragraph(vec![Inline::Image(Image {
55    ///         destination: "/image.jpg".to_string(),
56    ///         title: None,
57    ///         alt: "test".to_string(),
58    ///     })])],
59    /// };
60    /// let result = doc.transform_image_urls(|url| {
61    ///     format!("https://cdn.example.com{}", url)
62    /// });
63    /// ```
64    fn transform_image_urls<F>(self, f: F) -> Self
65    where
66        F: Fn(String) -> String;
67
68    /// Transform all link URLs with a function
69    ///
70    /// # Example
71    ///
72    /// ```rust
73    /// use markdown_ppp::ast::*;
74    /// use markdown_ppp::ast_transform::Transform;
75    ///
76    /// let doc = Document {
77    ///     blocks: vec![Block::Paragraph(vec![Inline::Link(Link {
78    ///         destination: "http://example.com".to_string(),
79    ///         title: None,
80    ///         children: vec![Inline::Text("link".to_string())],
81    ///     })])],
82    /// };
83    /// let result = doc.transform_link_urls(|url| {
84    ///     url.replace("http://", "https://")
85    /// });
86    /// ```
87    fn transform_link_urls<F>(self, f: F) -> Self
88    where
89        F: Fn(String) -> String;
90
91    /// Transform all autolink URLs with a function
92    fn transform_autolink_urls<F>(self, f: F) -> Self
93    where
94        F: Fn(String) -> String;
95
96    /// Transform all code spans with a function
97    fn transform_code<F>(self, f: F) -> Self
98    where
99        F: Fn(String) -> String;
100
101    /// Transform all HTML content with a function
102    fn transform_html<F>(self, f: F) -> Self
103    where
104        F: Fn(String) -> String;
105
106    /// Apply a custom transformer
107    fn transform_with<T: Transformer>(self, transformer: T) -> Self;
108
109    /// Transform conditionally based on a predicate
110    fn transform_if<F>(self, condition: bool, transform: F) -> Self
111    where
112        F: FnOnce(Self) -> Self,
113        Self: Sized;
114}
115
116impl Transform for Document {
117    fn transform_text<F>(self, f: F) -> Self
118    where
119        F: Fn(String) -> String,
120    {
121        let mut transformer = TextTransformer::new(f);
122        transformer.transform_document(self)
123    }
124
125    fn transform_image_urls<F>(self, f: F) -> Self
126    where
127        F: Fn(String) -> String,
128    {
129        let mut transformer = ImageUrlTransformer::new(f);
130        transformer.transform_document(self)
131    }
132
133    fn transform_link_urls<F>(self, f: F) -> Self
134    where
135        F: Fn(String) -> String,
136    {
137        let mut transformer = LinkUrlTransformer::new(f);
138        transformer.transform_document(self)
139    }
140
141    fn transform_autolink_urls<F>(self, f: F) -> Self
142    where
143        F: Fn(String) -> String,
144    {
145        let mut transformer = AutolinkTransformer::new(f);
146        transformer.transform_document(self)
147    }
148
149    fn transform_code<F>(self, f: F) -> Self
150    where
151        F: Fn(String) -> String,
152    {
153        let mut transformer = CodeTransformer::new(f);
154        transformer.transform_document(self)
155    }
156
157    fn transform_html<F>(self, f: F) -> Self
158    where
159        F: Fn(String) -> String,
160    {
161        let mut transformer = HtmlTransformer::new(f);
162        transformer.transform_document(self)
163    }
164
165    fn transform_with<T: Transformer>(self, mut transformer: T) -> Self {
166        transformer.transform_document(self)
167    }
168
169    fn transform_if<F>(self, condition: bool, transform: F) -> Self
170    where
171        F: FnOnce(Self) -> Self,
172    {
173        if condition {
174            transform(self)
175        } else {
176            self
177        }
178    }
179}
180
181// Internal transformer implementations
182
183struct TextTransformer<F> {
184    func: F,
185}
186
187impl<F> TextTransformer<F>
188where
189    F: Fn(String) -> String,
190{
191    fn new(func: F) -> Self {
192        Self { func }
193    }
194}
195
196impl<F> Transformer for TextTransformer<F>
197where
198    F: Fn(String) -> String,
199{
200    fn transform_inline(&mut self, inline: Inline) -> Inline {
201        match inline {
202            Inline::Text(text) => Inline::Text((self.func)(text)),
203            other => self.walk_transform_inline(other),
204        }
205    }
206}
207
208struct ImageUrlTransformer<F> {
209    func: F,
210}
211
212impl<F> ImageUrlTransformer<F>
213where
214    F: Fn(String) -> String,
215{
216    fn new(func: F) -> Self {
217        Self { func }
218    }
219}
220
221impl<F> Transformer for ImageUrlTransformer<F>
222where
223    F: Fn(String) -> String,
224{
225    fn transform_inline(&mut self, inline: Inline) -> Inline {
226        match inline {
227            Inline::Image(mut image) => {
228                image.destination = (self.func)(image.destination);
229                Inline::Image(image)
230            }
231            other => self.walk_transform_inline(other),
232        }
233    }
234}
235
236struct LinkUrlTransformer<F> {
237    func: F,
238}
239
240impl<F> LinkUrlTransformer<F>
241where
242    F: Fn(String) -> String,
243{
244    fn new(func: F) -> Self {
245        Self { func }
246    }
247}
248
249impl<F> Transformer for LinkUrlTransformer<F>
250where
251    F: Fn(String) -> String,
252{
253    fn transform_inline(&mut self, inline: Inline) -> Inline {
254        match inline {
255            Inline::Link(mut link) => {
256                link.destination = (self.func)(link.destination);
257                link.children = link
258                    .children
259                    .into_iter()
260                    .map(|child| self.transform_inline(child))
261                    .collect();
262                Inline::Link(link)
263            }
264            other => self.walk_transform_inline(other),
265        }
266    }
267}
268
269struct AutolinkTransformer<F> {
270    func: F,
271}
272
273impl<F> AutolinkTransformer<F>
274where
275    F: Fn(String) -> String,
276{
277    fn new(func: F) -> Self {
278        Self { func }
279    }
280}
281
282impl<F> Transformer for AutolinkTransformer<F>
283where
284    F: Fn(String) -> String,
285{
286    fn transform_inline(&mut self, inline: Inline) -> Inline {
287        match inline {
288            Inline::Autolink(url) => Inline::Autolink((self.func)(url)),
289            other => self.walk_transform_inline(other),
290        }
291    }
292}
293
294struct CodeTransformer<F> {
295    func: F,
296}
297
298impl<F> CodeTransformer<F>
299where
300    F: Fn(String) -> String,
301{
302    fn new(func: F) -> Self {
303        Self { func }
304    }
305}
306
307impl<F> Transformer for CodeTransformer<F>
308where
309    F: Fn(String) -> String,
310{
311    fn transform_inline(&mut self, inline: Inline) -> Inline {
312        match inline {
313            Inline::Code(code) => Inline::Code((self.func)(code)),
314            other => self.walk_transform_inline(other),
315        }
316    }
317}
318
319struct HtmlTransformer<F> {
320    func: F,
321}
322
323impl<F> HtmlTransformer<F>
324where
325    F: Fn(String) -> String,
326{
327    fn new(func: F) -> Self {
328        Self { func }
329    }
330}
331
332impl<F> Transformer for HtmlTransformer<F>
333where
334    F: Fn(String) -> String,
335{
336    fn transform_inline(&mut self, inline: Inline) -> Inline {
337        match inline {
338            Inline::Html(html) => Inline::Html((self.func)(html)),
339            other => self.walk_transform_inline(other),
340        }
341    }
342
343    fn transform_block(&mut self, block: Block) -> Block {
344        match block {
345            Block::HtmlBlock(html) => Block::HtmlBlock((self.func)(html)),
346            other => self.walk_transform_block(other),
347        }
348    }
349}
350
351/// Additional utility methods for filtering and common operations
352pub trait FilterTransform {
353    /// Remove empty paragraphs
354    fn remove_empty_paragraphs(self) -> Self;
355
356    /// Remove empty text elements
357    fn remove_empty_text(self) -> Self;
358
359    /// Normalize whitespace in text elements
360    fn normalize_whitespace(self) -> Self;
361
362    /// Remove specific block types
363    fn filter_blocks<F>(self, predicate: F) -> Self
364    where
365        F: Fn(&Block) -> bool;
366}
367
368impl FilterTransform for Document {
369    fn remove_empty_paragraphs(mut self) -> Self {
370        self.blocks
371            .retain(|block| !matches!(block, Block::Paragraph(inlines) if inlines.is_empty()));
372        self
373    }
374
375    fn remove_empty_text(self) -> Self {
376        let mut transformer = EmptyTextRemover;
377        transformer.transform_document(self)
378    }
379
380    fn normalize_whitespace(self) -> Self {
381        self.transform_text(|text| text.split_whitespace().collect::<Vec<_>>().join(" "))
382    }
383
384    fn filter_blocks<F>(mut self, predicate: F) -> Self
385    where
386        F: Fn(&Block) -> bool,
387    {
388        self.blocks.retain(|block| predicate(block));
389        self
390    }
391}
392
393struct EmptyTextRemover;
394
395impl Transformer for EmptyTextRemover {
396    fn transform_inline(&mut self, inline: Inline) -> Inline {
397        match inline {
398            Inline::Text(text) if text.trim().is_empty() => Inline::Empty,
399            other => self.walk_transform_inline(other),
400        }
401    }
402}