cari 0.3.1

Simple package manager for minecraft servers
use crate::{config::Server, modrinth};
use color_eyre::eyre::{OptionExt, Result};
use futures::stream::{self, StreamExt, TryStreamExt};
use heck::ToTitleCase;
use yansi::Paint;

struct Add {
    id: String,
    version: String,
}

async fn run_item(server: &Server, id: &str) -> Result<Option<Add>> {
    let cfg = &server.cfg;

    if cfg.content.iter().any(|item| item.id == id) {
        println!(
            " {} {} {}",
            "".yellow(),
            id.blue().dim(),
            "(already added)".dim()
        );
        return Ok(None);
    }

    let latest_version =
        modrinth::get_latest_project_version(id, &cfg.loader, &cfg.game_version).await?;

    println!(
        " {} {} {}",
        "+".green(),
        id.blue().dim(),
        latest_version.name.green()
    );

    Ok(Some(Add {
        id: id.to_string(),
        version: latest_version.id,
    }))
}

fn apply_adds(server: &Server, adds: &[Add]) -> Result<()> {
    let path = server.path.join("cari.json");
    let raw = std::fs::read_to_string(&path)?;
    let mut value: serde_json::Value = serde_json::from_str(&raw)?;

    let content = value
        .get_mut("content")
        .and_then(|c| c.as_array_mut())
        .ok_or_eyre("cari.json missing content array")?;

    for add in adds {
        content.push(serde_json::json!({
            "id": add.id,
            "version": add.version,
        }));
    }

    std::fs::write(
        &path,
        format!("{}\n", serde_json::to_string_pretty(&value)?),
    )?;

    Ok(())
}

pub async fn run(servers: Vec<Server>, projects: Vec<String>) -> Result<()> {
    for server in servers {
        println!(
            "   {}",
            server.name.to_title_case().blue().bold().underline(),
        );

        let adds = stream::iter(&projects)
            .map(|project| run_item(&server, project))
            .buffer_unordered(8)
            .try_collect::<Vec<_>>()
            .await?
            .into_iter()
            .flatten()
            .collect::<Vec<_>>();

        if !adds.is_empty() {
            apply_adds(&server, &adds)?;
        }
    }

    Ok(())
}