Skip to main content

dmc_transform/builtin/
disable_gfm.rs

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