markdown_utils/
transformers.rs

1pub fn transform_line_by_line_skipping_codeblocks(
2    text: &str,
3    func: &dyn Fn(String) -> String,
4) -> String {
5    let mut lines: Vec<String> = vec![];
6
7    let mut current_fenced_codeblock_delimiter: String = "".to_string();
8    for line in text.lines() {
9        let mut next_line: String = line.to_string();
10        if current_fenced_codeblock_delimiter == "" {
11            let trimmed_line = line.trim();
12            if trimmed_line.starts_with("```") || trimmed_line.starts_with("~~~") {
13                // enter fenced codeblock
14                current_fenced_codeblock_delimiter =
15                    trimmed_line[0..3].to_string();
16            } else if !line.starts_with("    ") || line.starts_with("     ") {
17                // don't enter indented code block (4 spaces)
18                // but yes in nested content (+4 spaces)
19                //
20                // perform the transformation
21                next_line = func(next_line.to_string())
22            }
23        } else if line.trim_start().starts_with(
24            &current_fenced_codeblock_delimiter
25        ) {
26            current_fenced_codeblock_delimiter = "".to_string();
27        }
28        lines.push(next_line);
29    }
30    lines.join("\n")
31}
32
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use rstest::rstest;
38
39    fn prepend_upper_a_transformer(line: String) -> String {
40        format!("A{}", line)
41    }
42
43    #[rstest]
44    #[case(
45        &"foo\nbar\n\nbaz",
46        prepend_upper_a_transformer,
47        "Afoo\nAbar\nA\nAbaz",
48    )]
49    #[case(
50        &concat!(
51            "foo\n```\nfoo\n```\n~~~\nfoo\n~~~\n\n    foo",
52            "\n     foo\n\nbar\n\nbaz",
53        ),
54        prepend_upper_a_transformer,
55        concat!(
56            "Afoo\n```\nfoo\n```\n~~~\nfoo\n~~~\nA\n    foo",
57            "\nA     foo\nA\nAbar\nA\nAbaz",
58        ).to_string(),
59    )]
60    fn transform_line_by_line_skipping_codeblocks_test(
61        #[case] text: &str,
62        #[case] func: impl Fn(String) -> String,
63        #[case] expected: String,
64    ) {
65        assert_eq!(
66            transform_line_by_line_skipping_codeblocks(
67                text,
68                &func,
69            ),
70            expected,
71        );
72    }
73}