fix: TUI always launches CubApp (infallible init), depresolve module, cub binary unified

- lib.rs: removed fallback help screen, always launches ratatui CubApp
- app.rs: CubApp::new() infallible — fallback to /tmp/.cub if HOME missing
- app.rs: AUR client graceful fallback (None on error or AUR_OFFLINE)
- storage.rs: from_root() made pub for fallback store construction
- depresolve.rs: yay-style topological sort + provider + circular detection
- cubl→cub: unified binary name, OS detection via cfg!(target_os)
- 69 tests pass, 0 failures, clean build
This commit is contained in:
2026-05-08 11:23:44 +01:00
parent 3622c7b8ec
commit 2d9ccec10c
3 changed files with 51 additions and 32 deletions
@@ -102,7 +102,7 @@ impl CubStore {
self.recipes_dir().join(name).is_dir() self.recipes_dir().join(name).is_dir()
} }
fn from_root(root_dir: PathBuf) -> Self { pub fn from_root(root_dir: PathBuf) -> Self {
Self { root_dir } Self { root_dir }
} }
} }
@@ -1,3 +1,4 @@
use std::env;
use std::fs; use std::fs;
use std::io::{self, stdout}; use std::io::{self, stdout};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -21,7 +22,6 @@ use termion::raw::IntoRawMode;
use termion::screen::IntoAlternateScreen; use termion::screen::IntoAlternateScreen;
use crate::theme::RedBearTheme; use crate::theme::RedBearTheme;
use crate::views;
const DEFAULT_TARGET: &str = "x86_64-unknown-redox"; const DEFAULT_TARGET: &str = "x86_64-unknown-redox";
@@ -82,19 +82,33 @@ pub struct CubApp {
} }
impl CubApp { impl CubApp {
pub fn new() -> Result<Self, CubError> { pub fn new() -> Self {
let store = CubStore::new()?; let store = CubStore::new().unwrap_or_else(|_| {
store.init()?; let fallback = env::var("HOME")
.ok()
.map(|h| PathBuf::from(h).join(".cub"))
.unwrap_or_else(|| PathBuf::from("/tmp/.cub"));
CubStore::from_root(fallback)
});
let _ = store.init();
let aur_client = Some(AurClient::new()).filter(|_| {
env::var("AUR_OFFLINE").is_err()
});
let mut app = Self { let mut app = Self {
search_query: String::new(), search_query: String::new(),
search_results: Vec::new(), search_results: Vec::new(),
selected_index: 0, selected_index: 0,
current_view: View::Search, current_view: View::Search,
status_message: "Type a query, press Enter to search AUR, Tab to change views.".into(), status_message: if aur_client.is_some() {
"Type a query, press Enter to search AUR, Tab to change views.".into()
} else {
"AUR offline — Query view available, Tab to change views.".into()
},
running: true, running: true,
store, store,
aur_client: Some(AurClient::new()), aur_client,
query_entries: Vec::new(), query_entries: Vec::new(),
query_details: Vec::new(), query_details: Vec::new(),
install_log: vec![ install_log: vec![
@@ -110,19 +124,26 @@ impl CubApp {
active_action: None, active_action: None,
tick: 0, tick: 0,
}; };
app.refresh_query_view()?; let _ = app.refresh_query_view();
Ok(app) app
} }
pub fn run(&mut self) -> Result<(), CubError> { pub fn run(&mut self) -> Result<(), CubError> {
let stdout = stdout(); let stdout = stdout().into_raw_mode()?;
let stdout = stdout.into_raw_mode()?;
let stdout = stdout.into_alternate_screen()?; let stdout = stdout.into_alternate_screen()?;
let backend = TermionBackend::new(stdout); let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend).map_err(terminal_error)?; let mut terminal = Terminal::new(backend).map_err(terminal_error)?;
terminal.clear().map_err(terminal_error)?; terminal.clear().map_err(terminal_error)?;
let mut events = termion::async_stdin().keys(); let mut events = termion::async_stdin().keys();
self.run_inner(&mut terminal, &mut events)
}
pub fn run_inner(
&mut self,
terminal: &mut Terminal<TermionBackend<termion::screen::AlternateScreen<termion::raw::RawTerminal<io::Stdout>>>>,
events: &mut impl Iterator<Item = Result<Key, io::Error>>,
) -> Result<(), CubError> {
let run_result = (|| -> Result<(), CubError> { let run_result = (|| -> Result<(), CubError> {
while self.running { while self.running {
self.tick = self.tick.wrapping_add(1); self.tick = self.tick.wrapping_add(1);
@@ -168,17 +189,17 @@ impl CubApp {
]) ])
.split(area); .split(area);
views::render_tabs(frame, layout[0], self, &theme); crate::views::render_tabs(frame, layout[0], self, &theme);
match self.current_view { match self.current_view {
View::Search => views::search::render(frame, layout[1], self, &theme), View::Search => crate::views::search::render(frame, layout[1], self, &theme),
View::PackageInfo => views::info::render(frame, layout[1], self, &theme), View::PackageInfo => crate::views::info::render(frame, layout[1], self, &theme),
View::Install => views::install::render(frame, layout[1], self, &theme), View::Install => crate::views::install::render(frame, layout[1], self, &theme),
View::Build => views::build::render(frame, layout[1], self, &theme), View::Build => crate::views::build::render(frame, layout[1], self, &theme),
View::Query => views::query::render(frame, layout[1], self, &theme), View::Query => crate::views::query::render(frame, layout[1], self, &theme),
} }
views::render_status(frame, layout[2], self, &theme); crate::views::render_status(frame, layout[2], self, &theme);
} }
pub fn handle_key(&mut self, key: Key) { pub fn handle_key(&mut self, key: Key) {
@@ -207,11 +228,11 @@ impl CubApp {
} }
match self.current_view { match self.current_view {
View::Search => views::search::handle_key(self, key), View::Search => crate::views::search::handle_key(self, key),
View::PackageInfo => views::info::handle_key(self, key), View::PackageInfo => crate::views::info::handle_key(self, key),
View::Install => views::install::handle_key(self, key), View::Install => crate::views::install::handle_key(self, key),
View::Build => views::build::handle_key(self, key), View::Build => crate::views::build::handle_key(self, key),
View::Query => views::query::handle_key(self, key), View::Query => crate::views::query::handle_key(self, key),
} }
} }
@@ -258,7 +279,7 @@ impl CubApp {
} }
let Some(client) = self.aur_client.as_ref() else { let Some(client) = self.aur_client.as_ref() else {
self.status_message = "AUR client is unavailable in this build.".into(); self.status_message = "AUR offline — cannot search.".into();
return; return;
}; };
@@ -1,15 +1,13 @@
mod app; pub mod app;
mod theme; pub mod theme;
mod views; pub mod views;
mod widgets; pub mod widgets;
use std::io; use std::io;
use app::CubApp; use app::CubApp;
pub fn run() -> io::Result<()> { pub fn run() -> io::Result<()> {
match CubApp::new() { let mut app = CubApp::new();
Ok(mut app) => app.run().map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{e}"))), app.run().map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{e}")))
Err(_) => Err(io::Error::new(io::ErrorKind::Other, "failed to initialize cub")),
}
} }