use anyhow::{Context, Result};
use serde::Deserialize;
use std::path::Path;
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct ToolchainConfig {
pub ignore: ToolchainIgnore,
}
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(default)]
pub struct ToolchainIgnore {
pub paths: Vec<String>,
}
impl ToolchainConfig {
pub fn discover(start_dir: &Path) -> Result<Option<Self>> {
let mut dir = Some(start_dir);
while let Some(d) = dir {
let candidate = d.join("toolchain.toml");
if candidate.is_file() {
let text = std::fs::read_to_string(&candidate)
.with_context(|| format!("Failed to read {}", candidate.display()))?;
let cfg: ToolchainConfig = toml::from_str(&text)
.with_context(|| format!("Failed to parse {}", candidate.display()))?;
return Ok(Some(cfg));
}
dir = d.parent();
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn discover_returns_none_when_absent() {
let dir = tempfile::tempdir().unwrap();
let result = ToolchainConfig::discover(dir.path()).unwrap();
assert!(result.is_none());
}
#[test]
fn discover_finds_toolchain_toml_in_start_dir() {
let dir = tempfile::tempdir().unwrap();
fs::write(
dir.path().join("toolchain.toml"),
"[ignore]\npaths = [\"vendor/**\"]\n",
)
.unwrap();
let cfg = ToolchainConfig::discover(dir.path()).unwrap().unwrap();
assert_eq!(cfg.ignore.paths, vec!["vendor/**".to_string()]);
}
#[test]
fn discover_walks_up_from_a_nested_subdirectory() {
let dir = tempfile::tempdir().unwrap();
fs::write(
dir.path().join("toolchain.toml"),
"[ignore]\npaths = [\"third_party/**\"]\n",
)
.unwrap();
let nested = dir.path().join("src").join("nested");
fs::create_dir_all(&nested).unwrap();
let cfg = ToolchainConfig::discover(&nested).unwrap().unwrap();
assert_eq!(cfg.ignore.paths, vec!["third_party/**".to_string()]);
}
#[test]
fn discover_ignores_unrelated_sections() {
let dir = tempfile::tempdir().unwrap();
fs::write(
dir.path().join("toolchain.toml"),
"[ignore]\npaths = [\"generated/**\"]\n\n[language.python]\nline_length = 88\n",
)
.unwrap();
let cfg = ToolchainConfig::discover(dir.path()).unwrap().unwrap();
assert_eq!(cfg.ignore.paths, vec!["generated/**".to_string()]);
}
}