creeper 1.0.0-alpha.5

Minecraft Package Manager
use clap::Parser;
use colored::Colorize;
use tracing::error;
use url::Url;

use crate::{
    Id, Install, Package,
    cmd::Execute,
    fabric::FabricMod,
    pack::{PackMeta, PackNode},
    util::{parse_or_prompt, prompt_save},
    zip::extract_zip,
};

#[derive(Clone, Debug, Parser)]
pub struct PackageFabricMod {
    pub url: Url,
}

impl Execute for PackageFabricMod {
    async fn execute(self, lib: &crate::Creeper) -> anyhow::Result<()> {
        let art = lib
            .download(self.url.to_string(), self.url.to_string(), None, None)
            .await?;

        let jar = lib.retrieve_artifact(&art).await?;

        let json = extract_zip(jar, "fabric.mod.json").await?;

        let metadata = serde_json::from_str::<FabricMod>(&json)?;

        let id = parse_or_prompt::<Id>(&metadata.id, "package id").await?;

        let license = match metadata.license {
            Some(x) if let Ok(x) = x.parse() => Some(x),
            Some(x) if let Ok(x) = format!("LicenseRef-{x}").parse() => Some(x),
            Some(x) => Some(parse_or_prompt(&x, "SPDX license expression").await?),
            None => None,
        };

        let mut node = PackNode::default();

        for (id, dep) in metadata.depends {
            let id = match id.parse() {
                Ok(id) => id,
                Err(_) => parse_or_prompt(&id, "package id").await?,
            };

            let req = dep.prompt_normalize().await?;

            node.dep.insert(id, req);
        }

        for (id, dep) in metadata.breaks {
            let id = match id.parse() {
                Ok(id) => id,
                Err(_) => parse_or_prompt(&id, "package id").await?,
            };

            let req = dep.prompt_normalize().await?;

            node.conflict.insert(id, req);
        }

        if !metadata.recommends.is_empty() {
            error!("does not support recommended dependencies in fabric.mod.json");
        }

        if !metadata.suggests.is_empty() {
            error!("does not support suggested dependencies in fabric.mod.json");
        }

        if !metadata.conflicts.is_empty() {
            error!("does not support conflict dependencies in fabric.mod.json");
        }

        if !metadata.provides.is_empty() {
            error!("does not support provided dependencies in fabric.mod.json");
        }

        let pack = Package {
            id,
            version: metadata.version,
            rev: 0,
            node,
            meta: PackMeta {
                name: metadata.name.unwrap_or(metadata.id),
                authors: metadata.authors.into_iter().map(|x| x.name()).collect(),
                desc: metadata.description,
                license,
            },
            install: Install {
                mc_mod: vec![art],
                ..Default::default()
            },
        };

        let toml = toml::to_string(&pack)?;

        eprintln!("{} {}@{}", "Packaged".bold().green(), pack.id, pack.version);

        println!("{toml}");

        let path = pack
            .id
            .indexed_path()
            .as_ref()
            .join(pack.version.to_string())
            .join("0.toml");

        prompt_save(toml, path).await?;

        Ok(())
    }
}