sps_common/
config.rs

1// sps-common/src/config.rs
2use std::env;
3use std::path::{Path, PathBuf};
4
5use directories::UserDirs; // Ensure this crate is in sps-common/Cargo.toml
6use tracing::debug;
7
8use super::error::Result; // Assuming SpsResult is Result from super::error
9
10// This constant will serve as a fallback if HOMEBREW_PREFIX is not set or is empty.
11const 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, // Public for direct construction in main for init if needed
17    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        // Try to get SPS_ROOT from HOMEBREW_PREFIX environment variable.
29        // Fallback to DEFAULT_FALLBACK_SPS_ROOT if not set or empty.
30        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") // Changed from "cellar" to "Cellar" to match Homebrew
70    }
71
72    pub fn cask_room_dir(&self) -> PathBuf {
73        self.sps_root.join("Caskroom") // Changed from "cask_room" to "Caskroom"
74    }
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") // Adjusted to match Homebrew structure
86    }
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]) // user, e.g., homebrew
168                    .join(format!("homebrew-{}", parts[1])), // repo, e.g., homebrew-core
169            )
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") // Standard Homebrew tap structure
179                .join(format!("{formula_name}.json"));
180            if json_path.exists() {
181                return Some(json_path);
182            }
183            // Fallback to .rb for completeness, though API primarily gives JSON
184            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}