Skip to main content

secunit_core/wisp/
scaffold.rs

1//! `wisp init` — scaffold the generic, operator-owned partials.
2
3use std::fs;
4
5use anyhow::{Context, Result};
6
7use super::{template, Format, InitOptions, InitReport};
8
9/// Write the bundled generic partials into `opts.dir`. Idempotent: existing
10/// files are skipped unless `opts.force` is set. Also seeds `logo.svg` (from
11/// `opts.logo` if given, else the bundled shield).
12pub fn init(opts: &InitOptions) -> Result<InitReport> {
13    let partials = template::bundled(opts.format)?;
14
15    fs::create_dir_all(&opts.dir)
16        .with_context(|| format!("create template dir {}", opts.dir.display()))?;
17
18    let mut written = Vec::new();
19    let mut skipped = Vec::new();
20
21    for &(name, body) in partials {
22        let dest = opts.dir.join(name);
23        if dest.exists() && !opts.force {
24            skipped.push(name.to_string());
25            continue;
26        }
27        fs::write(&dest, body).with_context(|| format!("write {}", dest.display()))?;
28        written.push(name.to_string());
29    }
30
31    // Seed the logo. A custom `--logo` is copied verbatim under its own
32    // extension; otherwise the bundled shield is written as `logo.svg`.
33    match &opts.logo {
34        Some(src) => {
35            let ext = src
36                .extension()
37                .and_then(|e| e.to_str())
38                .unwrap_or("svg")
39                .to_ascii_lowercase();
40            let dest = opts.dir.join(format!("logo.{ext}"));
41            if dest.exists() && !opts.force {
42                skipped.push(format!("logo.{ext}"));
43            } else {
44                fs::copy(src, &dest).with_context(|| {
45                    format!("copy logo {} -> {}", src.display(), dest.display())
46                })?;
47                written.push(format!("logo.{ext}"));
48            }
49        }
50        None => {
51            let dest = opts.dir.join("logo.svg");
52            if dest.exists() && !opts.force {
53                skipped.push("logo.svg".to_string());
54            } else {
55                fs::write(&dest, template::LOGO_SVG)
56                    .with_context(|| format!("write {}", dest.display()))?;
57                written.push("logo.svg".to_string());
58            }
59        }
60    }
61
62    Ok(InitReport {
63        dir: opts.dir.clone(),
64        format: opts.format,
65        written,
66        skipped,
67    })
68}
69
70/// Convenience for the common case: scaffold the default Typst template dir.
71pub fn init_default(dir: impl Into<std::path::PathBuf>) -> Result<InitReport> {
72    init(&InitOptions {
73        dir: dir.into(),
74        format: Format::Typst,
75        logo: None,
76        force: false,
77    })
78}