aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_wizard/src/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'libs/installer_wizard/src/utils.py')
-rw-r--r--libs/installer_wizard/src/utils.py170
1 files changed, 170 insertions, 0 deletions
diff --git a/libs/installer_wizard/src/utils.py b/libs/installer_wizard/src/utils.py
new file mode 100644
index 0000000..da85c48
--- /dev/null
+++ b/libs/installer_wizard/src/utils.py
@@ -0,0 +1,170 @@
+from __future__ import annotations
+
+import re
+from collections.abc import Sequence
+from pathlib import Path
+from typing import cast
+
+from wizard.tweaks import WizardINISetting, WizardINISettingEdit
+
+
+def make_obscript_ini_tweaks(tweaks: Sequence[WizardINISetting]) -> str:
+ lines: list[str] = []
+
+ for tweak in tweaks:
+ if not isinstance(tweak, WizardINISettingEdit):
+ continue
+ line = f"{tweak.section} {tweak.setting} to {tweak.value}"
+ if tweak.comment:
+ line += f" ; {tweak.comment}"
+
+ lines.append(line)
+
+ return "\n".join(["; Generated by Mod Organizer 2 via Wizard"] + sorted(lines))
+
+
+def make_standard_ini_tweaks(tweaks: Sequence[WizardINISetting]) -> str:
+ # group Tweaks per section
+ sections: dict[str, list[WizardINISetting]] = {m.section: [] for m in tweaks}
+ for m in tweaks:
+ sections[m.section].append(m)
+
+ lines: list[str] = []
+
+ for k in sorted(sections):
+ s_tweaks = sorted(sections[k], key=lambda m: m.setting)
+ lines.append(f"[{k}]")
+ for tw in s_tweaks:
+ if isinstance(tw, WizardINISettingEdit):
+ line = f"{tw.setting} = {tw.value}"
+ if tw.comment:
+ line += f" # {tw.comment}"
+ else:
+ line = f"# {tw.setting} - disabled"
+ lines.append(line)
+ lines.append("\n")
+
+ return "\n".join(lines[:-1])
+
+
+def merge_standard_ini_tweaks(tweaks: Sequence[WizardINISetting], file: Path) -> str:
+ import sys
+
+ print(f"Cannot merge INI Tweaks for {file.name}.", file=sys.stderr)
+ return make_standard_ini_tweaks(tweaks)
+
+
+def merge_obscript_ini_tweaks(tweaks: Sequence[WizardINISetting], file: Path) -> str:
+ # this is from Wrye Bash (and the function is inspired from Wrye Bash)
+ reComment = re.compile(";.*", re.U)
+ reDeleted = re.compile("" r";-(\w.*?)$", re.U)
+ reSet = re.compile("" r"\s*set\s+(.+?)\s+to\s+(.*)", re.I | re.U)
+ reSetGS = re.compile("" r"\s*setGS\s+(.+?)\s+(.*)", re.I | re.U)
+ reSetNGS = re.compile("" r"\s*SetNumericGameSetting\s+(.+?)\s+(.*)", re.I | re.U)
+
+ _regex_tuples = (
+ (reSet, "set", "set {} to {}"),
+ (reSetGS, "setgs", "setGS {} {}"),
+ (reSetNGS, "setnumericgamesetting", "SetNumericGameSetting {} {}"),
+ )
+
+ def _parse_obse_line(line: str):
+ for regex, sectionKey, format_string in _regex_tuples:
+ match = regex.match(line)
+ if match:
+ return match, sectionKey, format_string
+ return None, None, None
+
+ # read the original file
+ with open(file, "r") as fp:
+ olines = fp.readlines()
+
+ # map setting name to value
+ settings: dict[str, dict[str, WizardINISettingEdit]] = {}
+ deleted: dict[str, dict[str, WizardINISetting]] = {}
+ for tweak in tweaks:
+ if isinstance(tweak, WizardINISettingEdit):
+ if tweak.section not in settings:
+ settings[tweak.section.lower()] = {}
+ settings[tweak.section.lower()][tweak.setting] = tweak
+ else:
+ if tweak.section not in deleted:
+ deleted[tweak.section.lower()] = {}
+ deleted[tweak.section.lower()][tweak.setting] = tweak
+
+ # create the lines
+ lines: list[str] = []
+ for line in olines:
+ line = line.rstrip()
+ maDeleted = reDeleted.match(line)
+ if maDeleted:
+ stripped = maDeleted.group(1)
+ else:
+ stripped = line
+ stripped = reComment.sub("", stripped).strip()
+
+ match, section_key, format_string = _parse_obse_line(stripped)
+
+ if match:
+ assert format_string is not None
+ setting = match.group(1)
+ if section_key in settings and setting in settings[section_key]:
+ value = settings[section_key][setting].value
+ line = format_string.format(setting, value)
+ comment = ""
+ if settings[section_key][setting].comment:
+ comment = cast(str, settings[section_key][setting].comment)
+ comment += " "
+ comment += f"(set by MO2 via Wizard, was {match.group(2)})"
+ line = f"{line} ; {comment}"
+ del settings[section_key][setting]
+ elif (
+ not maDeleted
+ and section_key in deleted
+ and setting in deleted[section_key]
+ ):
+ line = f";-{line}"
+
+ lines.append(line)
+
+ for section in settings.values():
+ line = ""
+ for setting in section.values():
+ if setting.section.lower() == "set":
+ line = f"{setting.section} {setting.setting} to {setting.value}"
+ else:
+ line = f"{setting.section} {setting.setting} {setting.value}"
+
+ if setting.comment:
+ comment = setting.comment + " (set by MO2 via Wizard)"
+ else:
+ comment = "(set by MO2 via Wizard)"
+ line = f"{line} ; {comment}"
+
+ lines.append(line)
+
+ return "\n".join(lines)
+
+
+def make_ini_tweaks(tweaks: Sequence[WizardINISetting]) -> str:
+ is_set = [
+ tw.section.lower() in ("set", "setgs", "setnumericgamesetting") for tw in tweaks
+ ]
+
+ # assume there is no mix
+ if all(is_set):
+ return make_obscript_ini_tweaks(tweaks)
+ else:
+ return make_standard_ini_tweaks(tweaks)
+
+
+def merge_ini_tweaks(tweaks: Sequence[WizardINISetting], file: Path) -> str:
+ is_set = [
+ tw.section.lower() in ("set", "setgs", "setnumericgamesetting") for tw in tweaks
+ ]
+
+ # assume there is no mix
+ if all(is_set):
+ return merge_obscript_ini_tweaks(tweaks, file)
+ else:
+ return merge_standard_ini_tweaks(tweaks, file)