84 lines
2.5 KiB
Python
84 lines
2.5 KiB
Python
"""Fluffy-compatible modinfo.ini and screenshot discovery."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
IMAGE_EXTS = (".png", ".jpg", ".jpeg", ".webp")
|
|
|
|
|
|
@dataclass
|
|
class ModInfo:
|
|
name: str = ""
|
|
version: str = ""
|
|
author: str = ""
|
|
description: str = ""
|
|
category: str = ""
|
|
screenshot: str = "" # as written in the ini (relative)
|
|
nexus_mod_id: str = ""
|
|
|
|
|
|
def parse_modinfo(text: str) -> ModInfo:
|
|
"""Tolerant key=value parser; Fluffy files have no section header, but accept one."""
|
|
vals: dict[str, str] = {}
|
|
for line in text.splitlines():
|
|
line = line.strip()
|
|
if not line or line[0] in ";#[" or "=" not in line:
|
|
continue
|
|
k, _, v = line.partition("=")
|
|
vals[k.strip().lower()] = v.strip()
|
|
return ModInfo(
|
|
name=vals.get("name", ""),
|
|
version=vals.get("version", ""),
|
|
author=vals.get("author", ""),
|
|
description=vals.get("description", "").replace("\\n", "\n"),
|
|
category=vals.get("category", ""),
|
|
screenshot=vals.get("screenshot", ""),
|
|
nexus_mod_id=vals.get("nexusid", vals.get("nexus_mod_id", "")),
|
|
)
|
|
|
|
|
|
def _read_text(path: Path) -> str:
|
|
raw = path.read_bytes()
|
|
for enc in ("utf-8-sig", "utf-16", "latin-1"):
|
|
try:
|
|
return raw.decode(enc)
|
|
except UnicodeError:
|
|
continue
|
|
return raw.decode("utf-8", "replace")
|
|
|
|
|
|
def find_modinfo(root: Path) -> Path | None:
|
|
"""modinfo.ini at the root or one directory down."""
|
|
for base in [root, *sorted(p for p in root.iterdir() if p.is_dir())]:
|
|
for p in base.iterdir():
|
|
if p.is_file() and p.name.lower() == "modinfo.ini":
|
|
return p
|
|
return None
|
|
|
|
|
|
def read_modinfo(root: Path) -> tuple[ModInfo, Path | None]:
|
|
p = find_modinfo(root)
|
|
if p is None:
|
|
return ModInfo(), None
|
|
try:
|
|
return parse_modinfo(_read_text(p)), p.parent
|
|
except OSError:
|
|
return ModInfo(), None
|
|
|
|
|
|
def find_screenshot(base: Path, hint: str = "") -> Path | None:
|
|
"""Explicit `screenshot=` wins, else screenshot.png|jpg in the mod root (Fluffy convention)."""
|
|
if not base.is_dir():
|
|
return None
|
|
files = {p.name.lower(): p for p in base.iterdir() if p.is_file()}
|
|
if hint:
|
|
p = files.get(Path(hint).name.lower())
|
|
if p:
|
|
return p
|
|
for ext in IMAGE_EXTS:
|
|
p = files.get("screenshot" + ext)
|
|
if p:
|
|
return p
|
|
return None
|