""" arma_modlist_tools.compare ~~~~~~~~~~~~~~~~~~~~~~~~~~ Compare two or more Arma 3 mod presets (parsed by :mod:`arma_modlist_tools.parser`) and produce a breakdown of shared and preset-unique mods. Typical usage:: from arma_modlist_tools.parser import parse_modlist_dir from arma_modlist_tools.compare import compare_presets presets = parse_modlist_dir("modlist_html") result = compare_presets(*presets) """ from __future__ import annotations def _mod_key(mod: dict) -> str: """Return the identity key for a mod. Uses ``steam_id`` when available (canonical Workshop identifier), falls back to ``name`` for local mods that have no Workshop ID. """ return mod["steam_id"] or mod["name"] def compare_presets(*presets: dict) -> dict: """ Compare two or more preset dicts and return a comparison dict. :param presets: Two or more preset dicts as returned by :func:`~arma_modlist_tools.parser.parse_modlist_html`. :returns: Dict with keys: - ``compared_presets`` — list of preset names that were compared - ``shared`` — mods present in **every** preset - ``mod_count`` — number of shared mods - ``mods`` — list of mod entry dicts - ``unique`` — per-preset mods not present in any other preset - keyed by ``preset_name`` - each value has ``mod_count`` and ``mods`` :raises ValueError: If fewer than two presets are provided. """ if len(presets) < 2: raise ValueError("compare_presets requires at least two presets") # Build per-preset {identity_key -> mod_entry} mappings preset_maps: list[dict[str, dict]] = [ {_mod_key(mod): mod for mod in preset["mods"]} for preset in presets ] # Shared keys = intersection across ALL presets shared_keys: set[str] = set(preset_maps[0].keys()) for pm in preset_maps[1:]: shared_keys &= pm.keys() # Shared mods: take entries from the first preset (identical across all) shared_mods = [preset_maps[0][k] for k in preset_maps[0] if k in shared_keys] # Unique mods per preset: entries whose key is not in the shared set unique: dict[str, dict] = {} for preset, pm in zip(presets, preset_maps): unique_mods = [mod for k, mod in pm.items() if k not in shared_keys] unique[preset["preset_name"]] = { "mod_count": len(unique_mods), "mods": unique_mods, } return { "compared_presets": [p["preset_name"] for p in presets], "shared": { "mod_count": len(shared_mods), "mods": shared_mods, }, "unique": unique, }