kombrucha/lib.rs
1//! Kombrucha Library - Rust API for package management
2//!
3//! This library provides a programmatic interface to Homebrew package management operations.
4//! It enables downstream projects to interact with Homebrew packages, dependencies, and
5//! installation metadata without shelling out to the CLI.
6//!
7//! # Architecture
8//!
9//! - **api.rs**: Homebrew JSON API client with caching
10//! - **cellar.rs**: Local Cellar inspection (installed packages)
11//! - **download.rs**: Parallel bottle downloads from GHCR
12//! - **extract.rs**: Bottle extraction to Cellar
13//! - **symlink.rs**: Symlink management for installed packages
14//! - **tap.rs**: Custom tap management
15//! - **receipt.rs**: Installation receipt generation and metadata
16//! - **platform.rs**: Platform detection for bottle selection
17//! - **cache.rs**: Persistent disk caching of API data
18//! - **error.rs**: Unified error types
19//!
20//! # Key Concepts
21//!
22//! ## Cellar
23//!
24//! The Cellar is Homebrew's package directory (typically `/opt/homebrew/Cellar` on macOS).
25//! Each installed package has a directory structure:
26//! ```text
27//! /opt/homebrew/Cellar/
28//! ripgrep/
29//! 13.0.0/
30//! bin/
31//! lib/
32//! INSTALL_RECEIPT.json
33//! ```
34//!
35//! ## Bottles
36//!
37//! Bottles are precompiled `.tar.gz` archives containing binaries for a specific platform.
38//! The library can download, verify, and extract bottles from GitHub Container Registry (GHCR).
39//!
40//! ## API Client
41//!
42//! The [`BrewApi`] client queries Homebrew's public JSON API for formula metadata,
43//! with in-memory and persistent disk caching.
44//!
45//! ## Symlinks
46//!
47//! After extraction, symlinks make binaries and libraries accessible from standard
48//! directories (e.g., `/opt/homebrew/bin/ripgrep` → `../Cellar/ripgrep/13.0.0/bin/ripgrep`).
49//!
50//! # Quick Start
51//!
52//! ```no_run
53//! use kombrucha::{BrewApi, cellar};
54//!
55//! #[tokio::main]
56//! async fn main() -> anyhow::Result<()> {
57//! // List installed packages
58//! let installed = cellar::list_installed()?;
59//! for pkg in installed {
60//! println!("{} {}", pkg.name, pkg.version);
61//! }
62//!
63//! // Query package metadata
64//! let api = BrewApi::new()?;
65//! let formula = api.fetch_formula("ripgrep").await?;
66//! println!("{}: {}", formula.name, formula.desc.unwrap_or_default());
67//!
68//! Ok(())
69//! }
70//! ```
71//!
72//! # Common Tasks
73//!
74//! ## Query Package Information
75//!
76//! ```no_run
77//! use kombrucha::BrewApi;
78//!
79//! #[tokio::main]
80//! async fn main() -> anyhow::Result<()> {
81//! let api = BrewApi::new()?;
82//!
83//! // Fetch metadata for a formula
84//! let formula = api.fetch_formula("python").await?;
85//!
86//! println!("Name: {}", formula.name);
87//! println!("Version: {}", formula.versions.stable.unwrap_or_default());
88//! println!("Description: {}", formula.desc.unwrap_or_default());
89//! println!("Dependencies: {}", formula.dependencies.join(", "));
90//!
91//! Ok(())
92//! }
93//! ```
94//!
95//! ## List Installed Packages
96//!
97//! ```no_run
98//! use kombrucha::cellar;
99//!
100//! fn main() -> anyhow::Result<()> {
101//! // Get all installed packages
102//! let installed = cellar::list_installed()?;
103//! println!("Installed packages: {}", installed.len());
104//!
105//! for pkg in installed {
106//! println!(" {} {}", pkg.name, pkg.version);
107//! }
108//!
109//! // Get versions of a specific formula
110//! let versions = cellar::get_installed_versions("python")?;
111//! if !versions.is_empty() {
112//! println!("Python latest: {}", versions[0].version);
113//! }
114//!
115//! Ok(())
116//! }
117//! ```
118//!
119//! ## Search for Packages
120//!
121//! ```no_run
122//! use kombrucha::BrewApi;
123//!
124//! #[tokio::main]
125//! async fn main() -> anyhow::Result<()> {
126//! let api = BrewApi::new()?;
127//!
128//! // Search across all formulae and casks
129//! let results = api.search("python").await?;
130//!
131//! println!("Found {} formulae", results.formulae.len());
132//! for formula in &results.formulae {
133//! println!(" {} - {}", formula.name, formula.desc.as_deref().unwrap_or(""));
134//! }
135//!
136//! Ok(())
137//! }
138//! ```
139//!
140//! ## Download and Extract a Bottle
141//!
142//! ```no_run
143//! use kombrucha::{BrewApi, download, extract, symlink, cellar};
144//! use std::fs;
145//!
146//! #[tokio::main]
147//! async fn main() -> anyhow::Result<()> {
148//! let api = BrewApi::new()?;
149//! let client = reqwest::Client::new();
150//!
151//! // Fetch formula metadata
152//! let formula = api.fetch_formula("ripgrep").await?;
153//!
154//! // Step 1: Download bottle
155//! let bottle_path = download::download_bottle(&formula, None, &client).await?;
156//! println!("Downloaded bottle to: {}", bottle_path.display());
157//!
158//! // Step 2: Extract to Cellar
159//! let version = formula.versions.stable.unwrap();
160//! let cellar_dir = extract::extract_bottle(&bottle_path, "ripgrep", &version)?;
161//! println!("Extracted to: {}", cellar_dir.display());
162//!
163//! // Step 3: Create symlinks
164//! let linked = symlink::link_formula("ripgrep", &version)?;
165//! println!("Created {} symlinks", linked.len());
166//!
167//! // Step 4: Create version-agnostic links
168//! symlink::optlink("ripgrep", &version)?;
169//!
170//! Ok(())
171//! }
172//! ```
173//!
174//! ## Read Installation Metadata
175//!
176//! ```no_run
177//! use kombrucha::{receipt::InstallReceipt, cellar};
178//! use std::path::Path;
179//!
180//! fn main() -> anyhow::Result<()> {
181//! // Read an existing installation's receipt
182//! let cellar_path = cellar::cellar_path().join("ripgrep").join("13.0.0");
183//! let receipt = InstallReceipt::read(&cellar_path)?;
184//!
185//! println!("Installed with: {}", receipt.homebrew_version);
186//! println!("Installed on request: {}", receipt.installed_on_request);
187//! println!("Runtime dependencies: {}", receipt.runtime_dependencies.len());
188//!
189//! Ok(())
190//! }
191//! ```
192//!
193//! ## Work with Custom Taps
194//!
195//! ```no_run
196//! use kombrucha::tap;
197//!
198//! fn main() -> anyhow::Result<()> {
199//! // List installed taps
200//! let taps = tap::list_taps()?;
201//! println!("Installed taps: {}", taps.join(", "));
202//!
203//! // Parse formula metadata from a tap
204//! let formula_path = tap::tap_directory("user/repo")?
205//! .join("Formula")
206//! .join("mypackage.rb");
207//!
208//! if let Some(version) = tap::parse_formula_version(&formula_path)? {
209//! println!("Formula version: {}", version);
210//! }
211//!
212//! Ok(())
213//! }
214//! ```
215//!
216//! # Error Handling
217//!
218//! All fallible operations return [`Result<T>`], which is an alias for
219//! `std::result::Result<T, BruError>`. Common error variants:
220//!
221//! - [`BruError::FormulaNotFound`] - Formula doesn't exist in Homebrew
222//! - [`BruError::ApiError`] - Network request failed
223//! - [`BruError::IoError`] - File system operation failed
224//! - [`BruError::JsonError`] - JSON parsing failed
225//!
226//! ```no_run
227//! use kombrucha::{BrewApi, BruError};
228//!
229//! #[tokio::main]
230//! async fn main() {
231//! let api = BrewApi::new().unwrap();
232//! match api.fetch_formula("nonexistent-package").await {
233//! Ok(formula) => println!("Found: {}", formula.name),
234//! Err(BruError::FormulaNotFound(name)) => println!("'{}' not found", name),
235//! Err(e) => eprintln!("Error: {}", e),
236//! }
237//! }
238//! ```
239//!
240//! # Performance Characteristics
241//!
242//! - **API queries**: ~200-500ms per request (cached in-memory for session, disk cache 24h)
243//! - **List installed**: 10-50ms on typical systems (depends on number of packages)
244//! - **Download bottles**: Limited to 8 concurrent downloads; 500 Mbps connection downloads
245//! 10 bottles in ~5-10 seconds
246//! - **Extract bottles**: 50-200ms per bottle (depends on size and disk speed)
247//! - **Symlink creation**: 10-50ms per formula (parallelized with rayon)
248
249// Core library modules (no UI/CLI dependencies)
250pub mod api;
251pub mod cache;
252pub mod cellar;
253pub mod download;
254pub mod error;
255pub mod extract;
256pub mod package_manager;
257pub mod platform;
258pub mod receipt;
259pub mod symlink;
260pub mod tap;
261
262// Re-export commonly used types and functions
263pub use api::{Bottle, BrewApi, Cask, Formula, SearchResults, Versions};
264pub use cache::{get_cached_casks, get_cached_formulae, store_casks, store_formulae};
265pub use cellar::{InstalledPackage, RuntimeDependency, cellar_path, detect_prefix, list_installed};
266pub use download::cache_dir;
267pub use error::{BruError, Result};
268pub use extract::extract_bottle;
269pub use package_manager::{
270 CleanupResult, Dependencies, HealthCheck, InstallResult, OutdatedPackage, PackageManager,
271 ReinstallResult, UninstallResult, UpgradeResult,
272};
273pub use receipt::InstallReceipt;
274pub use symlink::{link_formula, normalize_path, optlink, unlink_formula, unoptlink};
275pub use tap::{get_core_formula_version, list_taps, parse_formula_info, parse_formula_version};