Pipeline: parse HTML presets, compare modlists, download from Caddy file server, create junctions/symlinks to Arma 3 Server directory. Includes update/sync flows, missing-mod reporting, OS compat layer, shared config, dep checker, comprehensive test suite (71 tests). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
80 lines
2.7 KiB
Python
80 lines
2.7 KiB
Python
"""
|
|
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,
|
|
}
|