add: plugin system
This commit is contained in:
parent
aa7b1d0a63
commit
68ed807e95
7 changed files with 119 additions and 15 deletions
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