use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
mod paths;
mod project;
pub use paths::{default_data_dir, expand_tilde, global_config_path};
pub use project::{MergedProjectConfig, ProjectConfig};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct GlobalConfig {
pub plugin: GlobalPluginConfig,
pub discovery: DiscoveryConfig,
pub plugin_store: PluginStoreConfig,
#[serde(flatten)]
pub extra: toml::Table,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct GlobalPluginConfig {
pub family_priority: Vec<String>,
pub priority: Vec<String>,
}
impl Default for GlobalPluginConfig {
fn default() -> Self {
Self {
family_priority: vec![
"node".into(),
"rust".into(),
"python".into(),
"go".into(),
"jvm".into(),
"dotnet".into(),
"php".into(),
"ruby".into(),
],
priority: vec![
"pnpm".into(),
"npm".into(),
"yarn".into(),
"bun".into(),
"cargo".into(),
],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct DiscoveryConfig {
pub walk_up: bool,
pub max_depth: usize,
pub stop_at_git: bool,
}
impl Default for DiscoveryConfig {
fn default() -> Self {
Self {
walk_up: true,
max_depth: 8,
stop_at_git: true,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct PluginStoreConfig {
pub data_dir: Option<PathBuf>,
pub prefer_prebuilt: Option<bool>,
}
impl PluginStoreConfig {
pub fn effective_data_dir(&self) -> Result<PathBuf> {
match &self.data_dir {
Some(p) => Ok(expand_tilde(&p.to_string_lossy())),
None => default_data_dir(),
}
}
pub fn effective_prefer_prebuilt(&self) -> bool {
self.prefer_prebuilt.unwrap_or(true)
}
}
impl GlobalConfig {
pub fn load() -> Result<Self> {
Self::load_from(&global_config_path()?)
}
pub fn load_from(path: &Path) -> Result<Self> {
match std::fs::read_to_string(path) {
Ok(text) => toml::from_str(&text)
.with_context(|| format!("failed to parse the global config: {}", path.display())),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Self::default()),
Err(e) => Err(e)
.with_context(|| format!("failed to read the global config: {}", path.display())),
}
}
}
#[cfg(test)]
mod tests;