Merge branch 'feat/cheddar-extensions'
This commit is contained in:
commit
14b5447aac
2 changed files with 120 additions and 36 deletions
|
@ -24,6 +24,9 @@ Twitter][].
|
||||||
challenges, before I ran out of interest
|
challenges, before I ran out of interest
|
||||||
* `tools/blog_cli` contains my tool for writing new blog posts and storing them
|
* `tools/blog_cli` contains my tool for writing new blog posts and storing them
|
||||||
in the DNS zone
|
in the DNS zone
|
||||||
|
* `tools/cheddar` contains a source code and Markdown rendering tool
|
||||||
|
that is integrated with my cgit instance to render files in various
|
||||||
|
views
|
||||||
* `ops/kms_pass.nix` is a tiny tool that emulates the user-interface of `pass`,
|
* `ops/kms_pass.nix` is a tiny tool that emulates the user-interface of `pass`,
|
||||||
but actually uses Google Cloud KMS for secret decryption
|
but actually uses Google Cloud KMS for secret decryption
|
||||||
* `ops/kontemplate` contains my Kubernetes resource templating tool (with which
|
* `ops/kontemplate` contains my Kubernetes resource templating tool (with which
|
||||||
|
@ -34,6 +37,9 @@ Twitter][].
|
||||||
* `nix/buildGo` implements a Nix library that can build Go software in the style
|
* `nix/buildGo` implements a Nix library that can build Go software in the style
|
||||||
of Bazel's `rules_go`. Go programs in this repository are built using this
|
of Bazel's `rules_go`. Go programs in this repository are built using this
|
||||||
library.
|
library.
|
||||||
|
* `nix/buildLisp` implements a Nix library that can build Common Lisp
|
||||||
|
software. Currently only SBCL is supported. Lisp programs in this
|
||||||
|
repository are built using this library.
|
||||||
* `tools/emacs-pkgs` contains various Emacs libraries that my Emacs setup uses,
|
* `tools/emacs-pkgs` contains various Emacs libraries that my Emacs setup uses,
|
||||||
for example:
|
for example:
|
||||||
* `dottime.el` provides [dottime][] in the Emacs modeline
|
* `dottime.el` provides [dottime][] in the Emacs modeline
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use comrak::nodes::{AstNode, NodeValue, NodeHtmlBlock};
|
use comrak::arena_tree::Node;
|
||||||
|
use comrak::nodes::{Ast, AstNode, NodeValue, NodeCodeBlock, NodeHtmlBlock};
|
||||||
use comrak::{Arena, parse_document, format_html, ComrakOptions};
|
use comrak::{Arena, parse_document, format_html, ComrakOptions};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
|
@ -104,27 +106,14 @@ fn iter_nodes<'a, F>(node: &'a AstNode<'a>, f: &F) where F : Fn(&'a AstNode<'a>)
|
||||||
// Instead, try finding a syntax match by comparing case insensitively (for
|
// Instead, try finding a syntax match by comparing case insensitively (for
|
||||||
// ASCII characters, anyways).
|
// ASCII characters, anyways).
|
||||||
fn find_syntax_case_insensitive(info: &str) -> Option<&'static SyntaxReference> {
|
fn find_syntax_case_insensitive(info: &str) -> Option<&'static SyntaxReference> {
|
||||||
|
// TODO(tazjin): memoize this lookup
|
||||||
SYNTAXES.syntaxes().iter().rev().find(|&s| info.eq_ignore_ascii_case(&s.name))
|
SYNTAXES.syntaxes().iter().rev().find(|&s| info.eq_ignore_ascii_case(&s.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_markdown() {
|
// Replaces code-block inside of a Markdown AST with HTML blocks rendered by
|
||||||
let document = {
|
// syntect. This enables static (i.e. no JavaScript) syntax highlighting, even
|
||||||
let mut buffer = String::new();
|
// of complex languages.
|
||||||
let stdin = io::stdin();
|
fn highlight_code_block(code_block: &NodeCodeBlock) -> NodeValue {
|
||||||
let mut stdin = stdin.lock();
|
|
||||||
stdin.read_to_string(&mut buffer).expect("failed to read stdin");
|
|
||||||
buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
let arena = Arena::new();
|
|
||||||
let root = parse_document(&arena, &document, &MD_OPTS);
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
match &ast.value {
|
|
||||||
NodeValue::CodeBlock(code_block) => {
|
|
||||||
let theme = &THEMES.themes["InspiredGitHub"];
|
let theme = &THEMES.themes["InspiredGitHub"];
|
||||||
let info = String::from_utf8_lossy(&code_block.info);
|
let info = String::from_utf8_lossy(&code_block.info);
|
||||||
|
|
||||||
|
@ -156,10 +145,99 @@ fn format_markdown() {
|
||||||
literal: rendered.into_bytes(),
|
literal: rendered.into_bytes(),
|
||||||
};
|
};
|
||||||
|
|
||||||
ast.value = NodeValue::HtmlBlock(block);
|
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();
|
||||||
|
let stdin = io::stdin();
|
||||||
|
let mut stdin = stdin.lock();
|
||||||
|
stdin.read_to_string(&mut buffer).expect("failed to read stdin");
|
||||||
|
buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(new_value) = new {
|
||||||
|
ast.value = new_value
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
format_html(root, &MD_OPTS, &mut io::stdout())
|
format_html(root, &MD_OPTS, &mut io::stdout())
|
||||||
|
|
Loading…
Add table
Reference in a new issue