1use std::path::{Path, PathBuf};
4
5#[derive(Debug, Clone)]
7pub struct Paths {
8 pub stout_dir: PathBuf,
10 pub prefix: PathBuf,
12 pub cellar: PathBuf,
14}
15
16impl Paths {
17 pub fn new(stout_dir: PathBuf, prefix: PathBuf) -> Self {
19 let cellar = prefix.join("Cellar");
20 Self {
21 stout_dir,
22 prefix,
23 cellar,
24 }
25 }
26
27 pub fn config_file(&self) -> PathBuf {
29 self.stout_dir.join("config.toml")
30 }
31
32 pub fn index_db(&self) -> PathBuf {
34 self.stout_dir.join("index.db")
35 }
36
37 pub fn manifest(&self) -> PathBuf {
39 self.stout_dir.join("manifest.json")
40 }
41
42 pub fn installed_file(&self) -> PathBuf {
44 self.stout_dir.join("state").join("installed.toml")
45 }
46
47 pub fn history_file(&self) -> PathBuf {
49 self.stout_dir.join("state").join("history.json")
50 }
51
52 pub fn formula_cache(&self) -> PathBuf {
54 self.stout_dir.join("cache").join("formulas")
55 }
56
57 pub fn download_cache(&self) -> PathBuf {
59 self.stout_dir.join("cache").join("downloads")
60 }
61
62 pub fn ensure_dirs(&self) -> std::io::Result<()> {
64 std::fs::create_dir_all(&self.stout_dir)?;
65 std::fs::create_dir_all(self.stout_dir.join("state"))?;
66 std::fs::create_dir_all(self.formula_cache())?;
67 std::fs::create_dir_all(self.download_cache())?;
68 Ok(())
69 }
70
71 pub fn package_path(&self, name: &str, version: &str) -> PathBuf {
73 self.cellar.join(name).join(version)
74 }
75
76 pub fn is_installed(&self, name: &str, version: &str) -> bool {
78 self.package_path(name, version).exists()
79 }
80
81 pub fn installed_versions(&self, name: &str) -> Vec<String> {
83 let pkg_dir = self.cellar.join(name);
84 if !pkg_dir.exists() {
85 return Vec::new();
86 }
87
88 std::fs::read_dir(&pkg_dir)
89 .ok()
90 .map(|entries| {
91 entries
92 .filter_map(|e| e.ok())
93 .filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
94 .filter_map(|e| e.file_name().into_string().ok())
95 .collect()
96 })
97 .unwrap_or_default()
98 }
99}
100
101fn detect_homebrew_prefix() -> PathBuf {
103 let candidates = [
105 "/opt/homebrew", "/usr/local", "/home/linuxbrew/.linuxbrew", ];
109
110 for path in candidates {
111 let p = Path::new(path);
112 if p.join("Cellar").exists() {
113 return p.to_path_buf();
114 }
115 }
116
117 #[cfg(target_os = "macos")]
119 {
120 #[cfg(target_arch = "aarch64")]
122 return PathBuf::from("/opt/homebrew");
123 #[cfg(not(target_arch = "aarch64"))]
124 return PathBuf::from("/usr/local");
125 }
126
127 #[cfg(target_os = "linux")]
128 {
129 if let Some(home) = dirs::home_dir() {
131 return home.join(".local").join("stout");
132 }
133 PathBuf::from("/home/linuxbrew/.linuxbrew")
135 }
136
137 #[cfg(not(any(target_os = "macos", target_os = "linux")))]
138 {
139 PathBuf::from("/opt/homebrew")
140 }
141}
142
143impl Default for Paths {
144 fn default() -> Self {
145 let stout_dir = dirs::home_dir()
146 .map(|h| h.join(".stout"))
147 .unwrap_or_else(|| PathBuf::from(".stout"));
148
149 let prefix = detect_homebrew_prefix();
150 let cellar = prefix.join("Cellar");
151
152 Self {
153 stout_dir,
154 prefix,
155 cellar,
156 }
157 }
158}