mcmodbuild 0.1.1

Format for describing how mods should be built and automatically building them from a file.
Documentation
mod binary;
mod cli;
mod installer;
mod macros;
mod structs;

use std::{
    env, fs,
    path::{Path, PathBuf},
    process,
};

use anyhow::{anyhow, Result};
use binary::deserialize;
use clap::Parser;
use colored::Colorize;
use installer::Installer;
use oxfmt::Serializable;
use structs::ModBuild;

use crate::cli::{McModBuild, Subcommands};

fn main() {
    if let Err(err) = __main() {
        eprintln!("{}", err.to_string().red().bold());
        process::exit(1);
    }
}

fn __main() -> Result<()> {
    let cli = McModBuild::parse();

    match cli.subcommand {
        Subcommands::Build { file, destination } => build(file, destination),
        Subcommands::Install { file, destination } => install(file, destination),
        Subcommands::Init { destination } => {
            init(destination).map_err(|err| anyhow!("Failed to create files: {err}"))
        }
    }
}

fn init(destination: Option<PathBuf>) -> Result<()> {
    let destination = destination.unwrap_or(env::current_dir()?);

    fs::write(
        destination.join("mymod.yml"),
        serde_yml::to_string(&ModBuild::default())?,
    )?;

    println!(
        "{}{}",
        "Successfully created build file at ".green(),
        destination.to_string_lossy().bright_green().bold()
    );

    Ok(())
}

fn build(file: PathBuf, destination: Option<PathBuf>) -> Result<()> {
    let content = fs::read_to_string(file)?;
    let build: ModBuild = serde_yml::from_str(&content)?;

    let id = build.id.clone();
    let name = format!("{id}.mcmodbuild");
    let path: PathBuf = destination.unwrap_or(PathBuf::from(&name));

    fs::write(path, build.serialize()?)?;

    Ok(())
}

fn install(file: PathBuf, destination: Option<PathBuf>) -> Result<()> {
    let content = fs::read_to_string(file)?;
    let build = deserialize(content.as_bytes())?;

    let id = build.id.clone();
    let branch = build.branch.clone();
    let name = format!("{id}-{branch}.jar");
    let destination = destination.unwrap_or(Path::new(&name).to_path_buf());

    let installer = Installer::new(build)?;
    installer.install(destination)?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use crate::structs::{BuildType, ExcludePair, ExcludeType};

    use super::*;

    #[test]
    fn roundtrip_serialize_deserialize() {
        let build = ModBuild {
            id: "testmod".into(),
            name: "Test mod".into(),
            git: "https://repo.git".into(),
            branch: "1.21.7".into(),
            build: BuildType::Cmd,
            cmd: Some("./gradlew build".into()),
            out: "@/target/".into(),
            exclude: vec![
                ExcludePair {
                    type_name: ExcludeType::Ends,
                    value: "-source.jar".into(),
                },
                ExcludePair {
                    type_name: ExcludeType::Starts,
                    value: "dev-".into(),
                },
            ],
        };

        let serialized = build.clone().serialize().unwrap();
        println!("Serialized: {serialized:?}");
        let deserialized = deserialize(&serialized).unwrap();
        println!("Deserialized: {deserialized:?}");
        assert_eq!(build, deserialized);
    }
}