use crate::error::{Error, Result};
#[cfg(feature = "validation")]
use crate::schema::Schema;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateSchema {
pub name: String,
pub display_name: String,
pub description: String,
pub version: String,
pub authors: Vec<String>,
pub category: TemplateCategory,
#[serde(default)]
pub metadata_schema: serde_json::Value,
#[serde(default)]
pub required_files: Vec<String>,
#[serde(default)]
pub options: Vec<TemplateOption>,
}
impl TemplateSchema {
pub fn from_directory(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let toml_path = path.join("template.toml");
if toml_path.exists() {
let content = std::fs::read_to_string(&toml_path)?;
let template: Self = toml::from_str(&content)?;
return Ok(template);
}
let json_path = path.join("template.json");
if json_path.exists() {
let content = std::fs::read_to_string(&json_path)?;
let template: Self = serde_json::from_str(&content)?;
return Ok(template);
}
Err(Error::SchemaNotFound {
name: path.display().to_string(),
})
}
#[cfg(feature = "validation")]
pub fn metadata_validator(&self) -> Result<Schema> {
Schema::new(&self.name, self.metadata_schema.clone())
}
#[must_use]
pub fn requires_file(&self, filename: &str) -> bool {
self.required_files.iter().any(|f| f == filename)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TemplateCategory {
Article,
Book,
Report,
Cv,
Presentation,
Documentation,
Letter,
Other,
}
impl std::fmt::Display for TemplateCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Article => write!(f, "article"),
Self::Book => write!(f, "book"),
Self::Report => write!(f, "report"),
Self::Cv => write!(f, "cv"),
Self::Presentation => write!(f, "presentation"),
Self::Documentation => write!(f, "documentation"),
Self::Letter => write!(f, "letter"),
Self::Other => write!(f, "other"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateOption {
pub name: String,
pub label: String,
pub description: String,
pub option_type: OptionType,
pub default: serde_json::Value,
#[serde(default)]
pub required: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OptionType {
String,
Boolean,
Integer,
Number,
Enum(Vec<String>),
Color,
File,
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_template() -> TemplateSchema {
TemplateSchema {
name: "ieee-article".to_string(),
display_name: "IEEE Article".to_string(),
description: "Template for IEEE journal articles".to_string(),
version: "1.0.0".to_string(),
authors: vec!["NovaType Team".to_string()],
category: TemplateCategory::Article,
metadata_schema: serde_json::json!({
"type": "object",
"properties": {
"title": { "type": "string" }
},
"required": ["title"]
}),
required_files: vec!["main.typ".to_string()],
options: vec![],
}
}
#[test]
fn template_serialization() {
let template = sample_template();
let json = serde_json::to_string_pretty(&template).unwrap();
assert!(json.contains("ieee-article"));
assert!(json.contains("IEEE Article"));
}
#[test]
fn template_deserialization() {
let json = r#"{
"name": "test",
"display_name": "Test Template",
"description": "A test template",
"version": "0.1.0",
"authors": ["Test Author"],
"category": "article",
"metadata_schema": {},
"required_files": ["main.typ"],
"options": []
}"#;
let template: TemplateSchema = serde_json::from_str(json).unwrap();
assert_eq!(template.name, "test");
assert_eq!(template.category, TemplateCategory::Article);
}
#[test]
fn template_requires_file() {
let template = sample_template();
assert!(template.requires_file("main.typ"));
assert!(!template.requires_file("other.typ"));
}
#[test]
fn template_category_display() {
assert_eq!(TemplateCategory::Article.to_string(), "article");
assert_eq!(TemplateCategory::Book.to_string(), "book");
assert_eq!(TemplateCategory::Cv.to_string(), "cv");
}
#[test]
#[cfg(feature = "validation")]
fn template_metadata_validator() {
let template = sample_template();
let validator = template.metadata_validator().unwrap();
let valid = serde_json::json!({ "title": "Test" });
let invalid = serde_json::json!({});
assert!(validator.is_valid(&valid));
assert!(!validator.is_valid(&invalid));
}
}