about summary refs log tree commit diff
path: root/tools
diff options
context:
space:
mode:
authorVincent Ambo <Vincent Ambo>2020-01-11T05·06+0000
committerVincent Ambo <Vincent Ambo>2020-01-11T05·06+0000
commit2236d43ff7b496616c0072b0f02173e7022d81fa (patch)
treecc5af803c0f5b2d8f1722265aa547dec474bd987 /tools
parentd4e469508f9bb9fdd8c349c53ab4fd0ef5f3e87e (diff)
feat(cheddar): Implement callout paragraphs
Implements support for tagging paragraphs that begin with a callout
word (TODO, WARNING, QUESTION, TIP) with an additional `cheddar-*`
class that makes it possible to render these callouts specially.

This is currently not the nicest implementation, but it works.
Diffstat (limited to 'tools')
-rw-r--r--tools/cheddar/src/main.rs68
1 files changed, 68 insertions, 0 deletions
diff --git a/tools/cheddar/src/main.rs b/tools/cheddar/src/main.rs
index 3eb6fc605e..0912e29ec0 100644
--- a/tools/cheddar/src/main.rs
+++ b/tools/cheddar/src/main.rs
@@ -1,6 +1,8 @@
+use comrak::arena_tree::Node;
 use comrak::nodes::{Ast, AstNode, NodeValue, NodeCodeBlock, NodeHtmlBlock};
 use comrak::{Arena, parse_document, format_html, ComrakOptions};
 use lazy_static::lazy_static;
+use std::cell::RefCell;
 use std::env;
 use std::ffi::OsStr;
 use std::io::BufRead;
@@ -122,6 +124,52 @@ fn highlight_code_block(code_block: &NodeCodeBlock) -> NodeValue {
     NodeValue::HtmlBlock(block)
 }
 
+// Supported callout elements (which each have their own distinct rendering):
+enum Callout {
+    Todo,
+    Warning,
+    Question,
+    Tip,
+}
+
+// Determine whether the first child of the supplied node contains a text that
+// should cause a callout section to be rendered.
+fn has_callout<'a>(node: &Node<'a, RefCell<Ast>>) -> Option<Callout> {
+    match node.first_child().map(|c| c.data.borrow()) {
+        Some(child) => match &child.value {
+            NodeValue::Text(text) => {
+                if text.starts_with("TODO".as_bytes()) {
+                    return Some(Callout::Todo)
+                } else if text.starts_with("WARNING".as_bytes()) {
+                    return Some(Callout::Warning)
+                } else if text.starts_with("QUESTION".as_bytes()) {
+                    return Some(Callout::Question)
+                } else if text.starts_with("TIP".as_bytes()) {
+                    return Some(Callout::Tip)
+                }
+
+                return None
+            },
+            _ => return None,
+        },
+        _ => return None,
+    }
+}
+
+fn format_callout_paragraph(callout: Callout) -> NodeValue {
+    let class = match callout {
+        Callout::Todo => "cheddar-todo",
+        Callout::Warning => "cheddar-warning",
+        Callout::Question => "cheddar-question",
+        Callout::Tip => "cheddar-tip",
+    };
+
+    NodeValue::HtmlBlock(NodeHtmlBlock {
+        block_type: 1,
+        literal: format!("<p class=\"cheddar-callout {}\">", class).into_bytes(),
+    })
+}
+
 fn format_markdown() {
     let document = {
         let mut buffer = String::new();
@@ -134,12 +182,32 @@ fn format_markdown() {
     let arena = Arena::new();
     let root = parse_document(&arena, &document, &MD_OPTS);
 
+    // This node must exist with a lifetime greater than that of the parsed AST
+    // in case that callouts are encountered (otherwise insertion into the tree
+    // is not possible).
+    let p_close = Node::new(RefCell::new(Ast {
+        start_line: 0, // TODO(tazjin): hrmm
+        content: vec![],
+        open: false,
+        last_line_blank: false,
+        value: NodeValue::HtmlBlock(NodeHtmlBlock {
+            block_type: 1,
+            literal: "</p>".as_bytes().to_vec(),
+        }),
+    }));
+
     // Syntax highlighting is implemented by traversing the arena and
     // replacing all code blocks with HTML blocks rendered by syntect.
     iter_nodes(root, &|node| {
         let mut ast = node.data.borrow_mut();
         let new = match &ast.value {
             NodeValue::CodeBlock(code) => Some(highlight_code_block(code)),
+            NodeValue::Paragraph => if let Some(callout) = has_callout(node) {
+                node.insert_after(&p_close);
+                Some(format_callout_paragraph(callout))
+            } else {
+                None
+            },
             _ => None,
         };