use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[cfg(feature = "config-management")]
use std::fs;
#[cfg(feature = "config-management")]
use dirs;
#[derive(Debug, Clone)]
#[cfg(feature = "config-management")]
pub struct ConfigManager {
config_dir: PathBuf,
current_config: XQPathConfig,
profiles: HashMap<String, XQPathConfig>,
active_profile: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct XQPathConfig {
pub debug: DebugConfig,
pub performance: PerformanceConfig,
pub paths: PathsConfig,
pub features: FeaturesConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DebugConfig {
pub level: String,
pub output: String,
pub file: Option<PathBuf>,
pub timing: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceConfig {
pub memory_limit: String,
pub timeout: String,
pub cache_size: u32,
pub parallel_jobs: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathsConfig {
pub cache_dir: PathBuf,
pub log_dir: PathBuf,
pub config_dir: PathBuf,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeaturesConfig {
pub colored_output: bool,
pub interactive_mode: bool,
pub auto_backup: bool,
}
pub type ConfigResult<T> = Result<T, ConfigError>;
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("配置文件不存在: {0}")]
FileNotFound(PathBuf),
#[error("配置文件解析错误: {0}")]
ParseError(String),
#[error("配置写入错误: {0}")]
WriteError(String),
#[error("无效的配置值: {key} = {value}")]
InvalidValue { key: String, value: String },
#[error("配置目录创建失败: {0}")]
DirectoryCreationFailed(String),
#[error("权限不足: {0}")]
PermissionDenied(String),
}
impl Default for XQPathConfig {
fn default() -> Self {
Self {
debug: DebugConfig {
level: "info".to_string(),
output: "stderr".to_string(),
file: None,
timing: false,
},
performance: PerformanceConfig {
memory_limit: "1GB".to_string(),
timeout: "30s".to_string(),
cache_size: 1000,
parallel_jobs: 4,
},
paths: PathsConfig {
cache_dir: PathBuf::from("~/.xqpath/cache"),
log_dir: PathBuf::from("~/.xqpath/logs"),
config_dir: PathBuf::from("~/.xqpath"),
},
features: FeaturesConfig {
colored_output: true,
interactive_mode: false,
auto_backup: true,
},
}
}
}
#[cfg(feature = "config-management")]
impl ConfigManager {
pub fn new() -> ConfigResult<Self> {
let config_dir = Self::get_config_directory()?;
let default_config = XQPathConfig::default();
let mut manager = ConfigManager {
config_dir,
current_config: default_config.clone(),
profiles: HashMap::new(),
active_profile: "default".to_string(),
};
manager
.profiles
.insert("default".to_string(), default_config);
if let Ok(config) = manager.load_config() {
manager.current_config = config;
}
Ok(manager)
}
fn get_config_directory() -> ConfigResult<PathBuf> {
dirs::config_dir()
.map(|dir| dir.join(".xqpath"))
.ok_or_else(|| {
ConfigError::DirectoryCreationFailed(
"无法确定配置目录".to_string(),
)
})
}
pub fn load_config(&self) -> ConfigResult<XQPathConfig> {
let config_file = self.config_dir.join("config.yaml");
if !config_file.exists() {
return Ok(XQPathConfig::default());
}
let content = fs::read_to_string(&config_file)
.map_err(|e| ConfigError::ParseError(e.to_string()))?;
let config: XQPathConfig = serde_yaml::from_str(&content)
.map_err(|e| ConfigError::ParseError(e.to_string()))?;
Ok(config)
}
pub fn save_config(&self) -> ConfigResult<()> {
fs::create_dir_all(&self.config_dir)
.map_err(|e| ConfigError::DirectoryCreationFailed(e.to_string()))?;
let config_file = self.config_dir.join("config.yaml");
let content = serde_yaml::to_string(&self.current_config)
.map_err(|e| ConfigError::WriteError(e.to_string()))?;
fs::write(&config_file, content)
.map_err(|e| ConfigError::WriteError(e.to_string()))?;
Ok(())
}
pub fn set_config_value(
&mut self,
key: &str,
value: &str,
) -> ConfigResult<()> {
match key {
"debug.level" => {
if !["trace", "debug", "info", "warn", "error"].contains(&value)
{
return Err(ConfigError::InvalidValue {
key: key.to_string(),
value: value.to_string(),
});
}
self.current_config.debug.level = value.to_string();
}
"debug.timing" => {
self.current_config.debug.timing =
value.parse().map_err(|_| ConfigError::InvalidValue {
key: key.to_string(),
value: value.to_string(),
})?;
}
"performance.cache_size" => {
self.current_config.performance.cache_size =
value.parse().map_err(|_| ConfigError::InvalidValue {
key: key.to_string(),
value: value.to_string(),
})?;
}
"features.colored_output" => {
self.current_config.features.colored_output =
value.parse().map_err(|_| ConfigError::InvalidValue {
key: key.to_string(),
value: value.to_string(),
})?;
}
_ => {
return Err(ConfigError::InvalidValue {
key: key.to_string(),
value: value.to_string(),
});
}
}
Ok(())
}
pub fn get_config(&self) -> &XQPathConfig {
&self.current_config
}
pub fn reset_config(&mut self) -> ConfigResult<()> {
self.current_config = XQPathConfig::default();
self.save_config()
}
pub fn create_template(&self, name: &str) -> ConfigResult<()> {
let template_file =
self.config_dir.join(format!("templates/{name}.yaml"));
if let Some(parent) = template_file.parent() {
fs::create_dir_all(parent).map_err(|e| {
ConfigError::DirectoryCreationFailed(e.to_string())
})?;
}
let content = serde_yaml::to_string(&self.current_config)
.map_err(|e| ConfigError::WriteError(e.to_string()))?;
fs::write(&template_file, content)
.map_err(|e| ConfigError::WriteError(e.to_string()))?;
Ok(())
}
pub fn create_profile(&mut self, name: &str) -> ConfigResult<()> {
self.profiles
.insert(name.to_string(), self.current_config.clone());
let profile_file =
self.config_dir.join(format!("profiles/{name}.yaml"));
if let Some(parent) = profile_file.parent() {
fs::create_dir_all(parent).map_err(|e| {
ConfigError::DirectoryCreationFailed(e.to_string())
})?;
}
let content = serde_yaml::to_string(&self.current_config)
.map_err(|e| ConfigError::WriteError(e.to_string()))?;
fs::write(&profile_file, content)
.map_err(|e| ConfigError::WriteError(e.to_string()))?;
Ok(())
}
pub fn switch_profile(&mut self, name: &str) -> ConfigResult<()> {
if let Some(config) = self.profiles.get(name) {
self.current_config = config.clone();
self.active_profile = name.to_string();
Ok(())
} else {
let profile_file =
self.config_dir.join(format!("profiles/{name}.yaml"));
if profile_file.exists() {
let content = fs::read_to_string(&profile_file)
.map_err(|e| ConfigError::ParseError(e.to_string()))?;
let config: XQPathConfig = serde_yaml::from_str(&content)
.map_err(|e| ConfigError::ParseError(e.to_string()))?;
self.current_config = config.clone();
self.profiles.insert(name.to_string(), config);
self.active_profile = name.to_string();
Ok(())
} else {
Err(ConfigError::FileNotFound(profile_file))
}
}
}
pub fn get_active_profile(&self) -> &str {
&self.active_profile
}
pub fn list_profiles(&self) -> Vec<String> {
self.profiles.keys().cloned().collect()
}
}
#[cfg(not(feature = "config-management"))]
pub fn get_default_config() -> XQPathConfig {
XQPathConfig::default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = XQPathConfig::default();
assert_eq!(config.debug.level, "info");
assert_eq!(config.performance.cache_size, 1000);
assert!(config.features.colored_output);
}
#[cfg(feature = "config-management")]
#[test]
fn test_config_serialization() {
let config = XQPathConfig::default();
let serialized = serde_yaml::to_string(&config).unwrap();
let deserialized: XQPathConfig =
serde_yaml::from_str(&serialized).unwrap();
assert_eq!(config.debug.level, deserialized.debug.level);
}
}