Some checks failed
CI / Check typos (push) Successful in 8s
CI / Check links (push) Failing after 12s
CI / Clippy (push) Successful in 54s
CI / Build and test (push) Successful in 1m11s
CI / Build container (push) Successful in 52s
CI / Deploy on waypoint (push) Successful in 44s
196 lines
3.6 KiB
Rust
196 lines
3.6 KiB
Rust
use markdown_it::parser::inline::{InlineRule, InlineState};
|
|
use markdown_it::{Node, NodeValue, Renderer};
|
|
use maud::Render;
|
|
|
|
use crate::components::mangle::{MangledBetaEmail, MangledGoogleEmail};
|
|
|
|
#[derive(Debug)]
|
|
pub struct InlineMdx(String);
|
|
|
|
impl NodeValue for InlineMdx {
|
|
fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
|
|
if mdx_style(&self.0, node, fmt) {
|
|
return;
|
|
}
|
|
|
|
if mdx_include(&self.0, node, fmt) {
|
|
return;
|
|
}
|
|
|
|
fmt.open("code", &[]);
|
|
fmt.text(&self.0);
|
|
fmt.close("code");
|
|
}
|
|
}
|
|
|
|
impl InlineRule for InlineMdx {
|
|
const MARKER: char = '{';
|
|
|
|
fn run(state: &mut InlineState<'_, '_>) -> Option<(Node, usize)> {
|
|
let input = &state.src[state.pos..state.pos_max];
|
|
if !input.starts_with('{') {
|
|
return None;
|
|
}
|
|
|
|
let mut balance = 1;
|
|
let mut end = 1;
|
|
for i in input[1..].bytes() {
|
|
match i {
|
|
b'}' => balance -= 1,
|
|
b'{' => balance += 1,
|
|
_ => {}
|
|
}
|
|
|
|
if balance == 0 {
|
|
break;
|
|
}
|
|
|
|
end += 1;
|
|
}
|
|
|
|
if balance != 0 {
|
|
return None;
|
|
}
|
|
|
|
let content = &input[1..end];
|
|
Some((Node::new(InlineMdx(content.to_owned())), content.len() + 2))
|
|
}
|
|
}
|
|
|
|
fn mdx_style(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
|
|
// Parse inside of mdx: `color(value, "text")`
|
|
let mdx = mdx
|
|
.trim()
|
|
.trim_start_matches('{')
|
|
.trim_end_matches('}')
|
|
.trim();
|
|
|
|
// Find the function name (everything before the opening parenthesis)
|
|
let paren_pos = match mdx.find('(') {
|
|
Some(x) => x,
|
|
None => return false,
|
|
};
|
|
|
|
if mdx[..paren_pos].trim() != "color" {
|
|
return false;
|
|
};
|
|
|
|
// Find matching closing parenthesis
|
|
let skip = paren_pos + 1;
|
|
let mut balance = 1;
|
|
let mut end = skip;
|
|
for i in mdx[skip..].bytes() {
|
|
match i {
|
|
b')' => balance -= 1,
|
|
b'(' => balance += 1,
|
|
_ => {}
|
|
}
|
|
|
|
if balance == 0 {
|
|
break;
|
|
}
|
|
|
|
end += 1;
|
|
}
|
|
|
|
if balance != 0 {
|
|
return false;
|
|
}
|
|
|
|
let args = mdx[skip..end].trim();
|
|
|
|
// Parse arguments: should be "value, text" or "value, \"text\""
|
|
let comma_pos = match args.find(',') {
|
|
Some(x) => x,
|
|
None => return false,
|
|
};
|
|
|
|
let value = args[..comma_pos].trim();
|
|
let text = args[comma_pos + 1..].trim();
|
|
|
|
// Strip quotes from text if present
|
|
let text = if (text.starts_with('"') && text.ends_with('"'))
|
|
|| (text.starts_with('\'') && text.ends_with('\''))
|
|
{
|
|
&text[1..text.len() - 1]
|
|
} else {
|
|
text
|
|
};
|
|
|
|
let mut style_str = String::new();
|
|
|
|
if value.starts_with("#") {
|
|
style_str.push_str("color:");
|
|
style_str.push_str(value);
|
|
style_str.push(';');
|
|
} else if value.starts_with("--") {
|
|
style_str.push_str("color:var(");
|
|
style_str.push_str(value);
|
|
style_str.push_str(");");
|
|
} else {
|
|
style_str.push_str("color:");
|
|
style_str.push_str(value);
|
|
style_str.push(';');
|
|
}
|
|
|
|
fmt.open("span", &[("style", style_str)]);
|
|
fmt.text(text);
|
|
fmt.close("span");
|
|
|
|
return true;
|
|
}
|
|
|
|
fn mdx_include(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
|
|
// Parse inside of mdx: `include(<args>)`
|
|
let args = {
|
|
let mdx = mdx
|
|
.trim()
|
|
.trim_start_matches('{')
|
|
.trim_end_matches('}')
|
|
.trim();
|
|
|
|
if !mdx.starts_with("include(") {
|
|
return false;
|
|
}
|
|
|
|
let skip = 8;
|
|
let mut balance = 1;
|
|
let mut end = skip;
|
|
for i in mdx[skip..].bytes() {
|
|
match i {
|
|
b')' => balance -= 1,
|
|
b'(' => balance += 1,
|
|
_ => {}
|
|
}
|
|
|
|
if balance == 0 {
|
|
break;
|
|
}
|
|
|
|
end += 1;
|
|
}
|
|
|
|
if balance != 0 {
|
|
return false;
|
|
}
|
|
|
|
let args = mdx[skip..end].trim();
|
|
let trail = mdx[end + 1..].trim();
|
|
if !trail.is_empty() {
|
|
return false;
|
|
}
|
|
|
|
args
|
|
};
|
|
|
|
let str = match args {
|
|
"email_beta" => MangledBetaEmail {}.render().0,
|
|
"email_goog" => MangledGoogleEmail {}.render().0,
|
|
_ => return false,
|
|
};
|
|
|
|
fmt.text_raw(&str);
|
|
|
|
return true;
|
|
}
|