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}
52
53impl Config {
54    pub fn load() -> Result<Self> {
55        debug!("Loadingspsconfiguration");
56        let prefix = determine_prefix();
57        let cellar = prefix.join("Cellar");
58        let taps_dir = prefix.join("Library/Taps");
59        let cache_dir = cache::get_cache_dir()?;
60        let api_base_url = "https://formulae.brew.sh/api".to_string();
61
62        let artifact_domain = env::var("HOMEBREW_ARTIFACT_DOMAIN").ok();
63        let docker_registry_token = env::var("HOMEBREW_DOCKER_REGISTRY_TOKEN").ok();
64        let docker_registry_basic_auth = env::var("HOMEBREW_DOCKER_REGISTRY_BASIC_AUTH_TOKEN").ok();
65        let github_api_token = env::var("HOMEBREW_GITHUB_API_TOKEN").ok();
66
67        if artifact_domain.is_some() {
68            debug!("Loaded HOMEBREW_ARTIFACT_DOMAIN");
69        }
70        if docker_registry_token.is_some() {
71            debug!("Loaded HOMEBREW_DOCKER_REGISTRY_TOKEN");
72        }
73        if docker_registry_basic_auth.is_some() {
74            debug!("Loaded HOMEBREW_DOCKER_REGISTRY_BASIC_AUTH_TOKEN");
75        }
76        if github_api_token.is_some() {
77            debug!("Loaded HOMEBREW_GITHUB_API_TOKEN");
78        }
79
80        debug!("Configuration loaded successfully.");
81        Ok(Self {
82            prefix,
83            cellar,
84            taps_dir,
85            cache_dir,
86            api_base_url,
87            artifact_domain,
88            docker_registry_token,
89            docker_registry_basic_auth,
90            github_api_token,
91        })
92    }
93
94    // --- Start: New Path Methods ---
95
96    pub fn prefix(&self) -> &Path {
97        &self.prefix
98    }
99
100    pub fn cellar_path(&self) -> &Path {
101        &self.cellar
102    }
103
104    pub fn caskroom_dir(&self) -> PathBuf {
105        self.prefix.join("Caskroom")
106    }
107
108    pub fn opt_dir(&self) -> PathBuf {
109        self.prefix.join("opt")
110    }
111
112    pub fn bin_dir(&self) -> PathBuf {
113        self.prefix.join("bin")
114    }
115
116    pub fn applications_dir(&self) -> PathBuf {
117        if cfg!(target_os = "macos") {
118            PathBuf::from("/Applications")
119        } else {
120            self.prefix.join("Applications")
121        }
122    }
123
124    pub fn formula_cellar_dir(&self, formula_name: &str) -> PathBuf {
125        self.cellar_path().join(formula_name)
126    }
127
128    pub fn formula_keg_path(&self, formula_name: &str, version_str: &str) -> PathBuf {
129        self.formula_cellar_dir(formula_name).join(version_str)
130    }
131
132    pub fn formula_opt_link_path(&self, formula_name: &str) -> PathBuf {
133        self.opt_dir().join(formula_name)
134    }
135
136    pub fn cask_dir(&self, cask_token: &str) -> PathBuf {
137        self.caskroom_dir().join(cask_token)
138    }
139
140    pub fn cask_version_path(&self, cask_token: &str, version_str: &str) -> PathBuf {
141        self.cask_dir(cask_token).join(version_str)
142    }
143
144    /// Returns the path to the current user's home directory.
145    pub fn home_dir(&self) -> PathBuf {
146        dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"))
147    }
148
149    /// Returns the base manpage directory (e.g., /usr/local/share/man).
150    pub fn manpagedir(&self) -> PathBuf {
151        self.prefix.join("share").join("man")
152    }
153
154    // --- End: New Path Methods ---
155
156    pub fn get_tap_path(&self, name: &str) -> Option<PathBuf> {
157        let parts: Vec<&str> = name.split('/').collect();
158        if parts.len() == 2 {
159            Some(
160                self.taps_dir
161                    .join(parts[0])
162                    .join(format!("homebrew-{}", parts[1])),
163            )
164        } else {
165            None
166        }
167    }
168
169    pub fn get_formula_path_from_tap(&self, tap_name: &str, formula_name: &str) -> Option<PathBuf> {
170        self.get_tap_path(tap_name).and_then(|tap_path| {
171            let json_path = tap_path
172                .join("Formula")
173                .join(format!("{formula_name}.json"));
174            if json_path.exists() {
175                return Some(json_path);
176            }
177            let rb_path = tap_path.join("Formula").join(format!("{formula_name}.rb"));
178            if rb_path.exists() {
179                return Some(rb_path);
180            }
181            None
182        })
183    }
184}
185
186impl Default for Config {
187    fn default() -> Self {
188        Self::load().expect("Failed to load default configuration")
189    }
190}
191
192pub fn load_config() -> Result<Config> {
193    Config::load()
194}