use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
pub fn ancestor_configs(start: &Path) -> Vec<PathBuf> {
start
.ancestors()
.map(|dir| dir.join("qail.toml"))
.filter(|candidate| candidate.is_file())
.collect()
}
pub fn config_root(config_path: &Path) -> &Path {
config_path.parent().unwrap_or(Path::new("."))
}
pub fn current_dir() -> Result<PathBuf> {
std::env::current_dir()
.context("Failed to determine the current directory for qail.toml lookup")
}
#[cfg(test)]
pub(crate) struct TempTree(PathBuf);
#[cfg(test)]
impl TempTree {
pub(crate) fn new() -> Self {
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let root = std::env::temp_dir().join(format!("qail-cfg-test-{}-{}", std::process::id(), n));
std::fs::create_dir_all(&root).expect("create temp root");
Self(root)
}
pub(crate) fn dir(&self, rel: &str) -> PathBuf {
let path = self.0.join(rel);
std::fs::create_dir_all(&path).expect("create dir");
path
}
pub(crate) fn write(&self, rel: &str, contents: &str) {
let path = self.0.join(rel);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("create parent");
}
std::fs::write(path, contents).expect("write file");
}
pub(crate) fn path(&self, rel: &str) -> PathBuf {
self.0.join(rel)
}
pub(crate) fn ancestors_are_clean(&self) -> bool {
self.0
.ancestors()
.skip(1)
.all(|dir| !dir.join("qail.toml").is_file())
}
}
#[cfg(test)]
impl Drop for TempTree {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
#[cfg(test)]
mod tests {
use super::{ancestor_configs, config_root};
use std::path::Path;
#[test]
fn config_root_is_the_declaring_files_directory() {
assert_eq!(
config_root(Path::new("/repo/gateway/qail.toml")),
Path::new("/repo/gateway")
);
}
#[test]
fn ancestor_configs_is_empty_for_a_path_with_none() {
let found = ancestor_configs(Path::new("/nonexistent-qail-probe-6f2a/deep"));
assert!(found.is_empty());
}
}