putter 0.1.0

A tool to put files in the right place
Documentation
use clap::Parser;
use clap_verbosity_flag::{Verbosity, WarnLevel};
use env_logger::Target;
use putter::{FilesState, Manifest};
use std::path::{Path, PathBuf};

#[derive(Debug, clap::Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
    #[command(flatten)]
    verbose: Verbosity<WarnLevel>,
    #[command(subcommand)]
    command: Option<Command>,
}

#[derive(Debug, clap::Subcommand)]
enum Command {
    Apply {
        /// Path to manifest in JSON format.
        manifest_file: PathBuf,
        /// Path to state file.
        #[arg(long)]
        state_file: PathBuf,
        /// Perform a trial run, without making any changes.
        #[arg(short = 'n', long, default_value_t = false)]
        dry_run: bool,
    },
    Check {
        /// Path to manifest in JSON format.
        manifest_file: PathBuf,
        /// Path to state file.
        #[arg(long)]
        state_file: PathBuf,
    },
}

fn apply(manifest_file: PathBuf, state_file: PathBuf, dry_run: bool) -> anyhow::Result<()> {
    let manifest = Manifest::load(manifest_file)?;
    let state = if state_file.try_exists()? {
        FilesState::load(&state_file)?
    } else {
        FilesState::EMPTY
    };

    let manifest = manifest.into_manifest_low()?;

    if dry_run {
        putter::apply_dry_run(&state, &manifest)?;
    } else {
        let state = putter::apply(&state, &manifest)?;
        state.save(state_file)?;
    }

    Ok(())
}

fn check(manifest_file: PathBuf, state_file: &Path) -> anyhow::Result<()> {
    let manifest = Manifest::load(manifest_file)?;

    let state = if state_file.try_exists()? {
        FilesState::load(state_file)?
    } else {
        FilesState::EMPTY
    };

    let manifest = manifest.into_manifest_low()?;

    putter::check(&state, &manifest)?;

    Ok(())
}

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

    env_logger::Builder::new()
        .filter_level(cli.verbose.log_level_filter())
        .target(Target::Stdout)
        .format_level(false)
        .format_target(false)
        .init();

    match cli.command {
        Some(Command::Apply {
            manifest_file,
            state_file,
            dry_run,
        }) => apply(manifest_file, state_file, dry_run),
        Some(Command::Check {
            manifest_file,
            state_file,
        }) => check(manifest_file, &state_file),
        None => Ok(()),
    }
}