diff --git a/basic_features/__init__.py b/basic_features/__init__.py index 66e9a76..64200b5 100644 --- a/basic_features/__init__.py +++ b/basic_features/__init__.py @@ -1,4 +1,4 @@ -from .basic_mod_data_checker import BasicModDataChecker +from .basic_mod_data_checker import BasicModDataChecker, GlobPatterns from .basic_save_game_info import BasicGameSaveGameInfo -__all__ = ["BasicModDataChecker", "BasicGameSaveGameInfo"] +__all__ = ["BasicModDataChecker", "BasicGameSaveGameInfo", "GlobPatterns"] diff --git a/basic_features/basic_mod_data_checker.py b/basic_features/basic_mod_data_checker.py index 4bceac1..8c80919 100644 --- a/basic_features/basic_mod_data_checker.py +++ b/basic_features/basic_mod_data_checker.py @@ -3,7 +3,7 @@ from __future__ import annotations import fnmatch import re from dataclasses import dataclass, field -from typing import Iterable +from typing import Iterable, Literal import mobase @@ -60,6 +60,13 @@ class RegexPatterns: return None +def _merge_list(l1: list[str] | None, l2: list[str] | None) -> list[str] | None: + if l1 is None and l2 is None: + return None + + return (l1 or []) + (l2 or []) + + @dataclass(frozen=True, unsafe_hash=True) class GlobPatterns: """ @@ -71,6 +78,41 @@ class GlobPatterns: delete: list[str] | None = None move: dict[str, str] = field(default_factory=dict) + def merge( + self, other: GlobPatterns, mode: Literal["merge", "replace"] = "replace" + ) -> GlobPatterns: + """ + Construct a new GlobPatterns by merging the current one with the given one. + + There are two different modes: + - 'merge': In this mode, unfold/valid/delete are concatenated and move + will contain the union of key from self and other, with values from other + overriding common keys. + - 'replace': The merged object will contains attributes from other, except + for None attributes taken from self. + + Args: + other: Other patterns to "merge" with this one. + mode: Merge mode. + + Returns: + A new glob pattern representing the merge of this one with other. + """ + if mode == "merge": + return GlobPatterns( + unfold=_merge_list(self.unfold, other.unfold), + valid=_merge_list(self.valid, other.valid), + delete=_merge_list(self.delete, other.delete), + move=self.move | other.move, + ) + else: + return GlobPatterns( + unfold=other.unfold or self.unfold, + valid=other.valid or self.valid, + delete=other.delete or self.delete, + move=other.move or self.move, + ) + class BasicModDataChecker(mobase.ModDataChecker): """Game feature that is used to check and fix the content of a data tree diff --git a/games/game_subnautica.py b/games/game_subnautica.py index d72677e..b2c64c3 100644 --- a/games/game_subnautica.py +++ b/games/game_subnautica.py @@ -8,7 +8,7 @@ from pathlib import Path import mobase from PyQt6.QtCore import QDir, qWarning -from ..basic_features import BasicModDataChecker +from ..basic_features import BasicModDataChecker, GlobPatterns from ..basic_features.basic_save_game_info import ( BasicGameSaveGame, BasicGameSaveGameInfo, @@ -17,18 +17,21 @@ from ..basic_game import BasicGame class SubnauticaModDataChecker(BasicModDataChecker): - default_file_patterns = { - "unfold": ["BepInExPack_Subnautica"], - "valid": ["winhttp.dll", "doorstop_config.ini", "BepInEx", "QMods"], - "delete": [ - "*.txt", - "*.md", - "icon.png", - "license", - "manifest.json", - ], - "move": {"plugins": "BepInEx/", "patchers": "BepInEx/", "*": "QMods/"}, - } + def __init__(self, patterns: GlobPatterns = GlobPatterns()): + super().__init__( + GlobPatterns( + unfold=["BepInExPack_Subnautica"], + valid=["winhttp.dll", "doorstop_config.ini", "BepInEx", "QMods"], + delete=[ + "*.txt", + "*.md", + "icon.png", + "license", + "manifest.json", + ], + move={"plugins": "BepInEx/", "patchers": "BepInEx/", "*": "QMods/"}, + ).merge(patterns), + ) class SubnauticaGame(BasicGame, mobase.IPluginFileMapper):