vibe-action 0.1.4

Command router — execute shell commands and LLM prompts via simple YAML actions.
//! Benchmark runner.
//! Executes benchmark cases from benchmarks.yaml and prints results.

use clap::Args;
use std::time::Instant;

use crate::{bench::runner::Bench, output::output::OutputKind, print_template, print_text, utils};

#[derive(Args)]
pub struct BenchArgs {
    /// Run benchmarks for a specific action only
    #[arg(long, short = 'a')]
    action: Option<String>,

    /// Show command output
    #[arg(long, short = 'v')]
    verbose: bool,
}

/// Execute all benchmarks.
pub async fn execute(args: BenchArgs) {
    let bench = match Bench::load() {
        Ok(b) => b,
        Err(e) => {
            print_text!(OutputKind::Error, "{}", e);
            std::process::exit(1);
        }
    };

    let actions: Vec<_> = if let Some(ref filter) = args.action {
        bench
            .config
            .benchmarks
            .iter()
            .filter(|(name, _)| *name == filter)
            .collect()
    } else {
        bench.config.benchmarks.iter().collect()
    };

    if actions.is_empty() {
        print_text!(OutputKind::Info, "No benchmark found");
        return;
    }

    let mut passed = 0;
    let mut failed = 0;
    let mut index = 0;
    let total: usize = actions.iter().map(|(_, cases)| cases.len()).sum();
    let start_time = Instant::now();

    print_template!(
        OutputKind::Info,
        "Start execute task benchmarks ({total} cases)",
        "total" => total
    );

    for (action, cases) in &actions {
        print_template!(
            OutputKind::Info,
            "Start {action}...",
            "action" => action
        );

        for case in cases.iter() {
            index += 1;
            let start = Instant::now();

            match bench.run(action, case).await {
                Ok(output) => {
                    passed += 1;
                    let duration = start.elapsed();
                    let args_str = case
                        .args
                        .iter()
                        .map(|(_, v)| v.split_whitespace().collect::<Vec<_>>().join(" "))
                        .collect::<Vec<_>>()
                        .join(", ");

                    let args_display = if args_str.is_empty() {
                        "(no args)".to_string()
                    } else if args_str.chars().count() > 100 {
                        let mut take_chars = args_str.chars().take(100).collect::<String>();
                        take_chars.push_str("...");
                        take_chars
                    } else {
                        args_str
                    };

                    if args.verbose {
                        print_template!(
                            OutputKind::Progress,
                            "{percent}% ({current}/{total}) | {duration} | {action} {args_display}",
                            "percent" => format!("{:.0}", (index as f32 / total as f32) * 100.0),
                            "current" => index.to_string(),
                            "total" => total.to_string(),
                            "duration" => utils::format::format_duration(duration),
                            "action" => action,
                            "args_display" => args_display
                        );

                        if output.is_empty() {
                            print_text!(OutputKind::Info, "No matches found.");
                        } else {
                            print_template!(
                                OutputKind::Success,
                                "{output}",
                                "output" => output
                            );
                        }
                    } else {
                        // Стандартный плоский вывод прогресса
                        print_template!(
                            OutputKind::Progress,
                            "{percent}% ({current}/{total}) | {duration} | {args_display}",
                            "percent" => format!("{:.0}", (index as f32 / total as f32) * 100.0),
                            "current" => index.to_string(),
                            "total" => total.to_string(),
                            "duration" => utils::format::format_duration(duration),
                            "args_display" => args_display
                        );
                    }
                }
                Err(e) => {
                    failed += 1;
                    let duration = start.elapsed();
                    print_template!(
                        OutputKind::Progress,
                        "{current}/{total} | {duration} | FAIL: {error}",
                        "current" => index.to_string(),
                        "total" => total.to_string(),
                        "duration" => utils::format::format_duration(duration),
                        "error" => e.to_string()
                    );
                }
            }
        }
    }

    // Исправлено: точные строковые плейсхолдеры для итогового лога
    print_template!(
        OutputKind::Info,
        "{passed} passed, {failed} failed in {duration}",
        "passed" => passed.to_string(),
        "failed" => failed.to_string(),
        "duration" => utils::format::format_duration(start_time.elapsed())
    );
}