use std::path::{Path, PathBuf};
const PROJECT_MARKERS: &[&str] = &[
".code-graph",
"code-graph.toml",
"Cargo.toml",
"package.json",
"go.mod",
"pyproject.toml",
];
pub fn detect_project_root() -> Option<PathBuf> {
let cwd = std::env::current_dir().ok()?;
detect_project_root_from(&cwd)
}
fn detect_project_root_from(start: &Path) -> Option<PathBuf> {
let mut dir = start.to_path_buf();
loop {
for marker in PROJECT_MARKERS {
if dir.join(marker).exists() {
return Some(dir);
}
}
if !dir.pop() {
return None;
}
}
}
pub fn resolve_project_root(path: Option<PathBuf>) -> PathBuf {
if let Some(p) = path {
std::fs::canonicalize(&p).unwrap_or(p)
} else {
detect_project_root()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_project_root_with_cargo_toml() {
let tmp = tempfile::tempdir().unwrap();
let sub = tmp.path().join("a").join("b").join("c");
std::fs::create_dir_all(&sub).unwrap();
std::fs::write(tmp.path().join("Cargo.toml"), "[package]").unwrap();
let result = detect_project_root_from(&sub);
assert_eq!(result, Some(tmp.path().to_path_buf()));
}
#[test]
fn test_detect_project_root_prefers_code_graph_dir() {
let tmp = tempfile::tempdir().unwrap();
let inner = tmp.path().join("inner");
std::fs::create_dir_all(&inner).unwrap();
std::fs::create_dir_all(inner.join(".code-graph")).unwrap();
std::fs::write(tmp.path().join("Cargo.toml"), "").unwrap();
let result = detect_project_root_from(&inner);
assert_eq!(result, Some(inner));
}
#[test]
fn test_detect_project_root_no_marker() {
let tmp = tempfile::tempdir().unwrap();
let sub = tmp.path().join("empty");
std::fs::create_dir_all(&sub).unwrap();
let _result = detect_project_root_from(&sub);
}
#[test]
fn test_resolve_project_root_with_some_path() {
let tmp = tempfile::tempdir().unwrap();
let result = resolve_project_root(Some(tmp.path().to_path_buf()));
assert!(result.exists());
}
#[test]
fn test_resolve_project_root_with_none_falls_back() {
let result = resolve_project_root(None);
assert!(!result.as_os_str().is_empty());
}
}