pkgs-cli 0.2.0

A simple cli tool to manage packages.
Documentation
use std::io::Stdout;

use anyhow::Result;
use clap::Parser;
use schemars::schema_for;

use pkgs::cli::{Cli, Command};
use pkgs::config::Config;
use pkgs::logger::WriterOutput;
use pkgs::meta::TRACE_FILE;
use pkgs::trace::Trace;

type Runner = pkgs::runner::Runner<WriterOutput<Stdout>>;

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

    if let Command::Schema = &cli.command {
        let schema = schema_for!(Config);
        println!("{}", serde_json::to_string_pretty(&schema)?);
        return Ok(());
    };

    let cwd = std::env::current_dir()?;
    let stdout = WriterOutput::new(std::io::stdout());
    let runner = Runner::new(&cwd, stdout);

    let config = runner.read_config()?;
    let available = config.packages.keys();

    match &cli.command {
        Command::Load { modules } => load(&config, modules.get(available)?, runner),
        Command::Unload { modules } => unload(modules.get(available)?, runner),
        Command::List { oneline } => {
            println!(
                "{}",
                available
                    .into_iter()
                    .cloned()
                    .collect::<Vec<_>>()
                    .join(if *oneline { "\n" } else { " " })
            );
            Ok(())
        }
        Command::Schema => unreachable!(),
    }
}

fn load(config: &Config, modules: Vec<String>, mut runner: Runner) -> Result<()> {
    let pkgs_dir = runner.create_pkgs_dir()?;

    let trace_file = pkgs_dir.join(TRACE_FILE);
    let mut trace = if trace_file.exists() {
        Trace::read_from_file(&trace_file)?
    } else {
        Trace::default()
    };

    for name in modules {
        let pkg_trace = trace.packages.get(&name);
        let package = config.get(&name)?;

        match runner.load_module(&package, pkg_trace) {
            Ok(pkg_trace) => {
                println!("Loaded package: {name}");
                trace.packages.insert(name.clone(), pkg_trace);
            }
            Err(e) => {
                eprintln!("{e}");
                runner.rollback()?;
            }
        }
    }

    trace.write_to_file(&trace_file)?;

    Ok(())
}

fn unload(modules: Vec<String>, mut runner: Runner) -> Result<()> {
    let pkgs_dir = runner.get_pkgs_dir()?;

    let trace_file = pkgs_dir.join(TRACE_FILE);
    let mut trace = if trace_file.exists() {
        Trace::read_from_file(&trace_file)?
    } else {
        Trace::default()
    };

    for name in modules {
        let Some(pkg_trace) = trace.packages.get(&name) else {
            eprintln!("Warning! Package '{name}' is not loaded.");
            continue;
        };

        match runner.unload_module(&name, pkg_trace) {
            Ok(()) => {
                println!("Unloaded package: {name}");
                trace.packages.remove(&name);
            }
            Err(e) => {
                eprintln!("{e}");
                runner.rollback()?;
            }
        }
    }

    trace.write_to_file(&trace_file)?;

    Ok(())
}