Upgrade mdx parser
This commit is contained in:
@@ -9,7 +9,7 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use crate::components::fa::FAIcon;
|
use crate::components::fa::FAIcon;
|
||||||
use crate::components::mangle::{MangledBetaEmail, MangledGoogleEmail};
|
use crate::components::mangle::{MangledBetaEmail, MangledGoogleEmail};
|
||||||
use crate::components::misc::Backlinks;
|
use crate::components::misc::{Backlinks, FarLink};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref MdParser: MarkdownIt = {
|
static ref MdParser: MarkdownIt = {
|
||||||
@@ -159,6 +159,10 @@ impl NodeValue for InlineMdx {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mdx_external(&self.0, node, fmt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fmt.open("code", &[]);
|
fmt.open("code", &[]);
|
||||||
fmt.text(&self.0);
|
fmt.text(&self.0);
|
||||||
fmt.close("code");
|
fmt.close("code");
|
||||||
@@ -200,78 +204,83 @@ impl InlineRule for InlineMdx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mdx_style(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
|
fn mdx_style(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
|
||||||
// Parse inside of mdx: `style(<style>) <content>`
|
// Parse inside of mdx: `color(value, "text")`
|
||||||
let (style, content) = {
|
let mdx = mdx
|
||||||
let mdx = mdx.trim();
|
.trim()
|
||||||
if !mdx.starts_with("style(") {
|
.trim_start_matches('{')
|
||||||
return false;
|
.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,
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let skip = 6;
|
if balance == 0 {
|
||||||
let mut balance = 1;
|
break;
|
||||||
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 {
|
end += 1;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let style = mdx[skip..end].trim();
|
if balance != 0 {
|
||||||
let content = mdx[end + 1..].trim();
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
(style, content)
|
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();
|
let mut style_str = String::new();
|
||||||
|
|
||||||
for kv in style.split(";") {
|
if value.starts_with("#") {
|
||||||
let mut s = kv.split(":");
|
style_str.push_str("color:");
|
||||||
let k = s.next();
|
style_str.push_str(value);
|
||||||
let v = s.next();
|
style_str.push(';');
|
||||||
|
} else if value.starts_with("--") {
|
||||||
if k.is_none() || v.is_none() || s.next().is_some() {
|
style_str.push_str("color:var(");
|
||||||
return false;
|
style_str.push_str(value);
|
||||||
}
|
style_str.push_str(");");
|
||||||
|
} else {
|
||||||
#[expect(clippy::unwrap_used)] // Checked previously
|
style_str.push_str("color:");
|
||||||
let k = k.unwrap().trim();
|
style_str.push_str(value);
|
||||||
|
style_str.push(';');
|
||||||
#[expect(clippy::unwrap_used)] // Checked previously
|
|
||||||
let v = v.unwrap().trim();
|
|
||||||
|
|
||||||
match k {
|
|
||||||
"color" => {
|
|
||||||
style_str.push_str("color:");
|
|
||||||
style_str.push_str(v);
|
|
||||||
style_str.push(';');
|
|
||||||
}
|
|
||||||
|
|
||||||
"color_var" => {
|
|
||||||
style_str.push_str("color:var(--");
|
|
||||||
style_str.push_str(v);
|
|
||||||
style_str.push_str(");");
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => continue,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only works with text, could be reworked to do basic md styling
|
|
||||||
// (italics, bold, tab, code)
|
|
||||||
fmt.open("span", &[("style", style_str)]);
|
fmt.open("span", &[("style", style_str)]);
|
||||||
fmt.text(content);
|
fmt.text(text);
|
||||||
fmt.close("span");
|
fmt.close("span");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -280,7 +289,12 @@ fn mdx_style(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
|
|||||||
fn mdx_include(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
|
fn mdx_include(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
|
||||||
// Parse inside of mdx: `include(<args>)`
|
// Parse inside of mdx: `include(<args>)`
|
||||||
let args = {
|
let args = {
|
||||||
let mdx = mdx.trim();
|
let mdx = mdx
|
||||||
|
.trim()
|
||||||
|
.trim_start_matches('{')
|
||||||
|
.trim_end_matches('}')
|
||||||
|
.trim();
|
||||||
|
|
||||||
if !mdx.starts_with("include(") {
|
if !mdx.starts_with("include(") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -326,6 +340,105 @@ fn mdx_include(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mdx_external(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
|
||||||
|
// Parse inside of mdx: `external("text", "link")`
|
||||||
|
let args = {
|
||||||
|
let mdx = mdx
|
||||||
|
.trim()
|
||||||
|
.trim_start_matches('{')
|
||||||
|
.trim_end_matches('}')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if !mdx.starts_with("external(") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let skip = 9;
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: better parsing, handle , in string
|
||||||
|
let mut split = args.split(",");
|
||||||
|
|
||||||
|
let title = match split.next() {
|
||||||
|
Some(mut x) => {
|
||||||
|
x = x.trim();
|
||||||
|
|
||||||
|
if &x[0..1] == "\"" {
|
||||||
|
x = &x[1..]
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if &x[x.len() - 1..] == "\"" {
|
||||||
|
x = &x[0..x.len() - 1]
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let link = match split.next() {
|
||||||
|
Some(mut x) => {
|
||||||
|
x = x.trim();
|
||||||
|
|
||||||
|
if &x[0..1] == "\"" {
|
||||||
|
x = &x[1..]
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if &x[x.len() - 1..] == "\"" {
|
||||||
|
x = &x[0..x.len() - 1]
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if split.next().is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.text_raw(&FarLink(link, title).render().0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// MARK: yaml frontmatter
|
// MARK: yaml frontmatter
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -6,23 +6,26 @@ slug = "handouts"
|
|||||||
|
|
||||||
# Mark's Handouts
|
# Mark's Handouts
|
||||||
|
|
||||||
[ORMC]: https://circles.math.ucla.edu/circles
|
|
||||||
|
|
||||||
This page lists all the handouts I've written for my classes at the [ORMC],
|
This page lists all the handouts I've written for my classes at the
|
||||||
|
{{external("ORMC", "https://circles.math.ucla.edu/circles")}},
|
||||||
arguably the best math circle in the western world. We teach students mathematics
|
arguably the best math circle in the western world. We teach students mathematics
|
||||||
far beyond the regular school curriculum, much like [AOPS](https://artofproblemsolving.com)
|
far beyond the regular school curriculum, much like
|
||||||
and the [BMC](https://mathcircle.berkeley.edu).
|
{{external("AOPS", "https://artofproblemsolving.com")}}
|
||||||
|
and the
|
||||||
|
{{external("BMC", "https://mathcircle.berkeley.edu")}}
|
||||||
|
|
||||||
|
|
||||||
<br></br>
|
<br></br>
|
||||||
|
|
||||||
{style(color_var:pink) For my students: } \
|
{{color(--pink, "For my students:")}} \
|
||||||
Don't look at solutions we haven't discussed,
|
Don't look at solutions we haven't discussed,
|
||||||
and don't start any handouts before class. That spoils all the fun!
|
and don't start any handouts before class. That spoils all the fun!
|
||||||
|
|
||||||
{style(color_var:green) For everyone else:} \
|
{{color(--green, "For everyone else:")}} \
|
||||||
If you're using any of these, please let me know---especially \
|
If you're using any of these, please let me know---especially \
|
||||||
if you find errors, mistakes, or a poorly designed section. \
|
if you find errors, mistakes, or a poorly designed section. \
|
||||||
Such things must be fixed! { include(email_beta) }
|
Such things must be fixed! {{ include(email_beta) }}
|
||||||
|
|
||||||
<br></br>
|
<br></br>
|
||||||
|
|
||||||
@@ -33,8 +36,10 @@ are written with this in mind.\
|
|||||||
I do not expect the average student to finish all problems during this two-hour session.
|
I do not expect the average student to finish all problems during this two-hour session.
|
||||||
If the class finishes early, the lesson is either too short or too easy.
|
If the class finishes early, the lesson is either too short or too easy.
|
||||||
|
|
||||||
The sources for all these handouts are available [here](https://git.betalupi.com/mark/handouts).\
|
The sources for all these handouts are available
|
||||||
Some are written in LaTeX, some are in [Typst](https://typst.app). \
|
{{external("here", "https://git.betalupi.com/mark/handouts")}} \
|
||||||
|
Some are written in LaTeX, some are in
|
||||||
|
{{external("Typst", "https://typst.app")}}. \
|
||||||
The latter is vastly superior.
|
The latter is vastly superior.
|
||||||
|
|
||||||
<br></br>
|
<br></br>
|
||||||
|
|||||||
@@ -9,37 +9,37 @@ Also see [what's a "betalupi?"](/whats-a-betalupi)
|
|||||||
|
|
||||||
## Projects
|
## Projects
|
||||||
|
|
||||||
- **RedoxOS**, a general-purpose, microkernel-based operating system written in Rust. _{ style(color_var:grey) [enthusiast] }_
|
- **RedoxOS**, a general-purpose, microkernel-based operating system written in Rust. _{{color(--grey, "[enthusiast]")}}
|
||||||
|
|
||||||
- { style(color_var:grey) Status: } { style(color_var:yellow) Passive. }
|
- {{color(--grey, "Status: ")}} {{color(--yellow, "Passive.")}}
|
||||||
- { style(color_var:grey) Website: } [:fa-link: redox-os.org](https://www.redox-os.org/)
|
- {{color(--grey, "Website: ")}} [:fa-link: redox-os.org](https://www.redox-os.org/)
|
||||||
|
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
- **Tectonic**, the LaTeX engine that is pleasant to use.
|
- **Tectonic**, the LaTeX engine that is pleasant to use.
|
||||||
Experimental, but fully functional. _{ style(color_var:grey) [co-maintainer] }_
|
Experimental, but fully functional. _{{color(--grey, "[co-maintainer]")}}_
|
||||||
|
|
||||||
- { style(color_var:grey) Status: } { style(color_var:yellow) Passive. } [Typst](https://github.com/typst/typst) is better.
|
- {{color(--grey, "Status: ")}} {{color(--yellow, "Abandoned. ")}} [Typst](https://github.com/typst/typst) is better.
|
||||||
- { style(color_var:grey) Main repo: } [:fa-github: Tectonic](https://github.com/tectonic-typesetting/tectonic)
|
- {{color(--grey, "Main repo: ")}} [:fa-github: Tectonic](https://github.com/tectonic-typesetting/tectonic)
|
||||||
- { style(color_var:grey) Bundle tools: } [:fa-github: tectonic-texlive-bundles](https://github.com/tectonic-typesetting/tectonic-texlive-bundles)
|
- {{color(--grey, "Bundle tools: ")}} [:fa-github: tectonic-texlive-bundles](https://github.com/tectonic-typesetting/tectonic-texlive-bundles)
|
||||||
|
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
- **Daisy**, a pretty TUI scientific calculator. _{style(color_var:grey) [author] }_
|
- **Daisy**, a pretty TUI scientific calculator. _{{color(--grey, "[author]")}}_
|
||||||
|
|
||||||
- {style(color_var:grey) Status: } {style(color_var:orange) Done. } Used this to learn Rust. [Numbat](https://numbat.dev) is better.
|
- {{color(--grey, "Status: ")}} {{color(--orange, "Done. ")}} Used this to learn Rust. [Numbat](https://numbat.dev) is better.
|
||||||
- {style(color_var:grey) Repository: } [:fa-github: rm-dr/daisy](https://github.com/rm-dr/daisy)
|
- {{color(--grey, "Repository: ")}} [:fa-github: rm-dr/daisy](https://github.com/rm-dr/daisy)
|
||||||
- {style(color_var:grey) Website: } [:fa-link: daisy.betalupi.com](https://daisy.betalupi.com) (WASM demo)
|
- {{color(--grey, "Website: ")}} [:fa-link: daisy.betalupi.com](https://daisy.betalupi.com) (WASM demo)
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
- **Lamb**, a lambda calculus engine. _{style(color_var:grey) [author] }_
|
- **Lamb**, a lambda calculus engine. _{{color(--grey, "[author] ")}}_
|
||||||
- {style(color_var:grey) Status: } {style(color_var:orange) Done. } Fun little project.
|
- {{color(--grey, "Status: ")}} {{color(--orange, "Done. ")}} Fun little project.
|
||||||
- {style(color_var:grey) Repository: } [:fa-github: rm-dr/lamb](https://github.com/rm-dr/lamb)
|
- {{color(--grey, "Repository: ")}} [:fa-github: rm-dr/lamb](https://github.com/rm-dr/lamb)
|
||||||
- {style(color_var:grey) PyPi: } [:fa-python: lamb-engine](https://pypi.org/project/lamb-engine)
|
- {{color(--grey, "PyPi: ")}} [:fa-python: lamb-engine](https://pypi.org/project/lamb-engine)
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
Reference in New Issue
Block a user