1use std::path::{Path, PathBuf};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ShepPaths {
17 pub home: PathBuf,
19 pub daemon_config: PathBuf,
21 pub snapshot: PathBuf,
23 pub logs: PathBuf,
25 pub pids: PathBuf,
27 pub run: PathBuf,
29 pub socket: PathBuf,
31 pub barks: PathBuf,
33 pub kv: PathBuf,
35}
36
37impl ShepPaths {
38 #[must_use]
43 pub fn pipe_name(&self) -> String {
44 let sanitized: String = self
45 .home
46 .to_string_lossy()
47 .chars()
48 .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
49 .collect();
50 let trimmed = sanitized.trim_matches('-');
51 format!(r"\\.\pipe\shep-{trimmed}")
52 }
53
54 #[must_use]
56 pub fn resolve(env: &dyn Fn(&str) -> Option<String>, home_dir: &Path) -> Self {
57 let home = env("SHEP_HOME")
58 .map(PathBuf::from)
59 .unwrap_or_else(|| home_dir.join(".shep"));
60 let run = home.join("run");
61 Self {
62 daemon_config: home.join("shep.toml"),
63 snapshot: home.join("flock.json"),
64 logs: home.join("logs"),
65 pids: home.join("pids"),
66 socket: run.join("shep.sock"),
67 barks: home.join("barks.jsonl"),
68 kv: home.join("kv.json"),
69 run,
70 home,
71 }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use std::path::Path;
79
80 fn no_env(_: &str) -> Option<String> {
81 None
82 }
83
84 #[test]
85 fn default_layout_under_home_dir() {
86 let p = ShepPaths::resolve(&no_env, Path::new("/home/rin"));
87 assert_eq!(p.home, Path::new("/home/rin/.shep"));
88 assert_eq!(p.daemon_config, Path::new("/home/rin/.shep/shep.toml"));
89 assert_eq!(p.snapshot, Path::new("/home/rin/.shep/flock.json"));
90 assert_eq!(p.logs, Path::new("/home/rin/.shep/logs"));
91 assert_eq!(p.pids, Path::new("/home/rin/.shep/pids"));
92 assert_eq!(p.run, Path::new("/home/rin/.shep/run"));
93 assert_eq!(p.socket, Path::new("/home/rin/.shep/run/shep.sock"));
94 assert_eq!(p.barks, Path::new("/home/rin/.shep/barks.jsonl"));
95 assert_eq!(p.kv, Path::new("/home/rin/.shep/kv.json"));
96 }
97
98 #[test]
99 fn shep_home_env_overrides_root() {
100 let env = |key: &str| (key == "SHEP_HOME").then(|| "/srv/shep".to_string());
101 let p = ShepPaths::resolve(&env, Path::new("/home/rin"));
102 assert_eq!(p.home, Path::new("/srv/shep"));
103 assert_eq!(p.socket, Path::new("/srv/shep/run/shep.sock"));
104 }
105
106 #[test]
107 fn pipe_name_is_per_home_and_sanitized() {
108 let p = ShepPaths::resolve(&no_env, Path::new("/home/rin"));
111 assert_eq!(p.pipe_name(), r"\\.\pipe\shep-home-rin--shep");
112 let env = |key: &str| (key == "SHEP_HOME").then(|| "/srv/shep".to_string());
113 let q = ShepPaths::resolve(&env, Path::new("/home/rin"));
114 assert_eq!(q.pipe_name(), r"\\.\pipe\shep-srv-shep");
115 }
116}