From be2a4ddd0a9f9d3ca88ab6eede4387a3e871d22c Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sat, 25 Oct 2025 12:06:54 +0700 Subject: [PATCH] Tell what recipe error, optimized rendering --- src/bin/repo.rs | 160 ++++++++++++++++++++++++++------------------ src/cook/package.rs | 4 ++ 2 files changed, 99 insertions(+), 65 deletions(-) diff --git a/src/bin/repo.rs b/src/bin/repo.rs index 8f867db7..378118fb 100644 --- a/src/bin/repo.rs +++ b/src/bin/repo.rs @@ -6,7 +6,7 @@ use std::str::FromStr; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Arc, mpsc}; use std::time::Duration; -use std::{env, fs}; +use std::{cmp, env, fs}; use std::{process, thread}; use anyhow::{Context, anyhow, bail}; @@ -145,15 +145,23 @@ fn main_inner() -> anyhow::Result<()> { let (config, command, recipe_names) = parse_args(args)?; if command == CliCommand::Cook && config.cook.tui { - if let Some(e) = run_tui_cook(config, recipe_names)? { + if let Some((name, e)) = run_tui_cook(config, recipe_names)? { let _ = stderr().write(e.as_bytes()); let _ = stderr().write(b"\n\n"); + eprintln!( + "{}{}cook - failed at {}{}{}", + style::Bold, + color::Fg(color::AnsiValue(196)), + name.as_str(), + color::Fg(color::Reset), + style::Reset, + ); return Err(anyhow!("Execution has failed")); } else { eprintln!( "{}{}cook - successful{}{}", style::Bold, - color::Fg(color::AnsiValue(215)), + color::Fg(color::AnsiValue(46)), color::Fg(color::Reset), style::Reset, ); @@ -274,7 +282,7 @@ fn handle_fetch( true => fetch_offline(recipe_dir, &recipe.recipe, logger), false => fetch(recipe_dir, &recipe.recipe, logger), } - .map_err(|e| anyhow!(e))?; + .map_err(|e| anyhow!("failed to fetch: {:?}", e))?; Ok(source_dir) } @@ -298,7 +306,7 @@ fn handle_cook( !is_deps, logger, ) - .map_err(|err| anyhow!("failed to build: {}", err))?; + .map_err(|err| anyhow!("failed to build: {:?}", err))?; package( &stage_dir, @@ -307,7 +315,7 @@ fn handle_cook( &recipe.recipe, &auto_deps, ) - .map_err(|err| anyhow!("failed to package: {}", err))?; + .map_err(|err| anyhow!("failed to package: {:?}", err))?; Ok(()) } @@ -385,18 +393,18 @@ struct TuiApp { active_fetch: Option, active_cook: Option, logs: HashMap>, - log_scroll: u16, + log_scroll: usize, log_view_job: JobType, auto_scroll: bool, - fetch_scroll: u16, - cook_scroll: u16, + fetch_scroll: usize, + cook_scroll: usize, fetch_complete: bool, cook_complete: bool, fetch_panel_rect: Option, cook_panel_rect: Option, log_panel_rect: Option, prompt: Option, - dump_logs_on_exit: Option, + dump_logs_on_exit: Option<(PackageName, String)>, } impl TuiApp { @@ -506,7 +514,10 @@ impl TuiApp { } } -fn run_tui_cook(config: CliConfig, recipes: Vec) -> anyhow::Result> { +fn run_tui_cook( + config: CliConfig, + recipes: Vec, +) -> anyhow::Result> { let (work_tx, work_rx) = mpsc::channel::<(CookRecipe, PathBuf)>(); let (status_tx, status_rx) = mpsc::channel::(); @@ -729,11 +740,7 @@ fn run_tui_cook(config: CliConfig, recipes: Vec) -> anyhow::Result) -> anyhow::Result = if let Some(active_name) = active_name { - app.logs - .get(active_name) - .cloned() - .unwrap_or_else(|| vec!["Waiting for logs...".to_string()]) - } else { - vec!["No active job.".to_string()] - }; + let mut enable_auto_scroll = false; - let log_pane_height = chunks[if app.fetch_complete { 1 } else { 2 }] - .height - .saturating_sub(2); - let total_log_lines = log_text.len() as u16; + let log_lines: Vec = if let Some(log_text) = log_text + && log_text.len() > 0 + { + let log_pane_height = chunks[if app.fetch_complete { 1 } else { 2 }] + .height + .saturating_sub(2) as usize; + let total_log_lines = log_text.len() as usize; - if app.auto_scroll { - if total_log_lines > log_pane_height { - app.log_scroll = total_log_lines - log_pane_height; - } else { - app.log_scroll = 0; - } - } else { - if total_log_lines > log_pane_height { - if app.log_scroll >= total_log_lines - log_pane_height { - app.log_scroll = total_log_lines - log_pane_height; - app.auto_scroll = true; + let start = if app.auto_scroll { + if total_log_lines > log_pane_height { + total_log_lines - log_pane_height + } else { + 0 } } else { - app.log_scroll = 0; - } - } + if total_log_lines > log_pane_height { + if app.log_scroll >= total_log_lines - log_pane_height { + enable_auto_scroll = true; + total_log_lines - log_pane_height + } else { + app.log_scroll + } + } else { + 0 + } + }; - let log_paragraph = Paragraph::new(log_text.join("\n")) + let end = cmp::min(log_pane_height + start, total_log_lines - 1); + + log_text[start..end] + .iter() + .map(|s| Line::from(s.clone())) + .collect() + } else { + vec![Line::from("No logs yet")] + }; + + let log_paragraph = Paragraph::new(log_lines) .block(Block::default().title(log_title).borders(Borders::ALL)) - .wrap(Wrap { trim: false }) - .scroll((app.log_scroll, 0)); + .wrap(Wrap { trim: false }); f.render_widget( log_paragraph, chunks[if app.fetch_complete { 1 } else { 2 }], ); - if let Some(prompt) = &app.prompt { draw_prompt(f, prompt); } + if enable_auto_scroll { + app.auto_scroll = true; + } while let Ok(event) = input_rx.try_recv() { - if app.prompt.is_some() { - if let Some(res) = handle_prompt_input(event, &mut app, &running) { - prompting.swap(res as u32, Ordering::SeqCst); - if res == PromptOption::Exit { - app.dump_logs_on_exit = Some(log_text.join("\n")) + if let Some((app, res)) = handle_prompt_input(&event, &mut app) { + prompting.swap(res as u32, Ordering::SeqCst); + if res == PromptOption::Exit { + let (name, log) = get_active_log(&app); + if let Some(name) = name + && let Some(log) = log + { + app.dump_logs_on_exit = Some((name.to_owned(), log.join("\n"))); } - app.prompt = None; + running.store(false, Ordering::SeqCst); } + app.prompt = None; } else { - handle_main_event(&mut app, event); + handle_main_event(&mut app, &event); } } })?; @@ -819,7 +839,22 @@ fn run_tui_cook(config: CliConfig, recipes: Vec) -> anyhow::Result (Option, Option<&Vec>) { + let active_name = if app.log_view_job == JobType::Cook { + app.active_cook.clone() + } else { + app.active_fetch.clone() + }; + + let log_text = if let Some(active_name) = &active_name { + app.logs.get(active_name) + } else { + None + }; + (active_name, log_text) +} + +fn handle_main_event(app: &mut TuiApp, event: &Event) { match event { Event::Key(key) => match key { Key::Char('1') => { @@ -904,27 +939,22 @@ fn handle_main_event(app: &mut TuiApp, event: Event) { } } -fn handle_prompt_input( - event: Event, - app: &mut TuiApp, - running: &Arc, -) -> Option { +fn handle_prompt_input<'a>( + event: &Event, + app: &'a mut TuiApp, +) -> Option<(&'a mut TuiApp, PromptOption)> { if let Some(prompt) = &mut app.prompt { match event { Event::Key(key) => match key { Key::Char('q') | Key::Ctrl('c') | Key::Esc => { // Treat as "Exit" - running.store(false, Ordering::SeqCst); - return Some(PromptOption::Exit); + return Some((app, PromptOption::Exit)); } Key::Left | Key::BackTab => prompt.prev(), Key::Right | Key::Char('\t') => prompt.next(), Key::Char('\n') => { let prompt = app.prompt.take().unwrap(); - if prompt.selected == PromptOption::Exit { - running.store(false, Ordering::SeqCst); - } - return Some(prompt.selected); + return Some((app, prompt.selected)); } _ => {} }, diff --git a/src/cook/package.rs b/src/cook/package.rs index a02ad9b1..b4fed138 100644 --- a/src/cook/package.rs +++ b/src/cook/package.rs @@ -36,6 +36,7 @@ pub fn package( } let package_file = target_dir.join("stage.pkgar"); + let package_meta = target_dir.join("stage.toml"); // Rebuild package if stage is newer //TODO: rebuild on recipe changes if package_file.is_file() { @@ -47,6 +48,7 @@ pub fn package( package_file.display() ); remove_all(&package_file)?; + remove_all(&package_meta)?; } } if !package_file.is_file() { @@ -56,7 +58,9 @@ pub fn package( stage_dir.to_str().unwrap(), ) .map_err(|err| format!("failed to create pkgar archive: {:?}", err))?; + } + if !package_meta.is_file() { package_toml(target_dir, name, recipe, auto_deps)?; }