pacsea/
lib.rs

1//! # Pacsea Crate Overview
2//!
3//! Pacsea bundles the core event loop, data pipelines, and UI helpers that power the
4//! `pacsea` terminal application. Integration tests and downstream tooling can depend on this
5//! crate to drive the runtime without going through the binary entrypoint.
6//!
7//! ## Why Pacsea?
8//! > **TUI-first workflow:** Navigate Arch + AUR results with instant filtering, modal install
9//! > previews, and keyboard-first ergonomics.
10//! >
11//! > **Complete ecosystem coverage:** Async workers query official repos, the AUR, mirrors, and
12//! > Arch news so you can browse and act from one dashboard.
13//! >
14//! > **Aggressive caching & telemetry:** Persistent caches (`app::persist`) and ranked searches
15//! > (`util::match_rank`) keep navigation snappy while structured tracing calls expose bottlenecks.
16//!
17//! ## Highlights
18//! - TUI runtime (`app::runtime`) orchestrating async tasks, caches, and rendering.
19//! - Modular subsystems for install flows, package index querying, and translation loading.
20//! - Reusable helpers for theme paths, serialization, and UI composition.
21//!
22//! ## Crate Layout
23//! - [`app`]: runtime, caches, and persistence glue for the interactive TUI.
24//! - [`events`], [`logic`], [`install`]: event handling and command execution pipelines.
25//! - [`index`], [`sources`]: Arch/AUR metadata fetchers plus enrichment.
26//! - [`state`], [`theme`], [`ui`], [`util`]: configuration, rendering, and misc helpers.
27//!
28//! ## Quick Start
29//! ```no_run
30//! use pacsea::app;
31//! use tracing_subscriber::EnvFilter;
32//!
33//! #[tokio::main]
34//! async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
35//!     let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("pacsea=info"));
36//!     tracing_subscriber::fmt()
37//!         .with_env_filter(filter)
38//!         .with_target(false)
39//!         .init();
40//!
41//!     // Drive the full TUI runtime (set `true` for dry-run install previews)
42//!     app::run(false).await?;
43//!     Ok(())
44//! }
45//! ```
46//!
47//! See `src/main.rs` for the full CLI wiring (argument parsing, log file setup, and mode flags).
48//!
49//! ## Subsystem Map
50//! | Module | Jump points | Responsibilities |
51//! | --- | --- | --- |
52//! | [`app`] | `app::run`, `app::sandbox_cache`, `app::services_cache` | Terminal runtime orchestration, cache persistence, sandbox + service metadata. |
53//! | [`events`] | `events::search`, `events::recent` | Keyboard/mouse dispatchers that mutate `state::AppState`.
54//! | [`logic`] | `logic::send_query`, `logic::deps::resolve_dependencies` | Core business rules for querying indices, ranking, and dependency analysis. |
55//! | [`install`] | `install::command`, `install::spawn_install`, `install::spawn_remove_all` | Batch + single install orchestration, scan integrations, terminal helpers. |
56//! | [`index`] | `index::load_from_disk`, `index::all_official`, `index::save_to_disk` | Persistent Arch index management and enrichment queues. |
57//! | [`state`] | `state::AppState`, `state::types::PackageItem` | Shared UI/runtime data model and domain structs. |
58//! | [`theme`] & [`ui`] | `theme::settings`, `ui::middle`, `ui::details` | Theme resolution, keymaps, and ratatui component tree. |
59//! | [`util`] | `util::match_rank`, `util::repo_order`, `util::ts_to_date` | Pure helpers for scoring, formatting, and sorting.
60//!
61//! ## Testing Hooks
62//! - `pacsea::global_test_mutex()` / `pacsea::global_test_mutex_lock()` serialize tests that mutate
63//!   global environment variables or touch shared caches.
64//! - `state::test_mutex()` (private) is used inside state tests; prefer the crate-level guard for
65//!   integration suites that spawn the runtime.
66//!
67//! ```rust,ignore
68//! #[tokio::test]
69//! async fn installs_are_serialized() {
70//!     let _guard = pacsea::global_test_mutex_lock();
71//!     std::env::set_var("PATH", "/tmp/pacsea-tests/bin");
72//!     // run test body that mutates process globals
73//! }
74//! ```
75//!
76//! ## Common Tasks
77//! **Kick off a search programmatically**
78//! ```rust
79//! use pacsea::logic::send_query;
80//! use pacsea::state::{AppState, QueryInput};
81//! use tokio::sync::mpsc;
82//!
83//! fn trigger_query(term: &str) {
84//!     let mut app = AppState {
85//!         input: term.to_string(),
86//!         ..Default::default()
87//!     };
88//!     let (tx, _rx) = mpsc::unbounded_channel::<QueryInput>();
89//!     send_query(&mut app, &tx);
90//! }
91//! ```
92//!
93//! **Inject a fake official index during tests**
94//! ```rust
95//! use pacsea::index::{load_from_disk, OfficialIndex, OfficialPkg};
96//! use std::collections::HashMap;
97//! use std::path::PathBuf;
98//!
99//! fn seed_index() {
100//!     let mut tmp = PathBuf::from(std::env::temp_dir());
101//!     tmp.push("pacsea_index_fixture.json");
102//!     let snapshot = OfficialIndex {
103//!         pkgs: vec![OfficialPkg {
104//!             name: "pacsea-demo".into(),
105//!             repo: "extra".into(),
106//!             arch: "x86_64".into(),
107//!             version: "1.0".into(),
108//!             description: "fixture".into(),
109//!         }],
110//!         name_to_idx: HashMap::new(), // Skipped during serialization
111//!     };
112//!     std::fs::write(&tmp, serde_json::to_string(&snapshot).unwrap()).unwrap();
113//!     load_from_disk(&tmp);
114//!     let _ = std::fs::remove_file(tmp);
115//! }
116//! ```
117//!
118//! The modules listed below link to detailed documentation for each subsystem.
119
120pub mod announcements;
121pub mod app;
122
123pub mod events;
124pub mod i18n;
125pub mod index;
126pub mod install;
127pub mod logic;
128pub mod sources;
129pub mod state;
130pub mod theme;
131pub mod ui;
132pub mod util;
133
134#[cfg(test)]
135mod test_utils;
136
137// Backwards-compat shim: keep `crate::ui_helpers::*` working
138#[doc(hidden)]
139pub use crate::ui::helpers as ui_helpers;
140
141#[cfg(test)]
142static GLOBAL_TEST_MUTEX: std::sync::OnceLock<std::sync::Mutex<()>> = std::sync::OnceLock::new();
143
144#[cfg(test)]
145/// What: Provide a global mutex to serialize all tests that mutate PATH or other global environment variables.
146///
147/// Input: None.
148/// Output: `&'static Mutex<()>` guard to synchronize tests touching global environment state.
149///
150/// Details:
151/// - Lazily initializes a global `Mutex` via `OnceLock` for cross-test coordination.
152/// - All tests that modify PATH, `WAYLAND_DISPLAY`, or other global environment variables should use this mutex.
153/// - This ensures tests run serially even when --test-threads=1 is used, preventing race conditions.
154/// - Handles poisoned mutexes gracefully by recovering from panics in previous tests.
155pub fn global_test_mutex() -> &'static std::sync::Mutex<()> {
156    GLOBAL_TEST_MUTEX.get_or_init(|| std::sync::Mutex::new(()))
157}
158
159#[cfg(test)]
160/// What: Lock the global test mutex, handling poisoned mutexes gracefully.
161///
162/// Input: None.
163/// Output: `MutexGuard<()>` that will be released when dropped.
164///
165/// Details:
166/// - If the mutex is poisoned (from a previous test panic), recovers by acquiring the lock anyway.
167/// - This allows tests to continue running even if a previous test panicked while holding the lock.
168pub fn global_test_mutex_lock() -> std::sync::MutexGuard<'static, ()> {
169    global_test_mutex()
170        .lock()
171        .unwrap_or_else(std::sync::PoisonError::into_inner)
172}