oliver 0.1.8

Lightweight CLI mod manager for Baldur's Gate 3 on Linux
Documentation
use crate::Settings;
use anyhow::Result;
use larian_formats::bg3::{raw::Save, ModuleInfo};
use std::collections::{HashMap, HashSet};

impl Settings {
    pub(crate) fn get_installed_mod_info_helper(&self) -> Result<ModSet> {
        let parsed_settings = self.read_current_installed_mods()?;
        let installed_mod_info = get_installed_mod_info(&parsed_settings)?;

        Ok(ModSet {
            inner: installed_mod_info.collect(),
        })
    }

    pub(crate) fn get_load_order_helper(&self) -> Result<LoadOrder> {
        let parsed_settings = self.read_current_installed_mods()?;
        let installed_mod_info = get_installed_mod_info(&parsed_settings)?;
        let ordered_uuids = get_ordered_uuids(&parsed_settings)?;

        get_ordered_mods(ordered_uuids, installed_mod_info)
    }
}

#[derive(Debug, Clone)]
pub struct ModSet {
    inner: HashSet<ModuleInfo>,
}

impl ModSet {
    pub fn iter(&self) -> impl Iterator<Item = &ModuleInfo> {
        self.inner.iter()
    }
}

#[derive(Debug, Clone)]
pub struct LoadOrder {
    ordered_mods: Vec<ModuleInfo>,
    extras: ModSet,
}

impl LoadOrder {
    pub fn ordered(&self) -> impl Iterator<Item = &ModuleInfo> {
        self.ordered_mods.iter()
    }

    pub fn has_extras(&self) -> bool {
        !self.extras.inner.is_empty()
    }

    pub fn extras(&self) -> impl Iterator<Item = &ModuleInfo> {
        self.extras.iter()
    }

    pub fn order_len(&self) -> usize {
        self.ordered_mods.len()
    }
}

fn get_installed_mod_info(parsed_settings: &Save) -> Result<impl Iterator<Item = ModuleInfo> + '_> {
    Ok(parsed_settings
        .find_node_by_id("Mods")?
        .children
        .first()
        .into_iter()
        .flat_map(|c| {
            c.node
                .iter()
                .cloned()
                .filter_map(|n| ModuleInfo::try_from(n).ok())
        }))
}

fn get_ordered_uuids(parsed_settings: &Save) -> Result<impl Iterator<Item = &str> + '_> {
    Ok(parsed_settings
        .find_node_by_id("ModOrder")?
        .children
        .first()
        .into_iter()
        .flat_map(|c| {
            c.node
                .iter()
                .filter_map(|n| n.get_attribute_value_where_id(|id| id == "UUID"))
        }))
}

fn get_ordered_mods<'a>(
    ordered_uuids: impl Iterator<Item = &'a str>,
    installed_mod_info: impl Iterator<Item = ModuleInfo>,
) -> Result<LoadOrder> {
    let mut uuid_indexed_mods: HashMap<_, _> = installed_mod_info
        .map(|mod_info| (mod_info.uuid.clone(), mod_info))
        .collect();
    let ordered_mods = ordered_uuids
        .filter_map(|uuid| uuid_indexed_mods.remove(uuid))
        .collect();
    let extras = ModSet {
        inner: uuid_indexed_mods.into_values().collect(),
    };

    Ok(LoadOrder {
        ordered_mods,
        extras,
    })
}