use std::path::{Path, PathBuf};
use etcetera::BaseStrategy;
use crate::config::ProjectPin;
pub const PROJECT_FILE: &str = ".tracker.toml";
#[derive(Debug, thiserror::Error)]
pub enum PathsError {
#[error("no home directory found")]
NoHome(#[from] etcetera::HomeDirError),
}
pub fn config_file() -> Result<PathBuf, PathsError> {
Ok(etcetera::choose_base_strategy()?
.config_dir()
.join("ytcli")
.join("config.toml"))
}
#[must_use]
pub fn find_project_pin(start: &Path) -> Option<(PathBuf, ProjectPin)> {
for dir in start.ancestors() {
let candidate = dir.join(PROJECT_FILE);
if !candidate.is_file() {
continue;
}
let Ok(text) = std::fs::read_to_string(&candidate) else {
continue;
};
match toml_from_str(&text) {
Some(pin) => return Some((candidate, pin)),
None => {
tracing::warn!(path = %candidate.display(), "ignoring malformed .tracker.toml");
}
}
}
None
}
fn toml_from_str(text: &str) -> Option<ProjectPin> {
use figment::Figment;
use figment::providers::{Format, Toml};
Figment::new().merge(Toml::string(text)).extract().ok()
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn finds_pin_in_parent_directory() {
let root = tempfile::tempdir().expect("tempdir");
std::fs::write(
root.path().join(PROJECT_FILE),
"profile = \"work\"\nqueue = \"PROJ\"\n",
)
.expect("write pin");
let nested = root.path().join("a").join("b");
std::fs::create_dir_all(&nested).expect("create nested");
let (path, pin) = find_project_pin(&nested).expect("pin found");
assert_eq!(path, root.path().join(PROJECT_FILE));
assert_eq!(pin.profile.as_deref(), Some("work"));
assert_eq!(pin.queue.as_deref(), Some("PROJ"));
}
#[test]
fn malformed_pin_is_ignored_not_fatal() {
let root = tempfile::tempdir().expect("tempdir");
std::fs::write(root.path().join(PROJECT_FILE), "profile = [[[").expect("write pin");
assert!(find_project_pin(root.path()).is_none());
}
}