use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateManifest {
pub name: String,
pub version: String,
pub description: String,
pub author: String,
pub kind: TemplateKind,
pub variables: Vec<TemplateVariable>,
pub files: Vec<TemplateFile>,
pub post_generate: Vec<String>,
pub min_rust_version: Option<String>,
pub edition: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TemplateKind {
CliApp,
Library,
WebService,
Embedded,
Workspace,
Custom,
}
impl TemplateKind {
pub fn description(&self) -> &'static str {
match self {
Self::CliApp => "Command-line application with clap and tokio",
Self::Library => "Library crate with comprehensive testing",
Self::WebService => "Web service with async runtime",
Self::Embedded => "Embedded application with no_std support",
Self::Workspace => "Multi-crate workspace",
Self::Custom => "Custom project template",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateVariable {
pub name: String,
pub description: String,
pub default: Option<String>,
pub required: bool,
pub pattern: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateFile {
pub source: PathBuf,
pub destination: PathBuf,
pub process: bool,
pub permissions: Option<u32>,
}
impl TemplateManifest {
pub fn new(name: String, kind: TemplateKind) -> Self {
Self {
name,
version: "1.0.0".to_string(),
description: String::new(),
author: String::new(),
kind,
variables: Vec::new(),
files: Vec::new(),
post_generate: Vec::new(),
min_rust_version: None,
edition: "2024".to_string(),
}
}
pub fn add_variable(&mut self, variable: TemplateVariable) {
self.variables.push(variable);
}
pub fn add_file(&mut self, file: TemplateFile) {
self.files.push(file);
}
pub fn add_post_generate(&mut self, command: String) {
self.post_generate.push(command);
}
pub fn validate(&self) -> crate::Result<()> {
if self.name.is_empty() {
return Err(crate::Error::validation("Template name cannot be empty"));
}
if self.files.is_empty() {
return Err(crate::Error::validation(
"Template must include at least one file",
));
}
let mut seen = std::collections::HashSet::new();
for var in &self.variables {
if !seen.insert(&var.name) {
return Err(crate::Error::validation(format!(
"Duplicate variable name: {}",
var.name
)));
}
}
Ok(())
}
}
impl TemplateVariable {
pub fn required(name: String, description: String) -> Self {
Self {
name,
description,
default: None,
required: true,
pattern: None,
}
}
pub fn optional(name: String, description: String, default: String) -> Self {
Self {
name,
description,
default: Some(default),
required: false,
pattern: None,
}
}
pub fn with_pattern(mut self, pattern: String) -> Self {
self.pattern = Some(pattern);
self
}
}
impl TemplateFile {
pub fn new(source: PathBuf, destination: PathBuf) -> Self {
Self {
source,
destination,
process: true,
permissions: None,
}
}
pub fn static_file(source: PathBuf, destination: PathBuf) -> Self {
Self {
source,
destination,
process: false,
permissions: None,
}
}
pub fn with_permissions(mut self, permissions: u32) -> Self {
self.permissions = Some(permissions);
self
}
}