1use crate::error::{Error, Result};
7#[cfg(feature = "validation")]
8use crate::schema::Schema;
9use serde::{Deserialize, Serialize};
10use std::path::Path;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct TemplateSchema {
15 pub name: String,
17
18 pub display_name: String,
20
21 pub description: String,
23
24 pub version: String,
26
27 pub authors: Vec<String>,
29
30 pub category: TemplateCategory,
32
33 #[serde(default)]
35 pub metadata_schema: serde_json::Value,
36
37 #[serde(default)]
39 pub required_files: Vec<String>,
40
41 #[serde(default)]
43 pub options: Vec<TemplateOption>,
44}
45
46impl TemplateSchema {
47 pub fn from_directory(path: impl AsRef<Path>) -> Result<Self> {
55 let path = path.as_ref();
56
57 let toml_path = path.join("template.toml");
59 if toml_path.exists() {
60 let content = std::fs::read_to_string(&toml_path)?;
61 let template: Self = toml::from_str(&content)?;
62 return Ok(template);
63 }
64
65 let json_path = path.join("template.json");
67 if json_path.exists() {
68 let content = std::fs::read_to_string(&json_path)?;
69 let template: Self = serde_json::from_str(&content)?;
70 return Ok(template);
71 }
72
73 Err(Error::SchemaNotFound {
74 name: path.display().to_string(),
75 })
76 }
77
78 #[cfg(feature = "validation")]
84 pub fn metadata_validator(&self) -> Result<Schema> {
85 Schema::new(&self.name, self.metadata_schema.clone())
86 }
87
88 #[must_use]
90 pub fn requires_file(&self, filename: &str) -> bool {
91 self.required_files.iter().any(|f| f == filename)
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
97#[serde(rename_all = "lowercase")]
98pub enum TemplateCategory {
99 Article,
101 Book,
103 Report,
105 Cv,
107 Presentation,
109 Documentation,
111 Letter,
113 Other,
115}
116
117impl std::fmt::Display for TemplateCategory {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 match self {
120 Self::Article => write!(f, "article"),
121 Self::Book => write!(f, "book"),
122 Self::Report => write!(f, "report"),
123 Self::Cv => write!(f, "cv"),
124 Self::Presentation => write!(f, "presentation"),
125 Self::Documentation => write!(f, "documentation"),
126 Self::Letter => write!(f, "letter"),
127 Self::Other => write!(f, "other"),
128 }
129 }
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct TemplateOption {
135 pub name: String,
137
138 pub label: String,
140
141 pub description: String,
143
144 pub option_type: OptionType,
146
147 pub default: serde_json::Value,
149
150 #[serde(default)]
152 pub required: bool,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157#[serde(rename_all = "lowercase")]
158pub enum OptionType {
159 String,
161 Boolean,
163 Integer,
165 Number,
167 Enum(Vec<String>),
169 Color,
171 File,
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 fn sample_template() -> TemplateSchema {
180 TemplateSchema {
181 name: "ieee-article".to_string(),
182 display_name: "IEEE Article".to_string(),
183 description: "Template for IEEE journal articles".to_string(),
184 version: "1.0.0".to_string(),
185 authors: vec!["NovaType Team".to_string()],
186 category: TemplateCategory::Article,
187 metadata_schema: serde_json::json!({
188 "type": "object",
189 "properties": {
190 "title": { "type": "string" }
191 },
192 "required": ["title"]
193 }),
194 required_files: vec!["main.typ".to_string()],
195 options: vec![],
196 }
197 }
198
199 #[test]
200 fn template_serialization() {
201 let template = sample_template();
202 let json = serde_json::to_string_pretty(&template).unwrap();
203 assert!(json.contains("ieee-article"));
204 assert!(json.contains("IEEE Article"));
205 }
206
207 #[test]
208 fn template_deserialization() {
209 let json = r#"{
210 "name": "test",
211 "display_name": "Test Template",
212 "description": "A test template",
213 "version": "0.1.0",
214 "authors": ["Test Author"],
215 "category": "article",
216 "metadata_schema": {},
217 "required_files": ["main.typ"],
218 "options": []
219 }"#;
220
221 let template: TemplateSchema = serde_json::from_str(json).unwrap();
222 assert_eq!(template.name, "test");
223 assert_eq!(template.category, TemplateCategory::Article);
224 }
225
226 #[test]
227 fn template_requires_file() {
228 let template = sample_template();
229 assert!(template.requires_file("main.typ"));
230 assert!(!template.requires_file("other.typ"));
231 }
232
233 #[test]
234 fn template_category_display() {
235 assert_eq!(TemplateCategory::Article.to_string(), "article");
236 assert_eq!(TemplateCategory::Book.to_string(), "book");
237 assert_eq!(TemplateCategory::Cv.to_string(), "cv");
238 }
239
240 #[test]
241 #[cfg(feature = "validation")]
242 fn template_metadata_validator() {
243 let template = sample_template();
244 let validator = template.metadata_validator().unwrap();
245
246 let valid = serde_json::json!({ "title": "Test" });
247 let invalid = serde_json::json!({});
248
249 assert!(validator.is_valid(&valid));
250 assert!(!validator.is_valid(&invalid));
251 }
252}