atagen

roundabout

public
Star0
Watch0
9117440444eb609ecea8476b5a62b03917077591
1
ssh://brewery@pls.lobotomise.me:33/atagen/roundabout.git
src/paths.rsRust153 lines4.7 KB
1
use std::{
2
    env, fs,
3
    os::unix::fs::{DirBuilderExt, PermissionsExt},
4
    path::{Path, PathBuf},
5
};
6
7
fn home() -> PathBuf {
8
    env::var_os("HOME").map_or_else(|| PathBuf::from("/"), PathBuf::from)
9
}
10
11
fn xdg(var: &str, fallback: &str) -> PathBuf {
12
    env::var_os(var)
13
        .map(PathBuf::from)
14
        .filter(|p| p.is_absolute())
15
        .unwrap_or_else(|| home().join(fallback))
16
}
17
18
pub fn config_file() -> PathBuf {
19
    if let Some(p) = env::var_os("ROUNDABOUT_CONFIG") {
20
        return p.into();
21
    }
22
    let user = xdg("XDG_CONFIG_HOME", ".config").join("roundabout/config.toml");
23
    let system = PathBuf::from("/etc/roundabout/config.toml");
24
    if !user.exists() && system.exists() {
25
        system
26
    } else {
27
        user
28
    }
29
}
30
31
pub fn state_dir() -> PathBuf {
32
    let dir = env::var_os("ROUNDABOUT_STATE_DIR").map_or_else(
33
        || xdg("XDG_STATE_HOME", ".local/state").join("roundabout"),
34
        PathBuf::from,
35
    );
36
    let _ = fs::create_dir_all(&dir);
37
    dir
38
}
39
40
pub fn runtime_dir() -> PathBuf {
41
    if let Some(dir) = env::var_os("ROUNDABOUT_RUNTIME_DIR") {
42
        let dir = PathBuf::from(dir);
43
        let _ = fs::DirBuilder::new()
44
            .recursive(true)
45
            .mode(0o700)
46
            .create(&dir);
47
        return dir;
48
    }
49
    if let Some(dir) = env::var_os("XDG_RUNTIME_DIR").map(PathBuf::from)
50
        && dir.is_absolute()
51
        && dir.is_dir()
52
    {
53
        return dir;
54
    }
55
    let uid = unsafe { libc::getuid() };
56
    let dir = env::temp_dir().join(format!("roundabout-{uid}"));
57
    let _ = fs::DirBuilder::new().mode(0o700).create(&dir);
58
    let _ = fs::set_permissions(&dir, fs::Permissions::from_mode(0o700));
59
    dir
60
}
61
62
pub fn socket() -> PathBuf {
63
    runtime_dir().join("roundabout.sock")
64
}
65
66
pub fn spawn_lock() -> PathBuf {
67
    runtime_dir().join("roundabout.lock")
68
}
69
70
pub fn git_root(cwd: &Path) -> Option<PathBuf> {
71
    cwd.ancestors()
72
        .find(|dir| dir.join(".git").exists())
73
        .map(Path::to_path_buf)
74
}
75
76
pub fn project_config_file(cwd: &Path) -> Option<PathBuf> {
77
    let root = git_root(cwd)?;
78
    for dir in cwd.ancestors() {
79
        if dir.join(".resrc").is_file() {
80
            return Some(dir.join(".resrc"));
81
        }
82
        if dir == root {
83
            break;
84
        }
85
    }
86
    None
87
}
88
89
pub fn project_root(cwd: &Path) -> PathBuf {
90
    const MARKERS: [&str; 6] = [
91
        ".jj",
92
        ".git",
93
        ".hg",
94
        "flake.nix",
95
        "Cargo.toml",
96
        "package.json",
97
    ];
98
    let mut best = None;
99
    for dir in cwd.ancestors() {
100
        if dir.join(".jj").exists() || dir.join(".git").exists() || dir.join(".hg").exists() {
101
            return dir.to_path_buf();
102
        }
103
        if best.is_none() && MARKERS.iter().any(|m| dir.join(m).exists()) {
104
            best = Some(dir.to_path_buf());
105
        }
106
    }
107
    best.unwrap_or_else(|| cwd.to_path_buf())
108
}
109
110
#[cfg(test)]
111
mod tests {
112
    use std::{
113
        fs,
114
        path::PathBuf,
115
        time::{SystemTime, UNIX_EPOCH},
116
    };
117
118
    use super::project_config_file;
119
120
    fn temp_tree() -> PathBuf {
121
        let stamp = SystemTime::now()
122
            .duration_since(UNIX_EPOCH)
123
            .expect("clock before epoch")
124
            .as_nanos();
125
        let root = std::env::temp_dir().join(format!("roundabout-paths-{stamp}"));
126
        fs::create_dir_all(&root).expect("create temporary tree");
127
        root
128
    }
129
130
    #[test]
131
    fn project_config_is_nearest_and_git_bounded() {
132
        let outer = temp_tree();
133
        let root = outer.join("repo");
134
        fs::create_dir(&root).expect("create repository tree");
135
        let child = root.join("child");
136
        let nested = child.join("nested");
137
        fs::create_dir_all(&nested).expect("create nested tree");
138
        fs::create_dir(root.join(".git")).expect("create git marker");
139
        fs::write(root.join(".resrc"), "").expect("write root config");
140
        fs::write(child.join(".resrc"), "").expect("write child config");
141
        fs::write(nested.join(".resrc"), "").expect("write nested config");
142
        assert_eq!(project_config_file(&nested), Some(nested.join(".resrc")));
143
        fs::remove_file(nested.join(".resrc")).expect("remove nested config");
144
        assert_eq!(project_config_file(&nested), Some(child.join(".resrc")));
145
        fs::remove_file(child.join(".resrc")).expect("remove child config");
146
        assert_eq!(project_config_file(&nested), Some(root.join(".resrc")));
147
        fs::remove_file(root.join(".resrc")).expect("remove root config");
148
        fs::write(outer.join(".resrc"), "").expect("write outside config");
149
        assert_eq!(project_config_file(&nested), None);
150
        fs::remove_file(outer.join(".resrc")).expect("remove outside config");
151
        fs::remove_dir_all(outer).expect("remove temporary tree");
152
    }
153
}