use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShepPaths {
pub home: PathBuf,
pub daemon_config: PathBuf,
pub snapshot: PathBuf,
pub logs: PathBuf,
pub pids: PathBuf,
pub run: PathBuf,
pub socket: PathBuf,
pub barks: PathBuf,
pub kv: PathBuf,
}
impl ShepPaths {
#[must_use]
pub fn pipe_name(&self) -> String {
let sanitized: String = self
.home
.to_string_lossy()
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
.collect();
let trimmed = sanitized.trim_matches('-');
format!(r"\\.\pipe\shep-{trimmed}")
}
#[must_use]
pub fn resolve(env: &dyn Fn(&str) -> Option<String>, home_dir: &Path) -> Self {
let home = env("SHEP_HOME")
.map(PathBuf::from)
.unwrap_or_else(|| home_dir.join(".shep"));
let run = home.join("run");
Self {
daemon_config: home.join("shep.toml"),
snapshot: home.join("flock.json"),
logs: home.join("logs"),
pids: home.join("pids"),
socket: run.join("shep.sock"),
barks: home.join("barks.jsonl"),
kv: home.join("kv.json"),
run,
home,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
fn no_env(_: &str) -> Option<String> {
None
}
#[test]
fn default_layout_under_home_dir() {
let p = ShepPaths::resolve(&no_env, Path::new("/home/rin"));
assert_eq!(p.home, Path::new("/home/rin/.shep"));
assert_eq!(p.daemon_config, Path::new("/home/rin/.shep/shep.toml"));
assert_eq!(p.snapshot, Path::new("/home/rin/.shep/flock.json"));
assert_eq!(p.logs, Path::new("/home/rin/.shep/logs"));
assert_eq!(p.pids, Path::new("/home/rin/.shep/pids"));
assert_eq!(p.run, Path::new("/home/rin/.shep/run"));
assert_eq!(p.socket, Path::new("/home/rin/.shep/run/shep.sock"));
assert_eq!(p.barks, Path::new("/home/rin/.shep/barks.jsonl"));
assert_eq!(p.kv, Path::new("/home/rin/.shep/kv.json"));
}
#[test]
fn shep_home_env_overrides_root() {
let env = |key: &str| (key == "SHEP_HOME").then(|| "/srv/shep".to_string());
let p = ShepPaths::resolve(&env, Path::new("/home/rin"));
assert_eq!(p.home, Path::new("/srv/shep"));
assert_eq!(p.socket, Path::new("/srv/shep/run/shep.sock"));
}
#[test]
fn pipe_name_is_per_home_and_sanitized() {
let p = ShepPaths::resolve(&no_env, Path::new("/home/rin"));
assert_eq!(p.pipe_name(), r"\\.\pipe\shep-home-rin--shep");
let env = |key: &str| (key == "SHEP_HOME").then(|| "/srv/shep".to_string());
let q = ShepPaths::resolve(&env, Path::new("/home/rin"));
assert_eq!(q.pipe_name(), r"\\.\pipe\shep-srv-shep");
}
}