Skip to main content

dmc_transform/builtin/
disable_gfm.rs

1//! GFM stripper. See `transformers/disable-gfm.md` for full docs.
2
3use crate::pipeline::Transformer;
4use dmc_diagnostic::{Code, metadata::SourceMeta};
5use dmc_parser::ast::*;
6
7/// Serialise GFM-only constructs back to plain markdown. `~~strike~~`
8/// becomes literal text, tables flatten to pipe-delimited text, task list
9/// items lose their checkbox state and become plain list items.
10#[derive(Default)]
11pub struct DisableGfm;
12
13impl Transformer for DisableGfm {
14  fn name(&self) -> &str {
15    "disable-gfm"
16  }
17  fn transform(
18    &self,
19    doc: &mut Document,
20    _meta: &SourceMeta,
21    _diag_engine: &mut duck_diagnostic::DiagnosticEngine<Code>,
22  ) {
23    Self::rewrite(&mut doc.children);
24  }
25}
26
27impl DisableGfm {
28  /// Rewrite any GFM-only node in `nodes` to a plain equivalent. Recurses
29  /// into containers.
30  fn rewrite(nodes: &mut [Node]) {
31    for node in nodes.iter_mut() {
32      match node {
33        Node::Strikethrough(inner) => {
34          let span = inner.span.clone();
35          Self::rewrite(&mut inner.children);
36          let mut buf = String::from("~~");
37          Self::flatten(&inner.children, &mut buf);
38          buf.push_str("~~");
39          *node = Node::Text(Text { value: buf, span });
40        },
41        Node::Table(t) => {
42          let span = t.span.clone();
43          let mut buf = String::new();
44          for row in &t.children {
45            for (i, cell) in row.cells.iter().enumerate() {
46              if i > 0 {
47                buf.push_str(" | ");
48              }
49              Self::flatten(&cell.children, &mut buf);
50            }
51            buf.push('\n');
52          }
53          *node =
54            Node::Paragraph(Paragraph { children: vec![Node::Text(Text { value: buf, span: span.clone() })], span });
55        },
56        Node::TaskListItem(it) => {
57          let span = it.span.clone();
58          Self::rewrite(&mut it.children);
59          let prefix = if it.checked { "[x] " } else { "[ ] " };
60          let mut new_li = ListItem { children: it.children.clone(), span: span.clone() };
61          if let Some(Node::Paragraph(p)) = new_li.children.first_mut() {
62            p.children.insert(0, Node::Text(Text { value: prefix.into(), span }));
63          } else {
64            new_li.children.insert(0, Node::Text(Text { value: prefix.into(), span }));
65          }
66          *node = Node::ListItem(new_li);
67        },
68        Node::Paragraph(p) => Self::rewrite(&mut p.children),
69        Node::Heading(h) => Self::rewrite(&mut h.children),
70        Node::Bold(i) | Node::Italic(i) => Self::rewrite(&mut i.children),
71        Node::List(l) => Self::rewrite(&mut l.children),
72        Node::ListItem(li) => Self::rewrite(&mut li.children),
73        Node::Blockquote(b) => Self::rewrite(&mut b.children),
74        Node::Link(l) => Self::rewrite(&mut l.children),
75        Node::JsxElement(j) => Self::rewrite(&mut j.children),
76        Node::JsxFragment(f) => Self::rewrite(&mut f.children),
77        _ => {},
78      }
79    }
80  }
81
82  /// Append a markdown-ish flattening of `nodes` to `buf`.
83  fn flatten(nodes: &[Node], buf: &mut String) {
84    for n in nodes {
85      match n {
86        Node::Text(t) => buf.push_str(&t.value),
87        Node::InlineCode(c) => {
88          buf.push('`');
89          buf.push_str(&c.value);
90          buf.push('`');
91        },
92        Node::Bold(i) => {
93          buf.push_str("**");
94          Self::flatten(&i.children, buf);
95          buf.push_str("**");
96        },
97        Node::Italic(i) => {
98          buf.push('*');
99          Self::flatten(&i.children, buf);
100          buf.push('*');
101        },
102        Node::Link(l) => {
103          buf.push('[');
104          Self::flatten(&l.children, buf);
105          buf.push_str("](");
106          buf.push_str(&l.href);
107          buf.push(')');
108        },
109        _ => {},
110      }
111    }
112  }
113}