use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Config {
#[serde(default)]
pub user: UserConfig,
#[serde(default)]
pub institution: InstitutionConfig,
#[serde(default)]
pub defaults: DefaultsConfig,
#[serde(default)]
pub templates: TemplatesConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct UserConfig {
pub name: Option<String>,
pub email: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct InstitutionConfig {
pub name: Option<String>,
pub address: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DefaultsConfig {
pub documentclass: Option<String>,
pub fontsize: Option<String>,
pub papersize: Option<String>,
pub language: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TemplatesConfig {
pub source: Option<String>,
pub auto_update: Option<bool>,
pub watch: Option<bool>,
}
#[allow(dead_code)]
fn config_dir() -> Result<PathBuf> {
if let Ok(xdg_config) = std::env::var("XDG_CONFIG_HOME") {
let path = PathBuf::from(xdg_config).join("texforge");
return Ok(path);
}
let home = dirs::home_dir().ok_or_else(|| anyhow!("Could not determine home directory"))?;
Ok(home.join(".texforge"))
}
pub fn config_file_path() -> Result<PathBuf> {
if let Ok(xdg_config) = std::env::var("XDG_CONFIG_HOME") {
let path = PathBuf::from(xdg_config).join("texforge/config.toml");
return Ok(path);
}
let home = dirs::home_dir().ok_or_else(|| anyhow!("Could not determine home directory"))?;
Ok(home.join(".texforge/config.toml"))
}
pub fn load() -> Result<Config> {
let path = config_file_path()?;
if !path.exists() {
return Ok(Config::default());
}
let content = fs::read_to_string(&path)
.with_context(|| format!("Failed to read config file: {}", path.display()))?;
toml::from_str(&content).context("Failed to parse config TOML")
}
pub fn save(config: &Config) -> Result<()> {
let path = config_file_path()?;
let dir = path
.parent()
.ok_or_else(|| anyhow!("Invalid config path"))?;
fs::create_dir_all(dir).context("Failed to create config directory")?;
let content = toml::to_string_pretty(config).context("Failed to serialize config")?;
fs::write(&path, content)
.with_context(|| format!("Failed to write config file: {}", path.display()))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_config() {
let toml_str = r#"
[user]
name = "Jane Doe"
email = "jane@example.com"
[defaults]
documentclass = "article"
fontsize = "11pt"
"#;
let config: Config = toml::from_str(toml_str).unwrap();
assert_eq!(config.user.name, Some("Jane Doe".to_string()));
assert_eq!(config.defaults.fontsize, Some("11pt".to_string()));
}
#[test]
fn test_serialize_config() {
let mut config = Config::default();
config.user.name = Some("John Doe".to_string());
config.user.email = Some("john@example.com".to_string());
let toml_str = toml::to_string_pretty(&config).unwrap();
assert!(toml_str.contains("John Doe"));
assert!(toml_str.contains("john@example.com"));
}
}