use rust_embed::RustEmbed;
use crate::config;
use crate::error::Result;
#[derive(RustEmbed)]
#[folder = "typst/"]
#[prefix = ""]
pub struct Assets;
fn root() -> Result<std::path::PathBuf> {
Ok(config::assets_path()?.join("contracts"))
}
pub fn ensure_extracted() -> Result<()> {
let root = root()?;
std::fs::create_dir_all(&root)?;
for path in Assets::iter() {
let file = Assets::get(&path).expect("embedded asset");
let dest = root.join(path.as_ref());
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent)?;
}
let needs_write = match std::fs::read(&dest) {
Ok(existing) => existing != file.data.as_ref(),
Err(_) => true,
};
if needs_write {
std::fs::write(&dest, file.data.as_ref())?;
}
}
Ok(())
}
pub fn template_dir() -> Result<std::path::PathBuf> {
Ok(root()?.join("templates"))
}
pub fn template_path(name: &str) -> Result<std::path::PathBuf> {
Ok(template_dir()?.join(format!("{name}.typ")))
}
pub fn list_templates() -> Result<Vec<String>> {
ensure_extracted()?;
let dir = template_dir()?;
let mut names = Vec::new();
if dir.exists() {
for entry in std::fs::read_dir(&dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("typ") {
if let Some(name) = path.file_stem().and_then(|s| s.to_str()) {
names.push(name.to_string());
}
}
}
}
names.sort();
Ok(names)
}
pub fn has_template(name: &str) -> Result<bool> {
ensure_extracted()?;
Ok(template_path(name)?.exists())
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct TemplateMeta {
pub name: String,
pub description: String,
pub mood: Vec<String>,
pub tags: Vec<String>,
pub fonts: String,
pub paper: String,
}
pub fn template_meta(name: &str) -> Result<TemplateMeta> {
let src = std::fs::read_to_string(template_path(name)?)?;
let mut meta = TemplateMeta {
name: name.to_string(),
description: String::new(),
mood: Vec::new(),
tags: Vec::new(),
fonts: String::new(),
paper: String::new(),
};
for line in src.lines() {
let Some(rest) = line.trim().strip_prefix("//!") else {
if line.trim().is_empty() || line.trim().starts_with("//") {
continue;
}
break; };
if let Some((key, value)) = rest.split_once(':') {
let value = value.trim().to_string();
match key.trim() {
"description" => meta.description = value,
"mood" => meta.mood = split_list(&value),
"tags" => meta.tags = split_list(&value),
"fonts" => meta.fonts = value,
"paper" => meta.paper = value,
_ => {}
}
}
}
if meta.description.is_empty() {
meta.description = "(custom template — no //! metadata header)".into();
}
Ok(meta)
}
pub fn list_template_meta() -> Result<Vec<TemplateMeta>> {
list_templates()?
.iter()
.map(|n| template_meta(n))
.collect()
}
fn split_list(s: &str) -> Vec<String> {
s.split(',')
.map(|t| t.trim().to_lowercase())
.filter(|t| !t.is_empty())
.collect()
}
pub fn project_root() -> Result<std::path::PathBuf> {
root()
}