Skip to main content

dfx_core/config/
project_templates.rs

1use crate::config::model::project_template::ProjectTemplateCategory;
2use itertools::Itertools;
3use std::collections::BTreeMap;
4use std::fmt::Display;
5use std::io;
6use std::path::PathBuf;
7use std::sync::OnceLock;
8
9type GetArchiveFn = fn() -> Result<tar::Archive<flate2::read::GzDecoder<&'static [u8]>>, io::Error>;
10
11#[derive(Debug, Clone)]
12pub enum ResourceLocation {
13    /// The template's assets are compiled into the dfx binary
14    Bundled { get_archive_fn: GetArchiveFn },
15
16    /// The templates assets are in a directory on the filesystem
17    Directory { path: PathBuf },
18}
19
20#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
21pub struct ProjectTemplateName(pub String);
22
23impl Display for ProjectTemplateName {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "{}", self.0)
26    }
27}
28
29#[derive(Debug, Clone)]
30pub struct ProjectTemplate {
31    /// The name of the template as specified on the command line,
32    /// for example `--type rust`
33    pub name: ProjectTemplateName,
34
35    /// The name used for display and sorting
36    pub display: String,
37
38    /// How to obtain the template's files
39    pub resource_location: ResourceLocation,
40
41    /// Used to determine which CLI group (`--type`, `--backend`, `--frontend`)
42    /// as well as for interactive selection
43    pub category: ProjectTemplateCategory,
44
45    /// Other project templates to patch in alongside this one
46    pub requirements: Vec<ProjectTemplateName>,
47
48    /// Run a command after adding the canister to dfx.json
49    pub post_create: Vec<String>,
50
51    /// If set, display a spinner while this command runs
52    pub post_create_spinner_message: Option<String>,
53
54    /// If the post-create command fails, display this warning but don't fail
55    pub post_create_failure_warning: Option<String>,
56
57    /// The sort order is fixed rather than settable in properties:
58    /// For backend:
59    ///   - motoko=0
60    ///   - rust=1
61    ///   - everything else=2 (and then by display name)
62    ///
63    /// For frontend:
64    ///   - SvelteKit=0
65    ///   - React=1
66    ///   - Vue=2
67    ///   - Vanilla JS=3
68    ///   - No JS Template=4
69    ///   - everything else=5 (and then by display name)
70    ///
71    /// For extras:
72    ///   - Internet Identity=0
73    ///   - Bitcoin=1
74    ///   - everything else=2 (and then by display name)
75    ///   - Frontend Tests
76    pub sort_order: u32,
77}
78
79type ProjectTemplates = BTreeMap<ProjectTemplateName, ProjectTemplate>;
80
81static PROJECT_TEMPLATES: OnceLock<ProjectTemplates> = OnceLock::new();
82
83pub fn populate(builtin_templates: Vec<ProjectTemplate>, loaded_templates: Vec<ProjectTemplate>) {
84    let templates: ProjectTemplates = builtin_templates
85        .into_iter()
86        .map(|t| (t.name.clone(), t))
87        .chain(loaded_templates.into_iter().map(|t| (t.name.clone(), t)))
88        .collect();
89
90    PROJECT_TEMPLATES.set(templates).unwrap();
91}
92
93pub fn get_project_template(name: &ProjectTemplateName) -> ProjectTemplate {
94    PROJECT_TEMPLATES.get().unwrap().get(name).cloned().unwrap()
95}
96
97pub fn find_project_template(name: &ProjectTemplateName) -> Option<ProjectTemplate> {
98    PROJECT_TEMPLATES.get().unwrap().get(name).cloned()
99}
100
101pub fn get_sorted_templates(category: ProjectTemplateCategory) -> Vec<ProjectTemplate> {
102    PROJECT_TEMPLATES
103        .get()
104        .unwrap()
105        .values()
106        .filter(|t| t.category == category)
107        .cloned()
108        .sorted_by(|a, b| {
109            a.sort_order
110                .cmp(&b.sort_order)
111                .then_with(|| a.display.cmp(&b.display))
112        })
113        .collect()
114}
115
116pub fn project_template_cli_names(category: ProjectTemplateCategory) -> Vec<String> {
117    PROJECT_TEMPLATES
118        .get()
119        .map(|templates| {
120            templates
121                .values()
122                .filter(|t| t.category == category)
123                .map(|t| t.name.0.clone())
124                .collect()
125        })
126        .unwrap_or_default()
127}