This commit is contained in:
Maciej Jur 2024-09-16 23:01:38 +02:00
parent f97ec9e036
commit 1c9d234430
Signed by: kamov
GPG key ID: 191CBFF5F72ECAFD
9 changed files with 480 additions and 455 deletions

View file

@ -1,8 +1,8 @@
use camino::Utf8Path; use camino::Utf8Path;
use hauchiwa::{Link, LinkDate, Sack}; use hauchiwa::Sack;
use hypertext::{html_elements, maud, maud_move, GlobalAttributes, Raw, Renderable}; use hypertext::{html_elements, maud, maud_move, GlobalAttributes, Raw, Renderable};
use crate::{html::Post, text::md::parse}; use crate::{html::Post, text::md::parse, Link, LinkDate};
const INTRO: &str = r#" const INTRO: &str = r#"
## ##

View file

@ -1,7 +1,7 @@
use hauchiwa::{LinkDate, Sack}; use hauchiwa::Sack;
use hypertext::{html_elements, maud_move, GlobalAttributes, Renderable}; use hypertext::{html_elements, maud_move, GlobalAttributes, Renderable};
use crate::html::page; use crate::{html::page, LinkDate};
pub fn list<'s, 'g, 'html>( pub fn list<'s, 'g, 'html>(
sack: &'s Sack, sack: &'s Sack,

View file

@ -1,6 +1,10 @@
use hauchiwa::{Outline, Sack, TreePage}; use std::collections::HashMap;
use camino::Utf8Path;
use hauchiwa::{Outline, Sack};
use hypertext::{html_elements, maud_move, GlobalAttributes, Raw, Renderable}; use hypertext::{html_elements, maud_move, GlobalAttributes, Raw, Renderable};
use crate::{html::Wiki, Link};
/// Render the outline for a document /// Render the outline for a document
pub(crate) fn show_outline(outline: Outline) -> impl Renderable { pub(crate) fn show_outline(outline: Outline) -> impl Renderable {
@ -41,9 +45,50 @@ pub(crate) fn emit_bibliography(bib: Vec<String>) -> impl Renderable {
) )
} }
#[derive(Debug)]
pub struct TreePage {
pub link: Option<Link>,
pub subs: HashMap<String, TreePage>,
}
impl TreePage {
fn new() -> Self {
TreePage {
link: None,
subs: HashMap::new(),
}
}
fn add_link(&mut self, link: &Link) {
let mut ptr = self;
for part in link.path.iter().skip(1) {
ptr = ptr.subs.entry(part.to_string()).or_insert(TreePage::new());
}
ptr.link = Some(link.clone());
}
fn from_iter(iter: impl Iterator<Item = Link>) -> Self {
let mut tree = Self::new();
for link in iter {
tree.add_link(&link);
}
tree
}
}
/// Render the page tree /// Render the page tree
pub(crate) fn show_page_tree(sack: &Sack, glob: &str) -> impl Renderable { pub(crate) fn show_page_tree(sack: &Sack, glob: &str) -> impl Renderable {
let tree = sack.get_tree(glob); let tree =
TreePage::from_iter(
sack.get_meta::<Wiki>(glob)
.into_iter()
.map(|(path, meta)| Link {
path: Utf8Path::new("/").join(path),
name: meta.title.clone(),
desc: None,
}),
);
maud_move!( maud_move!(
h2 .link-tree__heading { h2 .link-tree__heading {

View file

@ -9,19 +9,19 @@ pub mod wiki;
use std::collections::HashMap; use std::collections::HashMap;
use camino::{Utf8Path, Utf8PathBuf}; use camino::Utf8Path;
use chrono::{DateTime, Datelike, Utc}; use chrono::Datelike;
use hauchiwa::{Bibliography, Link, LinkDate, Linkable, Outline, Sack}; use hauchiwa::{Bibliography, Outline, Sack};
use hayagriva::Library;
use hypertext::{html_elements, maud, maud_move, GlobalAttributes, Raw, Renderable}; use hypertext::{html_elements, maud, maud_move, GlobalAttributes, Raw, Renderable};
pub(crate) use home::home; pub(crate) use home::home;
use post::article; use post::article;
pub(crate) use post::Post; pub(crate) use post::Post;
use serde::Deserialize;
pub(crate) use slideshow::Slideshow; pub(crate) use slideshow::Slideshow;
pub(crate) use wiki::Wiki; pub(crate) use wiki::Wiki;
use crate::LinkDate;
fn navbar() -> impl Renderable { fn navbar() -> impl Renderable {
static ITEMS: &[(&str, &str)] = &[ static ITEMS: &[(&str, &str)] = &[
("Posts", "/posts/"), ("Posts", "/posts/"),
@ -198,7 +198,7 @@ where
) )
} }
pub(crate) fn search<'s, 'html>(sack: &'s Sack) -> String { pub(crate) fn search(sack: &Sack) -> String {
page( page(
sack, sack,
maud!( maud!(
@ -222,7 +222,7 @@ pub fn as_html(
flox(&meta.title, parsed, sack, outline, bibliography) flox(&meta.title, parsed, sack, outline, bibliography)
} }
pub(crate) fn flox<'p, 's, 'html>( pub(crate) fn flox(
title: &str, title: &str,
parsed: &str, parsed: &str,
sack: &Sack, sack: &Sack,

View file

@ -1,6 +1,6 @@
use camino::{Utf8Path, Utf8PathBuf}; use camino::Utf8Path;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use hauchiwa::{Bibliography, Link, LinkDate, Linkable, Outline, Sack}; use hauchiwa::{Bibliography, Outline, Sack};
use hayagriva::Library; use hayagriva::Library;
use hypertext::{html_elements, maud_move, GlobalAttributes, Raw, Renderable}; use hypertext::{html_elements, maud_move, GlobalAttributes, Raw, Renderable};
use serde::Deserialize; use serde::Deserialize;
@ -8,103 +8,92 @@ use serde::Deserialize;
/// Represents a simple post. /// Represents a simple post.
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct Post { pub struct Post {
pub title: String, pub title: String,
#[serde(with = "super::isodate")] #[serde(with = "super::isodate")]
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
pub desc: Option<String>, pub desc: Option<String>,
pub scripts: Option<Vec<String>>, pub scripts: Option<Vec<String>>,
} }
pub fn parse_content( pub fn parse_content(
content: &str, content: &str,
sack: &Sack, sack: &Sack,
path: &Utf8Path, path: &Utf8Path,
library: Option<&Library>, library: Option<&Library>,
) -> (String, Outline, Bibliography) { ) -> (String, Outline, Bibliography) {
crate::text::md::parse(content, sack, path, library) crate::text::md::parse(content, sack, path, library)
} }
pub fn as_html( pub fn as_html(
meta: &Post, meta: &Post,
parsed: &str, parsed: &str,
sack: &Sack, sack: &Sack,
outline: Outline, outline: Outline,
bibliography: Bibliography, bibliography: Bibliography,
) -> String { ) -> String {
post(meta, parsed, sack, outline, bibliography) post(meta, parsed, sack, outline, bibliography)
.unwrap() .unwrap()
.render() .render()
.into() .into()
}
pub fn as_link(meta: &Post, path: Utf8PathBuf) -> Option<Linkable> {
Some(Linkable::Date(LinkDate {
link: Link {
path,
name: meta.title.to_owned(),
desc: meta.desc.to_owned(),
},
date: meta.date.to_owned(),
}))
} }
pub fn post<'s, 'p, 'html>( pub fn post<'s, 'p, 'html>(
meta: &'p Post, meta: &'p Post,
parsed: &'p str, parsed: &'p str,
sack: &'s Sack, sack: &'s Sack,
outline: Outline, outline: Outline,
bibliography: Bibliography, bibliography: Bibliography,
) -> Result<impl Renderable + 'html, String> ) -> Result<impl Renderable + 'html, String>
where where
's: 'html, 's: 'html,
'p: 'html, 'p: 'html,
{ {
let main = maud_move!( let main = maud_move!(
main { main {
(article(&meta.title, parsed, sack, outline, bibliography)) (article(&meta.title, parsed, sack, outline, bibliography))
} }
); );
crate::html::page(sack, main, meta.title.clone(), meta.scripts.as_deref()) crate::html::page(sack, main, meta.title.clone(), meta.scripts.as_deref())
} }
pub fn article<'p, 's, 'html>( pub fn article<'p, 's, 'html>(
title: &'p str, title: &'p str,
parsed: &'p str, parsed: &'p str,
_: &'s Sack, _: &'s Sack,
outline: Outline, outline: Outline,
bibliography: Bibliography, bibliography: Bibliography,
) -> impl Renderable + 'html ) -> impl Renderable + 'html
where where
's: 'html, 's: 'html,
'p: 'html, 'p: 'html,
{ {
maud_move!( maud_move!(
div .wiki-main { div .wiki-main {
// Slide in/out for mobile // Slide in/out for mobile
input #wiki-aside-shown type="checkbox" hidden; input #wiki-aside-shown type="checkbox" hidden;
aside .wiki-aside { aside .wiki-aside {
// Slide button // Slide button
label .wiki-aside__slider for="wiki-aside-shown" { label .wiki-aside__slider for="wiki-aside-shown" {
img .wiki-icon src="/static/svg/double-arrow.svg" width="24" height="24"; img .wiki-icon src="/static/svg/double-arrow.svg" width="24" height="24";
} }
(crate::html::misc::show_outline(outline)) (crate::html::misc::show_outline(outline))
} }
article .wiki-article /*class:list={classlist)*/ { article .wiki-article /*class:list={classlist)*/ {
header class="markdown" { header class="markdown" {
h1 #top { (title) } h1 #top { (title) }
} }
section .wiki-article__markdown.markdown { section .wiki-article__markdown.markdown {
(Raw(parsed)) (Raw(parsed))
} }
@if let Some(bib) = bibliography.0 { @if let Some(bib) = bibliography.0 {
(crate::html::misc::emit_bibliography(bib)) (crate::html::misc::emit_bibliography(bib))
} }
} }
} }
) )
} }

View file

@ -1,6 +1,6 @@
use camino::{Utf8Path, Utf8PathBuf}; use camino::Utf8Path;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use hauchiwa::{Bibliography, Link, LinkDate, Linkable, Outline, Sack}; use hauchiwa::{Bibliography, Outline, Sack};
use hayagriva::Library; use hayagriva::Library;
use hypertext::{html_elements, maud, GlobalAttributes, Raw, Renderable}; use hypertext::{html_elements, maud, GlobalAttributes, Raw, Renderable};
use serde::Deserialize; use serde::Deserialize;
@ -16,77 +16,66 @@ const CSS: &str = r#"
/// Represents a slideshow /// Represents a slideshow
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub(crate) struct Slideshow { pub(crate) struct Slideshow {
pub title: String, pub title: String,
#[serde(with = "super::isodate")] #[serde(with = "super::isodate")]
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
pub desc: Option<String>, pub desc: Option<String>,
} }
pub fn parse_content( pub fn parse_content(
content: &str, content: &str,
sack: &Sack, sack: &Sack,
path: &Utf8Path, path: &Utf8Path,
library: Option<&Library>, library: Option<&Library>,
) -> (String, Outline, Bibliography) { ) -> (String, Outline, Bibliography) {
let parsed = content let parsed = content
.split("\n-----\n") .split("\n-----\n")
.map(|chunk| { .map(|chunk| {
chunk chunk
.split("\n---\n") .split("\n---\n")
.map(|slide| crate::text::md::parse(&slide, sack, path, library).0) .map(|slide| crate::text::md::parse(slide, sack, path, library).0)
.collect::<Vec<_>>() .collect::<Vec<_>>()
}) })
.map(|stack| match stack.len() > 1 { .map(|stack| match stack.len() > 1 {
true => format!( true => format!(
"<section>{}</section>", "<section>{}</section>",
stack stack
.into_iter() .into_iter()
.map(|slide| format!("<section>{slide}</section>")) .map(|slide| format!("<section>{slide}</section>"))
.collect::<String>() .collect::<String>()
), ),
false => format!("<section>{}</section>", stack[0]), false => format!("<section>{}</section>", stack[0]),
}) })
.collect::<String>(); .collect::<String>();
(parsed, Outline(vec![]), Bibliography(None)) (parsed, Outline(vec![]), Bibliography(None))
} }
pub fn as_html( pub fn as_html(
slides: &Slideshow, slides: &Slideshow,
parsed: &str, parsed: &str,
sack: &Sack, sack: &Sack,
_: Outline, _: Outline,
_: Bibliography, _: Bibliography,
) -> String { ) -> String {
show(slides, sack, parsed) show(slides, sack, parsed)
}
pub fn as_link(slides: &Slideshow, path: Utf8PathBuf) -> Option<Linkable> {
Some(Linkable::Date(LinkDate {
link: Link {
path,
name: slides.title.to_owned(),
desc: slides.desc.to_owned(),
},
date: slides.date.to_owned(),
}))
} }
pub fn show(fm: &Slideshow, sack: &Sack, slides: &str) -> String { pub fn show(fm: &Slideshow, sack: &Sack, slides: &str) -> String {
crate::html::bare( crate::html::bare(
sack, sack,
maud!( maud!(
div .reveal { div .reveal {
div .slides { div .slides {
(Raw(slides)) (Raw(slides))
} }
} }
style { (Raw(CSS)) } style { (Raw(CSS)) }
), ),
fm.title.clone(), fm.title.clone(),
Some(&["reveal".into()]), Some(&["reveal".into()]),
) )
.unwrap() .unwrap()
.render() .render()
.into() .into()
} }

View file

@ -1,5 +1,5 @@
use camino::{Utf8Path, Utf8PathBuf}; use camino::Utf8Path;
use hauchiwa::{Bibliography, Link, Linkable, Outline, Sack}; use hauchiwa::{Bibliography, Outline, Sack};
use hayagriva::Library; use hayagriva::Library;
use hypertext::{html_elements, maud_move, GlobalAttributes, Raw, Renderable}; use hypertext::{html_elements, maud_move, GlobalAttributes, Raw, Renderable};
use serde::Deserialize; use serde::Deserialize;
@ -29,14 +29,6 @@ pub fn as_html(
wiki(meta, parsed, sack, outline, bibliography) wiki(meta, parsed, sack, outline, bibliography)
} }
pub fn as_link(meta: &Wiki, path: Utf8PathBuf) -> Option<Linkable> {
Some(Linkable::Link(Link {
path,
name: meta.title.to_owned(),
desc: None,
}))
}
fn wiki( fn wiki(
matter: &Wiki, matter: &Wiki,
parsed: &str, parsed: &str,

View file

@ -2,9 +2,10 @@ mod html;
mod text; mod text;
mod ts; mod ts;
use camino::Utf8Path; use camino::{Utf8Path, Utf8PathBuf};
use chrono::{DateTime, Utc};
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use hauchiwa::{Collection, Link, LinkDate, Processor, Website}; use hauchiwa::{Collection, Processor, Website};
use html::{Post, Slideshow, Wiki}; use html::{Post, Slideshow, Wiki};
use hypertext::Renderable; use hypertext::Renderable;
@ -20,6 +21,25 @@ enum Mode {
Watch, Watch,
} }
#[derive(Debug, Clone)]
pub struct Link {
pub path: Utf8PathBuf,
pub name: String,
pub desc: Option<String>,
}
#[derive(Debug, Clone)]
pub struct LinkDate {
pub link: Link,
pub date: DateTime<Utc>,
}
#[derive(Debug, Clone)]
pub enum Linkable {
Link(Link),
Date(LinkDate),
}
fn main() { fn main() {
let args = Args::parse(); let args = Args::parse();
@ -32,7 +52,6 @@ fn main() {
Processor { Processor {
read_content: crate::html::post::parse_content, read_content: crate::html::post::parse_content,
to_html: crate::html::post::as_html, to_html: crate::html::post::as_html,
to_link: crate::html::post::as_link,
}, },
), ),
Collection::glob_with::<Post>( Collection::glob_with::<Post>(
@ -42,7 +61,6 @@ fn main() {
Processor { Processor {
read_content: crate::html::post::parse_content, read_content: crate::html::post::parse_content,
to_html: crate::html::post::as_html, to_html: crate::html::post::as_html,
to_link: crate::html::post::as_link,
}, },
), ),
Collection::glob_with::<Slideshow>( Collection::glob_with::<Slideshow>(
@ -52,7 +70,6 @@ fn main() {
Processor { Processor {
read_content: crate::html::slideshow::parse_content, read_content: crate::html::slideshow::parse_content,
to_html: crate::html::slideshow::as_html, to_html: crate::html::slideshow::as_html,
to_link: crate::html::slideshow::as_link,
}, },
), ),
Collection::glob_with::<Wiki>( Collection::glob_with::<Wiki>(
@ -62,7 +79,6 @@ fn main() {
Processor { Processor {
read_content: crate::html::wiki::parse_content, read_content: crate::html::wiki::parse_content,
to_html: crate::html::wiki::as_html, to_html: crate::html::wiki::as_html,
to_link: crate::html::wiki::as_link,
}, },
), ),
Collection::glob_with::<Post>( Collection::glob_with::<Post>(
@ -72,7 +88,6 @@ fn main() {
Processor { Processor {
read_content: crate::html::post::parse_content, read_content: crate::html::post::parse_content,
to_html: crate::html::as_html, to_html: crate::html::as_html,
to_link: crate::html::post::as_link,
}, },
), ),
]) ])
@ -85,7 +100,7 @@ fn main() {
|sack| crate::html::map(sack).unwrap().render().to_owned().into(), |sack| crate::html::map(sack).unwrap().render().to_owned().into(),
"map/index.html".into(), "map/index.html".into(),
) )
.add_virtual(|sack| crate::html::search(sack), "search/index.html".into()) .add_virtual(crate::html::search, "search/index.html".into())
.add_virtual( .add_virtual(
|sack| { |sack| {
crate::html::to_list( crate::html::to_list(

View file

@ -1,12 +1,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use camino::{Utf8Path, Utf8PathBuf}; use camino::Utf8Path;
use hauchiwa::{Bibliography, Outline, Sack}; use hauchiwa::{Bibliography, Outline, Sack};
use hayagriva::{ use hayagriva::{
archive::ArchivedStyle, archive::ArchivedStyle,
citationberg::{IndependentStyle, Locale, Style}, citationberg::{IndependentStyle, Locale, Style},
BibliographyDriver, BibliographyRequest, BufWriteFormat, CitationItem, CitationRequest, BibliographyDriver, BibliographyRequest, BufWriteFormat, CitationItem, CitationRequest,
Library, Library,
}; };
use hypertext::Renderable; use hypertext::Renderable;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -16,369 +16,364 @@ use regex::Regex;
use crate::ts; use crate::ts;
static OPTS: Lazy<Options> = Lazy::new(|| { static OPTS: Lazy<Options> = Lazy::new(|| {
Options::empty() Options::empty()
.union(Options::ENABLE_MATH) .union(Options::ENABLE_MATH)
.union(Options::ENABLE_TABLES) .union(Options::ENABLE_TABLES)
.union(Options::ENABLE_TASKLISTS) .union(Options::ENABLE_TASKLISTS)
.union(Options::ENABLE_STRIKETHROUGH) .union(Options::ENABLE_STRIKETHROUGH)
.union(Options::ENABLE_SMART_PUNCTUATION) .union(Options::ENABLE_SMART_PUNCTUATION)
}); });
static KATEX_I: Lazy<katex::Opts> = Lazy::new(|| { static KATEX_I: Lazy<katex::Opts> = Lazy::new(|| {
katex::opts::Opts::builder() katex::opts::Opts::builder()
.output_type(katex::OutputType::Mathml) .output_type(katex::OutputType::Mathml)
.build() .build()
.unwrap() .unwrap()
}); });
static KATEX_B: Lazy<katex::Opts> = Lazy::new(|| { static KATEX_B: Lazy<katex::Opts> = Lazy::new(|| {
katex::opts::Opts::builder() katex::opts::Opts::builder()
.output_type(katex::OutputType::Mathml) .output_type(katex::OutputType::Mathml)
.display_mode(true) .display_mode(true)
.build() .build()
.unwrap() .unwrap()
}); });
static LOCALE: Lazy<Vec<Locale>> = Lazy::new(hayagriva::archive::locales); static LOCALE: Lazy<Vec<Locale>> = Lazy::new(hayagriva::archive::locales);
static STYLE: Lazy<IndependentStyle> = static STYLE: Lazy<IndependentStyle> =
Lazy::new( Lazy::new(
|| match ArchivedStyle::InstituteOfElectricalAndElectronicsEngineers.get() { || match ArchivedStyle::InstituteOfElectricalAndElectronicsEngineers.get() {
Style::Independent(style) => style, Style::Independent(style) => style,
Style::Dependent(_) => unreachable!(), Style::Dependent(_) => unreachable!(),
}, },
); );
pub fn parse( pub fn parse(
content: &str, content: &str,
sack: &Sack, sack: &Sack,
path: &Utf8Path, path: &Utf8Path,
library: Option<&Library>, library: Option<&Library>,
) -> (String, Outline, Bibliography) { ) -> (String, Outline, Bibliography) {
let (outline, stream) = { let (outline, stream) = {
let stream = Parser::new_ext(content, *OPTS); let stream = Parser::new_ext(content, *OPTS);
let mut stream: Vec<_> = TextMergeStream::new(stream).collect(); let mut stream: Vec<_> = TextMergeStream::new(stream).collect();
let outline = set_heading_ids(&mut stream); let outline = set_heading_ids(&mut stream);
(outline, stream) (outline, stream)
}; };
let stream = stream let stream = stream
.into_iter() .into_iter()
.map(make_math) .map(make_math)
.map(make_emoji) .map(make_emoji)
.map(swap_hashed_image(path, sack)) .map(swap_hashed_image(path, sack))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let stream = make_code(stream) let stream = make_code(stream)
.into_iter() .into_iter()
.flat_map(make_ruby) .flat_map(make_ruby)
.flat_map(make_cite) .flat_map(make_cite)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let (stream, bib) = match library { let (stream, bib) = match library {
Some(lib) => make_bib(stream, lib), Some(lib) => make_bib(stream, lib),
None => (stream, None), None => (stream, None),
}; };
let mut parsed = String::new(); let mut parsed = String::new();
pulldown_cmark::html::push_html(&mut parsed, stream.into_iter()); pulldown_cmark::html::push_html(&mut parsed, stream.into_iter());
(parsed, outline, Bibliography(bib)) (parsed, outline, Bibliography(bib))
} }
fn make_bib<'a, 'b>( fn make_bib<'a>(stream: Vec<Event<'a>>, lib: &Library) -> (Vec<Event<'a>>, Option<Vec<String>>) {
stream: Vec<Event<'a>>, let mut driver = BibliographyDriver::new();
lib: &'b Library,
) -> (Vec<Event<'a>>, Option<Vec<String>>) {
let mut driver = BibliographyDriver::new();
for event in stream.iter() { for event in stream.iter() {
match event { if let Event::InlineMath(ref text) = event {
Event::InlineMath(ref text) => match lib.get(text) { if let Some(entry) = lib.get(text) {
Some(entry) => driver.citation(CitationRequest::from_items( driver.citation(CitationRequest::from_items(
vec![CitationItem::with_entry(entry)], vec![CitationItem::with_entry(entry)],
&STYLE, &STYLE,
&LOCALE, &LOCALE,
)), ))
None => (), }
}, }
_ => (), }
}
}
// add fake citation to make all entries show up // add fake citation to make all entries show up
driver.citation(CitationRequest::from_items( driver.citation(CitationRequest::from_items(
lib.iter().map(CitationItem::with_entry).collect(), lib.iter().map(CitationItem::with_entry).collect(),
&STYLE, &STYLE,
&LOCALE, &LOCALE,
)); ));
let res = driver.finish(BibliographyRequest { let res = driver.finish(BibliographyRequest {
style: &STYLE, style: &STYLE,
locale: None, locale: None,
locale_files: &LOCALE, locale_files: &LOCALE,
}); });
let mut n = 0; let mut n = 0;
let stream = stream let stream = stream
.into_iter() .into_iter()
.map(|event| match event { .map(|event| match event {
Event::InlineMath(name) => { Event::InlineMath(name) => {
let mut buffer = String::from("<cite>"); let mut buffer = String::from("<cite>");
match res.citations.get(n) { match res.citations.get(n) {
Some(rf) => rf Some(rf) => rf
.citation .citation
.write_buf(&mut buffer, BufWriteFormat::Html) .write_buf(&mut buffer, BufWriteFormat::Html)
.unwrap(), .unwrap(),
None => buffer.push_str(&name), None => buffer.push_str(&name),
}; };
buffer.push_str("</cite>"); buffer.push_str("</cite>");
n += 1; n += 1;
Event::InlineHtml(buffer.into()) Event::InlineHtml(buffer.into())
} }
_ => event, _ => event,
}) })
.collect(); .collect();
let bib = res.bibliography.map(|bib| { let bib = res.bibliography.map(|bib| {
bib.items bib.items
.iter() .iter()
.map(|x| { .map(|x| {
let mut buffer = String::new(); let mut buffer = String::new();
x.content x.content
.write_buf(&mut buffer, BufWriteFormat::Html) .write_buf(&mut buffer, BufWriteFormat::Html)
.unwrap(); .unwrap();
buffer buffer
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
}); });
(stream, bib) (stream, bib)
} }
static RE_CITE: Lazy<Regex> = Lazy::new(|| Regex::new(r":cite\[([^\]]+)\]").unwrap()); static RE_CITE: Lazy<Regex> = Lazy::new(|| Regex::new(r":cite\[([^\]]+)\]").unwrap());
#[derive(Debug)] #[derive(Debug)]
enum Annotated_<'a> { enum Annotated_<'a> {
Text(&'a str), Text(&'a str),
Cite(&'a str), Cite(&'a str),
} }
fn annotate_(input: &str) -> Vec<Annotated_> { fn annotate_(input: &str) -> Vec<Annotated_> {
let mut parts: Vec<Annotated_> = Vec::new(); let mut parts: Vec<Annotated_> = Vec::new();
let mut last_index = 0; let mut last_index = 0;
for cap in RE_CITE.captures_iter(input) { for cap in RE_CITE.captures_iter(input) {
let cite = cap.get(1).unwrap().as_str(); let cite = cap.get(1).unwrap().as_str();
let index = cap.get(0).unwrap().start(); let index = cap.get(0).unwrap().start();
if index > last_index { if index > last_index {
parts.push(Annotated_::Text(&input[last_index..index])); parts.push(Annotated_::Text(&input[last_index..index]));
} }
parts.push(Annotated_::Cite(cite)); parts.push(Annotated_::Cite(cite));
last_index = cap.get(0).unwrap().end(); last_index = cap.get(0).unwrap().end();
} }
if last_index < input.len() { if last_index < input.len() {
parts.push(Annotated_::Text(&input[last_index..])); parts.push(Annotated_::Text(&input[last_index..]));
} }
parts parts
} }
fn make_cite(event: Event) -> Vec<Event> { fn make_cite(event: Event) -> Vec<Event> {
match event { match event {
Event::Text(ref text) => annotate_(text) Event::Text(ref text) => annotate_(text)
.into_iter() .into_iter()
.map(|e| match e { .map(|e| match e {
Annotated_::Text(text) => Event::Text(text.to_owned().into()), Annotated_::Text(text) => Event::Text(text.to_owned().into()),
Annotated_::Cite(cite) => Event::InlineMath(cite.to_owned().into()), Annotated_::Cite(cite) => Event::InlineMath(cite.to_owned().into()),
}) })
.collect(), .collect(),
_ => vec![event], _ => vec![event],
} }
} }
fn set_heading_ids(events: &mut [Event]) -> Outline { fn set_heading_ids(events: &mut [Event]) -> Outline {
let mut cnt = HashMap::<String, i32>::new(); let mut cnt = HashMap::<String, i32>::new();
let mut out = Vec::new(); let mut out = Vec::new();
let mut buf = String::new(); let mut buf = String::new();
let mut ptr = None; let mut ptr = None;
for event in events { for event in events {
match event { match event {
Event::Start(ref mut tag @ Tag::Heading { .. }) => { Event::Start(ref mut tag @ Tag::Heading { .. }) => {
ptr = Some(tag); ptr = Some(tag);
} }
Event::Text(ref text) if ptr.is_some() => buf.push_str(text), Event::Text(ref text) if ptr.is_some() => buf.push_str(text),
Event::End(TagEnd::Heading(..)) => { Event::End(TagEnd::Heading(..)) => {
let txt = std::mem::take(&mut buf); let txt = std::mem::take(&mut buf);
let url = txt.to_lowercase().replace(' ', "-"); let url = txt.to_lowercase().replace(' ', "-");
let url = match cnt.get_mut(&url) { let url = match cnt.get_mut(&url) {
Some(ptr) => { Some(ptr) => {
*ptr += 1; *ptr += 1;
format!("{url}-{ptr}") format!("{url}-{ptr}")
} }
None => { None => {
cnt.insert(url.clone(), 0); cnt.insert(url.clone(), 0);
url url
} }
}; };
match ptr.take().unwrap() { match ptr.take().unwrap() {
Tag::Heading { ref mut id, .. } => *id = Some(url.clone().into()), Tag::Heading { ref mut id, .. } => *id = Some(url.clone().into()),
_ => unreachable!(), _ => unreachable!(),
} }
out.push((txt, url)); out.push((txt, url));
} }
_ => (), _ => (),
} }
} }
Outline(out) Outline(out)
} }
fn make_math(event: Event) -> Event { fn make_math(event: Event) -> Event {
match event { match event {
Event::InlineMath(math) => { Event::InlineMath(math) => {
Event::InlineHtml(katex::render_with_opts(&math, &*KATEX_I).unwrap().into()) Event::InlineHtml(katex::render_with_opts(&math, &*KATEX_I).unwrap().into())
} }
Event::DisplayMath(math) => { Event::DisplayMath(math) => {
Event::Html(katex::render_with_opts(&math, &*KATEX_B).unwrap().into()) Event::Html(katex::render_with_opts(&math, &*KATEX_B).unwrap().into())
} }
_ => event, _ => event,
} }
} }
fn make_code(es: Vec<Event>) -> Vec<Event> { fn make_code(es: Vec<Event>) -> Vec<Event> {
let mut buff = Vec::new(); let mut buff = Vec::new();
let mut lang = None; let mut lang = None;
let mut code = String::new(); let mut code = String::new();
for event in es { for event in es {
match event { match event {
Event::Start(Tag::CodeBlock(kind)) => match kind { Event::Start(Tag::CodeBlock(kind)) => match kind {
CodeBlockKind::Indented => (), CodeBlockKind::Indented => (),
CodeBlockKind::Fenced(name) => lang = Some(name), CodeBlockKind::Fenced(name) => lang = Some(name),
}, },
Event::End(TagEnd::CodeBlock) => { Event::End(TagEnd::CodeBlock) => {
let lang = lang.take().unwrap_or("".into()); let lang = lang.take().unwrap_or("".into());
let html = ts::highlight(&lang, &code).render().as_str().to_owned(); let html = ts::highlight(&lang, &code).render().as_str().to_owned();
buff.push(Event::Html(html.into())); buff.push(Event::Html(html.into()));
code.clear(); code.clear();
} }
Event::Text(text) => match lang { Event::Text(text) => match lang {
None => buff.push(Event::Text(text)), None => buff.push(Event::Text(text)),
Some(_) => code.push_str(&text), Some(_) => code.push_str(&text),
}, },
_ => buff.push(event), _ => buff.push(event),
} }
} }
buff buff
} }
static RE_RUBY: Lazy<Regex> = Lazy::new(|| Regex::new(r"\[([^\]]+)\]\{([^}]+)\}").unwrap()); static RE_RUBY: Lazy<Regex> = Lazy::new(|| Regex::new(r"\[([^\]]+)\]\{([^}]+)\}").unwrap());
#[derive(Debug)] #[derive(Debug)]
enum Annotated<'a> { enum Annotated<'a> {
Text(&'a str), Text(&'a str),
Ruby(&'a str, &'a str), Ruby(&'a str, &'a str),
} }
fn annotate(input: &str) -> Vec<Annotated> { fn annotate(input: &str) -> Vec<Annotated> {
let mut parts: Vec<Annotated> = Vec::new(); let mut parts: Vec<Annotated> = Vec::new();
let mut last_index = 0; let mut last_index = 0;
for cap in RE_RUBY.captures_iter(input) { for cap in RE_RUBY.captures_iter(input) {
let text = cap.get(1).unwrap().as_str(); let text = cap.get(1).unwrap().as_str();
let ruby = cap.get(2).unwrap().as_str(); let ruby = cap.get(2).unwrap().as_str();
let index = cap.get(0).unwrap().start(); let index = cap.get(0).unwrap().start();
if index > last_index { if index > last_index {
parts.push(Annotated::Text(&input[last_index..index])); parts.push(Annotated::Text(&input[last_index..index]));
} }
parts.push(Annotated::Ruby(text, ruby)); parts.push(Annotated::Ruby(text, ruby));
last_index = cap.get(0).unwrap().end(); last_index = cap.get(0).unwrap().end();
} }
if last_index < input.len() { if last_index < input.len() {
parts.push(Annotated::Text(&input[last_index..])); parts.push(Annotated::Text(&input[last_index..]));
} }
parts parts
} }
fn make_ruby(event: Event) -> Vec<Event> { fn make_ruby(event: Event) -> Vec<Event> {
match event { match event {
Event::Text(ref text) => annotate(text) Event::Text(ref text) => annotate(text)
.into_iter() .into_iter()
.map(|el| match el { .map(|el| match el {
Annotated::Text(text) => Event::Text(text.to_owned().into()), Annotated::Text(text) => Event::Text(text.to_owned().into()),
Annotated::Ruby(t, f) => Event::InlineHtml( Annotated::Ruby(t, f) => Event::InlineHtml(
format!("<ruby>{t}<rp>(</rp><rt>{f}</rt><rp>)</rp></ruby>").into(), format!("<ruby>{t}<rp>(</rp><rt>{f}</rt><rp>)</rp></ruby>").into(),
), ),
}) })
.collect(), .collect(),
_ => vec![event], _ => vec![event],
} }
} }
fn make_emoji(event: Event) -> Event { fn make_emoji(event: Event) -> Event {
match event { match event {
Event::Text(ref text) => { Event::Text(ref text) => {
let mut buf = None; let mut buf = None;
let mut top = 0; let mut top = 0;
let mut old = 0; let mut old = 0;
for (idx, _) in text.match_indices(':') { for (idx, _) in text.match_indices(':') {
let key = &text[old..idx]; let key = &text[old..idx];
if let Some(emoji) = emojis::get_by_shortcode(key) { if let Some(emoji) = emojis::get_by_shortcode(key) {
let buf = buf.get_or_insert_with(|| String::with_capacity(text.len())); let buf = buf.get_or_insert_with(|| String::with_capacity(text.len()));
buf.push_str(&text[top..old - 1]); buf.push_str(&text[top..old - 1]);
buf.push_str(emoji.as_str()); buf.push_str(emoji.as_str());
top = idx + 1; top = idx + 1;
} }
old = idx + 1; old = idx + 1;
} }
if let Some(ref mut buf) = buf { if let Some(ref mut buf) = buf {
buf.push_str(&text[top..]); buf.push_str(&text[top..]);
} }
match buf { match buf {
None => event, None => event,
Some(buf) => Event::Text(buf.into()), Some(buf) => Event::Text(buf.into()),
} }
} }
_ => event, _ => event,
} }
} }
fn swap_hashed_image<'a>(dir: &'a Utf8Path, sack: &'a Sack) -> impl Fn(Event) -> Event + 'a { fn swap_hashed_image<'a>(dir: &'a Utf8Path, sack: &'a Sack) -> impl Fn(Event) -> Event + 'a {
move |event| match event { move |event| match event {
Event::Start(start) => match start { Event::Start(start) => match start {
Tag::Image { Tag::Image {
dest_url, dest_url,
link_type, link_type,
title, title,
id, id,
} => { } => {
let rel = dir.join(dest_url.as_ref()); let rel = dir.join(dest_url.as_ref());
let img = sack.get_image(&rel); let img = sack.get_image(&rel);
let hashed = img.map(|path| path.as_str().to_owned().into()); let hashed = img.map(|path| path.as_str().to_owned().into());
Event::Start(Tag::Image { Event::Start(Tag::Image {
link_type, link_type,
dest_url: hashed.unwrap_or(dest_url), dest_url: hashed.unwrap_or(dest_url),
title, title,
id, id,
}) })
} }
_ => Event::Start(start), _ => Event::Start(start),
}, },
_ => event, _ => event,
} }
} }