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()
}
fn from_root(root_dir: PathBuf) -> Self {
pub fn from_root(root_dir: PathBuf) -> Self {
Self { root_dir }
}
}
@@ -1,3 +1,4 @@
use std::env;
use std::fs;
use std::io::{self, stdout};
use std::path::{Path, PathBuf};
@@ -21,7 +22,6 @@ use termion::raw::IntoRawMode;
use termion::screen::IntoAlternateScreen;
use crate::theme::RedBearTheme;
use crate::views;
const DEFAULT_TARGET: &str = "x86_64-unknown-redox";
@@ -82,19 +82,33 @@ pub struct CubApp {
}
impl CubApp {
pub fn new() -> Result<Self, CubError> {
let store = CubStore::new()?;
store.init()?;
pub fn new() -> Self {
let store = CubStore::new().unwrap_or_else(|_| {
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 {
search_query: String::new(),
search_results: Vec::new(),
selected_index: 0,
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,
store,
aur_client: Some(AurClient::new()),
aur_client,
query_entries: Vec::new(),
query_details: Vec::new(),
install_log: vec![
@@ -110,19 +124,26 @@ impl CubApp {
active_action: None,
tick: 0,
};
app.refresh_query_view()?;
Ok(app)
let _ = app.refresh_query_view();
app
}
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 backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend).map_err(terminal_error)?;
terminal.clear().map_err(terminal_error)?;
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> {
while self.running {
self.tick = self.tick.wrapping_add(1);
@@ -168,17 +189,17 @@ impl CubApp {
])
.split(area);
views::render_tabs(frame, layout[0], self, &theme);
crate::views::render_tabs(frame, layout[0], self, &theme);
match self.current_view {
View::Search => views::search::render(frame, layout[1], self, &theme),
View::PackageInfo => views::info::render(frame, layout[1], self, &theme),
View::Install => views::install::render(frame, layout[1], self, &theme),
View::Build => views::build::render(frame, layout[1], self, &theme),
View::Query => views::query::render(frame, layout[1], self, &theme),
View::Search => crate::views::search::render(frame, layout[1], self, &theme),
View::PackageInfo => crate::views::info::render(frame, layout[1], self, &theme),
View::Install => crate::views::install::render(frame, layout[1], self, &theme),
View::Build => crate::views::build::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) {
@@ -207,11 +228,11 @@ impl CubApp {
}
match self.current_view {
View::Search => views::search::handle_key(self, key),
View::PackageInfo => views::info::handle_key(self, key),
View::Install => views::install::handle_key(self, key),
View::Build => views::build::handle_key(self, key),
View::Query => views::query::handle_key(self, key),
View::Search => crate::views::search::handle_key(self, key),
View::PackageInfo => crate::views::info::handle_key(self, key),
View::Install => crate::views::install::handle_key(self, key),
View::Build => crate::views::build::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 {
self.status_message = "AUR client is unavailable in this build.".into();
self.status_message = "AUR offline — cannot search.".into();
return;
};
@@ -1,15 +1,13 @@
mod app;
mod theme;
mod views;
mod widgets;
pub mod app;
pub mod theme;
pub mod views;
pub mod widgets;
use std::io;
use app::CubApp;
pub fn run() -> io::Result<()> {
match CubApp::new() {
Ok(mut app) => 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")),
}
let mut app = CubApp::new();
app.run().map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{e}")))
}