compi 0.5.0

A build system written in Rust.
use clap::Parser;
use std::process;

mod cache;
mod cli;
mod error;
mod execution;
mod output;
mod task;
mod util;

use cache::{load_cache, save_cache};
use cli::Cli;
use error::Result;
use execution::TaskRunner;
use output::OutputMode;
use task::{get_required_tasks, load_tasks, show_task_relationships, sort_topologically};

#[tokio::main]
async fn main() -> Result<()> {
    let args = Cli::parse();

    match run_compi(args).await {
        Ok(()) => Ok(()),
        Err(e) => {
            eprintln!("Error: {}", e);
            process::exit(1);
        }
    }
}

async fn run_compi(args: Cli) -> Result<()> {
    let config = load_tasks(&args.file)?;
    let mut tasks = config.tasks;

    show_task_relationships(&tasks, args.verbose);

    let task_list = match &args.task {
        Some(task_id) => get_required_tasks(&tasks, task_id)?,
        None => {
            if let Some(default) = &config.default_task {
                get_required_tasks(&tasks, default)?
            } else {
                sort_topologically(&tasks)
            }
        }
    };

    tasks.retain(|task| task_list.contains(&task.id));

    if args.verbose {
        println!("Task execution order: {}", task_list.join(" -> "));
    }

    if args.dry_run {
        println!("Dry run mode - showing what would be executed:");
        for task_id in &task_list {
            if let Some(task) = tasks.iter().find(|t| t.id == *task_id) {
                println!("  {} would run: {}", task.id, task.command);
            }
        }
        return Ok(());
    }

    let workers = args.workers.or(config.workers);
    let default_timeout = args.timeout.or(config.default_timeout);
    let output_mode = args
        .output
        .clone()
        .or(config.output.clone())
        .unwrap_or(OutputMode::Group);

    let mut cache = load_cache(config.cache_dir.as_deref(), &args.file);
    let mut runner = TaskRunner::new(
        &tasks,
        &mut cache,
        args.rm,
        args.verbose,
        default_timeout,
        workers,
        args.continue_on_failure,
        output_mode,
    );
    let cache_changed = runner.run_tasks(&task_list).await;

    if cache_changed {
        save_cache(&cache, config.cache_dir.as_deref(), &args.file);
    } else if args.verbose {
        println!("No changes detected, cache not saved.");
    }

    Ok(())
}