Initial release: full Arma 3 mod management toolchain
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>
This commit is contained in:
109
arma_modlist_tools/config.py
Normal file
109
arma_modlist_tools/config.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user