Compare commits
No commits in common. "master" and "v0.0.1" have entirely different histories.
14 changed files with 59 additions and 472 deletions
90
Cargo.lock
generated
90
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
3
build.rs
3
build.rs
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
println!("cargo:rerun-if-changed=prepends/");
|
||||
}
|
||||
8
common.typ
Normal file
8
common.typ
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#let image(source, width: "400px") = {
|
||||
html.elem("img", attrs: (
|
||||
src: "/static/articles/" + source,
|
||||
alt: source,
|
||||
width: str(width),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
@ -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")
|
||||
|
|
@ -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
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
// Typst version 14.0.2 outputs a block quote as a <blockquote> followed by
|
||||
// a <p> for the attribution. This makes it difficult to target the
|
||||
// attribution for styling. This snippet instead uses a <blockquote> and
|
||||
// a <figcaption> wrapped in a <figure> 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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[toolchain]
|
||||
channel = "stable"
|
||||
|
||||
10
shell.nix
Normal file
10
shell.nix
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
cargo
|
||||
rustc
|
||||
rust-analyzer
|
||||
];
|
||||
}
|
||||
|
||||
217
src/lib.rs
217
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<PathBuf>,
|
||||
plugins: &[impl AsRef<str>],
|
||||
include_title: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
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| {
|
||||
let mut template: EcoString = EcoString::new();
|
||||
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 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();
|
||||
})?,
|
||||
);
|
||||
|
||||
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 `<ul>` 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("</ul>\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<u32> {
|
||||
match tag {
|
||||
"<h2>" => Some(2),
|
||||
"<h3>" => Some(3),
|
||||
"<h4>" => Some(4),
|
||||
"<h5>" => Some(5),
|
||||
"<h6>" => 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 == "<h2>" {
|
||||
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(),
|
||||
"<h2>" | "<h3>" | "<h4>" | "<h5>" | "<h6>"
|
||||
) {
|
||||
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("<ul>\n");
|
||||
*ul_depth += 1;
|
||||
*curr_level += 1;
|
||||
outline.push_str(" ".repeat(*curr_level as usize - 2).as_str());
|
||||
outline.push_str("<ul>\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("</ul>\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!(
|
||||
"<li><a href=\"#{}\">{}</a></li>\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<PathBuf>,
|
||||
plugins: &[impl AsRef<str>],
|
||||
include_title_in_outline: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
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(())
|
||||
}
|
||||
|
|
|
|||
38
src/main.rs
38
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<PathBuf>,
|
||||
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
plugin: Vec<String>,
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, String> {
|
||||
static TABLE: OnceLock<HashMap<String, String>> = 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<String> {
|
||||
let mut v: Vec<String> = prepends_table().keys().cloned().collect();
|
||||
v.sort();
|
||||
v
|
||||
}
|
||||
|
||||
pub fn embedded_prepend_source(id: &str) -> Result<String, String> {
|
||||
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<str>]) -> Result<String, String> {
|
||||
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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue