Skip to main content

ready_set/
project.rs

1//! Project root detection.
2//!
3//! The dispatcher walks upward from the current working directory looking for
4//! a `.git` directory, the nearest `Cargo.toml`, or `.ready-set.toml`. The
5//! first hit wins. The result is exported as `READY_SET_PROJECT_ROOT` for
6//! plugins that want it.
7
8use std::path::{Path, PathBuf};
9
10/// Walk upward from `cwd` to detect a project root. Returns `None` if the
11/// filesystem root is reached without finding any marker.
12#[must_use]
13pub fn detect_project_root(cwd: &Path) -> Option<PathBuf> {
14    let mut cur: Option<&Path> = Some(cwd);
15    let mut nearest_cargo: Option<PathBuf> = None;
16    while let Some(dir) = cur {
17        if dir.join(".git").exists() {
18            return Some(dir.to_path_buf());
19        }
20        if dir.join(".ready-set.toml").exists() {
21            return Some(dir.to_path_buf());
22        }
23        if nearest_cargo.is_none() && dir.join("Cargo.toml").exists() {
24            nearest_cargo = Some(dir.to_path_buf());
25        }
26        cur = dir.parent();
27    }
28    nearest_cargo
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn finds_git_root() {
37        let dir = tempfile::tempdir().unwrap();
38        std::fs::create_dir_all(dir.path().join(".git")).unwrap();
39        let inner = dir.path().join("a/b/c");
40        std::fs::create_dir_all(&inner).unwrap();
41        let root = detect_project_root(&inner).unwrap();
42        assert_eq!(root, dir.path());
43    }
44
45    #[test]
46    fn finds_ready_set_toml() {
47        let dir = tempfile::tempdir().unwrap();
48        std::fs::write(
49            dir.path().join(".ready-set.toml"),
50            "[ready-set]\nschema_version = 1\n",
51        )
52        .unwrap();
53        let inner = dir.path().join("a");
54        std::fs::create_dir_all(&inner).unwrap();
55        let root = detect_project_root(&inner).unwrap();
56        assert_eq!(root, dir.path());
57    }
58
59    #[test]
60    fn falls_back_to_nearest_cargo_toml() {
61        let dir = tempfile::tempdir().unwrap();
62        std::fs::write(dir.path().join("Cargo.toml"), "[package]\nname=\"x\"\n").unwrap();
63        let inner = dir.path().join("src");
64        std::fs::create_dir_all(&inner).unwrap();
65        let root = detect_project_root(&inner).unwrap();
66        assert_eq!(root, dir.path());
67    }
68
69    #[test]
70    fn returns_none_when_nothing_found() {
71        let dir = tempfile::tempdir().unwrap();
72        let inner = dir.path().join("a/b");
73        std::fs::create_dir_all(&inner).unwrap();
74        // tempdir() lives somewhere with parents that may have markers.
75        // We can't assert None reliably (the system /tmp parents may have
76        // a .git somewhere); just assert the function does not panic.
77        drop(detect_project_root(&inner));
78    }
79}