mirror of
https://github.com/ModOrganizer2/modorganizer-installer_wizard.git
synced 2026-07-27 14:07:59 -07:00
Compare commits
27
Commits
1.0.0-beta
..
2_4_x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb91a1d2b6 | ||
|
|
9f71ac1517 | ||
|
|
775d764950 | ||
|
|
538a0f73e9 | ||
|
|
ecefc5eb8a | ||
|
|
b9653b8322 | ||
|
|
b39be8c295 | ||
|
|
28deb0ce25 | ||
|
|
1cf557a33c | ||
|
|
753871d62a | ||
|
|
8b49e0d3cd | ||
|
|
52e20cf676 | ||
|
|
b695baaf1d | ||
|
|
c0104b0951 | ||
|
|
759291e6d4 | ||
|
|
83d39ee762 | ||
|
|
c1385c7b96 | ||
|
|
a6dadf39eb | ||
|
|
c2b6d8da43 | ||
|
|
4fe5299a42 | ||
|
|
88ac3075f0 | ||
|
|
a5e896ff1b | ||
|
|
a3722f900b | ||
|
|
d3df91babb | ||
|
|
cdc2f14347 | ||
|
|
29e4cad187 | ||
|
|
ec01ca3e92 |
@@ -0,0 +1,24 @@
|
||||
name: linters
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tox
|
||||
- name: Test with tox
|
||||
run: tox -e py38-lint
|
||||
@@ -8,5 +8,12 @@ src/ui/*.py
|
||||
.tox
|
||||
.vscode
|
||||
|
||||
# MO2 build files:
|
||||
vsbuild
|
||||
mob.log
|
||||
|
||||
# Generated files:
|
||||
lib
|
||||
src/lib
|
||||
installer_wizard
|
||||
installer_wizard-*.zip
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(installer_wizard LANGUAGES NONE)
|
||||
set(project_type python_module_plugin)
|
||||
set(enable_warnings OFF)
|
||||
|
||||
if(DEFINED DEPENDENCIES_DIR)
|
||||
include(${DEPENDENCIES_DIR}/modorganizer_super/cmake_common/project.cmake)
|
||||
else()
|
||||
include(../cmake_common/project.cmake)
|
||||
endif()
|
||||
|
||||
add_subdirectory(src)
|
||||
@@ -2,9 +2,74 @@
|
||||
|
||||
This plugin can be used to install BAIN archives containing a wizard script (`wizard.txt`).
|
||||
|
||||
## How to use?
|
||||
## How to install?
|
||||
|
||||
Go to the releases page and download the latest release for your MO2 version.
|
||||
Go to the [releases page](https://github.com/Holt59/modorganizer-installer_wizard/releases) and download
|
||||
the latest release for your MO2 version.
|
||||
|
||||
### A few words on INI Tweaks
|
||||
|
||||
Mod Organizer 2 does not currently manage INI Tweaks, so the Wizard installer is partially functional
|
||||
regarding them.
|
||||
|
||||
- The installer will create proper INI Tweaks when requested, but these will not be applied
|
||||
to the game INI files automatically. If INI Tweaks are present, a pop-up should appear at
|
||||
the end of the installation.
|
||||
- INI Tweaks for OBSE script are directly applied to the OBSE scripts.
|
||||
|
||||
## How to contribute?
|
||||
|
||||
### Setting-up the environment
|
||||
|
||||
Below are the steps to setup a development environment.
|
||||
|
||||
|
||||
1. Clone this repository into the Mod Organizer 2 plugins folder.
|
||||
|
||||
```bash
|
||||
# (Optional) you can change the name of the folder:
|
||||
git clone https://github.com/Holt59/modorganizer-installer_wizard installer_wizard
|
||||
```
|
||||
|
||||
2. **Requirements:** You need a Python 3.8 installation. The list of requirements is in
|
||||
[`requirements.txt`](requirements.txt):
|
||||
|
||||
```bash
|
||||
# Those are only the development requirements.
|
||||
pip install -r requirements
|
||||
```
|
||||
|
||||
3. "Build" the installer:
|
||||
|
||||
```bash
|
||||
# This will install the 3rd party libraries in src/lib (required for the installer) and convert the .ui files into .py files.
|
||||
make.ps1
|
||||
```
|
||||
|
||||
4. Create a root `__init__.py` - MO2 will not find and load the plugin unless there is a
|
||||
`__init__.py` file in the root of the folder, so you need to create one:
|
||||
|
||||
```python
|
||||
from .src import createPlugin
|
||||
```
|
||||
|
||||
### Opening a Pull-Request
|
||||
|
||||
Once you are satisfied with your changes, you can
|
||||
[open a pull-request](https://github.com/Holt59/modorganizer-installer_wizard/pulls).
|
||||
Before doing so, you should check that your code is properly
|
||||
formatted and clean:
|
||||
|
||||
```bash
|
||||
# The -vv option is mandatory, otherwise tox will crash...
|
||||
tox -vv -e py38-lint
|
||||
```
|
||||
|
||||
### The interpreter
|
||||
|
||||
The interpreter used by the installer is from the
|
||||
[`bain-wizard-interpreter`](https://github.com/Holt59/bain-wizard-interpreter) package.
|
||||
For issues related to interpreter (i.e. the script is wrongly parsed), open issues on the interpreter repository.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+3
-2
@@ -6,8 +6,9 @@ $target = ".\installer_wizard\"
|
||||
|
||||
Remove-Item -Recurse -Force -ErrorAction Ignore $target
|
||||
New-Item -Path $target -Type Directory | Out-Null
|
||||
Copy-Item -Recurse -Path .\src\* -Exclude "*.ui", "__pycache__" -Destination .\installer_wizard
|
||||
Copy-Item .\installer_wizard_en.ts .\README.md .\LICENSE $target
|
||||
Copy-Item -Recurse -Path .\src\* -Exclude "*.ui" -Destination $target
|
||||
Get-ChildItem -Recurse $target -Include "__pycache__" | Remove-Item -Recurse -Force
|
||||
Copy-Item .\installer_wizard_en.ts, .\README.md, .\LICENSE $target
|
||||
|
||||
# Find the version:
|
||||
$ctx = Get-Content .\src\installer.py | Select-String -Pattern "def version\(self\):" -Context 0, 1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Install the lib:
|
||||
pip install --target=.\src\lib --upgrade git+https://github.com/Holt59/bain-wizard-interpreter | Out-Null
|
||||
pip install --target=.\src\lib --upgrade -r .\plugin-requirements.txt | Out-Null
|
||||
|
||||
# Convert ui files:
|
||||
Get-ChildItem -Recurse -File -Include "*.ui" | ForEach-Object {
|
||||
@@ -7,4 +7,4 @@ Get-ChildItem -Recurse -File -Include "*.ui" | ForEach-Object {
|
||||
}
|
||||
|
||||
# Generate the .ts file:
|
||||
pylupdate5 (Get-ChildItem -Exclude lib | Get-ChildItem -Recurse -File -Include "*.py") -ts installer_wizard_en.ts
|
||||
pylupdate5 (Get-ChildItem src -Exclude lib | Get-ChildItem -Recurse -File -Include "*.py") -ts installer_wizard_en.ts
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
git+https://github.com/Holt59/bain-wizard-interpreter
|
||||
@@ -0,0 +1,7 @@
|
||||
black
|
||||
PyQt5
|
||||
mypy
|
||||
flake8
|
||||
flake8-black
|
||||
mobase-stubs
|
||||
git+https://github.com/Holt59/bain-wizard-interpreter
|
||||
@@ -17,6 +17,9 @@ ignore_missing_imports = True
|
||||
[mypy-.ui.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-src.ui.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-wizard.antlr4.*]
|
||||
ignore_errors = True
|
||||
|
||||
@@ -34,7 +37,8 @@ deps =
|
||||
mypy
|
||||
flake8
|
||||
flake8-black
|
||||
mobase-stubs
|
||||
mobase-stubs==2.4.0.a3
|
||||
git+https://github.com/Holt59/bain-wizard-interpreter
|
||||
commands =
|
||||
black src --check --diff --exclude "lib" --exclude "ui"
|
||||
flake8 src
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
if(DEFINED DEPENDENCIES_DIR)
|
||||
include(${DEPENDENCIES_DIR}/modorganizer_super/cmake_common/src.cmake)
|
||||
else()
|
||||
include(../../cmake_common/src.cmake)
|
||||
endif()
|
||||
+136
-37
@@ -1,10 +1,11 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, List, Mapping, Tuple
|
||||
from typing import Any, Dict, List, Mapping, Optional, Set, Tuple
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
from PyQt5.QtGui import QPixmap, QKeySequence
|
||||
from PyQt5.QtGui import QPixmap, QKeySequence, QFontDatabase
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
import mobase
|
||||
@@ -29,6 +30,9 @@ from wizard.errors import WizardError
|
||||
from wizard.interpreter import WizardInterpreter
|
||||
from wizard.manager import SelectOption
|
||||
from wizard.runner import WizardRunnerState, WizardRunnerKeywordVisitor
|
||||
from wizard.tweaks import WizardINISetting
|
||||
|
||||
from .utils import make_ini_tweaks
|
||||
|
||||
WizardRunnerContext = WizardInterpreterContext[WizardRunnerState, Any]
|
||||
|
||||
@@ -133,19 +137,25 @@ class WizardInstallerRequiresVersionPage(QtWidgets.QWidget):
|
||||
|
||||
class WizardInstallerSelectPage(QtWidgets.QWidget):
|
||||
|
||||
_context: WizardRunnerContext
|
||||
# Signal emitted when an item is double-clicked, only for SelectOne
|
||||
# context:
|
||||
itemDoubleClicked = pyqtSignal()
|
||||
|
||||
_context: WizardSelectContext
|
||||
_images: Mapping[Path, Path]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: WizardSelectContext,
|
||||
images: Mapping[Path, Path],
|
||||
options: Optional[List[str]],
|
||||
parent: QtWidgets.QWidget,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
context: The context for this page.
|
||||
images: A mapping from path (in the archive) to extracted path.
|
||||
options: Potential list of options to select. Might not exactly match.
|
||||
parent: The parent widget.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
@@ -159,29 +169,43 @@ class WizardInstallerSelectPage(QtWidgets.QWidget):
|
||||
self.ui.optionList.currentItemChanged.connect(self.onCurrentItemChanged)
|
||||
|
||||
# Create list item widgets:
|
||||
options = context.options
|
||||
for option in options:
|
||||
for option in context.options:
|
||||
item = QtWidgets.QListWidgetItem()
|
||||
self.ui.optionList.addItem(item)
|
||||
|
||||
self.update_context(context)
|
||||
|
||||
# Extract previous select options:
|
||||
previous_options = []
|
||||
if options:
|
||||
previous_options = [
|
||||
option for option in context.options if option.name in options
|
||||
]
|
||||
else:
|
||||
if isinstance(context, WizardSelectManyContext):
|
||||
previous_options = context.defaults
|
||||
elif isinstance(context, WizardSelectOneContext):
|
||||
previous_options = [context.default]
|
||||
|
||||
# Set the default values:
|
||||
for i, option in enumerate(options):
|
||||
for i, option in enumerate(context.options):
|
||||
item = self.ui.optionList.item(i)
|
||||
if isinstance(context, WizardSelectManyContext):
|
||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable) # type: ignore
|
||||
if option in context.defaults:
|
||||
if option in previous_options:
|
||||
item.setCheckState(Qt.Checked)
|
||||
else:
|
||||
item.setCheckState(Qt.Unchecked)
|
||||
elif (
|
||||
isinstance(context, WizardSelectOneContext)
|
||||
and option is context.default
|
||||
and option in previous_options
|
||||
):
|
||||
item.setSelected(True)
|
||||
self.ui.optionList.setCurrentItem(item)
|
||||
|
||||
if isinstance(context, WizardSelectOneContext):
|
||||
self.ui.optionList.doubleClicked.connect(self.itemDoubleClicked.emit)
|
||||
|
||||
def update_context(self, context: WizardSelectContext):
|
||||
|
||||
self._context = context
|
||||
@@ -189,10 +213,6 @@ class WizardInstallerSelectPage(QtWidgets.QWidget):
|
||||
options = self._context.options
|
||||
assert len(options) == self.ui.optionList.count()
|
||||
|
||||
self.ui.selectDescriptionFrame.setStyleSheet(
|
||||
# "QFrame { border: 1px solid red; }"
|
||||
"QFrame { border-color: red; }"
|
||||
)
|
||||
self.ui.selectDescriptionLabel.setText(context.description)
|
||||
self.ui.selectDescriptionLabel.setMargin(4)
|
||||
|
||||
@@ -216,20 +236,24 @@ class WizardInstallerSelectPage(QtWidgets.QWidget):
|
||||
target = self._images[Path(image)]
|
||||
self.ui.imageLabel.setPixmap(QPixmap(target.as_posix()))
|
||||
else:
|
||||
self.ui.imageLabel.setText("")
|
||||
self.ui.imageLabel.setPixmap(QPixmap())
|
||||
|
||||
def selected(self) -> WizardSelectContext:
|
||||
def selectedOptions(self) -> List[SelectOption]:
|
||||
options = []
|
||||
if isinstance(self._context, WizardSelectOneContext):
|
||||
return self._context.select(
|
||||
self.ui.optionList.currentItem().data(Qt.UserRole)
|
||||
)
|
||||
elif isinstance(self._context, WizardSelectManyContext):
|
||||
options = []
|
||||
options.append(self.ui.optionList.currentItem().data(Qt.UserRole))
|
||||
else:
|
||||
for i in range(self.ui.optionList.count()):
|
||||
item = self.ui.optionList.item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
options.append(item.data(Qt.UserRole))
|
||||
return self._context.select(options)
|
||||
return options
|
||||
|
||||
def selected(self) -> WizardSelectContext:
|
||||
if isinstance(self._context, WizardSelectOneContext):
|
||||
return self._context.select(self.selectedOptions()[0])
|
||||
elif isinstance(self._context, WizardSelectManyContext):
|
||||
return self._context.select(self.selectedOptions())
|
||||
else:
|
||||
return self._context # type: ignore
|
||||
|
||||
@@ -255,36 +279,57 @@ class WizardInstallerCompletePage(QtWidgets.QWidget):
|
||||
kvisitor: WizardRunnerKeywordVisitor = context.factory.kvisitor # type: ignore
|
||||
|
||||
# The list of plugins in selected sub-packages:
|
||||
plugins: List[str] = []
|
||||
plugins: Set[str] = set()
|
||||
|
||||
# SubPackages:
|
||||
for sp in kvisitor.subpackages:
|
||||
item = QtWidgets.QListWidgetItem()
|
||||
item.setText(sp.name)
|
||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable) # type: ignore
|
||||
if sp.name in self.state.subpackages:
|
||||
item.setCheckState(Qt.Checked)
|
||||
plugins.extend(kvisitor.plugins_for(sp))
|
||||
else:
|
||||
item.setCheckState(Qt.Unchecked)
|
||||
item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable) # type: ignore
|
||||
plugins.update(kvisitor.plugins_for(sp))
|
||||
self.ui.subpackagesList.addItem(item)
|
||||
|
||||
# Switch the renamed plugins:
|
||||
for plugin in list(plugins):
|
||||
if plugin in self.state.renames:
|
||||
plugins.remove(plugin)
|
||||
plugins.add(self.state.renames[plugin])
|
||||
|
||||
# Plugins:
|
||||
for plugin in sorted(plugins):
|
||||
item = QtWidgets.QListWidgetItem()
|
||||
item.setText(plugin)
|
||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable) # type: ignore
|
||||
if plugin in self.state.plugins:
|
||||
item.setCheckState(Qt.Checked)
|
||||
else:
|
||||
item.setCheckState(Qt.Unchecked)
|
||||
item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable) # type: ignore
|
||||
self.ui.pluginsList.addItem(item)
|
||||
|
||||
# INI Tweaks:
|
||||
self.ui.tweaksWidget.setVisible(bool(self.state.tweaks))
|
||||
self.ui.tweaksList.currentItemChanged.connect(self.onCurrentTweakItemChanged)
|
||||
if self.state.tweaks:
|
||||
# TODO:
|
||||
pass
|
||||
# Group the tweaks per file:
|
||||
tweaks = {
|
||||
file: self.state.tweaks.tweaks(file)
|
||||
for file in self.state.tweaks.files()
|
||||
}
|
||||
|
||||
for file, ftweaks in tweaks.items():
|
||||
item = QtWidgets.QListWidgetItem()
|
||||
item.setText(file.replace("\\", "/"))
|
||||
item.setData(Qt.UserRole, ftweaks)
|
||||
self.ui.tweaksList.addItem(item)
|
||||
|
||||
self.ui.tweaksTextEdit.setFont(
|
||||
QFontDatabase.systemFont(QFontDatabase.FixedFont)
|
||||
)
|
||||
self.ui.tweaksList.setCurrentRow(0)
|
||||
|
||||
# Notes:
|
||||
md = ""
|
||||
@@ -293,6 +338,15 @@ class WizardInstallerCompletePage(QtWidgets.QWidget):
|
||||
self.ui.notesTextEdit.document().setIndentWidth(10)
|
||||
self.ui.notesTextEdit.setMarkdown(md)
|
||||
|
||||
def onCurrentTweakItemChanged(
|
||||
self, current: QtWidgets.QListWidgetItem, previous: QtWidgets.QListWidgetItem
|
||||
):
|
||||
# Clear text area and create the tweaks:
|
||||
self.ui.tweaksTextEdit.clear()
|
||||
self.ui.tweaksTextEdit.appendPlainText(
|
||||
make_ini_tweaks(current.data(Qt.UserRole))
|
||||
)
|
||||
|
||||
def subpackages(self) -> List[str]:
|
||||
"""
|
||||
Returns:
|
||||
@@ -306,19 +360,30 @@ class WizardInstallerCompletePage(QtWidgets.QWidget):
|
||||
sp.append(item.text())
|
||||
return sp
|
||||
|
||||
def plugins(self) -> List[str]:
|
||||
def plugins(self) -> Dict[str, bool]:
|
||||
"""
|
||||
Returns:
|
||||
The list of plugins selected in the UI (either automatically by the
|
||||
interpreter or by the user).
|
||||
"""
|
||||
sp: List[str] = []
|
||||
sp: Dict[str, bool] = {}
|
||||
for i in range(self.ui.pluginsList.count()):
|
||||
item = self.ui.pluginsList.item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
sp.append(item.text())
|
||||
sp[item.text()] = item.checkState() == Qt.Checked
|
||||
return sp
|
||||
|
||||
def tweaks(self) -> Mapping[str, List[WizardINISetting]]:
|
||||
"""
|
||||
Returns:
|
||||
The list of tweaks created by the wizard. The returned value maps filenames
|
||||
to INI tweaks.
|
||||
"""
|
||||
rets = {}
|
||||
for i in range(self.ui.tweaksList.count()):
|
||||
item = self.ui.tweaksList.item(i)
|
||||
rets[item.text()] = item.data(Qt.UserRole)
|
||||
return rets
|
||||
|
||||
|
||||
class WizardInstallerCancelPage(QtWidgets.QWidget):
|
||||
def __init__(
|
||||
@@ -379,6 +444,7 @@ class WizardInstallerDialog(QtWidgets.QDialog):
|
||||
# The interpreter:
|
||||
_interpreter: WizardInterpreter
|
||||
_images: Mapping[Path, Path]
|
||||
_options: Mapping[str, List[str]]
|
||||
|
||||
# The Wizard MO2 interface:
|
||||
_start_context: WizardTopLevelContext
|
||||
@@ -393,6 +459,7 @@ class WizardInstallerDialog(QtWidgets.QDialog):
|
||||
context: WizardTopLevelContext[WizardRunnerState],
|
||||
name: mobase.GuessedString,
|
||||
images: Mapping[Path, Path],
|
||||
options: Mapping[str, List[str]],
|
||||
parent: QtWidgets.QWidget,
|
||||
):
|
||||
"""
|
||||
@@ -401,6 +468,7 @@ class WizardInstallerDialog(QtWidgets.QDialog):
|
||||
context: The initial context of the script.
|
||||
name: The name of the mod.
|
||||
images: A mapping from path (in the archive) to extracted path.
|
||||
options: The previously selected options.
|
||||
parent: The parent widget.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
@@ -408,6 +476,7 @@ class WizardInstallerDialog(QtWidgets.QDialog):
|
||||
self._organizer = organizer
|
||||
self._interpreter = interpreter
|
||||
self._images = images
|
||||
self._options = options
|
||||
self._start_context = context
|
||||
self._pages = {}
|
||||
|
||||
@@ -461,7 +530,7 @@ class WizardInstallerDialog(QtWidgets.QDialog):
|
||||
assert isinstance(widget, WizardInstallerCompletePage)
|
||||
return widget.subpackages()
|
||||
|
||||
def plugins(self):
|
||||
def plugins(self) -> Dict[str, bool]:
|
||||
"""
|
||||
Returns:
|
||||
The list of plugins to install and enable. Only valid if exec() returned
|
||||
@@ -482,6 +551,29 @@ class WizardInstallerDialog(QtWidgets.QDialog):
|
||||
assert isinstance(widget, WizardInstallerCompletePage)
|
||||
return widget.state.renames
|
||||
|
||||
def tweaks(self) -> Mapping[str, List[WizardINISetting]]:
|
||||
"""
|
||||
Returns:
|
||||
The list of tweaks per file. Only valid if exec() returned Accepted.
|
||||
"""
|
||||
widget = self.ui.stackedWidget.currentWidget()
|
||||
assert isinstance(widget, WizardInstallerCompletePage)
|
||||
return widget.tweaks()
|
||||
|
||||
def selectedOptions(self) -> Mapping[str, List[str]]:
|
||||
"""
|
||||
Returns:
|
||||
The list of all currently selected options.
|
||||
"""
|
||||
result = {}
|
||||
for i in range(self.ui.stackedWidget.count()):
|
||||
page = self.ui.stackedWidget.widget(i)
|
||||
if isinstance(page, WizardInstallerSelectPage):
|
||||
result[page._context.description] = [
|
||||
option.name for option in page.selectedOptions()
|
||||
]
|
||||
return result
|
||||
|
||||
def isManualRequested(self):
|
||||
return self._manual
|
||||
|
||||
@@ -510,6 +602,7 @@ class WizardInstallerDialog(QtWidgets.QDialog):
|
||||
|
||||
if context.context in self._pages:
|
||||
page = self._pages[context.context]
|
||||
page.update_context(context)
|
||||
else:
|
||||
page = self._make_page(context)
|
||||
|
||||
@@ -537,13 +630,13 @@ class WizardInstallerDialog(QtWidgets.QDialog):
|
||||
|
||||
name: str = self.ui.nextBtn.text()
|
||||
if isinstance(widget, WizardInstallerSelectPage):
|
||||
name = self._tr("Next")
|
||||
name = self.__tr("Next")
|
||||
elif isinstance(widget, WizardInstallerRequiresVersionPage):
|
||||
name = self._tr("Install anyway")
|
||||
name = self.__tr("Install anyway")
|
||||
elif isinstance(widget, (WizardInstallerCancelPage, WizardInstallerErrorPage)):
|
||||
self.ui.nextBtn.setDisabled(True)
|
||||
else:
|
||||
name = self._tr("Install")
|
||||
name = self.__tr("Install")
|
||||
|
||||
self.ui.nextBtn.setText(name)
|
||||
|
||||
@@ -566,7 +659,13 @@ class WizardInstallerDialog(QtWidgets.QDialog):
|
||||
def _make_page(self, context: WizardRunnerContext) -> QtWidgets.QWidget:
|
||||
page: QtWidgets.QWidget
|
||||
if isinstance(context, WizardSelectContext):
|
||||
page = WizardInstallerSelectPage(context, self._images, self)
|
||||
page = WizardInstallerSelectPage(
|
||||
context,
|
||||
self._images,
|
||||
self._options.get(context.description, None),
|
||||
self,
|
||||
)
|
||||
page.itemDoubleClicked.connect(self.nextClicked)
|
||||
self._pages[context.context] = page # type: ignore
|
||||
elif isinstance(context, WizardRequireVersionsContext):
|
||||
page = WizardInstallerRequiresVersionPage(context, self._organizer, self)
|
||||
@@ -590,5 +689,5 @@ class WizardInstallerDialog(QtWidgets.QDialog):
|
||||
self._update_focus()
|
||||
return super().exec()
|
||||
|
||||
def _tr(self, txt: str) -> str:
|
||||
return QtWidgets.QApplication.translate("WizardInstaller", txt)
|
||||
def __tr(self, str):
|
||||
return QApplication.translate("WizardInstallerDialog", str)
|
||||
|
||||
+152
-40
@@ -1,10 +1,12 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Union
|
||||
from typing import Dict, Optional, List, Union
|
||||
|
||||
# MO2 ships with PyQt5, so you can use it in your plugins:
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
@@ -16,6 +18,7 @@ from wizard.runner import WizardRunnerState
|
||||
|
||||
from .dialog import WizardInstallerDialog
|
||||
from .runner import make_interpreter
|
||||
from .utils import make_ini_tweaks, merge_ini_tweaks
|
||||
|
||||
|
||||
class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
@@ -27,8 +30,16 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
it is valid (for this installer) and then modify it if required before extraction.
|
||||
"""
|
||||
|
||||
# Regex used to parse settings:
|
||||
RE_DESCRIPTION = re.compile(r"select([0-9]+)-description")
|
||||
RE_OPTION = re.compile(r"select([0-9]+)-option([0-9]+)")
|
||||
|
||||
_organizer: mobase.IOrganizer
|
||||
|
||||
# List of selected options:
|
||||
_installerOptions: Dict[str, List[str]]
|
||||
_installerUsed: bool
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@@ -42,14 +53,17 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
def name(self):
|
||||
return "BAIN Wizard Installer"
|
||||
|
||||
def localizedName(self) -> str:
|
||||
return self.__tr("BAIN Wizard Installer")
|
||||
|
||||
def author(self):
|
||||
return "Holt59"
|
||||
|
||||
def description(self):
|
||||
return self._tr("Installer for BAIN archive containing wizard scripts.")
|
||||
return self.__tr("Installer for BAIN archive containing wizard scripts.")
|
||||
|
||||
def version(self):
|
||||
return mobase.VersionInfo(1, 0, 0, mobase.ReleaseType.BETA)
|
||||
return mobase.VersionInfo(1, 0, 2)
|
||||
|
||||
def isActive(self):
|
||||
return self._organizer.pluginSetting(self.name(), "enabled")
|
||||
@@ -57,9 +71,18 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
def settings(self):
|
||||
return [
|
||||
mobase.PluginSetting("enabled", "check to enable this plugin", True),
|
||||
# TODO: I think this should be below FOMOD and above OMOD but this is
|
||||
# currently not possible since OMOD has a priority of 500...
|
||||
mobase.PluginSetting("priority", "priority of this installer", 80),
|
||||
mobase.PluginSetting(
|
||||
"prefer_fomod",
|
||||
"prefer FOMOD installer over this one when possible",
|
||||
True,
|
||||
),
|
||||
mobase.PluginSetting(
|
||||
"prefer_omod",
|
||||
"prefer OMOD installer over this one when possible",
|
||||
False,
|
||||
),
|
||||
# Above FOMOD:
|
||||
mobase.PluginSetting("priority", "priority of this installer", 120),
|
||||
]
|
||||
|
||||
# Method for IPluginInstallerSimple:
|
||||
@@ -73,14 +96,51 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
def onInstallationStart(
|
||||
self, archive: str, reinstallation: bool, mod: Optional[mobase.IModInterface]
|
||||
):
|
||||
# TODO: Reload choices.
|
||||
pass
|
||||
self._installerUsed = False
|
||||
self._installerOptions = {}
|
||||
|
||||
if mod:
|
||||
settings = mod.pluginSettings(self.name())
|
||||
|
||||
# First extract the description:
|
||||
descriptions: Dict[int, str] = {}
|
||||
options: Dict[int, Dict[int, str]] = defaultdict(dict)
|
||||
for setting, value in settings.items():
|
||||
mdesc = WizardInstaller.RE_DESCRIPTION.match(setting)
|
||||
if mdesc:
|
||||
select = int(mdesc.group(1))
|
||||
descriptions[select] = str(value)
|
||||
|
||||
mopt = WizardInstaller.RE_OPTION.match(setting)
|
||||
if mopt:
|
||||
select = int(mopt.group(1))
|
||||
index = int(mopt.group(2))
|
||||
options[select][index] = str(value)
|
||||
|
||||
for kdesc, desc in descriptions.items():
|
||||
self._installerOptions[desc] = []
|
||||
if kdesc in options:
|
||||
for index in sorted(options[kdesc].keys()):
|
||||
self._installerOptions[desc].append(options[kdesc][index])
|
||||
|
||||
def onInstallationEnd(
|
||||
self, result: mobase.InstallResult, mod: Optional[mobase.IModInterface]
|
||||
):
|
||||
# TODO: Save choices.
|
||||
pass
|
||||
if result != mobase.InstallResult.SUCCESS or not self._installerUsed or not mod:
|
||||
return
|
||||
|
||||
mod.clearPluginSettings(self.name())
|
||||
for i, desc in enumerate(self._installerOptions):
|
||||
mod.setPluginSetting(self.name(), f"select{i}-description", desc)
|
||||
for iopt, opt in enumerate(self._installerOptions[desc]):
|
||||
mod.setPluginSetting(self.name(), f"select{i}-option{iopt}", opt)
|
||||
|
||||
def _hasFomodInstaller(self) -> bool:
|
||||
# Do not consider the NCC installer.
|
||||
return self._organizer.isPluginEnabled("Fomod Installer")
|
||||
|
||||
def _hasOmodInstaller(self) -> bool:
|
||||
return self._organizer.isPluginEnabled("Omod Installer")
|
||||
|
||||
def _getWizardArchiveBase(
|
||||
self, tree: mobase.IFileTree, data_name: str, checker: mobase.ModDataChecker
|
||||
@@ -107,17 +167,17 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
|
||||
return None
|
||||
|
||||
def _getImageEntries(
|
||||
def _getEntriesToExtract(
|
||||
self,
|
||||
tree: mobase.IFileTree,
|
||||
extensions: List[str] = ["png", "jpg", "jpeg", "gif", "bmp"],
|
||||
extensions: List[str] = ["png", "jpg", "jpeg", "gif", "bmp", "ini"],
|
||||
) -> List[mobase.FileTreeEntry]:
|
||||
"""
|
||||
Retrieve all the entries corresponding to images in the given tree.
|
||||
Retrieve all the entries to extract from the given tree.
|
||||
|
||||
Args:
|
||||
tree: The tree containing the images.
|
||||
extensions: The extensions for images.
|
||||
tree: The tree.
|
||||
extensions: The extensions of files.
|
||||
|
||||
Returns:
|
||||
A list of entries corresponding to files with the given extensions.
|
||||
@@ -153,9 +213,24 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
mobase.ModDataChecker # type: ignore
|
||||
)
|
||||
|
||||
# Do the actual check - Convert to bool or do `is not None` to avoid
|
||||
# issue with Python > C++ conversion:
|
||||
return bool(self._getWizardArchiveBase(tree, data_name, checker))
|
||||
# Retrieve the base:
|
||||
base = self._getWizardArchiveBase(tree, data_name, checker)
|
||||
|
||||
if not base:
|
||||
return False
|
||||
|
||||
# Check FOMOD:
|
||||
fomod = base.exists("fomod/ModuleConfig.xml")
|
||||
if (
|
||||
fomod
|
||||
and self._hasFomodInstaller()
|
||||
and self._organizer.pluginSetting(self.name(), "prefer_fomod")
|
||||
):
|
||||
return False
|
||||
|
||||
# TODO: Check OMOD?
|
||||
|
||||
return True
|
||||
|
||||
def install(
|
||||
self,
|
||||
@@ -199,12 +274,11 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
if wizard is None:
|
||||
return mobase.InstallResult.NOT_ATTEMPTED
|
||||
|
||||
images = self._getImageEntries(otree)
|
||||
to_extract = self._getEntriesToExtract(otree)
|
||||
|
||||
# Extract the script:
|
||||
# TODO: Extract pictures, similar to the FOMOD installer.
|
||||
paths = self._manager().extractFiles([wizard] + images, silent=False)
|
||||
if len(paths) != len(images) + 1:
|
||||
paths = self._manager().extractFiles([wizard] + to_extract, silent=False)
|
||||
if len(paths) != len(to_extract) + 1:
|
||||
return mobase.InstallResult.FAILED
|
||||
|
||||
interpreter = make_interpreter(base, self._organizer)
|
||||
@@ -216,11 +290,16 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
interpreter,
|
||||
interpreter.make_top_level_context(Path(script), WizardRunnerState()),
|
||||
name,
|
||||
{Path(entry.path()): Path(path) for entry, path in zip(images, paths[1:])},
|
||||
{
|
||||
Path(entry.path()): Path(path)
|
||||
for entry, path in zip(to_extract, paths[1:])
|
||||
if not path.endswith(".ini")
|
||||
},
|
||||
self._installerOptions,
|
||||
self._parentWidget(),
|
||||
)
|
||||
|
||||
dialog.scriptButtonClicked.connect(lambda: os.startfile(script))
|
||||
dialog.scriptButtonClicked.connect(lambda: os.startfile(script)) # type: ignore
|
||||
|
||||
# Note: Unlike the official installer, we do not have a "silent" setting,
|
||||
# but it is really simple to add it.
|
||||
@@ -245,21 +324,6 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
|
||||
tree.merge(entry)
|
||||
|
||||
# Move all non-selected plugins to "optional":
|
||||
optionals: List[mobase.FileTreeEntry] = []
|
||||
for entry in tree:
|
||||
if entry.isFile() and (
|
||||
entry.hasSuffix(["esl", "esp", "esm"])
|
||||
and entry.name() not in dialog.plugins()
|
||||
):
|
||||
optionals.append(entry)
|
||||
|
||||
# Do not keep empty optional folder:
|
||||
if optionals:
|
||||
optional = tree.addDirectory("optional")
|
||||
for entry in optionals:
|
||||
optional.insert(entry)
|
||||
|
||||
# Handle renames:
|
||||
for original, new in dialog.renames().items():
|
||||
# Entry should be at the root:
|
||||
@@ -271,6 +335,54 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
|
||||
tree.move(entry, new)
|
||||
|
||||
# Move not selected plugins to optional:
|
||||
for plugin, enabled in dialog.plugins().items():
|
||||
if not enabled:
|
||||
entry = tree.find(plugin)
|
||||
if not entry:
|
||||
continue # silently fail since the plugin should be disabled
|
||||
tree.addDirectory("optional").insert(entry)
|
||||
|
||||
# TODO: INI Tweaks:
|
||||
alltweaks = dialog.tweaks()
|
||||
|
||||
for filename, tweaks in alltweaks.items():
|
||||
|
||||
# Find the original file (if any):
|
||||
o_entry = tree.find(filename)
|
||||
o_filename: Optional[str] = None
|
||||
if o_entry:
|
||||
# Find the filepath from the list of extracted files:
|
||||
index = to_extract.index(o_entry)
|
||||
|
||||
# +1 because the first one is the script.
|
||||
o_filename = paths[index + 1]
|
||||
|
||||
# If the file existed before, we keep the new one at the same
|
||||
# place:
|
||||
if o_entry or Path(filename).parts[0].lower() == "ini tweaks":
|
||||
entry = tree.addFile(filename, replace_if_exists=True)
|
||||
|
||||
# Otherwise we create it in INI Tweaks
|
||||
else:
|
||||
entry = tree.addFile(
|
||||
os.path.join("INI Tweaks", filename), replace_if_exists=True
|
||||
)
|
||||
|
||||
filepath = self._manager().createFile(entry)
|
||||
|
||||
if not o_filename:
|
||||
data = make_ini_tweaks(tweaks)
|
||||
else:
|
||||
data = merge_ini_tweaks(tweaks, Path(o_filename))
|
||||
|
||||
with open(filepath, "w") as fp:
|
||||
fp.write(data)
|
||||
|
||||
# Mark stuff for saving:
|
||||
self._installerUsed = True
|
||||
self._installerOptions = dict(dialog.selectedOptions())
|
||||
|
||||
# Return the tree:
|
||||
return tree
|
||||
|
||||
@@ -284,7 +396,7 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
|
||||
else:
|
||||
return mobase.InstallResult.CANCELED
|
||||
|
||||
def _tr(self, str):
|
||||
def __tr(self, str) -> str:
|
||||
# We need this to translate string in Python. Check the common documentation
|
||||
# for more details:
|
||||
return QApplication.translate("WizardInstaller", str)
|
||||
|
||||
@@ -1,34 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS><TS version="2.0">
|
||||
<context>
|
||||
<name>WizardInstallerComplete</name>
|
||||
<name>WizardInstaller</name>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallercomplete.py" line="92"/>
|
||||
<source>Form</source>
|
||||
<location filename="installer.py" line="63"/>
|
||||
<source>Installer for BAIN archive containing wizard scripts.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallercomplete.py" line="93"/>
|
||||
<location filename="installer.py" line="57"/>
|
||||
<source>BAIN Wizard Installer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>WizardInstallerComplete</name>
|
||||
<message>
|
||||
<location filename="ui/wizardinstallercomplete.ui" line="40"/>
|
||||
<source>The installer script has finished. The following sub-packages and plugins will be installed.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallercomplete.py" line="94"/>
|
||||
<location filename="ui/wizardinstallercomplete.ui" line="111"/>
|
||||
<source>Sub-Packages:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallercomplete.py" line="95"/>
|
||||
<location filename="ui/wizardinstallercomplete.ui" line="128"/>
|
||||
<source>Plugins:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallercomplete.py" line="96"/>
|
||||
<location filename="ui/wizardinstallercomplete.ui" line="180"/>
|
||||
<source>INI Tweaks:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallercomplete.py" line="97"/>
|
||||
<location filename="ui/wizardinstallercomplete.ui" line="236"/>
|
||||
<source>Notes:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -36,73 +44,60 @@
|
||||
<context>
|
||||
<name>WizardInstallerDialog</name>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerdialog.py" line="75"/>
|
||||
<source>BAIN Wizard Installer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerdialog.py" line="76"/>
|
||||
<source>Name</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerdialog.py" line="77"/>
|
||||
<source>Manual</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerdialog.py" line="78"/>
|
||||
<source>Script</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerdialog.py" line="79"/>
|
||||
<source>Back</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerdialog.py" line="80"/>
|
||||
<location filename="ui/wizardinstallerdialog.ui" line="102"/>
|
||||
<source>Next</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerdialog.py" line="81"/>
|
||||
<location filename="dialog.py" line="635"/>
|
||||
<source>Install anyway</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="dialog.py" line="639"/>
|
||||
<source>Install</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/wizardinstallerdialog.ui" line="14"/>
|
||||
<source>BAIN Wizard Installer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/wizardinstallerdialog.ui" line="25"/>
|
||||
<source>Name</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/wizardinstallerdialog.ui" line="56"/>
|
||||
<source>Manual</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/wizardinstallerdialog.ui" line="66"/>
|
||||
<source>Script</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/wizardinstallerdialog.ui" line="92"/>
|
||||
<source>Back</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/wizardinstallerdialog.ui" line="109"/>
|
||||
<source>Cancel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>WizardInstallerError</name>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallererror.py" line="52"/>
|
||||
<source>Form</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallererror.py" line="54"/>
|
||||
<source>TextLabel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>WizardInstallerPage</name>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerpage.py" line="91"/>
|
||||
<source>Form</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerpage.py" line="92"/>
|
||||
<source>TextLabel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerpage.py" line="93"/>
|
||||
<location filename="ui/wizardinstallerpage.ui" line="114"/>
|
||||
<source>Options:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerpage.py" line="94"/>
|
||||
<location filename="ui/wizardinstallerpage.ui" line="148"/>
|
||||
<source>Description:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
@@ -110,52 +105,47 @@
|
||||
<context>
|
||||
<name>WizardInstallerRequires</name>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerrequires.py" line="113"/>
|
||||
<source>Form</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerrequires.py" line="114"/>
|
||||
<location filename="ui/wizardinstallerrequires.ui" line="34"/>
|
||||
<source><html><head/><body><p><span style=" font-weight:600;">Warning:</span> The following version requirements are not met for using this installer.</p></body></html></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerrequires.py" line="115"/>
|
||||
<location filename="ui/wizardinstallerrequires.ui" line="44"/>
|
||||
<source>Requirements</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerrequires.py" line="116"/>
|
||||
<location filename="ui/wizardinstallerrequires.ui" line="52"/>
|
||||
<source>Graphics Extender</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerrequires.py" line="117"/>
|
||||
<location filename="ui/wizardinstallerrequires.ui" line="76"/>
|
||||
<source>Wrye Bash</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerrequires.py" line="118"/>
|
||||
<location filename="ui/wizardinstallerrequires.ui" line="86"/>
|
||||
<source>Need</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerrequires.py" line="119"/>
|
||||
<location filename="ui/wizardinstallerrequires.ui" line="103"/>
|
||||
<source>Game</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerrequires.py" line="122"/>
|
||||
<location filename="ui/wizardinstallerrequires.ui" line="151"/>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerrequires.py" line="121"/>
|
||||
<location filename="ui/wizardinstallerrequires.ui" line="141"/>
|
||||
<source>Have</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/ui/wizardinstallerrequires.py" line="123"/>
|
||||
<location filename="ui/wizardinstallerrequires.ui" line="165"/>
|
||||
<source>Script Extender</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
+24
-5
@@ -73,6 +73,21 @@ class MO2ManagerModInterface(ManagerModInterface):
|
||||
if checker:
|
||||
if checker.dataLooksValid(entry) == mobase.ModDataChecker.VALID:
|
||||
self._subpackages.append(MO2SubPackage(entry))
|
||||
continue
|
||||
|
||||
# Add entry with INI tweaks:
|
||||
if entry.exists("INI Tweaks") or entry.exists("INI"):
|
||||
self._subpackages.append(MO2SubPackage(entry))
|
||||
continue
|
||||
|
||||
# We add folder with format "XXX Docs" where "XXX" is a number.
|
||||
parts = entry.name().split()
|
||||
if (
|
||||
len(parts) >= 2
|
||||
and parts[0].isdigit()
|
||||
and parts[1].lower().startswith("doc")
|
||||
):
|
||||
self._subpackages.append(MO2SubPackage(entry))
|
||||
|
||||
@property
|
||||
def subpackages(self) -> SubPackages:
|
||||
@@ -129,7 +144,11 @@ class MO2ManagerModInterface(ManagerModInterface):
|
||||
path = None
|
||||
else:
|
||||
path = Path(filepath)
|
||||
files = self._organizer.findFiles(path.parent.as_posix(), path.name)
|
||||
parent = path.parent.as_posix()
|
||||
if parent == ".":
|
||||
parent = ""
|
||||
|
||||
files = self._organizer.findFiles(parent, "*" + path.name)
|
||||
if files:
|
||||
path = Path(files[0])
|
||||
else:
|
||||
@@ -146,11 +165,11 @@ class MO2ManagerModInterface(ManagerModInterface):
|
||||
def getPluginStatus(self, filename) -> int:
|
||||
state = self._organizer.pluginList().state(filename)
|
||||
|
||||
if state & mobase.PluginState.MISSING:
|
||||
return -1
|
||||
if state & mobase.PluginState.INACTIVE:
|
||||
if state == mobase.PluginState.ACTIVE:
|
||||
return 2
|
||||
if state == mobase.PluginState.INACTIVE:
|
||||
return 0 # Or 1?
|
||||
return 2
|
||||
return -1
|
||||
|
||||
def getFilename(self, filepath: str) -> str:
|
||||
path = self._resolve(filepath)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
@@ -30,52 +30,32 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>The installer script has finished. The following sub-packages and plugins will be installed.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Sub-Packages:</string>
|
||||
</property>
|
||||
<property name="heading" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="subpackagesList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Plugins:</string>
|
||||
</property>
|
||||
<property name="heading" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="pluginsList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="tweaksWidget" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_11">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
@@ -89,58 +69,199 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QLabel" name="tweaksLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="packagesWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>15</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>INI Tweaks:</string>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="heading" stdset="0">
|
||||
<bool>true</bool>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="tweaksLayout">
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="tweaksTextEdit1"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="tweaksTextEdit2"/>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Sub-Packages:</string>
|
||||
</property>
|
||||
<property name="heading" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="subpackagesList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Plugins:</string>
|
||||
</property>
|
||||
<property name="heading" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="pluginsList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tweaksWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>10</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QLabel" name="tweaksLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>INI Tweaks:</string>
|
||||
</property>
|
||||
<property name="heading" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="tweaksLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="tweaksList"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="tweaksTextEdit">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="notesWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>10</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_10">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<widget class="QLabel" name="notesLabel">
|
||||
<property name="text">
|
||||
<string>Notes:</string>
|
||||
</property>
|
||||
<property name="heading" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="notesTextEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="notesLabel">
|
||||
<property name="text">
|
||||
<string>Notes:</string>
|
||||
</property>
|
||||
<property name="heading" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="notesTextEdit">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
@@ -45,7 +45,7 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -68,7 +68,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="titleLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
@@ -82,6 +82,16 @@
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="markdown">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="html">
|
||||
<string notr="true"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
@@ -93,7 +93,7 @@
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import re
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Mapping
|
||||
|
||||
from wizard.tweaks import WizardINISetting, WizardINISettingEdit
|
||||
|
||||
|
||||
def make_obscript_ini_tweaks(tweaks: List[WizardINISetting]) -> str:
|
||||
|
||||
lines = []
|
||||
|
||||
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: List[WizardINISetting]) -> str:
|
||||
|
||||
# Group Tweaks per section:
|
||||
sections: Mapping[str, List[WizardINISetting]] = {m.section: [] for m in tweaks}
|
||||
for m in tweaks:
|
||||
sections[m.section].append(m)
|
||||
|
||||
lines = []
|
||||
|
||||
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: List[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: List[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):
|
||||
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 = []
|
||||
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:
|
||||
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 = settings[section_key][setting].comment # type: ignore
|
||||
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():
|
||||
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: List[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: List[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)
|
||||
Reference in New Issue
Block a user