use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateConfig {
pub search_paths: Vec<PathBuf>,
pub default_variables: HashMap<String, String>,
pub metadata_store: PathBuf,
pub cache_dir: Option<PathBuf>,
pub generation: GenerationOptions,
pub marketplace: MarketplaceSettings,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerationOptions {
pub default_output_dir: PathBuf,
pub auto_format: bool,
pub run_hooks: bool,
pub interactive: bool,
pub force_overwrite: bool,
pub validate_before_gen: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketplaceSettings {
pub enabled: bool,
pub package_cache: PathBuf,
pub auto_update: bool,
pub trusted_sources: Vec<String>,
}
impl Default for TemplateConfig {
fn default() -> Self {
Self {
search_paths: vec![PathBuf::from("templates"), PathBuf::from(".ggen/templates")],
default_variables: HashMap::new(),
metadata_store: PathBuf::from(".ggen/metadata.ttl"),
cache_dir: Some(PathBuf::from(".ggen/template-cache")),
generation: GenerationOptions::default(),
marketplace: MarketplaceSettings::default(),
}
}
}
impl Default for GenerationOptions {
fn default() -> Self {
Self {
default_output_dir: PathBuf::from("."),
auto_format: true,
run_hooks: true,
interactive: false,
force_overwrite: false,
validate_before_gen: true,
}
}
}
impl Default for MarketplaceSettings {
fn default() -> Self {
Self {
enabled: true,
package_cache: PathBuf::from(".ggen/packages"),
auto_update: false,
trusted_sources: vec![
"ggen-official".to_string(),
"community-verified".to_string(),
],
}
}
}
impl TemplateConfig {
pub fn load(path: &PathBuf) -> crate::config_lib::Result<Self> {
let content = std::fs::read_to_string(path)?;
let config: Self = toml::from_str(&content)?;
Ok(config)
}
pub fn save(&self, path: &PathBuf) -> crate::config_lib::Result<()> {
let content = toml::to_string_pretty(self).map_err(|e| {
crate::config_lib::ConfigError::Validation(format!("Failed to serialize config: {}", e))
})?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(path, content)?;
Ok(())
}
pub fn add_search_path(&mut self, path: PathBuf) {
if !self.search_paths.contains(&path) {
self.search_paths.push(path);
}
}
pub fn set_default_variable(&mut self, key: String, value: String) {
self.default_variables.insert(key, value);
}
pub fn get_default_variable(&self, key: &str) -> Option<&String> {
self.default_variables.get(key)
}
pub fn find_template(&self, template_name: &str) -> Option<PathBuf> {
for search_path in &self.search_paths {
let template_path = search_path.join(template_name);
if template_path.exists() {
return Some(template_path);
}
}
None
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = TemplateConfig::default();
assert_eq!(config.search_paths.len(), 2);
assert!(config.generation.auto_format);
assert!(config.marketplace.enabled);
}
#[test]
fn test_add_search_path() {
let mut config = TemplateConfig::default();
let new_path = PathBuf::from("/custom/templates");
config.add_search_path(new_path.clone());
assert!(config.search_paths.contains(&new_path));
let len_before = config.search_paths.len();
config.add_search_path(new_path);
assert_eq!(config.search_paths.len(), len_before);
}
#[test]
fn test_default_variables() {
let mut config = TemplateConfig::default();
config.set_default_variable("project_name".to_string(), "my-project".to_string());
config.set_default_variable("version".to_string(), "1.0.0".to_string());
assert_eq!(
config.get_default_variable("project_name").unwrap(),
"my-project"
);
assert_eq!(config.get_default_variable("version").unwrap(), "1.0.0");
assert!(config.get_default_variable("nonexistent").is_none());
}
}