spin_templates/
store.rs

1use anyhow::Context;
2use spin_common::data_dir::data_dir;
3use std::path::{Path, PathBuf};
4
5use crate::directory::subdirectories;
6
7pub(crate) struct TemplateStore {
8    root: PathBuf,
9}
10
11lazy_static::lazy_static! {
12    static ref UNSAFE_CHARACTERS: regex::Regex = regex::Regex::new("[^-_a-zA-Z0-9]").expect("Invalid identifier regex");
13}
14
15impl TemplateStore {
16    pub(crate) fn new(root: impl AsRef<Path>) -> Self {
17        Self {
18            root: root.as_ref().to_owned(),
19        }
20    }
21
22    pub(crate) fn try_default() -> anyhow::Result<Self> {
23        Ok(Self::new(data_dir()?.join("templates")))
24    }
25
26    pub(crate) fn get_directory(&self, id: impl AsRef<str>) -> PathBuf {
27        self.root.join(Self::relative_dir(id.as_ref()))
28    }
29
30    pub(crate) fn get_layout(&self, id: impl AsRef<str>) -> Option<TemplateLayout> {
31        let template_dir = self.get_directory(id);
32        if template_dir.exists() {
33            Some(TemplateLayout::new(&template_dir))
34        } else {
35            None
36        }
37    }
38
39    pub(crate) async fn list_layouts(&self) -> anyhow::Result<Vec<TemplateLayout>> {
40        if !self.root.exists() {
41            return Ok(vec![]);
42        }
43
44        let template_dirs = subdirectories(&self.root).with_context(|| {
45            format!(
46                "Failed to read template directories from {}",
47                self.root.display()
48            )
49        })?;
50
51        Ok(template_dirs.iter().map(TemplateLayout::new).collect())
52    }
53
54    fn relative_dir(id: &str) -> impl AsRef<Path> {
55        // Using the SHA could generate quite long directory names, which could be a problem on Windows
56        // if the template filenames are also long. Longer term, consider an alternative approach where
57        // we use an index or something for disambiguation, and/or disambiguating only if a clash is
58        // detected, etc.
59        let id_sha256 = spin_common::sha256::hex_digest_from_bytes(id);
60        format!("{}_{}", UNSAFE_CHARACTERS.replace_all(id, "_"), id_sha256)
61    }
62}
63
64pub(crate) struct TemplateLayout {
65    template_dir: PathBuf,
66}
67
68const METADATA_DIR_NAME: &str = "metadata";
69const CONTENT_DIR_NAME: &str = "content";
70const SNIPPETS_DIR_NAME: &str = "snippets";
71
72const MANIFEST_FILE_NAME: &str = "spin-template.toml";
73
74const INSTALLATION_RECORD_FILE_NAME: &str = ".install.toml";
75
76impl TemplateLayout {
77    pub fn new(template_dir: impl AsRef<Path>) -> Self {
78        Self {
79            template_dir: template_dir.as_ref().to_owned(),
80        }
81    }
82
83    pub fn metadata_dir(&self) -> PathBuf {
84        self.template_dir.join(METADATA_DIR_NAME)
85    }
86
87    pub fn manifest_path(&self) -> PathBuf {
88        self.metadata_dir().join(MANIFEST_FILE_NAME)
89    }
90
91    pub fn content_dir(&self) -> PathBuf {
92        self.template_dir.join(CONTENT_DIR_NAME)
93    }
94
95    pub fn snippets_dir(&self) -> PathBuf {
96        self.metadata_dir().join(SNIPPETS_DIR_NAME)
97    }
98
99    pub fn installation_record_file(&self) -> PathBuf {
100        self.template_dir.join(INSTALLATION_RECORD_FILE_NAME)
101    }
102}