use anyhow::{Context, Result};
use serde::Deserialize;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Deserialize, Clone)]
pub struct Config {
#[serde(default = "default_idle_timeout")]
pub idle_timeout: u64,
#[serde(default = "default_smart_wait")]
pub smart_wait: bool,
#[serde(default)]
pub server: HashMap<String, ServerConfig>,
}
const fn default_smart_wait() -> bool {
true
}
#[derive(Debug, Deserialize, Clone)]
pub struct ServerConfig {
pub command: String,
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub initialization_options: Option<serde_json::Value>,
}
const fn default_idle_timeout() -> u64 {
300
}
impl Config {
pub fn load(explicit_file: Option<PathBuf>) -> Result<Self> {
let mut builder = config::Config::builder();
builder = builder.set_default("idle_timeout", 300)?;
builder = builder.set_default("smart_wait", true)?;
if let Some(config_dir) = dirs::config_dir() {
let config_path = config_dir.join("catenary").join("config.toml");
if config_path.exists() {
builder = builder.add_source(config::File::from(config_path));
}
}
if let Ok(cwd) = std::env::current_dir() {
let mut current = Some(cwd.as_path());
while let Some(path) = current {
let config_path = path.join(".catenary.toml");
if config_path.exists() {
builder = builder.add_source(config::File::from(config_path));
break;
}
current = path.parent();
}
}
if let Some(path) = explicit_file {
builder = builder.add_source(config::File::from(path));
}
builder = builder.add_source(config::Environment::with_prefix("CATENARY"));
let config = builder.build().context("Failed to build configuration")?;
config
.try_deserialize()
.context("Failed to deserialize configuration")
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_config_load_local() -> Result<()> {
let dir = tempdir()?;
let local_config_path = dir.path().join(".catenary.toml");
fs::write(
&local_config_path,
r#"
idle_timeout = 42
smart_wait = false
[server.rust]
command = "rust-analyzer-local"
"#,
)?;
let original_dir = std::env::current_dir()?;
std::env::set_current_dir(dir.path())?;
let config = Config::load(None)?;
std::env::set_current_dir(original_dir)?;
assert_eq!(config.idle_timeout, 42);
assert!(!config.smart_wait);
assert_eq!(
config
.server
.get("rust")
.context("missing rust server")?
.command,
"rust-analyzer-local"
);
Ok(())
}
}