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>
110 lines
3.2 KiB
Python
110 lines
3.2 KiB
Python
"""
|
|
arma_modlist_tools.config
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
Load and expose project configuration from ``config.json``.
|
|
|
|
Search order for the config file:
|
|
1. Explicit path passed to :func:`load_config`
|
|
2. ``config.json`` in the current working directory
|
|
3. ``config.json`` two levels above this module (project root)
|
|
|
|
Typical usage::
|
|
|
|
from arma_modlist_tools.config import load_config
|
|
|
|
cfg = load_config()
|
|
print(cfg.server_url)
|
|
print(cfg.arma_dir)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
class Config:
|
|
"""Typed wrapper around the parsed ``config.json`` dict."""
|
|
|
|
def __init__(self, data: dict) -> None:
|
|
# Validate required keys immediately so callers get a clear error at
|
|
# load time rather than a confusing AttributeError deep in the pipeline.
|
|
_ = data["server"]["base_url"]
|
|
_ = data["server"]["username"]
|
|
_ = data["server"]["password"]
|
|
_ = data["paths"]["arma_dir"]
|
|
_ = data["paths"]["downloads"]
|
|
_ = data["paths"]["modlist_html"]
|
|
_ = data["paths"]["modlist_json"]
|
|
self._data = data
|
|
|
|
# ---- server ----
|
|
|
|
@property
|
|
def server_url(self) -> str:
|
|
return self._data["server"]["base_url"]
|
|
|
|
@property
|
|
def server_auth(self) -> tuple[str, str]:
|
|
return (self._data["server"]["username"], self._data["server"]["password"])
|
|
|
|
# ---- paths ----
|
|
|
|
@property
|
|
def arma_dir(self) -> Path:
|
|
return Path(self._data["paths"]["arma_dir"])
|
|
|
|
@property
|
|
def downloads(self) -> Path:
|
|
return Path(self._data["paths"]["downloads"])
|
|
|
|
@property
|
|
def modlist_html(self) -> Path:
|
|
return Path(self._data["paths"]["modlist_html"])
|
|
|
|
@property
|
|
def modlist_json(self) -> Path:
|
|
return Path(self._data["paths"]["modlist_json"])
|
|
|
|
# ---- derived paths ----
|
|
|
|
@property
|
|
def comparison(self) -> Path:
|
|
return self.modlist_json / "comparison.json"
|
|
|
|
@property
|
|
def missing_report(self) -> Path:
|
|
return self.modlist_json / "missing_report.json"
|
|
|
|
|
|
def load_config(path: Path | str | None = None) -> Config:
|
|
"""
|
|
Load ``config.json`` and return a :class:`Config` instance.
|
|
|
|
:param path: Explicit path to the config file. If ``None``, the function
|
|
searches the current working directory then the project root.
|
|
:raises FileNotFoundError: If no config file can be located.
|
|
:raises KeyError: If required keys are absent from the config file.
|
|
"""
|
|
if path is not None:
|
|
config_path = Path(path)
|
|
else:
|
|
# Try CWD first, then project root (two levels above this file)
|
|
cwd_path = Path.cwd() / "config.json"
|
|
root_path = Path(__file__).parent.parent / "config.json"
|
|
if cwd_path.exists():
|
|
config_path = cwd_path
|
|
elif root_path.exists():
|
|
config_path = root_path
|
|
else:
|
|
raise FileNotFoundError(
|
|
"config.json not found. "
|
|
f"Looked in:\n {cwd_path}\n {root_path}\n"
|
|
"Create config.json in the project root (copy from the template)."
|
|
)
|
|
|
|
with open(config_path, encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
return Config(data)
|