Files
arma-modlist-tools/CLAUDE.md
revernomad17 57895a04d3 Add GUI desktop application
- Add gui/ package: CustomTkinter app with dashboard, mods, tools, logs,
  and settings views; first-run SetupWizard for config.json generation
- Add gui.py root entry point (calls gui.run_app())
- Add selection.json for GUI selection state persistence
- Add customtkinter to requirements.txt
- Fix link_mods.py minor issues surfaced during GUI integration
- Add modlist_html/Test_Preset_A.html and Test_Preset_B.html as example inputs
- Update README.md: add GUI prerequisites, gui.py script section, gui/ folder
  structure, customtkinter to prerequisites table
- Update CLAUDE.md: add python gui.py to common commands, document GUI package
  architecture and views

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 15:33:58 +07:00

6.0 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

# Launch the GUI
python gui.py

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 from comparison["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.

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.

GUI package

gui/ is a CustomTkinter desktop application wrapping the CLI toolchain. Entry point is gui.py at the project root, which calls gui.run_app().

Key files:

  • gui/__init__.py — sets dark theme + blue color scheme; exports run_app()
  • gui/app.pyArmaModManagerApp main window; manages view routing, config loading, thread-safe log queue, and background pipeline execution
  • gui/wizard.pySetupWizard dialog shown on first launch when no config.json exists
  • gui/_constants.py — window dimensions, status color constants, file paths
  • gui/_io.py_QueueWriter redirects stdout/stderr to a thread-safe queue so pipeline output streams into the Logs view

Views (gui/views/): each inherits BaseView; build() runs once on creation, refresh() runs on each navigation:

  • dashboard.py — overview, status, quick stats
  • mods.py — browse and manage downloaded mods by group
  • tools.py — link/unlink, rename folders, sync missing mods, check server
  • logs.py — real-time log viewer fed from the stdout/stderr queue
  • settings.py — in-app editor for config.json (server URL, paths, credentials)

selection.json — GUI selection state file, tracked in git. Persists which mods/groups are selected between GUI sessions. Written by the GUI; safe to delete (GUI recreates it on next save).

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 from config.template.json)
  • downloads/ — downloaded mod files, can be several GB
  • modlist_json/ — generated JSON output

The .html preset files in modlist_html/ are tracked as example inputs.