atagen

roundabout

public
Star0
Watch0
9117440444eb609ecea8476b5a62b03917077591
1
ssh://brewery@pls.lobotomise.me:33/atagen/roundabout.git

Blame: src/paths.rs

6f5b84fad3efatagen2026-09-27 03:59 UTC1
use std::{
6f5b84fad3efatagen2026-09-27 03:59 UTC2
    env, fs,
6f5b84fad3efatagen2026-09-27 03:59 UTC3
    os::unix::fs::{DirBuilderExt, PermissionsExt},
6f5b84fad3efatagen2026-09-27 03:59 UTC4
    path::{Path, PathBuf},
6f5b84fad3efatagen2026-09-27 03:59 UTC5
};
6f5b84fad3efatagen2026-09-27 03:59 UTC6
6f5b84fad3efatagen2026-09-27 03:59 UTC7
fn home() -> PathBuf {
6f5b84fad3efatagen2026-09-27 03:59 UTC8
    env::var_os("HOME").map_or_else(|| PathBuf::from("/"), PathBuf::from)
6f5b84fad3efatagen2026-09-27 03:59 UTC9
}
6f5b84fad3efatagen2026-09-27 03:59 UTC10
6f5b84fad3efatagen2026-09-27 03:59 UTC11
fn xdg(var: &str, fallback: &str) -> PathBuf {
6f5b84fad3efatagen2026-09-27 03:59 UTC12
    env::var_os(var)
6f5b84fad3efatagen2026-09-27 03:59 UTC13
        .map(PathBuf::from)
6f5b84fad3efatagen2026-09-27 03:59 UTC14
        .filter(|p| p.is_absolute())
6f5b84fad3efatagen2026-09-27 03:59 UTC15
        .unwrap_or_else(|| home().join(fallback))
6f5b84fad3efatagen2026-09-27 03:59 UTC16
}
6f5b84fad3efatagen2026-09-27 03:59 UTC17
6f5b84fad3efatagen2026-09-27 03:59 UTC18
pub fn config_file() -> PathBuf {
61c6e94355d9atagen2026-09-27 03:59 UTC19
    if let Some(p) = env::var_os("ROUNDABOUT_CONFIG") {
61c6e94355d9atagen2026-09-27 03:59 UTC20
        return p.into();
61c6e94355d9atagen2026-09-27 03:59 UTC21
    }
61c6e94355d9atagen2026-09-27 03:59 UTC22
    let user = xdg("XDG_CONFIG_HOME", ".config").join("roundabout/config.toml");
61c6e94355d9atagen2026-09-27 03:59 UTC23
    let system = PathBuf::from("/etc/roundabout/config.toml");
61c6e94355d9atagen2026-09-27 03:59 UTC24
    if !user.exists() && system.exists() {
61c6e94355d9atagen2026-09-27 03:59 UTC25
        system
61c6e94355d9atagen2026-09-27 03:59 UTC26
    } else {
61c6e94355d9atagen2026-09-27 03:59 UTC27
        user
61c6e94355d9atagen2026-09-27 03:59 UTC28
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC29
}
6f5b84fad3efatagen2026-09-27 03:59 UTC30
6f5b84fad3efatagen2026-09-27 03:59 UTC31
pub fn state_dir() -> PathBuf {
6f5b84fad3efatagen2026-09-27 03:59 UTC32
    let dir = env::var_os("ROUNDABOUT_STATE_DIR").map_or_else(
6f5b84fad3efatagen2026-09-27 03:59 UTC33
        || xdg("XDG_STATE_HOME", ".local/state").join("roundabout"),
6f5b84fad3efatagen2026-09-27 03:59 UTC34
        PathBuf::from,
6f5b84fad3efatagen2026-09-27 03:59 UTC35
    );
6f5b84fad3efatagen2026-09-27 03:59 UTC36
    let _ = fs::create_dir_all(&dir);
6f5b84fad3efatagen2026-09-27 03:59 UTC37
    dir
6f5b84fad3efatagen2026-09-27 03:59 UTC38
}
6f5b84fad3efatagen2026-09-27 03:59 UTC39
6f5b84fad3efatagen2026-09-27 03:59 UTC40
pub fn runtime_dir() -> PathBuf {
6f5b84fad3efatagen2026-09-27 03:59 UTC41
    if let Some(dir) = env::var_os("ROUNDABOUT_RUNTIME_DIR") {
6f5b84fad3efatagen2026-09-27 03:59 UTC42
        let dir = PathBuf::from(dir);
6f5b84fad3efatagen2026-09-27 03:59 UTC43
        let _ = fs::DirBuilder::new()
6f5b84fad3efatagen2026-09-27 03:59 UTC44
            .recursive(true)
6f5b84fad3efatagen2026-09-27 03:59 UTC45
            .mode(0o700)
6f5b84fad3efatagen2026-09-27 03:59 UTC46
            .create(&dir);
6f5b84fad3efatagen2026-09-27 03:59 UTC47
        return dir;
6f5b84fad3efatagen2026-09-27 03:59 UTC48
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC49
    if let Some(dir) = env::var_os("XDG_RUNTIME_DIR").map(PathBuf::from)
6f5b84fad3efatagen2026-09-27 03:59 UTC50
        && dir.is_absolute()
6f5b84fad3efatagen2026-09-27 03:59 UTC51
        && dir.is_dir()
6f5b84fad3efatagen2026-09-27 03:59 UTC52
    {
6f5b84fad3efatagen2026-09-27 03:59 UTC53
        return dir;
6f5b84fad3efatagen2026-09-27 03:59 UTC54
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC55
    let uid = unsafe { libc::getuid() };
6f5b84fad3efatagen2026-09-27 03:59 UTC56
    let dir = env::temp_dir().join(format!("roundabout-{uid}"));
6f5b84fad3efatagen2026-09-27 03:59 UTC57
    let _ = fs::DirBuilder::new().mode(0o700).create(&dir);
6f5b84fad3efatagen2026-09-27 03:59 UTC58
    let _ = fs::set_permissions(&dir, fs::Permissions::from_mode(0o700));
6f5b84fad3efatagen2026-09-27 03:59 UTC59
    dir
6f5b84fad3efatagen2026-09-27 03:59 UTC60
}
6f5b84fad3efatagen2026-09-27 03:59 UTC61
6f5b84fad3efatagen2026-09-27 03:59 UTC62
pub fn socket() -> PathBuf {
6f5b84fad3efatagen2026-09-27 03:59 UTC63
    runtime_dir().join("roundabout.sock")
6f5b84fad3efatagen2026-09-27 03:59 UTC64
}
6f5b84fad3efatagen2026-09-27 03:59 UTC65
6f5b84fad3efatagen2026-09-27 03:59 UTC66
pub fn spawn_lock() -> PathBuf {
6f5b84fad3efatagen2026-09-27 03:59 UTC67
    runtime_dir().join("roundabout.lock")
6f5b84fad3efatagen2026-09-27 03:59 UTC68
}
6f5b84fad3efatagen2026-09-27 03:59 UTC69
9117440444ebatagen2026-10-26 02:44 UTC70
pub fn git_root(cwd: &Path) -> Option<PathBuf> {
9117440444ebatagen2026-10-26 02:44 UTC71
    cwd.ancestors()
9117440444ebatagen2026-10-26 02:44 UTC72
        .find(|dir| dir.join(".git").exists())
9117440444ebatagen2026-10-26 02:44 UTC73
        .map(Path::to_path_buf)
9117440444ebatagen2026-10-26 02:44 UTC74
}
9117440444ebatagen2026-10-26 02:44 UTC75
9117440444ebatagen2026-10-26 02:44 UTC76
pub fn project_config_file(cwd: &Path) -> Option<PathBuf> {
9117440444ebatagen2026-10-26 02:44 UTC77
    let root = git_root(cwd)?;
9117440444ebatagen2026-10-26 02:44 UTC78
    for dir in cwd.ancestors() {
9117440444ebatagen2026-10-26 02:44 UTC79
        if dir.join(".resrc").is_file() {
9117440444ebatagen2026-10-26 02:44 UTC80
            return Some(dir.join(".resrc"));
9117440444ebatagen2026-10-26 02:44 UTC81
        }
9117440444ebatagen2026-10-26 02:44 UTC82
        if dir == root {
9117440444ebatagen2026-10-26 02:44 UTC83
            break;
9117440444ebatagen2026-10-26 02:44 UTC84
        }
9117440444ebatagen2026-10-26 02:44 UTC85
    }
9117440444ebatagen2026-10-26 02:44 UTC86
    None
9117440444ebatagen2026-10-26 02:44 UTC87
}
9117440444ebatagen2026-10-26 02:44 UTC88
6f5b84fad3efatagen2026-09-27 03:59 UTC89
pub fn project_root(cwd: &Path) -> PathBuf {
6f5b84fad3efatagen2026-09-27 03:59 UTC90
    const MARKERS: [&str; 6] = [
6f5b84fad3efatagen2026-09-27 03:59 UTC91
        ".jj",
6f5b84fad3efatagen2026-09-27 03:59 UTC92
        ".git",
6f5b84fad3efatagen2026-09-27 03:59 UTC93
        ".hg",
6f5b84fad3efatagen2026-09-27 03:59 UTC94
        "flake.nix",
6f5b84fad3efatagen2026-09-27 03:59 UTC95
        "Cargo.toml",
6f5b84fad3efatagen2026-09-27 03:59 UTC96
        "package.json",
6f5b84fad3efatagen2026-09-27 03:59 UTC97
    ];
6f5b84fad3efatagen2026-09-27 03:59 UTC98
    let mut best = None;
6f5b84fad3efatagen2026-09-27 03:59 UTC99
    for dir in cwd.ancestors() {
6f5b84fad3efatagen2026-09-27 03:59 UTC100
        if dir.join(".jj").exists() || dir.join(".git").exists() || dir.join(".hg").exists() {
6f5b84fad3efatagen2026-09-27 03:59 UTC101
            return dir.to_path_buf();
6f5b84fad3efatagen2026-09-27 03:59 UTC102
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC103
        if best.is_none() && MARKERS.iter().any(|m| dir.join(m).exists()) {
6f5b84fad3efatagen2026-09-27 03:59 UTC104
            best = Some(dir.to_path_buf());
6f5b84fad3efatagen2026-09-27 03:59 UTC105
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC106
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC107
    best.unwrap_or_else(|| cwd.to_path_buf())
6f5b84fad3efatagen2026-09-27 03:59 UTC108
}
9117440444ebatagen2026-10-26 02:44 UTC109
9117440444ebatagen2026-10-26 02:44 UTC110
#[cfg(test)]
9117440444ebatagen2026-10-26 02:44 UTC111
mod tests {
9117440444ebatagen2026-10-26 02:44 UTC112
    use std::{
9117440444ebatagen2026-10-26 02:44 UTC113
        fs,
9117440444ebatagen2026-10-26 02:44 UTC114
        path::PathBuf,
9117440444ebatagen2026-10-26 02:44 UTC115
        time::{SystemTime, UNIX_EPOCH},
9117440444ebatagen2026-10-26 02:44 UTC116
    };
9117440444ebatagen2026-10-26 02:44 UTC117
9117440444ebatagen2026-10-26 02:44 UTC118
    use super::project_config_file;
9117440444ebatagen2026-10-26 02:44 UTC119
9117440444ebatagen2026-10-26 02:44 UTC120
    fn temp_tree() -> PathBuf {
9117440444ebatagen2026-10-26 02:44 UTC121
        let stamp = SystemTime::now()
9117440444ebatagen2026-10-26 02:44 UTC122
            .duration_since(UNIX_EPOCH)
9117440444ebatagen2026-10-26 02:44 UTC123
            .expect("clock before epoch")
9117440444ebatagen2026-10-26 02:44 UTC124
            .as_nanos();
9117440444ebatagen2026-10-26 02:44 UTC125
        let root = std::env::temp_dir().join(format!("roundabout-paths-{stamp}"));
9117440444ebatagen2026-10-26 02:44 UTC126
        fs::create_dir_all(&root).expect("create temporary tree");
9117440444ebatagen2026-10-26 02:44 UTC127
        root
9117440444ebatagen2026-10-26 02:44 UTC128
    }
9117440444ebatagen2026-10-26 02:44 UTC129
9117440444ebatagen2026-10-26 02:44 UTC130
    #[test]
9117440444ebatagen2026-10-26 02:44 UTC131
    fn project_config_is_nearest_and_git_bounded() {
9117440444ebatagen2026-10-26 02:44 UTC132
        let outer = temp_tree();
9117440444ebatagen2026-10-26 02:44 UTC133
        let root = outer.join("repo");
9117440444ebatagen2026-10-26 02:44 UTC134
        fs::create_dir(&root).expect("create repository tree");
9117440444ebatagen2026-10-26 02:44 UTC135
        let child = root.join("child");
9117440444ebatagen2026-10-26 02:44 UTC136
        let nested = child.join("nested");
9117440444ebatagen2026-10-26 02:44 UTC137
        fs::create_dir_all(&nested).expect("create nested tree");
9117440444ebatagen2026-10-26 02:44 UTC138
        fs::create_dir(root.join(".git")).expect("create git marker");
9117440444ebatagen2026-10-26 02:44 UTC139
        fs::write(root.join(".resrc"), "").expect("write root config");
9117440444ebatagen2026-10-26 02:44 UTC140
        fs::write(child.join(".resrc"), "").expect("write child config");
9117440444ebatagen2026-10-26 02:44 UTC141
        fs::write(nested.join(".resrc"), "").expect("write nested config");
9117440444ebatagen2026-10-26 02:44 UTC142
        assert_eq!(project_config_file(&nested), Some(nested.join(".resrc")));
9117440444ebatagen2026-10-26 02:44 UTC143
        fs::remove_file(nested.join(".resrc")).expect("remove nested config");
9117440444ebatagen2026-10-26 02:44 UTC144
        assert_eq!(project_config_file(&nested), Some(child.join(".resrc")));
9117440444ebatagen2026-10-26 02:44 UTC145
        fs::remove_file(child.join(".resrc")).expect("remove child config");
9117440444ebatagen2026-10-26 02:44 UTC146
        assert_eq!(project_config_file(&nested), Some(root.join(".resrc")));
9117440444ebatagen2026-10-26 02:44 UTC147
        fs::remove_file(root.join(".resrc")).expect("remove root config");
9117440444ebatagen2026-10-26 02:44 UTC148
        fs::write(outer.join(".resrc"), "").expect("write outside config");
9117440444ebatagen2026-10-26 02:44 UTC149
        assert_eq!(project_config_file(&nested), None);
9117440444ebatagen2026-10-26 02:44 UTC150
        fs::remove_file(outer.join(".resrc")).expect("remove outside config");
9117440444ebatagen2026-10-26 02:44 UTC151
        fs::remove_dir_all(outer).expect("remove temporary tree");
9117440444ebatagen2026-10-26 02:44 UTC152
    }
9117440444ebatagen2026-10-26 02:44 UTC153
}