1use std::env;
3use std::path::{Path, PathBuf};
4
5use directories::UserDirs; use tracing::debug;
7
8use super::error::Result; const DEFAULT_FALLBACK_SPS_ROOT: &str = "/opt/homebrew";
12const SPS_ROOT_MARKER_FILENAME: &str = ".sps_root_v1";
13
14#[derive(Debug, Clone)]
15pub struct Config {
16 pub sps_root: PathBuf, pub api_base_url: String,
18 pub artifact_domain: Option<String>,
19 pub docker_registry_token: Option<String>,
20 pub docker_registry_basic_auth: Option<String>,
21 pub github_api_token: Option<String>,
22}
23
24impl Config {
25 pub fn load() -> Result<Self> {
26 debug!("Loading sps configuration");
27
28 let sps_root_str = env::var("HOMEBREW_PREFIX").ok().filter(|s| !s.is_empty())
31 .unwrap_or_else(|| {
32 debug!(
33 "HOMEBREW_PREFIX environment variable not set or empty, falling back to default: {}",
34 DEFAULT_FALLBACK_SPS_ROOT
35 );
36 DEFAULT_FALLBACK_SPS_ROOT.to_string()
37 });
38
39 let sps_root_path = PathBuf::from(&sps_root_str);
40 debug!("Effective SPS_ROOT set to: {}", sps_root_path.display());
41
42 let api_base_url = "https://formulae.brew.sh/api".to_string();
43
44 let artifact_domain = env::var("HOMEBREW_ARTIFACT_DOMAIN").ok();
45 let docker_registry_token = env::var("HOMEBREW_DOCKER_REGISTRY_TOKEN").ok();
46 let docker_registry_basic_auth = env::var("HOMEBREW_DOCKER_REGISTRY_BASIC_AUTH_TOKEN").ok();
47 let github_api_token = env::var("HOMEBREW_GITHUB_API_TOKEN").ok();
48
49 debug!("Configuration loaded successfully.");
50 Ok(Self {
51 sps_root: sps_root_path,
52 api_base_url,
53 artifact_domain,
54 docker_registry_token,
55 docker_registry_basic_auth,
56 github_api_token,
57 })
58 }
59
60 pub fn sps_root(&self) -> &Path {
61 &self.sps_root
62 }
63
64 pub fn bin_dir(&self) -> PathBuf {
65 self.sps_root.join("bin")
66 }
67
68 pub fn cellar_dir(&self) -> PathBuf {
69 self.sps_root.join("Cellar") }
71
72 pub fn cask_room_dir(&self) -> PathBuf {
73 self.sps_root.join("Caskroom") }
75
76 pub fn cask_store_dir(&self) -> PathBuf {
77 self.sps_root.join("sps_cask_store")
78 }
79
80 pub fn opt_dir(&self) -> PathBuf {
81 self.sps_root.join("opt")
82 }
83
84 pub fn taps_dir(&self) -> PathBuf {
85 self.sps_root.join("Library/Taps") }
87
88 pub fn cache_dir(&self) -> PathBuf {
89 self.sps_root.join("sps_cache")
90 }
91
92 pub fn logs_dir(&self) -> PathBuf {
93 self.sps_root.join("sps_logs")
94 }
95
96 pub fn tmp_dir(&self) -> PathBuf {
97 self.sps_root.join("tmp")
98 }
99
100 pub fn state_dir(&self) -> PathBuf {
101 self.sps_root.join("state")
102 }
103
104 pub fn man_base_dir(&self) -> PathBuf {
105 self.sps_root.join("share").join("man")
106 }
107
108 pub fn sps_root_marker_path(&self) -> PathBuf {
109 self.sps_root.join(SPS_ROOT_MARKER_FILENAME)
110 }
111
112 pub fn applications_dir(&self) -> PathBuf {
113 if cfg!(target_os = "macos") {
114 PathBuf::from("/Applications")
115 } else {
116 self.home_dir().join("Applications")
117 }
118 }
119
120 pub fn formula_cellar_dir(&self, formula_name: &str) -> PathBuf {
121 self.cellar_dir().join(formula_name)
122 }
123
124 pub fn formula_keg_path(&self, formula_name: &str, version_str: &str) -> PathBuf {
125 self.formula_cellar_dir(formula_name).join(version_str)
126 }
127
128 pub fn formula_opt_path(&self, formula_name: &str) -> PathBuf {
129 self.opt_dir().join(formula_name)
130 }
131
132 pub fn cask_room_token_path(&self, cask_token: &str) -> PathBuf {
133 self.cask_room_dir().join(cask_token)
134 }
135
136 pub fn cask_store_token_path(&self, cask_token: &str) -> PathBuf {
137 self.cask_store_dir().join(cask_token)
138 }
139
140 pub fn cask_store_version_path(&self, cask_token: &str, version_str: &str) -> PathBuf {
141 self.cask_store_token_path(cask_token).join(version_str)
142 }
143
144 pub fn cask_store_app_path(
145 &self,
146 cask_token: &str,
147 version_str: &str,
148 app_name: &str,
149 ) -> PathBuf {
150 self.cask_store_version_path(cask_token, version_str)
151 .join(app_name)
152 }
153
154 pub fn cask_room_version_path(&self, cask_token: &str, version_str: &str) -> PathBuf {
155 self.cask_room_token_path(cask_token).join(version_str)
156 }
157
158 pub fn home_dir(&self) -> PathBuf {
159 UserDirs::new().map_or_else(|| PathBuf::from("/"), |ud| ud.home_dir().to_path_buf())
160 }
161
162 pub fn get_tap_path(&self, name: &str) -> Option<PathBuf> {
163 let parts: Vec<&str> = name.split('/').collect();
164 if parts.len() == 2 {
165 Some(
166 self.taps_dir()
167 .join(parts[0]) .join(format!("homebrew-{}", parts[1])), )
170 } else {
171 None
172 }
173 }
174
175 pub fn get_formula_path_from_tap(&self, tap_name: &str, formula_name: &str) -> Option<PathBuf> {
176 self.get_tap_path(tap_name).and_then(|tap_path| {
177 let json_path = tap_path
178 .join("Formula") .join(format!("{formula_name}.json"));
180 if json_path.exists() {
181 return Some(json_path);
182 }
183 let rb_path = tap_path.join("Formula").join(format!("{formula_name}.rb"));
185 if rb_path.exists() {
186 return Some(rb_path);
187 }
188 None
189 })
190 }
191}
192
193impl Default for Config {
194 fn default() -> Self {
195 Self::load().expect("Failed to load default configuration")
196 }
197}
198
199pub fn load_config() -> Result<Config> {
200 Config::load()
201}