Skip to main content

gammaloop_api/
templates.rs

1use color_eyre::Result;
2use rust_embed::RustEmbed;
3use std::fs;
4use std::path::Path;
5
6const LINNEST_PACKAGE_DIR: &str = "crates/linnest/typst";
7const KURVST_PACKAGE_DIR: &str = "crates/kurvst/typst";
8
9#[derive(RustEmbed)]
10#[folder = "../../assets/embedded"]
11pub struct Assets;
12
13#[derive(RustEmbed)]
14#[folder = "$CARGO_MANIFEST_DIR/../../assets/embedded/drawing/templates"]
15#[include = "*.typ"]
16struct GammaLoopTemplateAssets;
17
18#[derive(RustEmbed)]
19#[folder = "$CARGO_MANIFEST_DIR/../linnest/typst"]
20#[include = "src/*.typ"]
21#[include = "src/**/*.typ"]
22#[include = "typst.toml"]
23#[include = "linnest.wasm"]
24struct LinnestPackageAssets;
25
26#[derive(RustEmbed)]
27#[folder = "$CARGO_MANIFEST_DIR/../kurvst/typst"]
28#[include = "src/*.typ"]
29#[include = "src/**/*.typ"]
30#[include = "typst.toml"]
31#[include = "kurvst.wasm"]
32struct KurvstPackageAssets;
33
34impl Assets {
35    /// Get all app template paths written under drawings/templates.
36    pub fn template_paths() -> impl Iterator<Item = String> {
37        GammaLoopTemplateAssets::iter().map(|path| path.to_string())
38    }
39
40    /// Extract all drawing templates to drawings/templates relative to target_dir.
41    pub fn extract_templates<P: AsRef<Path>>(target_dir: P) -> Result<()> {
42        let target = target_dir.as_ref().join("drawings/templates");
43
44        fs::create_dir_all(&target)?;
45
46        extract_package::<GammaLoopTemplateAssets>(&target)?;
47        extract_package::<LinnestPackageAssets>(&target.join(LINNEST_PACKAGE_DIR))?;
48        extract_package::<KurvstPackageAssets>(&target.join(KURVST_PACKAGE_DIR))?;
49
50        Ok(())
51    }
52
53    /// Extract the Justfile to the target directory
54    pub fn extract_justfile<P: AsRef<Path>>(target_dir: P) -> Result<()> {
55        let justfile_path = target_dir.as_ref().join("justfile");
56
57        if let Some(file) = Self::get("drawing/justfile") {
58            fs::write(&justfile_path, file.data)?;
59        }
60
61        Ok(())
62    }
63}
64
65fn extract_package<E: RustEmbed>(target: &Path) -> Result<()> {
66    fs::create_dir_all(target)?;
67    for package_path in E::iter() {
68        if let Some(file) = E::get(package_path.as_ref()) {
69            let target_path = target.join(package_path.as_ref());
70
71            if let Some(parent) = target_path.parent() {
72                fs::create_dir_all(parent)?;
73            }
74
75            fs::write(&target_path, file.data)?;
76        }
77    }
78
79    Ok(())
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn extract_templates_uses_canonical_package_layout() -> Result<()> {
88        let tempdir = tempfile::tempdir()?;
89
90        Assets::extract_templates(tempdir.path())?;
91
92        let templates = tempdir.path().join("drawings/templates");
93        assert!(templates.join("figure.typ").is_file());
94        assert!(templates.join("grid.typ").is_file());
95        assert!(templates.join("layout.typ").is_file());
96        assert!(templates.join("layout-core.typ").is_file());
97        assert!(fs::read_to_string(templates.join("grid.typ"))?.contains("page_format"));
98        let figure = fs::read_to_string(templates.join("figure.typ"))?;
99        assert!(figure.contains("amplitude-mode"));
100        assert!(figure.contains("cross-section-mode"));
101        assert!(figure.contains("show-node-index"));
102        assert!(figure.contains("unit: 1.5"));
103        let layout = fs::read_to_string(templates.join("layout.typ"))?;
104        assert!(layout.contains("#import \"layout-core.typ\": bind-layout"));
105        assert!(layout.contains("#let layout = bind-layout("));
106        assert!(!layout.contains("graph.parse(input)"));
107        let layout_core = fs::read_to_string(templates.join("layout-core.typ"))?;
108        assert!(layout_core.contains("autogen-external-edge-fields"));
109        assert!(layout_core.contains("momentum-index"));
110        assert!(layout_core.contains("$(p_#index)$"));
111        assert!(layout_core.contains("$n_#index$"));
112        assert!(layout_core.contains("graph.style("));
113        assert!(layout_core.contains("length-scale: 0.4"));
114        assert!(layout_core.contains("z-spring-growth: 1.01"));
115
116        assert!(templates
117            .join("crates/linnest/typst/linnest.wasm")
118            .is_file());
119        assert!(templates
120            .join("crates/linnest/typst/src/curve.typ")
121            .is_file());
122        assert!(templates
123            .join("crates/linnest/typst/src/impl/graph.typ")
124            .is_file());
125        assert!(templates.join("crates/kurvst/typst/kurvst.wasm").is_file());
126        assert!(templates.join("crates/kurvst/typst/src/lib.typ").is_file());
127        assert!(templates.join("crates/kurvst/typst/src/impl.typ").is_file());
128
129        assert!(!templates.join("linnest.wasm").exists());
130        assert!(!templates.join("kurvst.wasm").exists());
131        assert!(!templates.join("curve.typ").exists());
132
133        Assets::extract_justfile(tempdir.path())?;
134        let justfile = fs::read_to_string(tempdir.path().join("justfile"))?;
135        assert!(justfile.contains("--style drawings/templates/layout-core.typ"));
136        assert!(justfile.contains("momentum_line_mark := \"auto\""));
137        assert!(justfile.contains("show_node_index := \"true\""));
138        assert!(justfile.contains("show_particle := \"false\""));
139
140        Ok(())
141    }
142}