sps_common/
config.rs

1// ===== sps-core/src/utils/config.rs =====
2use std::env;
3use std::path::{Path, PathBuf};
4
5use dirs;
6use tracing::debug;
7
8use super::cache;
9use super::error::Result; // for home directory lookup
10
11/// Default installation prefixes
12const DEFAULT_LINUX_PREFIX: &str = "/home/linuxbrew/.linuxbrew";
13const DEFAULT_MACOS_INTEL_PREFIX: &str = "/usr/local";
14const DEFAULT_MACOS_ARM_PREFIX: &str = "/opt/homebrew";
15
16/// Determines the active prefix for installation.
17/// Checks sps_PREFIX/HOMEBREW_PREFIX env vars, then OS-specific defaults.
18fn determine_prefix() -> PathBuf {
19    if let Ok(prefix) = env::var("SPS_PREFIX").or_else(|_| env::var("HOMEBREW_PREFIX")) {
20        debug!("Using prefix from environment variable: {}", prefix);
21        return PathBuf::from(prefix);
22    }
23
24    let default_prefix = if cfg!(target_os = "linux") {
25        DEFAULT_LINUX_PREFIX
26    } else if cfg!(target_os = "macos") {
27        if cfg!(target_arch = "aarch64") {
28            DEFAULT_MACOS_ARM_PREFIX
29        } else {
30            DEFAULT_MACOS_INTEL_PREFIX
31        }
32    } else {
33        // Fallback for unsupported OS
34        "/usr/local/sps"
35    };
36    debug!("Using default prefix for OS/Arch: {}", default_prefix);
37    PathBuf::from(default_prefix)
38}
39
40#[derive(Debug, Clone)]
41pub struct Config {
42    pub prefix: PathBuf,
43    pub cellar: PathBuf,
44    pub taps_dir: PathBuf,
45    pub cache_dir: PathBuf,
46    pub api_base_url: String,
47    pub artifact_domain: Option<String>,
48    pub docker_registry_token: Option<String>,
49    pub docker_registry_basic_auth: Option<String>,
50    pub github_api_token: Option<String>,
51    pub private_cask_store_dir: PathBuf,
52}
53
54impl Config {
55    pub fn load() -> Result<Self> {
56        debug!("Loadingspsconfiguration");
57        let prefix = determine_prefix();
58        let cellar = prefix.join("Cellar");
59        let taps_dir = prefix.join("Library/Taps");
60        let cache_dir = cache::get_cache_dir()?;
61        let api_base_url = "https://formulae.brew.sh/api".to_string();
62
63        // Set up private cask store in ~/.local/share/sps/cask_store
64        let mut private_cask_store_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"));
65        private_cask_store_dir.push(".local");
66        private_cask_store_dir.push("share");
67        private_cask_store_dir.push("sps");
68        private_cask_store_dir.push("cask_store");
69
70        let artifact_domain = env::var("HOMEBREW_ARTIFACT_DOMAIN").ok();
71        let docker_registry_token = env::var("HOMEBREW_DOCKER_REGISTRY_TOKEN").ok();
72        let docker_registry_basic_auth = env::var("HOMEBREW_DOCKER_REGISTRY_BASIC_AUTH_TOKEN").ok();
73        let github_api_token = env::var("HOMEBREW_GITHUB_API_TOKEN").ok();
74
75        if artifact_domain.is_some() {
76            debug!("Loaded HOMEBREW_ARTIFACT_DOMAIN");
77        }
78        if docker_registry_token.is_some() {
79            debug!("Loaded HOMEBREW_DOCKER_REGISTRY_TOKEN");
80        }
81        if docker_registry_basic_auth.is_some() {
82            debug!("Loaded HOMEBREW_DOCKER_REGISTRY_BASIC_AUTH_TOKEN");
83        }
84        if github_api_token.is_some() {
85            debug!("Loaded HOMEBREW_GITHUB_API_TOKEN");
86        }
87
88        debug!("Configuration loaded successfully.");
89        Ok(Self {
90            prefix,
91            cellar,
92            taps_dir,
93            cache_dir,
94            api_base_url,
95            artifact_domain,
96            docker_registry_token,
97            docker_registry_basic_auth,
98            github_api_token,
99            private_cask_store_dir,
100        })
101    }
102
103    // --- Start: New Path Methods ---
104
105    pub fn prefix(&self) -> &Path {
106        &self.prefix
107    }
108
109    pub fn cellar_path(&self) -> &Path {
110        &self.cellar
111    }
112
113    pub fn caskroom_dir(&self) -> PathBuf {
114        self.prefix.join("Caskroom")
115    }
116
117    pub fn opt_dir(&self) -> PathBuf {
118        self.prefix.join("opt")
119    }
120
121    pub fn bin_dir(&self) -> PathBuf {
122        self.prefix.join("bin")
123    }
124
125    pub fn applications_dir(&self) -> PathBuf {
126        if cfg!(target_os = "macos") {
127            PathBuf::from("/Applications")
128        } else {
129            self.prefix.join("Applications")
130        }
131    }
132
133    pub fn formula_cellar_dir(&self, formula_name: &str) -> PathBuf {
134        self.cellar_path().join(formula_name)
135    }
136
137    pub fn formula_keg_path(&self, formula_name: &str, version_str: &str) -> PathBuf {
138        self.formula_cellar_dir(formula_name).join(version_str)
139    }
140
141    pub fn formula_opt_link_path(&self, formula_name: &str) -> PathBuf {
142        self.opt_dir().join(formula_name)
143    }
144
145    pub fn cask_dir(&self, cask_token: &str) -> PathBuf {
146        self.caskroom_dir().join(cask_token)
147    }
148
149    /// Returns the path to the cask's token directory in the caskroom.
150    pub fn cask_token_path(&self, cask_token: &str) -> PathBuf {
151        self.caskroom_dir().join(cask_token)
152    }
153
154    /// Returns the base directory for the private cask store
155    pub fn private_cask_store_base_dir(&self) -> &Path {
156        &self.private_cask_store_dir
157    }
158
159    /// Returns the path to the cask's token directory in the private store
160    pub fn private_cask_token_path(&self, cask_token: &str) -> PathBuf {
161        self.private_cask_store_dir.join(cask_token)
162    }
163
164    /// Returns the path to the version directory in the private store
165    pub fn private_cask_version_path(&self, cask_token: &str, version_str: &str) -> PathBuf {
166        self.private_cask_token_path(cask_token).join(version_str)
167    }
168
169    /// Returns the path to an app in the private store
170    pub fn private_cask_app_path(
171        &self,
172        cask_token: &str,
173        version_str: &str,
174        app_name: &str,
175    ) -> PathBuf {
176        self.private_cask_version_path(cask_token, version_str)
177            .join(app_name)
178    }
179
180    pub fn cask_version_path(&self, cask_token: &str, version_str: &str) -> PathBuf {
181        self.cask_dir(cask_token).join(version_str)
182    }
183
184    /// Returns the path to the current user's home directory.
185    pub fn home_dir(&self) -> PathBuf {
186        dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"))
187    }
188
189    /// Returns the base manpage directory (e.g., /usr/local/share/man).
190    pub fn manpagedir(&self) -> PathBuf {
191        self.prefix.join("share").join("man")
192    }
193
194    // --- End: New Path Methods ---
195
196    pub fn get_tap_path(&self, name: &str) -> Option<PathBuf> {
197        let parts: Vec<&str> = name.split('/').collect();
198        if parts.len() == 2 {
199            Some(
200                self.taps_dir
201                    .join(parts[0])
202                    .join(format!("homebrew-{}", parts[1])),
203            )
204        } else {
205            None
206        }
207    }
208
209    pub fn get_formula_path_from_tap(&self, tap_name: &str, formula_name: &str) -> Option<PathBuf> {
210        self.get_tap_path(tap_name).and_then(|tap_path| {
211            let json_path = tap_path
212                .join("Formula")
213                .join(format!("{formula_name}.json"));
214            if json_path.exists() {
215                return Some(json_path);
216            }
217            let rb_path = tap_path.join("Formula").join(format!("{formula_name}.rb"));
218            if rb_path.exists() {
219                return Some(rb_path);
220            }
221            None
222        })
223    }
224}
225
226impl Default for Config {
227    fn default() -> Self {
228        Self::load().expect("Failed to load default configuration")
229    }
230}
231
232pub fn load_config() -> Result<Config> {
233    Config::load()
234}