tideway_cli/templates/
mod.rs1use anyhow::{Result, anyhow};
6use handlebars::Handlebars;
7use include_dir::{Dir, include_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 {
38 handlebars,
39 context,
40 })
41 }
42
43 pub fn render(&self, template_name: &str) -> Result<String> {
45 let style_suffix = match self.context.style {
47 Style::Shadcn => "shadcn",
48 Style::Tailwind => "tailwind",
49 Style::Unstyled => "unstyled",
50 };
51
52 let styled_name = format!("vue/{}.{}", template_name, style_suffix);
54 let default_name = format!("vue/{}", template_name);
55
56 let template_key = if self.handlebars.has_template(&styled_name) {
57 styled_name
58 } else if self.handlebars.has_template(&default_name) {
59 default_name
60 } else {
61 return Err(anyhow!("Template not found: {}", template_name));
62 };
63
64 self.handlebars
65 .render(&template_key, &self.context)
66 .map_err(|e| anyhow!("Failed to render template {}: {}", template_name, e))
67 }
68}
69
70fn register_templates(
72 handlebars: &mut Handlebars<'static>,
73 dir: &'static Dir<'static>,
74 prefix: &str,
75) -> Result<()> {
76 for entry in dir.entries() {
77 match entry {
78 include_dir::DirEntry::Dir(subdir) => {
79 let new_prefix = if prefix.is_empty() {
80 subdir.path().to_string_lossy().to_string()
81 } else {
82 format!(
83 "{}/{}",
84 prefix,
85 subdir.path().file_name().unwrap().to_string_lossy()
86 )
87 };
88 register_templates(handlebars, subdir, &new_prefix)?;
89 }
90 include_dir::DirEntry::File(file) => {
91 let path = file.path();
92 if path.extension().map_or(false, |ext| ext == "hbs") {
93 let name = path.file_stem().unwrap().to_string_lossy();
95 let template_key = if prefix.is_empty() {
96 name.to_string()
97 } else {
98 format!("{}/{}", prefix, name)
99 };
100
101 let content = file
102 .contents_utf8()
103 .ok_or_else(|| anyhow!("Invalid UTF-8 in template: {}", path.display()))?;
104
105 handlebars.register_template_string(&template_key, content)?;
106 }
107 }
108 }
109 }
110 Ok(())
111}
112
113impl Serialize for Style {
115 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
116 where
117 S: serde::Serializer,
118 {
119 serializer.serialize_str(&self.to_string())
120 }
121}
122
123#[derive(Serialize, Clone)]
125pub struct BackendTemplateContext {
126 pub project_name: String,
128 pub project_name_pascal: String,
130 pub has_organizations: bool,
132 pub database: String,
134 pub tideway_version: String,
136 pub tideway_features: Vec<String>,
138 pub has_tideway_features: bool,
140 pub has_auth_feature: bool,
142 pub has_database_feature: bool,
144 pub has_openapi_feature: bool,
146 pub needs_arc: bool,
148 pub has_config: bool,
150}
151
152pub struct BackendTemplateEngine {
154 handlebars: Handlebars<'static>,
155 context: BackendTemplateContext,
156}
157
158impl BackendTemplateEngine {
159 pub fn new(context: BackendTemplateContext) -> Result<Self> {
161 let mut handlebars = Handlebars::new();
162 handlebars.set_strict_mode(true);
163
164 register_templates(&mut handlebars, &TEMPLATES_DIR, "")?;
166
167 Ok(Self {
168 handlebars,
169 context,
170 })
171 }
172
173 pub fn render(&self, template_name: &str) -> Result<String> {
175 let template_key = format!("backend/{}", template_name);
176
177 if !self.handlebars.has_template(&template_key) {
178 return Err(anyhow!("Backend template not found: {}", template_name));
179 }
180
181 self.handlebars
182 .render(&template_key, &self.context)
183 .map_err(|e| anyhow!("Failed to render template {}: {}", template_name, e))
184 }
185
186 pub fn has_template(&self, template_name: &str) -> bool {
188 let template_key = format!("backend/{}", template_name);
189 self.handlebars.has_template(&template_key)
190 }
191}
192
193impl Serialize for BackendPreset {
195 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
196 where
197 S: serde::Serializer,
198 {
199 serializer.serialize_str(&self.to_string())
200 }
201}