tideway_cli/templates/
mod.rs1use anyhow::{anyhow, Result};
6use handlebars::Handlebars;
7use include_dir::{include_dir, Dir};
8use serde::Serialize;
9
10use crate::cli::{BackendPreset, Style};
11
12static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
14
15#[derive(Serialize, Clone)]
17pub struct TemplateContext {
18 pub api_base_url: String,
19 pub style: Style,
20}
21
22pub struct TemplateEngine {
24 handlebars: Handlebars<'static>,
25 context: TemplateContext,
26}
27
28impl TemplateEngine {
29 pub fn new(context: TemplateContext) -> Result<Self> {
31 let mut handlebars = Handlebars::new();
32 handlebars.set_strict_mode(true);
33
34 register_templates(&mut handlebars, &TEMPLATES_DIR, "")?;
36
37 Ok(Self { handlebars, context })
38 }
39
40 pub fn render(&self, template_name: &str) -> Result<String> {
42 let style_suffix = match self.context.style {
44 Style::Shadcn => "shadcn",
45 Style::Tailwind => "tailwind",
46 Style::Unstyled => "unstyled",
47 };
48
49 let styled_name = format!("vue/{}.{}", template_name, style_suffix);
51 let default_name = format!("vue/{}", template_name);
52
53 let template_key = if self.handlebars.has_template(&styled_name) {
54 styled_name
55 } else if self.handlebars.has_template(&default_name) {
56 default_name
57 } else {
58 return Err(anyhow!("Template not found: {}", template_name));
59 };
60
61 self.handlebars
62 .render(&template_key, &self.context)
63 .map_err(|e| anyhow!("Failed to render template {}: {}", template_name, e))
64 }
65}
66
67fn register_templates(
69 handlebars: &mut Handlebars<'static>,
70 dir: &'static Dir<'static>,
71 prefix: &str,
72) -> Result<()> {
73 for entry in dir.entries() {
74 match entry {
75 include_dir::DirEntry::Dir(subdir) => {
76 let new_prefix = if prefix.is_empty() {
77 subdir.path().to_string_lossy().to_string()
78 } else {
79 format!("{}/{}", prefix, subdir.path().file_name().unwrap().to_string_lossy())
80 };
81 register_templates(handlebars, subdir, &new_prefix)?;
82 }
83 include_dir::DirEntry::File(file) => {
84 let path = file.path();
85 if path.extension().map_or(false, |ext| ext == "hbs") {
86 let name = path.file_stem().unwrap().to_string_lossy();
88 let template_key = if prefix.is_empty() {
89 name.to_string()
90 } else {
91 format!("{}/{}", prefix, name)
92 };
93
94 let content = file
95 .contents_utf8()
96 .ok_or_else(|| anyhow!("Invalid UTF-8 in template: {}", path.display()))?;
97
98 handlebars.register_template_string(&template_key, content)?;
99 }
100 }
101 }
102 }
103 Ok(())
104}
105
106impl Serialize for Style {
108 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
109 where
110 S: serde::Serializer,
111 {
112 serializer.serialize_str(&self.to_string())
113 }
114}
115
116#[derive(Serialize, Clone)]
118pub struct BackendTemplateContext {
119 pub project_name: String,
121 pub project_name_pascal: String,
123 pub has_organizations: bool,
125 pub database: String,
127 pub tideway_version: String,
129 pub tideway_features: Vec<String>,
131 pub has_tideway_features: bool,
133 pub has_auth_feature: bool,
135 pub has_database_feature: bool,
137 pub has_openapi_feature: bool,
139 pub needs_arc: bool,
141 pub has_config: bool,
143}
144
145pub struct BackendTemplateEngine {
147 handlebars: Handlebars<'static>,
148 context: BackendTemplateContext,
149}
150
151impl BackendTemplateEngine {
152 pub fn new(context: BackendTemplateContext) -> Result<Self> {
154 let mut handlebars = Handlebars::new();
155 handlebars.set_strict_mode(true);
156
157 register_templates(&mut handlebars, &TEMPLATES_DIR, "")?;
159
160 Ok(Self { handlebars, context })
161 }
162
163 pub fn render(&self, template_name: &str) -> Result<String> {
165 let template_key = format!("backend/{}", template_name);
166
167 if !self.handlebars.has_template(&template_key) {
168 return Err(anyhow!("Backend template not found: {}", template_name));
169 }
170
171 self.handlebars
172 .render(&template_key, &self.context)
173 .map_err(|e| anyhow!("Failed to render template {}: {}", template_name, e))
174 }
175
176 pub fn has_template(&self, template_name: &str) -> bool {
178 let template_key = format!("backend/{}", template_name);
179 self.handlebars.has_template(&template_key)
180 }
181}
182
183impl Serialize for BackendPreset {
185 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
186 where
187 S: serde::Serializer,
188 {
189 serializer.serialize_str(&self.to_string())
190 }
191}