oliver 0.9.1

Lightweight CLI mod development tool for Baldur's Gate 3 on Linux
Documentation
use anyhow::Result;
use clap::{Parser, Subcommand};
use oliver::{DataFileType, convert, convert_all, pack, parse, unpack};
use std::{path::PathBuf, process::ExitCode};

#[derive(Debug, Parser)]
#[command(arg_required_else_help(true), author, version, about)]
struct Cli {
    #[clap(subcommand)]
    command: Command,
}

#[derive(Debug, Subcommand)]
enum Command {
    /// Print the list of file paths contained in a mod.
    #[cfg(unix)]
    Contents(Contents),

    /// Convert a single file from one type to another.
    Convert(Convert),

    /// Convert batches of files from one type to another.
    ConvertAll(ConvertAll),

    /// Pack loose mod files into an archive loadable by the game.
    Pack(Pack),

    /// Print a summary of the contents in a given file.
    Parse(Parse),

    /// Unpacks all of the files contained in a mod.
    Unpack(Unpack),
}

#[derive(Debug, Parser)]
#[command(arg_required_else_help(true))]
struct Contents {
    /// Path to the mod file to print the contents of.
    path: PathBuf,
}

#[derive(Debug, Parser)]
#[command(arg_required_else_help(true))]
struct Convert {
    /// Path to the file to convert from.
    ///
    /// The file must be either an LSF file with the extension `.lsf` or an XML file with the
    /// extension `.lsx`. The output file produced will be the other format (i.e. an XML file if
    /// the input file is an LSF or vice-versa.)
    #[clap(short, long, required = true)]
    from: PathBuf,

    /// Path to the output file to produce.
    #[clap(short, long, required = true)]
    to: PathBuf,
}

#[derive(Debug, Parser)]
#[command(arg_required_else_help(true))]
struct ConvertAll {
    /// Path to a directory containing files to convert.
    ///
    /// All files of the type specified by `from` under this path (either directly or recursively)
    /// will be converted to the type specified by `to`. The files will be created in the same
    /// directory as the ones they're being converted from with the same prefix and an extension
    /// matching the type they were converted to.
    #[clap(short, long, required = true)]
    path: PathBuf,

    /// The type of file to convert from.
    #[clap(short, long, required = true)]
    from: DataFileType,

    /// The type of file to convert to.
    #[clap(short, long, required = true)]
    to: DataFileType,
}

#[derive(Debug, Parser)]
#[command(arg_required_else_help(true))]
struct Parse {
    /// Paths to the files to summarize.
    ///
    /// Each file must be either a .pak format in the LSPK format or a .lsf file.
    paths: Vec<PathBuf>,

    /// Print extra information.
    #[clap(long, short)]
    verbose: bool,
}

#[derive(Debug, Parser)]
#[command(arg_required_else_help(true))]
struct Pack {
    /// Path to the directory containing loose mod files to pack.
    #[clap()]
    mod_files_root: PathBuf,

    /// The output file to pack the mod into.
    #[clap(long, short)]
    destination: Option<PathBuf>,
}

#[derive(Debug, Parser)]
#[command(arg_required_else_help(true))]
struct Unpack {
    /// The mod file to unpack.
    #[clap()]
    mod_file_path: PathBuf,

    /// The directory to unpack the mod into.
    #[clap(long, short)]
    destination: Option<PathBuf>,
}

fn run() -> Result<()> {
    match Cli::parse().command {
        Command::Parse(Parse { paths, verbose }) => parse(paths, verbose),
        Command::Pack(Pack {
            mod_files_root,
            destination,
        }) => pack(mod_files_root, destination),
        Command::Unpack(Unpack {
            mod_file_path,
            destination,
        }) => unpack(&mod_file_path, destination),
        #[cfg(unix)]
        Command::Contents(contents) => oliver::contents(&contents.path),
        Command::Convert(Convert { from, to }) => convert(&from, &to),
        Command::ConvertAll(ConvertAll { path, from, to }) => convert_all(path, from, to),
    }
}

fn main() -> ExitCode {
    match run() {
        Ok(()) => ExitCode::SUCCESS,
        Err(e) => {
            eprintln!("{e}");
            ExitCode::FAILURE
        }
    }
}