diff --git a/Cargo.lock b/Cargo.lock index c6be5de..1e3034e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -554,29 +554,6 @@ dependencies = [ "syn", ] -[[package]] -name = "env_filter" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1173,25 +1150,6 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" -[[package]] -name = "include_dir" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" -dependencies = [ - "include_dir_macros", -] - -[[package]] -name = "include_dir_macros" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" -dependencies = [ - "proc-macro2", - "quote", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -1222,30 +1180,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "jiff" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-static" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "js-sys" version = "0.3.77" @@ -1407,9 +1341,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -1656,15 +1590,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" -[[package]] -name = "portable-atomic-util" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" -dependencies = [ - "portable-atomic", -] - [[package]] name = "postcard" version = "1.1.1" @@ -1848,9 +1773,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1860,9 +1785,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2413,9 +2338,6 @@ name = "typssg" version = "0.1.0" dependencies = [ "clap", - "env_logger", - "include_dir", - "log", "typst", "typst-as-lib", "typst-html", diff --git a/Cargo.toml b/Cargo.toml index 28eeeb5..fd7f150 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,10 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -include_dir = "0.7.4" clap = { version = "4.6.1", features = ["derive"] } -env_logger = "0.11.10" -log = "0.4.29" typst = "0.14.2" typst-as-lib = { version = "0.15.4", features = ["typst-html", "typst-kit-fonts", "typst-kit-embed-fonts"] } typst-html = "0.14.2" diff --git a/build.rs b/build.rs deleted file mode 100644 index e5458ff..0000000 --- a/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=prepends/"); -} diff --git a/common.typ b/common.typ new file mode 100644 index 0000000..eabfd0a --- /dev/null +++ b/common.typ @@ -0,0 +1,8 @@ +#let image(source, width: "400px") = { + html.elem("img", attrs: ( + src: "/static/articles/" + source, + alt: source, + width: str(width), + )) +} + diff --git a/prepends/bibliography.typ b/prepends/bibliography.typ deleted file mode 100644 index f72b036..0000000 --- a/prepends/bibliography.typ +++ /dev/null @@ -1,17 +0,0 @@ -// By default, bibliography titles render with a level 1 heading. Typssg -// rather assumes that lvl 1 headings are just for page titles, and that -// section titles are denoted with lvl 2 headings. -#let _bibliography = bibliography -#let refs(content) = { - heading(level: 2, "References") - _bibliography(bytes(content.text), style: "ieee", title: none) -} - -// Override default bibliography so that users don't accidentally use it -#let bibliography = (path, ..args) => { - text[This project has enabled the bibliography extension which provides the `#refs()` funciton for convenient in-file bibliographies. If you _did_ intend to use the default bibliography function, call it instead with `#_bibliography().`] -} - -// Have in-text citations appear as just numbers, instead of the IEEE default -// style of [#] with brackets. -#set cite(style: "vancouver-superscript") diff --git a/prepends/card.typ b/prepends/card.typ deleted file mode 100644 index 25cc3e0..0000000 --- a/prepends/card.typ +++ /dev/null @@ -1,22 +0,0 @@ -#let card( - caption: "", - media: (), - score: 0, - defense: "", - offense: "", -) = { - text[= #caption (Card) - - #html.elem("div", attrs: (class: "card"))[ - #media.at(0) - ] - - score: #score - - == Defense - #defense - - == Offense - #offense - ] -} diff --git a/prepends/figure.typ b/prepends/figure.typ deleted file mode 100644 index 73ec4c8..0000000 --- a/prepends/figure.typ +++ /dev/null @@ -1,19 +0,0 @@ -// Given a figure with an image, this show rule will wrap the image in a link -// that goes to the full version of the pic. For example, image("bird.jpg") -// will get wrapped in a #link("bird_full.jpg"). This, of course, assumes -// bird_full.jpg also exists in the directory. -#show figure: it => { - if it.body.func() == image { - let src = it.body.source - let dot-pos = src.rev().position(".") - let full-src = if dot-pos != none { - src.slice(0, src.len() - dot-pos - 1) + "_full" + src.slice(src.len() - dot-pos - 1) - } else { - src + "_full" - } - show image: img => link(full-src, img) - it - } else { - it - } -} diff --git a/prepends/link.typ b/prepends/link.typ deleted file mode 100644 index ad107f9..0000000 --- a/prepends/link.typ +++ /dev/null @@ -1,15 +0,0 @@ -// Adds an "internal" class to links that stay within the application, -// allowing them to be styled differently from external links. -#show link: it => { - let dest = it.dest - if type(dest) == str { - let is-internal = dest.starts-with("/") or dest.starts-with(".") - let attrs = (href: dest) - if is-internal { - attrs = (href: dest, class: "internal") - } - html.elem("a", attrs: attrs, it.body) - } else { - it - } -} diff --git a/prepends/quote.typ b/prepends/quote.typ deleted file mode 100644 index bba122d..0000000 --- a/prepends/quote.typ +++ /dev/null @@ -1,15 +0,0 @@ -// Typst version 14.0.2 outputs a block quote as a
followed by -// a

for the attribution. This makes it difficult to target the -// attribution for styling. This snippet instead uses a

and -// a
wrapped in a
block. -#show quote.where(block: true): it => { - let inner = html.elem("blockquote", it.body) - if it.attribution != none { - html.elem("figure", { - inner - html.elem("figcaption", it.attribution) - }) - } else { - inner - } -} diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 9cab269..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,3 +0,0 @@ -[toolchain] -channel = "stable" - diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..afd09a7 --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + buildInputs = with pkgs; [ + cargo + rustc + rust-analyzer + ]; +} + diff --git a/src/lib.rs b/src/lib.rs index 34bc866..a03e23d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,87 +1,21 @@ -mod plugin; - -use std::fmt::Write; use std::fs; use std::path::PathBuf; -pub use plugin::{concat_plugin_sources, embedded_prepend_source, list_embedded_plugin_ids}; - use typst::ecow::EcoString; -use typst::syntax::Source; -use typst_as_lib::{typst_kit_options::TypstKitFontOptions, TypstAsLibError, TypstEngine}; +use typst_as_lib::{typst_kit_options::TypstKitFontOptions, TypstEngine}; use typst_html::{HtmlAttr, HtmlDocument, HtmlElement, HtmlNode}; -use log::info; - -fn format_typst_compile_error( - err: TypstAsLibError, - full_source: &str, - index_byte_start: usize, - index_source: &str, -) -> std::io::Error { - let report = match err { - TypstAsLibError::TypstSource(diagnostics) if !diagnostics.is_empty() => { - let combined = Source::detached(full_source); - let index_only = Source::detached(index_source); - let index_end = index_byte_start.saturating_add(index_source.len()); - let mut out = String::from("Typst compile failed:\n"); - for d in diagnostics.iter() { - let msg = d.message.as_str(); - if let Some(range) = combined.range(d.span) { - let byte = range.start; - if byte >= index_byte_start && byte < index_end { - let rel = byte - index_byte_start; - if let Some((line, col)) = index_only.lines().byte_to_line_column(rel) { - let _ = writeln!( - &mut out, - " index.typ:{}:{}: {}", - line + 1, - col + 1, - msg - ); - } else { - let _ = writeln!(&mut out, " {msg}"); - } - } else if let Some((line, col)) = combined.lines().byte_to_line_column(byte) { - let _ = writeln!( - &mut out, - " (preamble) line {}:{}: {}", - line + 1, - col + 1, - msg - ); - } else { - let _ = writeln!(&mut out, " {msg}"); - } - } else { - let _ = writeln!(&mut out, " {msg}"); - } - for hint in d.hints.iter() { - let _ = writeln!(&mut out, " hint: {}", hint.as_str()); - } - } - out - } - other => format!("typst compile failed: {other}"), - }; - std::io::Error::new(std::io::ErrorKind::Other, report) -} pub fn compile_article( article_dir: &PathBuf, prepend: &Option, - plugins: &[impl AsRef], - include_title: bool, ) -> Result<(), Box> { - info!("compiling {} ...", article_dir.display()); let template_file = article_dir.join("index.typ"); let output = article_dir.join("index.html"); let outline_file = article_dir.join("outline.html"); - let plugin_block = concat_plugin_sources(plugins)?; - - let user_prepend = if let Some(prepend_file) = prepend { + let prepend_content = if let Some(prepend_file) = prepend { fs::read_to_string(&prepend_file).map_err(|e| { format!( "could not read prepend file {}: {e}", @@ -92,26 +26,19 @@ pub fn compile_article( fs::read_to_string(article_dir.join("prepend.typ")).unwrap_or_default() }; - let index_source = fs::read_to_string(&template_file).map_err(|e| { - format!( - "could not read template {}: {e}", - template_file.display() - ) - })?; - let mut template: EcoString = EcoString::new(); - template.push_str(&plugin_block); - if !plugin_block.is_empty() && !user_prepend.is_empty() { - template.push('\n'); - } - template.push_str(&user_prepend); - let index_byte_start = template.len(); - template.push_str(&index_source); - - let full_source_str = template.to_string(); + template.push_str(&prepend_content); + template.push_str( + &fs::read_to_string(&template_file).map_err(|e| { + format!( + "could not read template {}: {e}", + template_file.display() + ) + })?, + ); let engine = TypstEngine::builder() - .main_file(full_source_str.clone()) + .main_file(template.to_string()) .search_fonts_with( TypstKitFontOptions::default() .include_system_fonts(false) @@ -123,27 +50,13 @@ pub fn compile_article( let mut doc: HtmlDocument = engine .compile() .output - .map_err(|e| format_typst_compile_error(e, &full_source_str, index_byte_start, &index_source))?; + .map_err(|e| format!("typst compile failed: {e}"))?; let mut outline = EcoString::new(); - let mut curr_level = 1u32; - let mut ul_depth = 0u32; - let mut title_h2_pending = !include_title; - let mut first_outline_heading = true; - parse_outline( - &mut doc.root, - &mut outline, - &mut curr_level, - &mut ul_depth, - include_title, - &mut title_h2_pending, - &mut first_outline_heading, - ); - // `ul_depth` counts open `
    ` tags; it can diverge from `curr_level - 1` when the first - // outline heading uses lazy depth (skip title). Always drain by `ul_depth`, not `curr_level`. - while ul_depth > 0 { - ul_depth -= 1; - outline.push_str(" ".repeat(ul_depth as usize).as_str()); + let mut curr_level = 1; + parse_outline(&mut doc.root, &mut outline, &mut curr_level); + for i in (1..curr_level).rev() { + outline.push_str(" ".repeat(i as usize - 1).as_str()); outline.push_str("
\n"); } fs::write(&outline_file, outline.as_bytes()).map_err(|e| { @@ -193,51 +106,11 @@ pub fn compile_article( Ok(()) } -fn heading_level_from_tag(tag: &str) -> Option { - match tag { - "

" => Some(2), - "

" => Some(3), - "

" => Some(4), - "

" => Some(5), - "
" => Some(6), - _ => None, - } -} - -fn parse_outline( - elem: &mut HtmlElement, - outline: &mut EcoString, - curr_level: &mut u32, - ul_depth: &mut u32, - include_title: bool, - title_h2_pending: &mut bool, - first_outline_heading: &mut bool, -) { - let tag = elem.tag.to_string(); - let tag_ref = tag.as_str(); - - if let Some(level) = heading_level_from_tag(tag_ref) { - if !include_title && *title_h2_pending { - *title_h2_pending = false; - if tag_ref == "

" { - for child in elem.children.make_mut().iter_mut() { - if let HtmlNode::Element(e) = child { - parse_outline( - e, - outline, - curr_level, - ul_depth, - include_title, - title_h2_pending, - first_outline_heading, - ); - } - } - return; - } - } - *title_h2_pending = false; - +fn parse_outline(elem: &mut HtmlElement, outline: &mut EcoString, curr_level: &mut u32) { + if matches!( + elem.tag.to_string().as_str(), + "

" | "

" | "

" | "

" | "
" + ) { let mut header_text = EcoString::new(); for child in &elem.children { @@ -263,26 +136,21 @@ fn parse_outline( .trim_matches('-') .replace("--", "-"); - if *first_outline_heading { - *first_outline_heading = false; - *curr_level = level.saturating_sub(1); - } + let level: u32 = elem.tag.to_string().chars().nth(2).unwrap() as u32 - '0' as u32; while level > *curr_level { - outline.push_str(" ".repeat(*ul_depth as usize).as_str()); - outline.push_str("
    \n"); - *ul_depth += 1; *curr_level += 1; + outline.push_str(" ".repeat(*curr_level as usize - 2).as_str()); + outline.push_str("
      \n"); } - while level < *curr_level && *ul_depth > 0 { - *curr_level -= 1; - *ul_depth -= 1; - outline.push_str(" ".repeat(*ul_depth as usize).as_str()); + while level < *curr_level { + outline.push_str(" ".repeat(*curr_level as usize - 2).as_str()); outline.push_str("
    \n"); + *curr_level -= 1; } *curr_level = level; - outline.push_str(" ".repeat(*ul_depth as usize).as_str()); + outline.push_str(" ".repeat(*curr_level as usize - 1).as_str()); outline.push_str( format!( "
  • {}
  • \n", @@ -297,39 +165,8 @@ fn parse_outline( for child in elem.children.make_mut().iter_mut() { match child { - HtmlNode::Element(e) => { - parse_outline( - e, - outline, - curr_level, - ul_depth, - include_title, - title_h2_pending, - first_outline_heading, - ); - } + HtmlNode::Element(e) => parse_outline(e, outline, curr_level), _ => {} } } } - -pub fn compile_all( - root_dir: &PathBuf, - prepend: &Option, - plugins: &[impl AsRef], - include_title_in_outline: bool, -) -> Result<(), Box> { - for entry in fs::read_dir(root_dir)? { - let entry = entry?; - let path = entry.path(); - - if path.is_dir() { - compile_all(&path, prepend, plugins, include_title_in_outline)?; - } else if path.file_name().is_some_and(|n| n == "index.typ") { - let dir = path.parent().unwrap().to_path_buf(); - compile_article(&dir, prepend, plugins, include_title_in_outline)?; - } - } - - Ok(()) -} diff --git a/src/main.rs b/src/main.rs index ba7c1a5..b836b3f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,7 @@ use std::env; use std::path::PathBuf; use clap::Parser; -use typssg::{compile_all, compile_article}; -use log::{info, error}; +use typssg::compile_article; #[derive(Parser)] struct Args { @@ -12,45 +11,18 @@ struct Args { #[arg(long)] prepend: Option, - - #[arg(long, value_delimiter = ',')] - plugin: Vec, - - #[arg(short)] - recursive: bool, - - #[arg(long)] - include_title_in_outline: bool, } fn main() { - env_logger::init(); - match env::current_dir() { - Ok(path) => info!("Starting in working directory: {}", path.display()), - Err(e) => error!("Error getting current directory: {}", e), + Ok(path) => println!("Current working directory: {}", path.display()), + Err(e) => eprintln!("Error getting current directory: {}", e), } let args = Args::parse(); - let result = if args.recursive { - compile_all( - &args.dir, - &args.prepend, - &args.plugin, - args.include_title_in_outline, - ) - } else { - compile_article( - &args.dir, - &args.prepend, - &args.plugin, - args.include_title_in_outline, - ) - }; - - if let Err(e) = result { - error!("{e}"); + if let Err(e) = compile_article(&args.dir, &args.prepend) { + eprintln!("{e}"); std::process::exit(1); } } diff --git a/src/plugin.rs b/src/plugin.rs deleted file mode 100644 index 3f17a7d..0000000 --- a/src/plugin.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::collections::HashMap; -use std::sync::OnceLock; - -use include_dir::{include_dir, Dir}; - -static PREPENDS_DIR: Dir<'static> = include_dir!("prepends"); - -fn prepends_table() -> &'static HashMap { - static TABLE: OnceLock> = OnceLock::new(); - TABLE.get_or_init(|| { - let mut m = HashMap::new(); - for file in PREPENDS_DIR.files() { - let Some(path_str) = file.path().to_str() else { - continue; - }; - if !path_str.ends_with(".typ") { - continue; - } - let id = path_str[..path_str.len() - 4].replace('\\', "/"); - let text = file - .contents_utf8() - .unwrap_or_else(|| panic!("prepends/{path_str} is not valid UTF-8")) - .to_string(); - if m.insert(id.clone(), text).is_some() { - panic!("duplicate prepend plugin id after normalize: {id}"); - } - } - m - }) -} - -pub fn list_embedded_plugin_ids() -> Vec { - let mut v: Vec = prepends_table().keys().cloned().collect(); - v.sort(); - v -} - -pub fn embedded_prepend_source(id: &str) -> Result { - let id = id.trim().replace('\\', "/"); - if id.is_empty() { - return Err("empty plugin id".into()); - } - prepends_table() - .get(&id) - .cloned() - .ok_or_else(|| { - let known = list_embedded_plugin_ids().join(", "); - if known.is_empty() { - format!("unknown plugin '{id}' (no embedded prepends in this build)") - } else { - format!("unknown plugin '{id}' (embedded: {known})") - } - }) -} - -pub fn concat_plugin_sources(plugin_ids: &[impl AsRef]) -> Result { - let mut out = String::new(); - for (i, id) in plugin_ids.iter().enumerate() { - if i > 0 { - out.push('\n'); - } - out.push_str(&embedded_prepend_source(id.as_ref())?); - } - Ok(out) -}