add: plugin system
This commit is contained in:
parent
aa7b1d0a63
commit
68ed807e95
7 changed files with 119 additions and 15 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -1173,6 +1173,25 @@ 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"
|
||||
|
|
@ -2395,6 +2414,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"clap",
|
||||
"env_logger",
|
||||
"include_dir",
|
||||
"log",
|
||||
"typst",
|
||||
"typst-as-lib",
|
||||
|
|
|
|||
|
|
@ -6,6 +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"
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
#let image(source, width: "400px") = {
|
||||
html.elem("img", attrs: (
|
||||
src: "/static/articles/" + source,
|
||||
alt: source,
|
||||
width: str(width),
|
||||
))
|
||||
}
|
||||
|
||||
1
prepends/image.typ
Normal file
1
prepends/image.typ
Normal file
|
|
@ -0,0 +1 @@
|
|||
#let image = (..) => [IMAGE]
|
||||
20
src/lib.rs
20
src/lib.rs
|
|
@ -1,6 +1,10 @@
|
|||
mod plugin;
|
||||
|
||||
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_as_lib::{typst_kit_options::TypstKitFontOptions, TypstEngine};
|
||||
use typst_html::{HtmlAttr, HtmlDocument, HtmlElement, HtmlNode};
|
||||
|
|
@ -10,6 +14,7 @@ use log::info;
|
|||
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());
|
||||
|
|
@ -18,7 +23,9 @@ pub fn compile_article(
|
|||
let output = article_dir.join("index.html");
|
||||
let outline_file = article_dir.join("outline.html");
|
||||
|
||||
let prepend_content = if let Some(prepend_file) = prepend {
|
||||
let plugin_block = concat_plugin_sources(plugins)?;
|
||||
|
||||
let user_prepend = if let Some(prepend_file) = prepend {
|
||||
fs::read_to_string(&prepend_file).map_err(|e| {
|
||||
format!(
|
||||
"could not read prepend file {}: {e}",
|
||||
|
|
@ -30,7 +37,11 @@ pub fn compile_article(
|
|||
};
|
||||
|
||||
let mut template: EcoString = EcoString::new();
|
||||
template.push_str(&prepend_content);
|
||||
template.push_str(&plugin_block);
|
||||
if !plugin_block.is_empty() && !user_prepend.is_empty() {
|
||||
template.push('\n');
|
||||
}
|
||||
template.push_str(&user_prepend);
|
||||
template.push_str(
|
||||
&fs::read_to_string(&template_file).map_err(|e| {
|
||||
format!(
|
||||
|
|
@ -186,6 +197,7 @@ fn parse_outline(
|
|||
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)? {
|
||||
|
|
@ -193,10 +205,10 @@ pub fn compile_all(
|
|||
let path = entry.path();
|
||||
|
||||
if path.is_dir() {
|
||||
compile_all(&path, prepend, include_title_in_outline)?;
|
||||
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, include_title_in_outline)?;
|
||||
compile_article(&dir, prepend, plugins, include_title_in_outline)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
19
src/main.rs
19
src/main.rs
|
|
@ -2,7 +2,7 @@ use std::env;
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use typssg::{compile_article, compile_all};
|
||||
use typssg::{compile_all, compile_article};
|
||||
use log::{info, error};
|
||||
|
||||
#[derive(Parser)]
|
||||
|
|
@ -13,6 +13,9 @@ struct Args {
|
|||
#[arg(long)]
|
||||
prepend: Option<PathBuf>,
|
||||
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
plugin: Vec<String>,
|
||||
|
||||
#[arg(short)]
|
||||
recursive: bool,
|
||||
|
||||
|
|
@ -31,9 +34,19 @@ fn main() {
|
|||
let args = Args::parse();
|
||||
|
||||
let result = if args.recursive {
|
||||
compile_all(&args.dir, &args.prepend, args.include_title_in_outline)
|
||||
compile_all(
|
||||
&args.dir,
|
||||
&args.prepend,
|
||||
&args.plugin,
|
||||
args.include_title_in_outline,
|
||||
)
|
||||
} else {
|
||||
compile_article(&args.dir, &args.prepend, args.include_title_in_outline)
|
||||
compile_article(
|
||||
&args.dir,
|
||||
&args.prepend,
|
||||
&args.plugin,
|
||||
args.include_title_in_outline,
|
||||
)
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
|
|
|
|||
65
src/plugin.rs
Normal file
65
src/plugin.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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