Covers: common commands, package vs CLI architecture, data flow, group naming convention, server index structure, junction/symlink rules (critical: never shutil.rmtree), check_names two-pass classification, Python 3.9 from __future__ requirement, and test suite structure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Common Commands
# Run all tests (no network required)
python test_suite.py
# Check Python version and dependencies
python check_deps.py
# Full pipeline (parse → compare → fetch → link)
python run.py
# Parse + compare only (no download, no linking)
python run.py --skip-fetch --skip-link
# Diagnose mod folder name / steam_id issues
python check_names.py
python check_names.py --fix --fix-ids
There is no build step, linter config, or package install beyond pip install -r requirements.txt.
Architecture
Package vs CLI layer
arma_modlist_tools/ is a pure library — no I/O side effects, no sys.exit, no print. All CLI scripts (run.py, fetch_mods.py, link_mods.py, etc.) sit at the project root and call into the package. New functionality goes in the package first, then a CLI script wraps it.
Data flow
modlist_html/*.html
└─ parser.parse_modlist_dir()
└─ compare.compare_presets()
└─ comparison.json ←─ source of truth for groups + mod identity
├─ fetcher.build_server_index() ←─ Caddy JSON API
│ └─ fetcher.find_mod_folder() (steam_id first, name fallback)
│ └─ downloads/{group}/@ModName/
│ └─ linker.link_group()
│ └─ arma_dir/@ModName (junction/symlink)
└─ reporter.build_missing_report() → missing_report.json
Group naming convention
"shared"— mods present in all compared presets"<preset_name>"— mods unique to one preset (key fromcomparison["unique"])
This group label is stored in missing_report.json per-mod so sync_missing.py knows where to place newly available mods without re-reading comparison.json.
Server index structure
build_server_index() returns:
{
"by_steam_id": {"450814997": "https://server/@cba_a3/"}, # primary lookup
"by_name": {"cbaa3": "https://server/@cba_a3/"}, # normalized fallback
"folders": [...] # raw Caddy listing
}
_normalize_name strips @, lowercases, removes all non-alphanumeric: "@CBA_A3" → "cbaa3". Used in both the index builder and every lookup.
Junction / symlink critical rules
Detection: os.path.islink() returns False for Windows junctions. Always use _is_junction() from linker.py, which checks st_file_attributes & 0x400 (FILE_ATTRIBUTE_REPARSE_POINT) on Windows.
Removal: Use os.rmdir() on Windows and os.unlink() on Linux. Never shutil.rmtree() — it follows the junction and deletes the target mod files.
Creation: cmd /c mklink /J <link> <target> on Windows, os.symlink() on Linux.
check_names.py classification (two-pass)
Pass 1 collects raw (server_name, local_steam_id) for every disk folder.
Pass 2 builds ok_disk_names — the set of disk names that already match the server exactly. Any MISMATCH whose proposed server name is in ok_disk_names is reclassified as ID_COLLISION (the local meta.cpp has a wrong publishedid that belongs to a different mod). This prevents false rename suggestions caused by shared/duplicate steam IDs on the server.
--fix-ids corrects meta.cpp using steam IDs from comparison.json (sourced from Steam Workshop URLs in the HTML presets) as the authoritative source.
Python Version Compatibility
Minimum is Python 3.9. All files that use X | Y union type annotations must have from __future__ import annotations as the first import. Without it, the | syntax raises TypeError at runtime on Python < 3.10. Every module in arma_modlist_tools/ already has it; any new CLI script you add must include it too.
Test Suite
test_suite.py uses a custom harness (no pytest/unittest dependency). Structure:
group("section name") # prints header
test("description", callable) # runs fn, catches exceptions, tracks pass/fail
skip("description", "reason") # marks skipped
Tests that exercise the linker use tempfile.TemporaryDirectory() — never the real arma_dir. Tests that would require network calls mock list_mod_files with unittest.mock.patch.
Key Files Not in Git
config.json— credentials + paths (copy fromconfig.template.json)downloads/— downloaded mod files, can be several GBmodlist_json/— generated JSON output
The .html preset files in modlist_html/ are tracked as example inputs.