1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2025-01-11 15:55:48 +00:00

Split markdown files

This commit is contained in:
Felix Ableitner 2025-01-10 10:31:04 +01:00
parent 7f2730470d
commit d4c4202281
4 changed files with 160 additions and 143 deletions

View file

@ -0,0 +1,76 @@
use markdown_it::{
parser::inline::{InlineRule, InlineState},
Node,
NodeValue,
Renderer,
};
#[derive(Debug)]
struct ArticleLink {
label: String,
title: String,
domain: String,
}
// This defines how your custom node should be rendered.
impl NodeValue for ArticleLink {
fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
let mut attrs = node.attrs.clone();
let link = format!("/article/{}@{}", self.title, self.domain);
attrs.push(("href", link));
fmt.open("a", &attrs);
fmt.text(&self.label);
fmt.close("a");
}
}
pub struct ArticleLinkScanner;
impl InlineRule for ArticleLinkScanner {
const MARKER: char = '[';
/// Find `[[Title@example.com]], return the position and split title/domain.
fn run(state: &mut InlineState) -> Option<(Node, usize)> {
let input = &state.src[state.pos..state.pos_max];
if !input.starts_with("[[") {
return None;
}
const SEPARATOR_LENGTH: usize = 2;
input.find("]]").and_then(|length| {
let start = state.pos + SEPARATOR_LENGTH;
let i = start + length - SEPARATOR_LENGTH;
let content = &state.src[start..i];
content.split_once('@').map(|(title, domain)| {
// Handle custom link label if provided, otherwise use title as label
let (domain, label) = domain.split_once('|').unwrap_or((domain, title));
let node = Node::new(ArticleLink {
label: label.to_string(),
title: title.to_string(),
domain: domain.to_string(),
});
(node, length + SEPARATOR_LENGTH)
})
})
}
}
#[test]
fn test_markdown_article_link() {
let parser = super::markdown_parser();
let plain = parser.parse("[[Title@example.com]]").render();
assert_eq!(
"<p><a href=\"/article/Title@example.com\">Title</a></p>\n",
plain
);
let with_label = parser
.parse("[[Title@example.com|Example Article]]")
.render();
assert_eq!(
"<p><a href=\"/article/Title@example.com\">Example Article</a></p>\n",
with_label
);
}

View file

@ -0,0 +1,72 @@
use katex;
use markdown_it::{
parser::inline::{InlineRule, InlineState},
Node,
NodeValue,
Renderer,
};
#[derive(Debug)]
struct MathEquation {
equation: String,
display_mode: bool,
}
impl NodeValue for MathEquation {
fn render(&self, _node: &Node, fmt: &mut dyn Renderer) {
let opts = katex::Opts::builder()
.throw_on_error(false)
.display_mode(self.display_mode)
.build()
.ok();
let katex_equation = opts.and_then(|o| katex::render_with_opts(&self.equation, o).ok());
fmt.text_raw(katex_equation.as_ref().unwrap_or(&self.equation))
}
}
pub struct MathEquationScanner;
impl InlineRule for MathEquationScanner {
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 display_mode = false;
if input.starts_with("$$\n") || input.starts_with("$$ ") {
display_mode = true;
}
const SEPARATOR_LENGTH: usize = 2;
input[SEPARATOR_LENGTH - 1..].find("$$").map(|length| {
let start = state.pos + SEPARATOR_LENGTH;
let i = start + length - SEPARATOR_LENGTH + 1;
if start > i {
return None;
}
let content = &state.src[start..i];
let node = Node::new(MathEquation {
equation: content.to_string(),
display_mode,
});
Some((node, length + SEPARATOR_LENGTH + 1))
})?
}
}
#[test]
#[expect(clippy::unwrap_used)]
fn test_markdown_equation_katex() {
let parser = super::markdown_parser();
let rendered = parser
.parse("here is a math equation: $$E=mc^2$$. Pretty cool, right?")
.render();
assert_eq!(
"<p>here is a math equation: ".to_owned()
+ &katex::render("E=mc^2").unwrap()
+ ". Pretty cool, right?</p>\n",
rendered
);
}

View file

@ -1,13 +1,15 @@
use katex; #![deny(clippy::unwrap_used)]
use article_link::ArticleLinkScanner;
use markdown_it::{ use markdown_it::{
parser::inline::{InlineRule, InlineState},
plugins::cmark::block::{heading::ATXHeading, lheading::SetextHeader}, plugins::cmark::block::{heading::ATXHeading, lheading::SetextHeader},
MarkdownIt, MarkdownIt,
Node,
NodeValue,
Renderer,
}; };
use math_equation::MathEquationScanner;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
pub mod article_link;
pub mod math_equation;
pub mod toc; pub mod toc;
pub fn render_markdown(text: &str) -> String { pub fn render_markdown(text: &str) -> String {
@ -81,138 +83,3 @@ fn markdown_parser() -> MarkdownIt {
parser parser
} }
#[derive(Debug)]
pub struct ArticleLink {
label: String,
title: String,
domain: String,
}
// This defines how your custom node should be rendered.
impl NodeValue for ArticleLink {
fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
let mut attrs = node.attrs.clone();
let link = format!("/article/{}@{}", self.title, self.domain);
attrs.push(("href", link));
fmt.open("a", &attrs);
fmt.text(&self.label);
fmt.close("a");
}
}
struct ArticleLinkScanner;
impl InlineRule for ArticleLinkScanner {
const MARKER: char = '[';
/// Find `[[Title@example.com]], return the position and split title/domain.
fn run(state: &mut InlineState) -> Option<(Node, usize)> {
let input = &state.src[state.pos..state.pos_max];
if !input.starts_with("[[") {
return None;
}
const SEPARATOR_LENGTH: usize = 2;
input.find("]]").and_then(|length| {
let start = state.pos + SEPARATOR_LENGTH;
let i = start + length - SEPARATOR_LENGTH;
let content = &state.src[start..i];
content.split_once('@').map(|(title, domain)| {
// Handle custom link label if provided, otherwise use title as label
let (domain, label) = domain.split_once('|').unwrap_or((domain, title));
let node = Node::new(ArticleLink {
label: label.to_string(),
title: title.to_string(),
domain: domain.to_string(),
});
(node, length + SEPARATOR_LENGTH)
})
})
}
}
#[derive(Debug)]
pub struct MathEquation {
equation: String,
display_mode: bool,
}
impl NodeValue for MathEquation {
fn render(&self, _node: &Node, fmt: &mut dyn Renderer) {
let opts = katex::Opts::builder()
.throw_on_error(false)
.display_mode(self.display_mode)
.build()
.ok();
let katex_equation = opts.and_then(|o| katex::render_with_opts(&self.equation, o).ok());
fmt.text_raw(katex_equation.as_ref().unwrap_or(&self.equation))
}
}
struct MathEquationScanner;
impl InlineRule for MathEquationScanner {
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 display_mode = false;
if input.starts_with("$$\n") || input.starts_with("$$ ") {
display_mode = true;
}
const SEPARATOR_LENGTH: usize = 2;
input[SEPARATOR_LENGTH - 1..].find("$$").map(|length| {
let start = state.pos + SEPARATOR_LENGTH;
let i = start + length - SEPARATOR_LENGTH + 1;
if start > i {
return None;
}
let content = &state.src[start..i];
let node = Node::new(MathEquation {
equation: content.to_string(),
display_mode,
});
Some((node, length + SEPARATOR_LENGTH + 1))
})?
}
}
#[test]
fn test_markdown_article_link() {
let parser = markdown_parser();
let plain = parser.parse("[[Title@example.com]]").render();
assert_eq!(
"<p><a href=\"/article/Title@example.com\">Title</a></p>\n",
plain
);
let with_label = parser
.parse("[[Title@example.com|Example Article]]")
.render();
assert_eq!(
"<p><a href=\"/article/Title@example.com\">Example Article</a></p>\n",
with_label
);
}
#[test]
#[expect(clippy::unwrap_used)]
fn test_markdown_equation_katex() {
let parser = markdown_parser();
let rendered = parser
.parse("here is a math equation: $$E=mc^2$$. Pretty cool, right?")
.render();
assert_eq!(
"<p>here is a math equation: ".to_owned()
+ &katex::render("E=mc^2").unwrap()
+ ". Pretty cool, right?</p>\n",
rendered
);
}

View file

@ -100,7 +100,7 @@ impl TocBuilder {
} }
} }
fn push(&mut self, level: u8, name: String, id: String) -> &str { fn push(&mut self, level: u8, name: String, id: String) -> String {
debug_assert!(level >= 1); debug_assert!(level >= 1);
self.fold_until(level); self.fold_until(level);
@ -132,8 +132,10 @@ impl TocBuilder {
}, },
}); });
let just_inserted = self.chain.last_mut().unwrap(); self.chain
&just_inserted.sec_number .last()
.map(|l| l.sec_number.clone())
.unwrap_or_default()
} }
} }