use std::path::{Path, PathBuf};
use anyhow::Context;
use serde::{Deserialize, Serialize};
use crate::error::AcrError;
const DEFAULT_TEMPLATE: &str = r#"#[allow(unused_imports)]
use itertools::Itertools;
#[allow(unused_imports)]
use std::collections::{HashMap, HashSet, BTreeMap, BTreeSet, VecDeque};
use proconio::input;
fn main() {
input! {
}
}
"#;
#[derive(Debug, Serialize, Deserialize)]
pub struct GlobalConfig {
pub editor: String,
pub browser: String,
}
impl Default for GlobalConfig {
fn default() -> Self {
Self {
editor: "vim".to_string(),
browser: "xdg-open".to_string(),
}
}
}
fn load_from(path: &Path) -> anyhow::Result<GlobalConfig> {
let content = std::fs::read_to_string(path).map_err(|_| AcrError::ConfigNotFound)?;
let config: GlobalConfig = toml::from_str(&content)
.with_context(|| format!("Failed to parse config: {}", path.display()))?;
Ok(config)
}
fn save_to(path: &Path, config: &GlobalConfig) -> anyhow::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("Failed to create directory: {}", parent.display()))?;
}
let content = toml::to_string(config).with_context(|| "Failed to serialize config to TOML")?;
std::fs::write(path, content)
.with_context(|| format!("Failed to write config: {}", path.display()))?;
Ok(())
}
fn load_template_from(path: &Path) -> String {
std::fs::read_to_string(path).unwrap_or_else(|_| DEFAULT_TEMPLATE.to_string())
}
fn config_path() -> anyhow::Result<PathBuf> {
Ok(crate::config::config_dir()?.join("config.toml"))
}
pub fn template_path() -> anyhow::Result<PathBuf> {
Ok(crate::config::config_dir()?.join("template.rs"))
}
pub fn load() -> anyhow::Result<GlobalConfig> {
load_from(&config_path()?)
}
pub fn save(config: &GlobalConfig) -> anyhow::Result<()> {
save_to(&config_path()?, config)
}
pub fn load_template() -> anyhow::Result<String> {
Ok(load_template_from(&template_path()?))
}
pub fn default_template() -> &'static str {
DEFAULT_TEMPLATE
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = GlobalConfig::default();
assert_eq!(config.editor, "vim");
assert_eq!(config.browser, "xdg-open");
}
#[test]
fn test_save_and_load_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("config.toml");
let config = GlobalConfig {
editor: "nvim".to_string(),
browser: "firefox".to_string(),
};
save_to(&path, &config).unwrap();
let loaded = load_from(&path).unwrap();
assert_eq!(loaded.editor, "nvim");
assert_eq!(loaded.browser, "firefox");
}
#[test]
fn test_load_missing_file_returns_config_not_found() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("nonexistent.toml");
let err = load_from(&path).unwrap_err();
assert!(err.downcast_ref::<AcrError>().is_some());
}
#[test]
fn test_load_template_returns_default_when_missing() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("template.rs");
let template = load_template_from(&path);
assert_eq!(template, DEFAULT_TEMPLATE);
}
#[test]
fn test_load_template_from_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("template.rs");
let custom = "fn main() {}";
std::fs::write(&path, custom).unwrap();
let template = load_template_from(&path);
assert_eq!(template, custom);
}
}