From d87069c17d8401fb420ec832a85db7b9f715caf1 Mon Sep 17 00:00:00 2001 From: Maciej Jur Date: Sun, 28 Apr 2024 00:26:19 +0200 Subject: [PATCH] render bibliography --- .../japanese/{tachi.md => tachi/index.md} | 14 --- content/wiki/japanese/tachi/ref.bib | 8 ++ src/gen/mod.rs | 4 +- src/gen/render.rs | 6 +- src/gen/sack.rs | 27 +++- src/html.rs | 1 + src/html/home.rs | 2 +- src/html/misc.rs | 95 ++++++++++++++ src/html/post.rs | 33 ++--- src/html/wiki.rs | 69 ++--------- src/main.rs | 29 +++-- src/text/md.rs | 117 +++++++++++++++++- styles/components/_bibliography.scss | 12 +- 13 files changed, 293 insertions(+), 124 deletions(-) rename content/wiki/japanese/{tachi.md => tachi/index.md} (77%) create mode 100644 content/wiki/japanese/tachi/ref.bib create mode 100644 src/html/misc.rs diff --git a/content/wiki/japanese/tachi.md b/content/wiki/japanese/tachi/index.md similarity index 77% rename from content/wiki/japanese/tachi.md rename to content/wiki/japanese/tachi/index.md index cc16515..250942e 100644 --- a/content/wiki/japanese/tachi.md +++ b/content/wiki/japanese/tachi/index.md @@ -15,17 +15,3 @@ I've come across 〜達 used for inanimate things several times with one example 奈落への滝が嗤い続け轟く “目指したトコロは此処だったのか?” 今は先を急ぐ **水達**に乗せられたまま - - -## Bibliography - -:::bibtex -@book{dojg-advanced, - title = {A dictionary of advanced Japanese grammar}, - author = {Makino, Seiichi and Tsutsui, Michio}, - isbn = {9784789012959}, - lccn = {2017414531}, - year = {2008}, - publisher = {Japan Times} -} -::: diff --git a/content/wiki/japanese/tachi/ref.bib b/content/wiki/japanese/tachi/ref.bib new file mode 100644 index 0000000..4965f55 --- /dev/null +++ b/content/wiki/japanese/tachi/ref.bib @@ -0,0 +1,8 @@ +@book{dojg-advanced, + title = {A dictionary of advanced Japanese grammar}, + author = {Makino, Seiichi and Tsutsui, Michio}, + isbn = {9784789012959}, + lccn = {2017414531}, + year = {2008}, + publisher = {Japan Times} +} diff --git a/src/gen/mod.rs b/src/gen/mod.rs index dec2202..b0959ee 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -3,6 +3,7 @@ mod render; mod sack; use camino::Utf8PathBuf; +use hayagriva::Library; use hypertext::Renderable; pub use load::{gather, Source, SourceKind}; @@ -19,6 +20,7 @@ pub trait Content { content: T, outline: Outline, sack: &'s Sack, + bib: Option>, ) -> impl Renderable + 'html where 'f: 'html, @@ -28,5 +30,5 @@ pub trait Content { fn as_link(&self, path: Utf8PathBuf) -> Option; - fn render(data: &str) -> (Outline, String); + fn render(data: &str, lib: Option<&Library>) -> (Outline, String, Option>); } diff --git a/src/gen/render.rs b/src/gen/render.rs index f534b4f..d04c8fe 100644 --- a/src/gen/render.rs +++ b/src/gen/render.rs @@ -73,12 +73,10 @@ pub fn render(items: &[Item]) { }) .collect(); - let sack = Sack::new(&assets); - for item in items { match item { - Item::Real(real) => render_real(real, &sack), - Item::Fake(fake) => render_fake(fake, &sack), + Item::Real(real) => render_real(real, &Sack::new(&assets, &real.out)), + Item::Fake(fake) => render_fake(fake, &Sack::new(&assets, &fake.0)), } } } diff --git a/src/gen/sack.rs b/src/gen/sack.rs index 5c9a8d0..0e55b93 100644 --- a/src/gen/sack.rs +++ b/src/gen/sack.rs @@ -1,8 +1,11 @@ use std::collections::HashMap; +use camino::Utf8PathBuf; +use hayagriva::Library; + use crate::html::{Link, LinkDate, Linkable}; -use super::Asset; +use super::{Asset, AssetKind}; #[derive(Debug)] @@ -35,11 +38,12 @@ impl TreePage { #[derive(Debug)] pub struct Sack<'a> { assets: &'a [&'a Asset], + path: &'a Utf8PathBuf, } impl<'a> Sack<'a> { - pub fn new(assets: &'a [&'a Asset]) -> Self { - Self { assets } + pub fn new(assets: &'a [&'a Asset], path: &'a Utf8PathBuf) -> Self { + Self { assets, path } } pub fn get_links(&self, path: &str) -> Vec { @@ -69,4 +73,21 @@ impl<'a> Sack<'a> { tree } + + pub fn get_library(&self) -> Option<&Library> { + let glob = format!("{}/*.bib", self.path.parent()?); + let glob = glob::Pattern::new(&glob).unwrap(); + let opts = glob::MatchOptions { + case_sensitive: true, + require_literal_separator: true, + require_literal_leading_dot: false, + }; + + self.assets.iter() + .filter(|asset| glob.matches_path_with(asset.out.as_ref(), opts)) + .find_map(|asset| match asset.kind { + AssetKind::Bib(ref lib) => Some(lib), + _ => None + }) + } } diff --git a/src/html.rs b/src/html.rs index 8b5a1bf..49b3756 100644 --- a/src/html.rs +++ b/src/html.rs @@ -6,6 +6,7 @@ mod list; mod show; mod special; mod wiki; +mod misc; pub use home::home; pub use page::page; diff --git a/src/html/home.rs b/src/html/home.rs index 20e2975..d91ea71 100644 --- a/src/html/home.rs +++ b/src/html/home.rs @@ -19,7 +19,7 @@ const INTRO: &str = r#" fn intro() -> impl Renderable { - let (_, html) = parse(INTRO); + let (_, html, _) = parse(INTRO, None); maud!( section .p-card.intro-jp lang="ja-JP" { (Raw(html)) diff --git a/src/html/misc.rs b/src/html/misc.rs new file mode 100644 index 0000000..17ae495 --- /dev/null +++ b/src/html/misc.rs @@ -0,0 +1,95 @@ +use hypertext::{html_elements, maud_move, GlobalAttributes, Renderable}; + +use crate::gen::{Sack, TreePage}; +use crate::text::md::Outline; + + +/// Render the outline for a document +pub fn show_outline(outline: Outline) -> impl Renderable { + maud_move!( + section .link-tree { + h2 .link-tree__heading { + a .link-tree__heading-text href="#top" { "Content" } + } + nav #table-of-contents .link-tree__nav { + ul .link-tree__nav-list { + @for (title, id) in outline.0 { + li .link-tree__nav-list-item { + a .link-tree__nav-list-text.link href=(format!("#{}", id)) { + (title) + } + } + } + } + } + } + ) +} + +/// Render the bibliography for a document +pub fn show_bibliography(bib: Vec) -> impl Renderable { + maud_move!( + section .markdown { + h2 { + "Bibliography" + } + ol .bibliography { + @for item in bib { + li { + (item) + } + } + } + } + ) +} + +/// Render the page tree +pub fn show_page_tree(sack: &Sack, glob: &str) -> impl Renderable { + let tree = sack.get_tree(glob); + + maud_move!( + h2 .link-tree__heading { + // {pages.chain(x => x.prefix) + // .map(pathify) + // .mapOrDefault(href => + // {heading}, + // {heading} + // )} + } + nav .link-tree__nav { + (show_page_tree_level(&tree)) + } + ) +} + +fn show_page_tree_level(tree: &TreePage) -> impl Renderable + '_ { + let subs = { + let mut subs: Vec<_> = tree.subs.iter().collect(); + subs.sort_by(|a, b| a.0.cmp(b.0)); + subs + }; + + maud_move!( + ul .link-tree__nav-list { + @for (key, next) in subs { + li .link-tree__nav-list-item { + span .link-tree__nav-list-text { + @if let Some(ref link) = next.link { + a .link-tree__nav-list-text.link href=(link.path.as_str()) { + (&link.name) + } + } @else { + span .link-tree__nav-list-text { + (key) + } + } + } + @if !next.subs.is_empty() { + (show_page_tree_level(next)) + } + } + } + } + ) +} diff --git a/src/html/post.rs b/src/html/post.rs index 72c98b0..e138e8e 100644 --- a/src/html/post.rs +++ b/src/html/post.rs @@ -1,35 +1,16 @@ use hypertext::{html_elements, maud_move, GlobalAttributes, Renderable}; -use crate::md::Post; + +use crate::html::misc::{show_bibliography, show_outline}; use crate::html::page; +use crate::md::Post; use crate::text::md::Outline; -pub fn tree(outline: Outline) -> impl Renderable { - maud_move!( - section .link-tree { - h2 .link-tree__heading { - a .link-tree__heading-text href="#top" { "Content" } - } - nav #table-of-contents .link-tree__nav { - ul .link-tree__nav-list { - @for (title, id) in outline.0 { - li .link-tree__nav-list-item { - a .link-tree__nav-list-text.link href=(format!("#{id}")) { - (title) - } - } - } - } - } - } - ) -} - - pub fn post<'fm, 'md, 'post, T>( fm: &'fm Post, content: T, outline: Outline, + bib: Option>, ) -> impl Renderable + 'post where 'fm: 'post, @@ -47,7 +28,7 @@ pub fn post<'fm, 'md, 'post, T>( label .wiki-aside__slider for="wiki-aside-shown" { img .wiki-icon src="/static/svg/double-arrow.svg" width="24" height="24"; } - (tree(outline)) + (show_outline(outline)) } article .wiki-article /*class:list={classlist)*/ { @@ -57,6 +38,10 @@ pub fn post<'fm, 'md, 'post, T>( section .wiki-article__markdown.markdown { (content) } + + @if let Some(bib) = bib { + (show_bibliography(bib)) + } } } ); diff --git a/src/html/wiki.rs b/src/html/wiki.rs index 567065d..2318163 100644 --- a/src/html/wiki.rs +++ b/src/html/wiki.rs @@ -1,62 +1,10 @@ use hypertext::{html_elements, maud_move, GlobalAttributes, Renderable}; -use crate::{ - gen::{Sack, TreePage}, - html::page, - md::Wiki, - text::md::Outline -}; - - - -fn tree(sack: &Sack) -> impl Renderable { - let tree = sack.get_tree("wiki/**/*.html"); - - maud_move!( - h2 .link-tree__heading { - // {pages.chain(x => x.prefix) - // .map(pathify) - // .mapOrDefault(href => - // {heading}, - // {heading} - // )} - } - nav .link-tree__nav { - (list(&tree)) - } - ) -} - -fn list(tree: &TreePage) -> impl Renderable + '_ { - let subs = { - let mut subs: Vec<_> = tree.subs.iter().collect(); - subs.sort_by(|a, b| a.0.cmp(b.0)); - subs - }; - - maud_move!( - ul .link-tree__nav-list { - @for (key, next) in subs { - li .link-tree__nav-list-item { - span .link-tree__nav-list-text { - @if let Some(ref link) = next.link { - a .link-tree__nav-list-text.link href=(link.path.as_str()) { - (&link.name) - } - } @else { - span .link-tree__nav-list-text { - (key) - } - } - } - @if !next.subs.is_empty() { - (list(next)) - } - } - } - } - ) -} +use crate::gen::Sack; +use crate::html::misc::show_page_tree; +use crate::html::{misc::show_bibliography, page}; +use crate::md::Wiki; +use crate::text::md::Outline; pub fn wiki<'data, 'html, 'sack, T>( @@ -64,6 +12,7 @@ pub fn wiki<'data, 'html, 'sack, T>( content: T, _: Outline, sack: &'sack Sack, + bib: Option>, ) -> impl Renderable + 'html where 'sack: 'html, @@ -84,7 +33,7 @@ pub fn wiki<'data, 'html, 'sack, T>( // Navigation tree section .link-tree { div { - (tree(sack)) + (show_page_tree(sack, "wiki/**/*.html")) } } } @@ -96,6 +45,10 @@ pub fn wiki<'data, 'html, 'sack, T>( section .wiki-article__markdown.markdown { (content) } + + @if let Some(bib) = bib { + (show_bibliography(bib)) + } } } ); diff --git a/src/main.rs b/src/main.rs index 5a20414..3013fe5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use std::fs; use camino::{Utf8Path, Utf8PathBuf}; use chrono::Datelike; use gen::{Asset, Sack, Content}; +use hayagriva::Library; use html::{Link, LinkDate, Linkable}; use hypertext::{Raw, Renderable}; use once_cell::sync::Lazy; @@ -54,13 +55,14 @@ impl Content for md::Post { content: T, outline: Outline, _: &'s Sack, + bib: Option>, ) -> impl Renderable + 'html where 'f: 'html, 'm: 'html, 's: 'html, T: Renderable + 'm { - html::post(self, content, outline) + html::post(self, content, outline, bib) } fn as_link(&self, path: Utf8PathBuf) -> Option { @@ -74,8 +76,8 @@ impl Content for md::Post { })) } - fn render(data: &str) -> (Outline, String) { - text::md::parse(data) + fn render(data: &str, lib: Option<&Library>) -> (Outline, String, Option>) { + text::md::parse(data, lib) } } @@ -85,6 +87,7 @@ impl Content for md::Slide { content: T, _: Outline, _: &'s Sack, + bib: Option>, ) -> impl Renderable + 'html where 'f: 'html, @@ -105,16 +108,16 @@ impl Content for md::Slide { })) } - fn render(data: &str) -> (Outline, String) { + fn render(data: &str, _: Option<&Library>) -> (Outline, String, Option>) { let html = data .split("\n-----\n") - .map(|chunk| chunk.split("\n---\n").map(text::md::parse).map(|e| e.1).collect::>()) + .map(|chunk| chunk.split("\n---\n").map(|s| text::md::parse(s, None)).map(|e| e.1).collect::>()) .map(|stack| match stack.len() > 1 { true => format!("
{}
", stack.into_iter().map(|slide| format!("
{slide}
")).collect::()), false => format!("
{}
", stack[0]) }) .collect::(); - (Outline(vec![]), html) + (Outline(vec![]), html, None) } } @@ -124,13 +127,14 @@ impl Content for md::Wiki { content: T, outline: Outline, sack: &'s Sack, + bib: Option>, ) -> impl Renderable + 'html where 'f: 'html, 'm: 'html, 's: 'html, T: Renderable + 'm { - html::wiki(self, content, outline, sack) + html::wiki(self, content, outline, sack, bib) } fn as_link(&self, path: Utf8PathBuf) -> Option { @@ -141,8 +145,8 @@ impl Content for md::Wiki { })) } - fn render(data: &str) -> (Outline, String) { - text::md::parse(data) + fn render(data: &str, lib: Option<&Library>) -> (Outline, String, Option>) { + text::md::parse(data, lib) } } @@ -185,8 +189,9 @@ fn transform(meta: gen::Source) -> Asset let link = T::as_link(&fm, Utf8Path::new("/").join(dir)); let call = move |sack: &Sack| { - let (outline, html) = T::render(&md); - T::transform(&fm, Raw(html), outline, sack).render().into() + let lib = sack.get_library(); + let (outline, html, bib) = T::render(&md, lib); + T::transform(&fm, Raw(html), outline, sack, bib).render().into() }; gen::Asset { @@ -249,7 +254,7 @@ fn main() { gen::Asset { kind: gen::AssetKind::Html(Box::new(|_| { let data = std::fs::read_to_string("content/index.md").unwrap(); - let (_, html) = text::md::parse(&data); + let (_, html, bib) = text::md::parse(&data, None); html::home(Raw(html)).render().to_owned().into() })), out: "index.html".into(), diff --git a/src/text/md.rs b/src/text/md.rs index 443d155..473eb7a 100644 --- a/src/text/md.rs +++ b/src/text/md.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use hayagriva::{archive::ArchivedStyle, citationberg::{IndependentStyle, Locale, Style}, BibliographyDriver, BibliographyRequest, CitationItem, CitationRequest, Library}; use hypertext::Renderable; use once_cell::sync::Lazy; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd, TextMergeStream}; @@ -32,10 +33,19 @@ static KATEX_B: Lazy = Lazy::new(|| .unwrap() ); +static LOCALE: Lazy> = Lazy::new(|| hayagriva::archive::locales()); + +static STYLE: Lazy = Lazy::new(|| + match ArchivedStyle::InstituteOfPhysicsNumeric.get() { + Style::Independent(style) => style, + Style::Dependent(_) => unreachable!(), + } +); + pub struct Outline(pub Vec<(String, String)>); -pub fn parse(text: &str) -> (Outline, String) { +pub fn parse(text: &str, lib: Option<&Library>) -> (Outline, String, Option>) { let (outline, stream) = { let stream = Parser::new_ext(text, *OPTS); let mut stream: Vec<_> = TextMergeStream::new(stream).collect(); @@ -50,12 +60,113 @@ pub fn parse(text: &str) -> (Outline, String) { let stream = make_code(stream) .into_iter() - .flat_map(make_ruby); + .flat_map(make_ruby) + .flat_map(make_cite) + .collect::>(); + + let (stream, bib) = match lib { + Some(lib) => make_bib(stream, lib), + None => (stream, None), + }; let mut html = String::new(); pulldown_cmark::html::push_html(&mut html, stream.into_iter()); - (outline, html) + (outline, html, bib) +} + +fn make_bib<'a, 'b>(stream: Vec>, lib: &'b Library) -> (Vec>, Option>) { + let mut driver = BibliographyDriver::new(); + + for event in stream.iter() { + match event { + Event::InlineMath(ref text) => match lib.get(text) { + Some(entry) => driver.citation(CitationRequest::from_items(vec![CitationItem::with_entry(entry)], &STYLE, &LOCALE)), + None => (), + }, + _ => (), + } + } + + let res = driver.finish(BibliographyRequest { style: &STYLE, locale: None, locale_files: &LOCALE }); + + let mut n = 0; + let stream = stream.into_iter() + .map(|event| match event { + Event::InlineMath(_) => { + let rf = match res.citations.get(n) { + Some(rf) => rf, + None => return event, + }; + let rf = rf.citation.to_string().replace("\u{1b}[0m", ""); + let rf = format!("{}", rf); + n += 1; + Event::InlineHtml(rf.into()) + }, + _ => event + }) + .collect(); + + let bib = match res.bibliography { + Some(ref bib) => { + let test = bib.items.iter() + .map(|x| x.content.to_string() + .replace("\u{1b}[0m", "") + .replace("\u{01b}[3mA", "") + ) + .collect::>(); + Some(test) + }, + None => None, + }; + + (stream, bib) +} + +static RE_CITE: Lazy = Lazy::new(|| Regex::new(r":cite\[([^\]]+)\]").unwrap()); + +#[derive(Debug)] +enum Annotated_<'a> { + Text(&'a str), + Cite(&'a str), +} + +fn annotate_(input: &str) -> Vec { + let mut parts: Vec = Vec::new(); + let mut last_index = 0; + + for cap in RE_CITE.captures_iter(input) { + let cite = cap.get(1).unwrap().as_str(); + let index = cap.get(0).unwrap().start(); + + if index > last_index { + parts.push(Annotated_::Text(&input[last_index..index])); + } + + parts.push(Annotated_::Cite(cite)); + last_index = cap.get(0).unwrap().end(); + } + + if last_index < input.len() { + parts.push(Annotated_::Text(&input[last_index..])); + } + + parts +} + +fn make_cite(event: Event) -> Vec { + match event { + Event::Text(ref text) => { + annotate_(text) + .into_iter() + .map(|e| match e { + Annotated_::Text(text) => Event::Text(text.to_owned().into()), + Annotated_::Cite(cite) => Event::InlineMath(cite.to_owned().into()), + }) + .collect() + }, + _ => vec![event], + } } fn set_heading_ids(events: &mut [Event]) -> Outline { diff --git a/styles/components/_bibliography.scss b/styles/components/_bibliography.scss index 68f9bff..798dc76 100644 --- a/styles/components/_bibliography.scss +++ b/styles/components/_bibliography.scss @@ -1,5 +1,9 @@ -.csl-bib-body { - display: flex; - flex-direction: column; - gap: 0.5em; +.bibliography { + li { + padding-left: 0.7em; + + &::marker { + content: '[' counter(list-item) ']'; + } + } }