creeper 1.0.0-alpha.9

Minecraft Package Manager
use anyhow::anyhow;
use clap::Parser;
use colored::Colorize;
use tracing::{error, info};
use url::Url;

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

#[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) => Some(prompt_correct_license(x).await?),
            None => None,
        };

        let mut node = PackNode::default();

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

            let id = if id.as_str() == "fabricloader" {
                Id::fabric()
            } else {
                id
            };

            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 extract = creeper_cache_dir()?.join("extract");

        for jij in metadata.jars.into_iter().map(|x| x.file) {
            let file = jij.file_name().ok_or(anyhow!("invalid filename"))?;

            let path = extract.join(file);

            extract_zip_to(&jar, jij, &path).await?;

            if let Ok(json) = extract_zip(&path, "fabric.mod.json").await
                && let Ok(metadata) = serde_json::from_str::<FabricMod>(&json)
                && let Ok(id) = metadata.id.parse()
                && let Some(req) = node.dep.get(&id)
                && req.matches(&metadata.version)
            {
                info!(
                    "{id}@{} is packaged jar-in-jar, skipping from dependencies",
                    metadata.version
                );
                node.dep.remove(&id);
            }
        }

        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(())
    }
}