elementary-row-operation-verifier 0.0.1

A tool to verify the correctness of elementary row operations on matrices
Documentation
use anyhow::{Context, Result};
use elementary_row_operation_verifier::cli::{Cli, Command};
use elementary_row_operation_verifier::parser::parse_file;
use elementary_row_operation_verifier::tui::TuiApp;
use elementary_row_operation_verifier::tui::check::CheckTui;
use elementary_row_operation_verifier::verifier::{VerificationResult, Verifier};
use std::fs;
use std::path::PathBuf;

fn main() -> Result<()> {
    let cli = Cli::parse();

    match &cli.command {
        Some(Command::Check { path }) => {
            run_check(path, &cli)?;
        }
        None => {
            let file = cli
                .file_path
                .as_ref()
                .context("No file specified. Usage: elementary-row-operation-verifier <FILE>")?;
            run_single(file, &cli)?;
        }
    }

    Ok(())
}

fn run_single(file: &str, cli: &Cli) -> Result<()> {
    let path = PathBuf::from(file);
    let _content = fs::read_to_string(&path)
        .with_context(|| format!("Failed to read file: {}", path.display()))?;
    let parsed = parse_file(&path)?;

    if cli.verbose {
        println!("File: {}", path.display());
        println!("Steps found: {}", parsed.steps.len());
        println!();
    }

    let verifier = Verifier::new();
    let results = verifier.verify_file(&parsed);

    if !cli.plain {
        // 始终启动 TUI
        let mut app = TuiApp::new(path, parsed, results);
        app.run()?;
    } else {
        print_results(&results, cli.verbose);
    }

    Ok(())
}

fn run_check(dir: &str, cli: &Cli) -> Result<()> {
    let dir_path = PathBuf::from(dir);
    if !dir_path.is_dir() {
        anyhow::bail!("'{}' is not a directory", dir_path.display());
    }
    if !cli.plain {
        let mut app = CheckTui::new(dir_path)?;
        app.run()?;
    } else {
        run_check_plain(dir, cli)?;
    }
    Ok(())
}

fn run_check_plain(dir: &str, cli: &Cli) -> Result<()> {
    let dir_path = PathBuf::from(dir);
    if !dir_path.is_dir() {
        anyhow::bail!("'{}' is not a directory", dir_path.display());
    }

    // collect all .lore files
    let mut lore_files: Vec<PathBuf> = Vec::new();
    for entry in fs::read_dir(&dir_path)
        .with_context(|| format!("Failed to read directory: {}", dir_path.display()))?
    {
        let entry = entry?;
        let path = entry.path();
        if path.extension().map_or(false, |ext| ext == "lore") {
            lore_files.push(path);
        }
    }

    lore_files.sort();

    if lore_files.is_empty() {
        println!("No .lore files found in '{}'", dir_path.display());
        return Ok(());
    }

    println!(
        "Checking {} .lore file(s) in '{}'",
        lore_files.len(),
        dir_path.display()
    );
    if cli.verbose {
        println!();
    }

    let verifier = Verifier::new();
    let mut total_correct: u32 = 0;
    let mut total_incorrect: u32 = 0;
    let mut total_errors: u32 = 0;
    let mut file_results: Vec<(&PathBuf, Vec<VerificationResult>)> = Vec::new();

    for file_path in &lore_files {
        let parsed = match parse_file(file_path) {
            Ok(p) => p,
            Err(e) => {
                eprintln!("{}: parse error - {}", file_path.display(), e);
                total_errors += 1;
                continue;
            }
        };

        let results = verifier.verify_file(&parsed);

        let (correct, incorrect, errors) = count_results(&results);
        total_correct += correct;
        total_incorrect += incorrect;
        total_errors += errors;

        if cli.verbose {
            let file_name = file_path.file_name().unwrap_or_default().to_string_lossy();
            println!(
                "  {}: {} correct, {} incorrect, {} errors",
                file_name, correct, incorrect, errors
            );
        }

        file_results.push((file_path, results));
    }

    // summary
    println!();
    println!("──────────────────────────────");
    println!(
        "  Total: {} correct, {} incorrect, {} errors",
        total_correct, total_incorrect, total_errors
    );

    if total_incorrect > 0 && !cli.plain && cli.verbose {
        println!();
        for (path, results) in &file_results {
            let has_issue = results
                .iter()
                .any(|r| !matches!(r, VerificationResult::Correct { .. }));
            if has_issue {
                println!(
                    "── {} ──",
                    path.file_name().unwrap_or_default().to_string_lossy()
                );
                print_results(results, true);
            }
        }
    } else if total_incorrect > 0 && !cli.plain {
        println!("  Run with -v to see details, or check individual files with TUI.");
    }

    Ok(())
}

fn count_results(results: &[VerificationResult]) -> (u32, u32, u32) {
    let mut correct = 0;
    let mut incorrect = 0;
    let mut errors = 0;
    for r in results {
        match r {
            VerificationResult::Correct { .. } => correct += 1,
            VerificationResult::Incorrect { .. } => incorrect += 1,
            VerificationResult::Error { .. } => errors += 1,
        }
    }
    (correct, incorrect, errors)
}

fn print_results(results: &[VerificationResult], verbose: bool) {
    for result in results {
        match result {
            VerificationResult::Correct { line, operation } => {
                if verbose {
                    println!("  ✓ Line {}: {}", line, operation);
                }
            }
            VerificationResult::Incorrect {
                line,
                operation,
                expected,
                got,
            } => {
                println!("  ✗ Line {}: {}", line, operation);
                println!("     Expected:\n{}", expected);
                println!("     Got:\n{}", got);
            }
            VerificationResult::Error {
                line,
                operation,
                message,
            } => {
                println!("  ⚠ Line {}: {} - {}", line, operation, message);
            }
        }
    }
}