oliver 0.1.2

Lightweight CLI mod manager for Baldur's Gate 3 on Linux
use crate::common::read_current_installed_mods;
use anyhow::Result;
use std::collections::HashMap;

pub fn list(order: bool, app_data_path: &str) -> Result<()> {
    let parsed_settings = read_current_installed_mods(app_data_path)?;

    let all_mods = parsed_settings
        .find_node_by_id("Mods")?
        .children
        .first()
        .into_iter()
        .flat_map(|c| {
            c.node.iter().filter_map(|n| {
                let mut info = [""; 3];

                for attr in &n.attribute {
                    match attr.id.as_str() {
                        "Name" => info[0] = &attr.value,
                        "Folder" => info[1] = &attr.value,
                        "UUID" => info[2] = &attr.value,
                        _anything_else => {}
                    }
                }

                if info.iter().any(|s| s.is_empty()) {
                    None
                } else {
                    Some(info)
                }
            })
        });

    if order {
        let nodes = parsed_settings
            .find_node_by_id("ModOrder")?
            .children
            .first();

        let order_length = nodes.as_ref().map_or(0, |c| c.node.len());

        let ordered_uuids = nodes.into_iter().flat_map(|c| {
            c.node
                .iter()
                .filter_map(|n| n.attribute.first().map(|a| a.value.as_str()))
        });

        print_ordered_mods(order_length, ordered_uuids, all_mods);
    } else {
        print_unordered_mods(all_mods);
    }

    Ok(())
}

fn print_unordered_mods<'a>(mods: impl Iterator<Item = [&'a str; 3]>) {
    for [name, _, uuid] in mods {
        println!("{uuid}: {name}");
    }
}

fn print_ordered_mods<'a>(
    order_length: usize,
    ordered_uuids: impl Iterator<Item = &'a str>,
    all_mods: impl Iterator<Item = [&'a str; 3]>,
) {
    let mut uuid_indexed_mods: HashMap<_, _> =
        all_mods.map(|[name, _, uuid]| (uuid, name)).collect();

    let padding = order_length
        .checked_ilog10()
        .and_then(|i| usize::try_from(i + 1).ok())
        .unwrap_or_default();

    println!("Load order used by game:");

    for (i, uuid) in ordered_uuids.enumerate() {
        let name = uuid_indexed_mods.remove(uuid).unwrap_or("<mod not found>");

        println!("{i:>padding$}. {uuid}: {name}");
    }

    if uuid_indexed_mods.is_empty() {
        return;
    }

    println!("\nMods not present in load order:");

    for (uuid, name) in uuid_indexed_mods {
        println!("?. {uuid} {name}");
    }
}