secunit_core/wisp/
template.rs1use std::path::{Path, PathBuf};
9
10use anyhow::{bail, Result};
11
12use super::Format;
13
14pub const DEFAULT_DIR: &str = "templates/wisp";
16
17pub const TYPST_PARTIALS: &[(&str, &str)] = &[
19 ("theme.typ", include_str!("assets/typst/theme.typ")),
20 ("header.typ", include_str!("assets/typst/header.typ")),
21 ("footer.typ", include_str!("assets/typst/footer.typ")),
22 ("cover.typ", include_str!("assets/typst/cover.typ")),
23 ("toc.typ", include_str!("assets/typst/toc.typ")),
24];
25
26pub const LOGO_SVG: &str = include_str!("assets/logo.svg");
28
29pub fn bundled(format: Format) -> Result<&'static [(&'static str, &'static str)]> {
32 match format {
33 Format::Typst => Ok(TYPST_PARTIALS),
34 Format::Html => bail!(
35 "HTML partials are not available yet — the HTML render backends \
36 (WeasyPrint/Chromium) are opt-in and not wired. Use `--format typst`."
37 ),
38 }
39}
40
41pub fn detect_format(dir: &Path) -> Option<Format> {
44 if dir.join("theme.typ").exists() {
45 Some(Format::Typst)
46 } else if dir.join("theme.css").exists() {
47 Some(Format::Html)
48 } else {
49 None
50 }
51}
52
53pub fn missing_partials(dir: &Path, format: Format) -> Vec<&'static str> {
56 format
57 .required_partials()
58 .iter()
59 .copied()
60 .filter(|name| !dir.join(name).exists())
61 .collect()
62}
63
64pub fn require_complete(dir: &Path, format: Format) -> Result<()> {
67 if !dir.exists() {
68 bail!(
69 "WISP template directory `{}` does not exist. Run `secunit wisp init` \
70 to scaffold the {} partials, review and commit them, then re-run export.",
71 dir.display(),
72 format
73 );
74 }
75 let missing = missing_partials(dir, format);
76 if !missing.is_empty() {
77 bail!(
78 "WISP template `{}` is missing required partial(s): {}. Run \
79 `secunit wisp init --force` to restore the defaults, or add them by hand.",
80 dir.display(),
81 missing.join(", ")
82 );
83 }
84 Ok(())
85}
86
87pub fn resolve_dir(root: &Path, override_dir: Option<&Path>) -> PathBuf {
89 match override_dir {
90 Some(d) if d.is_absolute() => d.to_path_buf(),
91 Some(d) => root.join(d),
92 None => root.join(DEFAULT_DIR),
93 }
94}