use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(default)]
pub default_author: Option<String>,
#[serde(default)]
pub default_license: Option<String>,
#[serde(default)]
pub default_ci: Option<String>,
#[serde(default)]
pub custom_template_dirs: Vec<PathBuf>,
#[serde(default = "default_remember_choices")]
pub remember_choices: bool,
}
fn default_remember_choices() -> bool {
true
}
pub trait ConfigDefaults {
fn new() -> Self;
}
impl ConfigDefaults for Config {
fn new() -> Self {
Config {
default_author: None,
default_license: None,
default_ci: None,
custom_template_dirs: Vec::new(),
remember_choices: true,
}
}
}
impl Config {
pub fn new() -> Self {
<Self as ConfigDefaults>::new()
}
pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
if !path.exists() {
return Ok(Self::new());
}
let content = fs::read_to_string(path)
.with_context(|| format!("Failed to read config file: {}", path.display()))?;
let config: Config = toml::from_str(&content)
.with_context(|| format!("Failed to parse config file: {}", path.display()))?;
Ok(config)
}
pub fn load_from_home() -> Result<Self> {
if let Some(home_dir) = dirs::home_dir() {
Self::load_from_home_with_path(&home_dir)
} else {
Ok(Self::new())
}
}
pub fn load_from_home_with_path<P: AsRef<Path>>(home_dir: P) -> Result<Self> {
let config_dir = home_dir.as_ref().join(".cargo-forge");
let config_path = config_dir.join("config.toml");
Self::load_from_file(&config_path)
}
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let path = path.as_ref();
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).with_context(|| {
format!("Failed to create config directory: {}", parent.display())
})?;
}
let content = toml::to_string_pretty(self).context("Failed to serialize config to TOML")?;
fs::write(path, content)
.with_context(|| format!("Failed to write config file: {}", path.display()))?;
Ok(())
}
pub fn save_to_home(&self) -> Result<()> {
if let Some(home_dir) = dirs::home_dir() {
let config_dir = home_dir.join(".cargo-forge");
let config_path = config_dir.join("config.toml");
self.save_to_file(&config_path)
} else {
anyhow::bail!("Could not determine home directory")
}
}
pub fn merge_with_cli(
&self,
cli_author: Option<String>,
cli_license: Option<String>,
cli_ci: Option<String>,
) -> Self {
Config {
default_author: cli_author.or_else(|| self.default_author.clone()),
default_license: cli_license.or_else(|| self.default_license.clone()),
default_ci: cli_ci.or_else(|| self.default_ci.clone()),
custom_template_dirs: self.custom_template_dirs.clone(),
remember_choices: self.remember_choices,
}
}
pub fn add_custom_template_directory(&mut self, path: PathBuf) {
if !self.custom_template_dirs.contains(&path) {
self.custom_template_dirs.push(path);
}
}
pub fn remember_choice(&mut self, choice_type: &str, value: &str) {
if !self.remember_choices {
return;
}
match choice_type {
"author" => self.default_author = Some(value.to_string()),
"license" => self.default_license = Some(value.to_string()),
"ci" => self.default_ci = Some(value.to_string()),
_ => {}
}
}
pub fn get_effective_author(&self, cli_author: Option<String>) -> Option<String> {
cli_author.or_else(|| self.default_author.clone())
}
pub fn get_effective_license(&self, cli_license: Option<String>) -> Option<String> {
cli_license.or_else(|| self.default_license.clone())
}
pub fn get_effective_ci(&self, cli_ci: Option<String>) -> Option<String> {
cli_ci.or_else(|| self.default_ci.clone())
}
}
impl Default for Config {
fn default() -> Self {
Self::new()
}
}