use std::path::{Path, PathBuf};
pub(crate) fn resolve_project_root(explicit: Option<&str>) -> Option<PathBuf> {
resolve_project_root_with_session(explicit, None)
}
pub(crate) fn resolve_project_root_with_session(
explicit: Option<&str>,
session_pin: Option<&Path>,
) -> Option<PathBuf> {
if let Some(s) = explicit {
let p = PathBuf::from(s);
if p.is_dir() {
return Some(p);
}
tracing::warn!("project_root '{}' is not a directory, falling back", s);
}
if let Some(pin) = session_pin {
if pin.is_dir() {
return Some(pin.to_path_buf());
}
tracing::warn!(
"session pin '{}' is not a directory, falling back",
pin.display()
);
}
if let Ok(env) = std::env::var("ALC_PROJECT_ROOT") {
if !env.is_empty() {
let p = PathBuf::from(&env);
if p.is_dir() {
return Some(p);
}
}
}
if let Ok(cwd) = std::env::current_dir() {
return walk_up_for_alc_toml(&cwd);
}
None
}
pub(crate) fn walk_up_for_alc_toml(start: &Path) -> Option<PathBuf> {
let mut current = start.to_path_buf();
loop {
if current.join("alc.toml").is_file() {
return Some(current);
}
if !current.pop() {
return None;
}
}
}
#[cfg(test)]
mod tests {
use super::super::test_support::with_env_var;
use super::*;
#[test]
fn resolve_project_root_via_env() {
let tmp = tempfile::tempdir().unwrap();
let tmp_path = tmp.path().to_path_buf();
with_env_var("ALC_PROJECT_ROOT", tmp_path.to_str().unwrap(), || {
let result = resolve_project_root(None);
assert_eq!(result, Some(tmp_path.clone()));
});
}
#[test]
fn resolve_project_root_explicit_wins_over_env() {
let explicit_dir = tempfile::tempdir().unwrap();
let env_dir = tempfile::tempdir().unwrap();
let explicit_path = explicit_dir.path().to_path_buf();
let env_path = env_dir.path().to_path_buf();
with_env_var("ALC_PROJECT_ROOT", env_path.to_str().unwrap(), || {
let result = resolve_project_root(Some(explicit_path.to_str().unwrap()));
assert_eq!(result, Some(explicit_path.clone()));
});
}
#[test]
fn resolve_project_root_walk_up() {
let tmp = tempfile::tempdir().unwrap();
let project_root = tmp.path().to_path_buf();
std::fs::write(project_root.join("alc.toml"), "[packages]\n").unwrap();
let sub = project_root.join("a").join("b");
std::fs::create_dir_all(&sub).unwrap();
let result = walk_up_for_alc_toml(&sub);
assert_eq!(result, Some(project_root));
}
#[test]
fn resolve_project_root_none_when_no_alc_toml() {
let tmp = tempfile::tempdir().unwrap();
let result = walk_up_for_alc_toml(tmp.path());
assert!(result.is_none());
}
#[test]
fn walk_up_stops_at_root_when_no_alc_toml() {
let root = PathBuf::from("/");
let result = walk_up_for_alc_toml(&root);
drop(result);
}
#[test]
fn resolve_project_root_explicit_non_dir_falls_back() {
let result = resolve_project_root(Some("/this/path/does/not/exist_xyz"));
drop(result);
}
#[test]
fn session_pin_overrides_env_when_explicit_absent() {
let session_dir = tempfile::tempdir().unwrap();
let env_dir = tempfile::tempdir().unwrap();
let session_path = session_dir.path().to_path_buf();
let env_path = env_dir.path().to_path_buf();
with_env_var("ALC_PROJECT_ROOT", env_path.to_str().unwrap(), || {
let result = resolve_project_root_with_session(None, Some(&session_path));
assert_eq!(
result,
Some(session_path.clone()),
"session pin (S) must beat env (E) when explicit (P) is absent"
);
});
}
#[test]
fn explicit_overrides_session_pin() {
let explicit_dir = tempfile::tempdir().unwrap();
let session_dir = tempfile::tempdir().unwrap();
let explicit_path = explicit_dir.path().to_path_buf();
let session_path = session_dir.path().to_path_buf();
let result = resolve_project_root_with_session(
Some(explicit_path.to_str().unwrap()),
Some(&session_path),
);
assert_eq!(
result,
Some(explicit_path),
"explicit (P) must beat session pin (S) for per-call debug overrides"
);
}
#[test]
fn session_pin_falls_through_when_not_a_directory() {
let env_dir = tempfile::tempdir().unwrap();
let env_path = env_dir.path().to_path_buf();
let bogus = std::path::PathBuf::from("/nonexistent/session/pin/xyz");
with_env_var("ALC_PROJECT_ROOT", env_path.to_str().unwrap(), || {
let result = resolve_project_root_with_session(None, Some(&bogus));
assert_eq!(
result,
Some(env_path.clone()),
"invalid session pin must fall through to env (E)"
);
});
}
#[test]
fn legacy_resolve_matches_no_session_pin() {
let env_dir = tempfile::tempdir().unwrap();
let env_path = env_dir.path().to_path_buf();
let env_path_for_assert = env_path.clone();
with_env_var("ALC_PROJECT_ROOT", env_path.to_str().unwrap(), move || {
let legacy = resolve_project_root(None);
let with_session = resolve_project_root_with_session(None, None);
assert_eq!(legacy, with_session);
assert_eq!(legacy, Some(env_path_for_assert));
});
}
}