use serde::{Deserialize, Serialize};
use std::env;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RezCoreConfig {
pub use_rust_version: bool,
pub use_rust_solver: bool,
pub use_rust_repository: bool,
pub rust_fallback: bool,
pub thread_count: Option<usize>,
pub cache: CacheConfig,
pub packages_path: Vec<String>,
pub local_packages_path: String,
pub release_packages_path: String,
pub default_shell: String,
pub version: String,
pub plugin_path: Vec<String>,
pub package_cache_path: Vec<String>,
pub tmpdir: String,
pub editor: String,
pub image_viewer: String,
pub browser: String,
pub difftool: String,
pub terminal_emulator_command: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheConfig {
pub enable_memory_cache: bool,
pub enable_disk_cache: bool,
pub memory_cache_size: usize,
pub cache_ttl_seconds: u64,
}
impl RezCoreConfig {
pub fn new() -> Self {
Self::default()
}
}
impl Default for RezCoreConfig {
fn default() -> Self {
Self {
use_rust_version: true,
use_rust_solver: true,
use_rust_repository: true,
rust_fallback: true,
thread_count: None, cache: CacheConfig::default(),
packages_path: vec![
"~/packages".to_string(),
"~/.rez/packages/int".to_string(),
"~/.rez/packages/ext".to_string(),
],
local_packages_path: "~/packages".to_string(),
release_packages_path: "~/.rez/packages/int".to_string(),
default_shell: if cfg!(windows) { "cmd" } else { "bash" }.to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
plugin_path: vec![],
package_cache_path: vec!["~/.rez/cache".to_string()],
tmpdir: std::env::temp_dir().to_string_lossy().to_string(),
editor: if cfg!(windows) { "notepad" } else { "vi" }.to_string(),
image_viewer: if cfg!(windows) { "mspaint" } else { "xdg-open" }.to_string(),
browser: if cfg!(windows) { "start" } else { "xdg-open" }.to_string(),
difftool: if cfg!(windows) { "fc" } else { "diff" }.to_string(),
terminal_emulator_command: if cfg!(windows) {
"cmd /c start cmd"
} else {
"xterm"
}
.to_string(),
}
}
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
enable_memory_cache: true,
enable_disk_cache: true,
memory_cache_size: 1000,
cache_ttl_seconds: 3600, }
}
}
impl RezCoreConfig {
pub fn get_search_paths() -> Vec<PathBuf> {
let mut paths = Vec::new();
if let Ok(exe_path) = env::current_exe() {
if let Some(exe_dir) = exe_path.parent() {
paths.push(exe_dir.join("rezconfig.yaml"));
paths.push(exe_dir.join("rezconfig.json"));
}
}
if let Ok(config_file) = env::var("REZ_CONFIG_FILE") {
for path in config_file.split(std::path::MAIN_SEPARATOR) {
paths.push(PathBuf::from(path));
}
}
if cfg!(unix) {
paths.push(PathBuf::from("/etc/rez/config.yaml"));
paths.push(PathBuf::from("/usr/local/etc/rez/config.yaml"));
} else if cfg!(windows) {
if let Ok(program_data) = env::var("PROGRAMDATA") {
paths.push(PathBuf::from(program_data).join("rez").join("config.yaml"));
}
}
if env::var("REZ_DISABLE_HOME_CONFIG")
.unwrap_or_default()
.to_lowercase()
!= "1"
{
if let Ok(home) = env::var("HOME") {
let home_path = PathBuf::from(&home);
paths.push(home_path.join(".rezconfig"));
paths.push(home_path.join(".rezconfig.yaml"));
paths.push(home_path.join(".rez").join("config.yaml"));
} else if cfg!(windows) {
if let Ok(userprofile) = env::var("USERPROFILE") {
let user_path = PathBuf::from(&userprofile);
paths.push(user_path.join(".rezconfig"));
paths.push(user_path.join(".rezconfig.yaml"));
paths.push(user_path.join(".rez").join("config.yaml"));
}
}
}
paths
}
pub fn get_sourced_paths() -> Vec<PathBuf> {
Self::get_search_paths()
.into_iter()
.filter(|path| path.exists())
.collect()
}
pub fn load() -> Self {
let mut config = Self::default();
for path in Self::get_search_paths() {
if path.exists() {
if let Ok(content) = std::fs::read_to_string(&path) {
if let Ok(loaded) = serde_yaml::from_str::<RezCoreConfig>(&content) {
config = loaded;
break;
}
if let Ok(loaded) = serde_json::from_str::<RezCoreConfig>(&content) {
config = loaded;
break;
}
}
}
}
if let Ok(packages_path) = env::var("REZ_PACKAGES_PATH") {
config.packages_path = packages_path.split(':').map(|s| s.to_string()).collect();
}
if let Ok(local_path) = env::var("REZ_LOCAL_PACKAGES_PATH") {
config.local_packages_path = local_path;
}
if let Ok(release_path) = env::var("REZ_RELEASE_PACKAGES_PATH") {
config.release_packages_path = release_path;
}
config
}
pub fn get_field(&self, field_path: &str) -> Option<serde_json::Value> {
let parts: Vec<&str> = field_path.split('.').collect();
let config_json = serde_json::to_value(self).ok()?;
let mut current = &config_json;
for part in parts {
current = current.get(part)?;
}
Some(current.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config_has_sensible_values() {
let cfg = RezCoreConfig::default();
assert!(cfg.use_rust_solver);
assert!(cfg.use_rust_version);
assert!(cfg.use_rust_repository);
assert!(cfg.rust_fallback);
assert!(!cfg.packages_path.is_empty());
assert!(!cfg.local_packages_path.is_empty());
assert!(!cfg.release_packages_path.is_empty());
assert!(!cfg.version.is_empty());
}
#[test]
fn test_default_cache_config() {
let cfg = RezCoreConfig::default();
assert!(cfg.cache.enable_memory_cache);
assert!(cfg.cache.enable_disk_cache);
assert!(cfg.cache.memory_cache_size > 0);
assert!(cfg.cache.cache_ttl_seconds > 0);
}
#[test]
fn test_get_field_simple() {
let cfg = RezCoreConfig::default();
let v = cfg.get_field("version");
assert!(v.is_some());
if let Some(serde_json::Value::String(s)) = v {
assert!(!s.is_empty());
}
}
#[test]
fn test_get_field_packages_path() {
let cfg = RezCoreConfig::default();
let v = cfg.get_field("packages_path");
assert!(v.is_some());
if let Some(serde_json::Value::Array(arr)) = v {
assert!(!arr.is_empty());
}
}
#[test]
fn test_get_field_nested() {
let cfg = RezCoreConfig::default();
let v = cfg.get_field("cache.enable_memory_cache");
assert!(v.is_some());
assert_eq!(v, Some(serde_json::Value::Bool(true)));
}
#[test]
fn test_get_field_nested_numeric() {
let cfg = RezCoreConfig::default();
let v = cfg.get_field("cache.memory_cache_size");
assert!(v.is_some());
}
#[test]
fn test_get_field_nonexistent() {
let cfg = RezCoreConfig::default();
assert!(cfg.get_field("nonexistent_field").is_none());
assert!(cfg.get_field("cache.nonexistent").is_none());
}
#[test]
fn test_get_search_paths_not_empty() {
let paths = RezCoreConfig::get_search_paths();
assert!(!paths.is_empty());
}
#[test]
fn test_get_search_paths_contain_home_config() {
let paths = RezCoreConfig::get_search_paths();
let has_home = paths.iter().any(|p| {
p.to_string_lossy().contains(".rezconfig") || p.to_string_lossy().contains(".rez")
});
assert!(has_home);
}
#[test]
fn test_load_returns_config() {
let cfg = RezCoreConfig::load();
assert!(!cfg.version.is_empty());
}
#[test]
fn test_env_override_packages_path() {
if std::env::var("REZ_PACKAGES_PATH").is_err() {
std::env::set_var("REZ_PACKAGES_PATH", "/tmp/test_pkgs:/tmp/other_pkgs");
let cfg = RezCoreConfig::load();
assert!(cfg.packages_path.contains(&"/tmp/test_pkgs".to_string()));
std::env::remove_var("REZ_PACKAGES_PATH");
}
}
#[test]
fn test_config_serialization_roundtrip() {
let cfg = RezCoreConfig::default();
let json = serde_json::to_string(&cfg).unwrap();
let restored: RezCoreConfig = serde_json::from_str(&json).unwrap();
assert_eq!(cfg.version, restored.version);
assert_eq!(cfg.packages_path, restored.packages_path);
assert_eq!(
cfg.cache.memory_cache_size,
restored.cache.memory_cache_size
);
}
#[test]
fn test_config_clone() {
let cfg = RezCoreConfig::default();
let cloned = cfg.clone();
assert_eq!(cfg.version, cloned.version);
assert_eq!(cfg.packages_path, cloned.packages_path);
}
}