mirror of
https://github.com/ModOrganizer2/modorganizer-plugin_python.git
synced 2026-07-27 14:03:33 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e11d5ec00d | ||
|
|
6623e19754 | ||
|
|
7cfc761e40 | ||
|
|
5bb4de039e | ||
|
|
8c044e28c5 | ||
|
|
790d0a1097 | ||
|
|
a966db7d19 | ||
|
|
86505f19ce | ||
|
|
a839d36235 | ||
|
|
bb0a24a00d | ||
|
|
ac7009d17f | ||
|
|
cc1bc4cb7f | ||
|
|
ebede37865 | ||
|
|
7a885ad014 | ||
|
|
ff3c601164 | ||
|
|
655e60acb7 | ||
|
|
e52163eb29 | ||
|
|
431caa9992 | ||
|
|
a644546b41 | ||
|
|
404fd3f4a3 | ||
|
|
0be763f3d2 | ||
|
|
7452df8bbb | ||
|
|
c6c7e5ef73 | ||
|
|
328b6e7710 | ||
|
|
eb2c874a39 | ||
|
|
5501814e00 | ||
|
|
d1609a79ae | ||
|
|
58737acf0e | ||
|
|
d3fec9c230 | ||
|
|
b99a5115cb | ||
|
|
923d600bc0 | ||
|
|
ba80ce9e20 | ||
|
|
57b30be723 | ||
|
|
f1e5d65868 | ||
|
|
5a8d3e1a72 | ||
|
|
809157115f | ||
|
|
be9b614a5e | ||
|
|
0ee795ac6a | ||
|
|
d7c109c344 | ||
|
|
7108d59267 | ||
|
|
1bffcfa8bf | ||
|
|
6c20f001ff | ||
|
|
0fff26df0c | ||
|
|
a5d84297fc | ||
|
|
1bc51b6626 | ||
|
|
e6190bf4f7 | ||
|
|
788f982eab | ||
|
|
cb750e72d1 | ||
|
|
1cdeb081b2 | ||
|
|
1effc57990 | ||
|
|
26d7de23f3 | ||
|
|
967f1d327b | ||
|
|
2430e85968 | ||
|
|
2eabaf1774 | ||
|
|
40c6119515 | ||
|
|
efaea1edfe | ||
|
|
e204c003ae | ||
|
|
29fc11dfd8 | ||
|
|
cc11a1a4f7 | ||
|
|
465059181a | ||
|
|
cb35c950cf | ||
|
|
e6c5923bb1 | ||
|
|
604c50d3b3 | ||
|
|
5b32c4f024 | ||
|
|
e788f44b85 | ||
|
|
28210d3d7a | ||
|
|
450bfa821a | ||
|
|
b17a1a3b07 | ||
|
|
19da0a9d8c | ||
|
|
b7e08ad862 | ||
|
|
d0a0ac0f0d | ||
|
|
4b0890e679 | ||
|
|
6268fe6d7b | ||
|
|
cf76d34cdf | ||
|
|
9bc343c944 | ||
|
|
72750ab4de | ||
|
|
7c6bbc2980 | ||
|
|
65ccc4af9c | ||
|
|
b42ce25b6e | ||
|
|
fa33e42e22 | ||
|
|
94bd8b82b4 | ||
|
|
639c3cf71d | ||
|
|
6030b443f7 | ||
|
|
08430a5df1 | ||
|
|
8a886733d4 | ||
|
|
2ef5dedfc0 | ||
|
|
97e9876987 | ||
|
|
e89616d695 | ||
|
|
cc0be7469a | ||
|
|
62f4a9679d | ||
|
|
958cf9de37 | ||
|
|
30a01cf783 | ||
|
|
6ea9d92b26 | ||
|
|
225c86d258 | ||
|
|
051c0589b5 | ||
|
|
907bbf3117 | ||
|
|
e5d4a4133c | ||
|
|
21aeebdcc8 | ||
|
|
275acba6dd | ||
|
|
68060b0e6b | ||
|
|
b2767887a9 | ||
|
|
d4f515ade1 | ||
|
|
64af744c8c | ||
|
|
dff9bd2404 | ||
|
|
4c7b452219 | ||
|
|
d0e883fd90 | ||
|
|
f904e25e76 | ||
|
|
55e8ce326a | ||
|
|
92a2429d58 | ||
|
|
5ec456f880 | ||
|
|
b92e3de535 | ||
|
|
c97724a98f | ||
|
|
3798370283 | ||
|
|
b2e1cb490c | ||
|
|
8e714f2eb6 | ||
|
|
9ad6c7f6ac | ||
|
|
4937fe9cc9 | ||
|
|
67ea7deb00 | ||
|
|
8bd7d08d1a | ||
|
|
4be4bc2a55 | ||
|
|
94ecbe2c16 | ||
|
|
ddaff7d03d | ||
|
|
3957187e0a | ||
|
|
03989ae0f1 | ||
|
|
cc129e4895 | ||
|
|
7e8748abf0 | ||
|
|
11c2bcac9a | ||
|
|
56de0fb4d1 | ||
|
|
a53d3fe5d3 | ||
|
|
d6a7efc266 | ||
|
|
574f79117e | ||
|
|
4d018999cd | ||
|
|
d678a22f99 | ||
|
|
4e8800b4d6 |
@@ -0,0 +1,21 @@
|
||||
---
|
||||
# We'll use defaults from the LLVM style, but with 4 columns indentation.
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
---
|
||||
Language: Cpp
|
||||
# Force pointers to the type for C++.
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Left
|
||||
AlignConsecutiveAssignments: true
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: Empty
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AccessModifierOffset: -4
|
||||
AlignTrailingComments: true
|
||||
SpacesBeforeTrailingComments: 2
|
||||
NamespaceIndentation: All
|
||||
MaxEmptyLinesToKeep: 1
|
||||
BreakBeforeBraces: Stroustrup
|
||||
ColumnLimit: 88
|
||||
@@ -0,0 +1,2 @@
|
||||
1bffcfa8bfba93a9505692b11fdd3f0903983542
|
||||
7108d59267b0fe4e9442f9bb146ea5d70e7704a8
|
||||
@@ -0,0 +1,8 @@
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
* text=auto
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
*.cpp text eol=crlf
|
||||
*.h text eol=crlf
|
||||
*.py text eol=crlf
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Build & Test Plugin Python
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: Build Plugin Python
|
||||
id: build-plugin-python
|
||||
uses: ModOrganizer2/build-with-mob-action@master
|
||||
with:
|
||||
mo2-third-parties: gtest python spdlog boost sip pyqt pybind11
|
||||
mo2-dependencies: cmake_common uibase
|
||||
mo2-cmake-command: -DPLUGIN_PYTHON_TESTS=1 ..
|
||||
- name: Build Plugin Python Tests
|
||||
run: cmake --build vsbuild --config RelWithDebInfo -j4 --target python-tests --target runner-tests
|
||||
working-directory: ${{ steps.build-plugin-python.outputs.working-directory }}
|
||||
- name: Test Plugin Python
|
||||
run: ctest --test-dir vsbuild -C RelWithDebInfo --output-on-failure
|
||||
working-directory: ${{ steps.build-plugin-python.outputs.working-directory }}
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Lint Plugin Python
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check format
|
||||
uses: ModOrganizer2/check-formatting-action@master
|
||||
with:
|
||||
check-path: "."
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- uses: abatilo/actions-poetry@v2
|
||||
- name: Check format Python tests
|
||||
run: |
|
||||
poetry install --no-root
|
||||
poetry run poe lint
|
||||
@@ -1,5 +1,12 @@
|
||||
# build
|
||||
edit
|
||||
CMakeLists.txt.user
|
||||
/msbuild.log
|
||||
/*std*.log
|
||||
/*build
|
||||
|
||||
# python
|
||||
tests/**/__pycache__
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
|
||||
+38
-8
@@ -1,15 +1,45 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(plugin_python)
|
||||
set(project_type plugin)
|
||||
set(enable_warnings OFF)
|
||||
set(create_translations OFF)
|
||||
cmake_policy(SET CMP0144 NEW)
|
||||
|
||||
if(DEFINED DEPENDENCIES_DIR)
|
||||
include(${DEPENDENCIES_DIR}/modorganizer_super/cmake_common/project.cmake)
|
||||
include(${DEPENDENCIES_DIR}/modorganizer_super/cmake_common/mo2.cmake)
|
||||
else()
|
||||
include(../cmake_common/project.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake_common/mo2.cmake)
|
||||
endif()
|
||||
add_subdirectory(src/proxy)
|
||||
|
||||
add_subdirectory(src/runner)
|
||||
project(plugin_python CXX)
|
||||
|
||||
set(PYTHON_BUILD_PATH ${PYTHON_ROOT}/PCBuild/amd64)
|
||||
|
||||
# find Python - lots of "Hints" since we have a weird setup
|
||||
set(Python_USE_STATIC_LIBS False)
|
||||
set(Python_INCLUDE_DIR ${PYTHON_ROOT}/Include)
|
||||
set(Python_EXECUTABLE ${PYTHON_BUILD_PATH}/python.exe)
|
||||
if (EXISTS "${PYTHON_BUILD_PATH}/python_d.exe")
|
||||
set(Python_EXECUTABLE ${PYTHON_BUILD_PATH}/python_d.exe)
|
||||
endif()
|
||||
file(GLOB Python_LIBRARY ${PYTHON_BUILD_PATH}/python[0-9][0-9]*.lib)
|
||||
find_package(Python COMPONENTS Interpreter Development REQUIRED)
|
||||
|
||||
# pybind11 needs uppercase (at least EXECUTABLE and LIBRARY)
|
||||
set(PYTHON_EXECUTABLE ${Python_EXECUTABLE})
|
||||
set(PYTHON_INCLUDE_DIR ${Python_INCLUDE_DIR})
|
||||
set(PYTHON_LIBRARY ${Python_LIBRARY})
|
||||
|
||||
# useful for naming DLL, zip, etc. (3.10 -> 310)
|
||||
set(Python_VERSION_SHORT ${Python_VERSION_MAJOR}${Python_VERSION_MINOR})
|
||||
|
||||
# pybind11
|
||||
add_subdirectory(${MO2_BUILD_PATH}/pybind11 ${CMAKE_CURRENT_BINARY_DIR}/pybind11)
|
||||
|
||||
# projects
|
||||
add_subdirectory(src)
|
||||
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT proxy)
|
||||
|
||||
# tests (if requested)
|
||||
set(PLUGIN_PYTHON_TESTS ${PLUGIN_PYTHON_TESTS} CACHE BOOL "build tests for plugin_python")
|
||||
if (PLUGIN_PYTHON_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# ModOrganizer2 - Python Proxy
|
||||
|
||||
This repository contains the Python proxy plugin for ModOrganizer2.
|
||||
The proxy plugin allow developers to write Python plugins for ModOrganizer2.
|
||||
|
||||
## Setup, build, tests
|
||||
|
||||
This repository is part of MO2 main repositories and should usually be build using
|
||||
[`mob`](https://github.com/ModOrganizer2/mob).
|
||||
|
||||
## Organization
|
||||
|
||||
This repositories contains 5 sub-projects in `src`.
|
||||
The interface between Python and C++ is done using the
|
||||
[`pybind11`](https://github.com/pybind/pybind11) library.
|
||||
See the `README` in the subfolder (when there is one) for more details.
|
||||
|
||||
- [`src/proxy`](src/proxy/) contains the actual proxy plugin.
|
||||
This project is a simple interface between MO2 and the runner (see below).
|
||||
The CMake code:
|
||||
- generates the `plugin_python.dll` library,
|
||||
- generates the translation file (under `src/`),
|
||||
- installs necessary files for the plugin (Python DLL, Python libraries, etc),
|
||||
including `mobase`.
|
||||
- [`src/runner`](src/runner/) contains the Python runner. This is the project that
|
||||
instantiates a Python interpreter and load/unload Python plugins.
|
||||
- [`src/pybind11-qt`](src/pybind11-qt/) contains many utility stuff to interface
|
||||
pybind11 with Qt and PyQt.
|
||||
- [`src/pybind11-utils`](src/pybind11-utils/) contains some utility stuff pybind11.
|
||||
This project is header-only.
|
||||
- [`src/mobase`](src/mobase) contains the Python plugin interface.
|
||||
- This projects generates the `mobase` Python library.
|
||||
|
||||
Some (woefully incomplete) tests are available under `tests`, split in three
|
||||
sub-directories:
|
||||
|
||||
- [`tests/mocks`](tests/mocks/) simply contains mocks of `uibase` interfaces to be used
|
||||
in the two other test projects.
|
||||
- [`tests/python`](tests/python/) contains Python tests for `pytest`.
|
||||
This project generates a bunch of Python test libraries that are then imported in
|
||||
Python test files (`test_*.py`) and tested using `pytest`.
|
||||
These tests mostly cover the pybind11 Qt and utility stuff, and some standalone
|
||||
MO2 classes and functions (`IFileTree`, `GuessedString`, etc).
|
||||
- [`tests/runner`](tests/runner/) contains C++ tests, using GTest
|
||||
Tests in this project instantiate a Python runner and then use it to check that
|
||||
plugins implemented in Python can be used properly on the C++ side.
|
||||
|
||||
## Building & Running tests
|
||||
|
||||
Tests are not built by default with `mob`, so you will need to run `cmake` manually
|
||||
with the proper arguments.
|
||||
|
||||
You need to define `PLUGIN_PYTHON_TESTS` with `-DPLUGIN_PYTHON_TESTS` when running
|
||||
the configure step of cmake.
|
||||
|
||||
You can then build the tests
|
||||
|
||||
```bash
|
||||
# replace vsbuild with your build folder
|
||||
cmake --build vsbuild --config RelWithDebInfo --target "python-tests" "runner-tests"
|
||||
```
|
||||
|
||||
To run the tests, use `ctest`
|
||||
|
||||
```bash
|
||||
# replace vsbuild with your build folder
|
||||
ctest.exe --test-dir vsbuild -C RelWithDebInfo
|
||||
```
|
||||
@@ -1,48 +0,0 @@
|
||||
version: 1.0.{build}
|
||||
skip_branch_with_pr: true
|
||||
image: Visual Studio 2019
|
||||
environment:
|
||||
WEBHOOK_URL:
|
||||
secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1Vbg57wBaeLhi49uGjxPw5GVujHku6kxN6ab89zhbS5GVeluR76GM83IbKV4Sh7udXzoYZZdg6YudtYHzdhCgUeiedpswbuczTq9ceIkkfSEWZuh/lMAAVVwvcGsJAnoPFw=
|
||||
build:
|
||||
parallel: true
|
||||
build_script:
|
||||
- pwsh: >-
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
git clone --depth=1 --no-single-branch https://github.com/ModOrganizer2/modorganizer-umbrella.git c:\projects\modorganizer-umbrella
|
||||
|
||||
New-Item -ItemType Directory -Path c:\projects\modorganizer-build
|
||||
|
||||
cd c:\projects\modorganizer-umbrella
|
||||
|
||||
($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH -eq $null) ? ($branch = $env:APPVEYOR_REPO_BRANCH) : ($branch = $env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH)
|
||||
|
||||
git checkout $(git show-ref --verify --quiet refs/remotes/origin/${branch} || echo '-b') ${branch}
|
||||
|
||||
C:\Python37-x64\python.exe unimake.py -d c:\projects\modorganizer-build -s Appveyor_Build=True ${env:APPVEYOR_PROJECT_NAME}
|
||||
|
||||
if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) }
|
||||
artifacts:
|
||||
- path: build\src\proxy\plugin_python.dll
|
||||
name: plugin_python_dll
|
||||
- path: build\src\proxy\plugin_python.pdb
|
||||
name: plugin_python_pdb
|
||||
- path: build\src\proxy\plugin_python.lib
|
||||
name: plugin_python_lib
|
||||
- path: build\src\runner\pythonrunner.dll
|
||||
name: pythonrunner_dll
|
||||
- path: build\src\runner\pythonrunner.pdb
|
||||
name: pythonrunner_pdb
|
||||
- path: build\src\runner\pythonrunner.lib
|
||||
name: pythonrunner_lib
|
||||
on_success:
|
||||
- ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER
|
||||
- ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
|
||||
- ps: ./send.ps1 success $env:WEBHOOK_URL
|
||||
on_failure:
|
||||
- ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER
|
||||
- ps: Push-AppveyorArtifact ${env:APPVEYOR_BUILD_FOLDER}\stdout.log
|
||||
- ps: Push-AppveyorArtifact ${env:APPVEYOR_BUILD_FOLDER}\stderr.log
|
||||
- ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
|
||||
- ps: ./send.ps1 failure $env:WEBHOOK_URL
|
||||
Generated
+254
@@ -0,0 +1,254 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.0.0"
|
||||
description = "brain-dead simple config-ini parsing"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mobase-stubs"
|
||||
version = "2.5.1a0"
|
||||
description = "PEP561 stub files for the mobase Python API."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.12"
|
||||
files = [
|
||||
{file = "mobase_stubs-2.5.1a0-py3-none-any.whl", hash = "sha256:bcaecfae038b890d82280fc518f7e44f38d22d35801a8ba7ffa480f7756d6823"},
|
||||
{file = "mobase_stubs-2.5.1a0.tar.gz", hash = "sha256:a8dc5574336ed3b1f673288447781f705a078472cf8808e05a36f129c81c8e20"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.9.1"
|
||||
description = "Node.js virtual environment builder"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
|
||||
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.1"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pastel"
|
||||
version = "0.2.1"
|
||||
description = "Bring colors to your terminal."
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
{file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"},
|
||||
{file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.5.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "poethepoet"
|
||||
version = "0.23.0"
|
||||
description = "A task runner that works well with poetry."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "poethepoet-0.23.0-py3-none-any.whl", hash = "sha256:d573ff31d7678e62b6f9bc9a1291ae2009ac14e0eead0a450598f9f05abb27a3"},
|
||||
{file = "poethepoet-0.23.0.tar.gz", hash = "sha256:62a0a6a518df5985c191aee0c1fcd2bb6a0a04eb102997786fcdf118e4147d22"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pastel = ">=0.2.1,<0.3.0"
|
||||
tomli = ">=1.2.2"
|
||||
|
||||
[package.extras]
|
||||
poetry-plugin = ["poetry (>=1.0,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pybind11-stubgen"
|
||||
version = "2.5.1"
|
||||
description = "PEP 561 type stubs generator for pybind11 modules"
|
||||
optional = false
|
||||
python-versions = "~=3.7"
|
||||
files = [
|
||||
{file = "pybind11-stubgen-2.5.1.tar.gz", hash = "sha256:4427a67038a00c5ac1637ffa6c65728c67c5b1251ecc23c7704152be0b14cc0b"},
|
||||
{file = "pybind11_stubgen-2.5.1-py3-none-any.whl", hash = "sha256:544d49df57da827c8761e7f6ef6bca996df80a33c9fd21c2521d694d4e72fe8d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6"
|
||||
version = "6.7.0"
|
||||
description = "Python bindings for the Qt cross platform application toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "PyQt6-6.7.0-1-cp38-abi3-macosx_10_14_universal2.whl", hash = "sha256:656734112853fde1be0963f0ad362e5efd87ba6c6ff234cb1f9fe8003ee254e6"},
|
||||
{file = "PyQt6-6.7.0-1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fa2d27fc2f5340f3f1e145c815101ef4550771a9e4bfafd4c7c2479fe83d9488"},
|
||||
{file = "PyQt6-6.7.0-1-cp38-abi3-win_amd64.whl", hash = "sha256:6a1f6dfe03752f888b5e628c208f9fd1a03bda7ebda59ffed8c13580289a1892"},
|
||||
{file = "PyQt6-6.7.0-cp38-abi3-macosx_10_14_universal2.whl", hash = "sha256:919ffb01020ece42209228bf94b4f2c156a6b77cc5a69a90a05e358b0333750b"},
|
||||
{file = "PyQt6-6.7.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e294f025f94493ee12b66efd6893fab309c9063172bb8a5b184f84dfc1ebcc49"},
|
||||
{file = "PyQt6-6.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:9d8865fb6357dba032002c4554a9648e88f2b4706c929cc51fba58edafad91fc"},
|
||||
{file = "PyQt6-6.7.0.tar.gz", hash = "sha256:3d31b2c59dc378ee26e16586d9469842483588142fc377280aad22aaf2fa6235"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
PyQt6-Qt6 = ">=6.7.0,<6.8.0"
|
||||
PyQt6-sip = ">=13.6,<14"
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-qt6"
|
||||
version = "6.7.2"
|
||||
description = "The subset of a Qt installation needed by PyQt6."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:065415589219a2f364aba29d6a98920bb32810286301acbfa157e522d30369e3"},
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7f817efa86a0e8eda9152c85b73405463fbf3266299090f32bbb2266da540ead"},
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:fc93945eaef4536d68bd53566535efcbe78a7c05c2a533790a8fd022bac8bfaa"},
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-win_amd64.whl", hash = "sha256:b2d7e5ddb1b9764cd60f1d730fa7bf7a1f0f61b2630967c81761d3d0a5a8a2e0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-sip"
|
||||
version = "13.6.0"
|
||||
description = "The sip module support for PyQt6"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "PyQt6_sip-13.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d6b5f699aaed0ac1fcd23e8fbca70d8a77965831b7c1ce474b81b1678817a49d"},
|
||||
{file = "PyQt6_sip-13.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8c282062125eea5baf830c6998587d98c50be7c3a817a057fb95fef647184012"},
|
||||
{file = "PyQt6_sip-13.6.0-cp310-cp310-win32.whl", hash = "sha256:fa759b6339ff7e25f9afe2a6b651b775f0a36bcb3f5fa85e81a90d3b033c83f4"},
|
||||
{file = "PyQt6_sip-13.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:8f9df9f7ccd8a9f0f1d36948c686f03ce1a1281543a3e636b7b7d5e086e1a436"},
|
||||
{file = "PyQt6_sip-13.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b9c6b6f9cfccb48cbb78a59603145a698fb4ffd176764d7083e5bf47631d8df"},
|
||||
{file = "PyQt6_sip-13.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:86a7b67c64436e32bffa9c28c9f21bf14a9faa54991520b12c3f6f435f24df7f"},
|
||||
{file = "PyQt6_sip-13.6.0-cp311-cp311-win32.whl", hash = "sha256:58f68a48400e0b3d1ccb18090090299bad26e3aed7ccb7057c65887b79b8aeea"},
|
||||
{file = "PyQt6_sip-13.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:0dfd22cfedd87e96f9d51e0778ca2ba3dc0be83e424e9e0f98f6994d8d9c90f0"},
|
||||
{file = "PyQt6_sip-13.6.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3bf03e130fbfd75c9c06e687b86ba375410c7a9e835e4e03285889e61dd4b0c4"},
|
||||
{file = "PyQt6_sip-13.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:43fb8551796030aae3d66d6e35e277494071ec6172cd182c9569ab7db268a2f5"},
|
||||
{file = "PyQt6_sip-13.6.0-cp312-cp312-win32.whl", hash = "sha256:13885361ca2cb2f5085d50359ba61b3fabd41b139fb58f37332acbe631ef2357"},
|
||||
{file = "PyQt6_sip-13.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:24441032a29791e82beb7dfd76878339058def0e97fdb7c1cea517f3a0e6e96b"},
|
||||
{file = "PyQt6_sip-13.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3075d8b325382750829e6cde6971c943352309d35768a4d4da0587459606d562"},
|
||||
{file = "PyQt6_sip-13.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a6ce80bc24618d8a41be8ca51ad9f10e8bc4296dd90ab2809573df30a23ae0e5"},
|
||||
{file = "PyQt6_sip-13.6.0-cp38-cp38-win32.whl", hash = "sha256:fa7b10af7488efc5e53b41dd42c0f421bde6c2865a107af7ae259aff9d841da9"},
|
||||
{file = "PyQt6_sip-13.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:9adf672f9114687533a74d5c2d4c03a9a929ad5ad9c3e88098a7da1a440ab916"},
|
||||
{file = "PyQt6_sip-13.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98bf954103b087162fa63b3a78f30b0b63da22fd6450b610ec1b851dbb798228"},
|
||||
{file = "PyQt6_sip-13.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:39854dba35f8e5a4288da26ecb5f40b4c5ec1932efffb3f49d5ea435a7f37fb3"},
|
||||
{file = "PyQt6_sip-13.6.0-cp39-cp39-win32.whl", hash = "sha256:747f6ca44af81777a2c696bd501bc4815a53ec6fc94d4e25830e10bc1391f8ab"},
|
||||
{file = "PyQt6_sip-13.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:33ea771fe777eb0d1a2c3ef35bcc3f7a286eb3ff09cd5b2fdd3d87d1f392d7e8"},
|
||||
{file = "PyQt6_sip-13.6.0.tar.gz", hash = "sha256:2486e1588071943d4f6657ba09096dc9fffd2322ad2c30041e78ea3f037b5778"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyright"
|
||||
version = "1.1.369"
|
||||
description = "Command line wrapper for pyright"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pyright-1.1.369-py3-none-any.whl", hash = "sha256:06d5167a8d7be62523ced0265c5d2f1e022e110caf57a25d92f50fb2d07bcda0"},
|
||||
{file = "pyright-1.1.369.tar.gz", hash = "sha256:ad290710072d021e213b98cc7a2f90ae3a48609ef5b978f749346d1a47eb9af8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
nodeenv = ">=1.6.0"
|
||||
|
||||
[package.extras]
|
||||
all = ["twine (>=3.4.1)"]
|
||||
dev = ["twine (>=3.4.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.2.2"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
|
||||
{file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=1.5,<2.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.2.2"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"},
|
||||
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"},
|
||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"},
|
||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"},
|
||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"},
|
||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"},
|
||||
{file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"},
|
||||
{file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"},
|
||||
{file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"},
|
||||
{file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "4a3746f1080ab40c4cfde6777517db502e124fe088e971895bd7f7c245c3fa3e"
|
||||
@@ -0,0 +1,53 @@
|
||||
[tool.poetry]
|
||||
name = "modorganizer-plugin_python"
|
||||
version = "3.0.0"
|
||||
description = ""
|
||||
authors = ["Mikaël Capelle <capelle.mikael@gmail.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.12"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pyright = "^1.1.369"
|
||||
ruff = "^0.2.1"
|
||||
poethepoet = "^0.23.0"
|
||||
mobase-stubs = { version = "^2.5.1a0", allow-prereleases = true }
|
||||
pyqt6 = "^6.7.0"
|
||||
pytest = "^8.2.2"
|
||||
pybind11-stubgen = "^2.5.1"
|
||||
|
||||
[tool.poe.tasks]
|
||||
format-imports = "ruff check --select I tests typings --fix"
|
||||
format-ruff = "ruff format tests typings"
|
||||
format.sequence = ["format-imports", "format-ruff"]
|
||||
lint-ruff = "ruff check tests typings"
|
||||
lint-ruff-format = "ruff format --check tests typings"
|
||||
lint.sequence = ["lint-ruff", "lint-ruff-format"]
|
||||
lint.ignore_fail = "return_non_zero"
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py312"
|
||||
|
||||
[tool.ruff.lint]
|
||||
extend-select = ["B", "Q", "I"]
|
||||
|
||||
[tool.ruff.lint.isort.sections]
|
||||
mobase = ["mobase"]
|
||||
mobase_tests = ["mobase_tests"]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
section-order = [
|
||||
"future",
|
||||
"standard-library",
|
||||
"third-party",
|
||||
"first-party",
|
||||
"mobase",
|
||||
"mobase_tests",
|
||||
"local-folder",
|
||||
]
|
||||
|
||||
[tool.pyright]
|
||||
typeCheckingMode = "strict"
|
||||
reportMissingTypeStubs = true
|
||||
reportMissingModuleSource = false
|
||||
pythonPlatform = "Windows"
|
||||
@@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# order matters
|
||||
add_subdirectory(pybind11-qt)
|
||||
add_subdirectory(pybind11-utils)
|
||||
add_subdirectory(mobase)
|
||||
add_subdirectory(runner)
|
||||
add_subdirectory(proxy)
|
||||
@@ -0,0 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
pybind11_add_module(mobase MODULE)
|
||||
mo2_configure_library(mobase
|
||||
SOURCE_TREE
|
||||
WARNINGS 4
|
||||
EXTERNAL_WARNINGS 4
|
||||
AUTOMOC ON
|
||||
TRANSLATIONS OFF
|
||||
PRIVATE_DEPENDS uibase Qt::Core
|
||||
)
|
||||
target_link_libraries(mobase PRIVATE pybind11::qt pybind11::utils)
|
||||
@@ -0,0 +1,110 @@
|
||||
# mobase
|
||||
|
||||
`mobase` is a ModOrganizer2 Python API.
|
||||
It provides access to the part of
|
||||
[`uibase`](https://github.com/ModOrganizer2/modorganizer-uibase) C++ API from
|
||||
Python through [`pybind11`](https://github.com/pybind/pybind11).
|
||||
|
||||
## Organization
|
||||
|
||||
**Important:** All (most) files should include `pybind11_all.h` (either directly
|
||||
or through another header) to get proper `type_caster` available.
|
||||
|
||||
- `mobase.cpp` contains the `PYBIND11_MODULE` definition of `mobase` but is otherwise
|
||||
the entrypoint for other functions.
|
||||
- `wrappers.h` contains the declaration of most functions implemented under
|
||||
`wrappers/`.
|
||||
- The other files under `wrappers/` contains bindings and trampoline classes (see
|
||||
below) for `uibase` classes.
|
||||
- `basic_classes.cpp` contains the bindings for most classes that cannot be extended
|
||||
in Python (`IOrganizer`, `IModInterface`, etc.)
|
||||
- `game_features.cpp` contains the bindings and trampoline classes for game features.
|
||||
- `pyfiletree.h` and `pyfiletree.cpp` contains bindings for the `IFileTree`-related
|
||||
classes.
|
||||
- `pyplugins.h` contains the trampoline classes for the `IPluginXXX` classes and
|
||||
`pyplugins.cpp` the bindings.
|
||||
- `pyplugins.h` is required since the trampoline classes are tagged with `Q_OBJECT`,
|
||||
and MOC does not work if the classes are declared in a C++ file.
|
||||
- `pyplugins.cpp` also contains the `extract_plugins` function in `mobase.private`
|
||||
that is used to extract plugins from Python object in the runner.
|
||||
- `widgets.cpp` contains the bindings for the widget classes.
|
||||
- `wrappers.cpp` contains the trampoline and bindings for non-plugin classes that can
|
||||
be extended through Python.
|
||||
|
||||
## Updating mobase
|
||||
|
||||
### Classes that cannot be extended through Python
|
||||
|
||||
Updating or adding classes that cannot be extended through Python is quite easily.
|
||||
One simply needs to declare the appropriate `py::class_` or add new `.def()`.
|
||||
|
||||
See below for things to remember when creating pybind11 bindings.
|
||||
|
||||
### Free functions
|
||||
|
||||
Similar to classes that cannot be extended through Python, see above.
|
||||
|
||||
### Classes that can be extended through Python: Plugins
|
||||
|
||||
To extend plugins, simply update the trampoline classes in `pyplugins.h` and the
|
||||
bindings in `pyplugins.cpp`.
|
||||
|
||||
**Note:** For new plugins, simply look at the existing one.
|
||||
|
||||
### Classes that can be extended through Python: Game Features
|
||||
|
||||
To extend or expose game features:
|
||||
|
||||
- Create (if there is not already one) a trampoline class for the feature in
|
||||
`game_features.cpp`.
|
||||
- Add implementation of missing functions if required.
|
||||
- Add the bindings in `add_game_feature_bindings` in `game_features.cpp`.
|
||||
- For new feature, add the feature type to `GameFeaturesHelper::GameFeatures` in
|
||||
`game_features.cpp`.
|
||||
|
||||
### Classes that can be extended through Python: Others
|
||||
|
||||
Non-plugin classes should be added to the `wrappers.cpp` file and should be exposed
|
||||
with `std::shared_ptr<>` or `qobject_holder<>` holders.
|
||||
|
||||
- If the classes extends `QObject`, use a `qobject_holder`.
|
||||
- Otherwise use a `std::shared_ptr<>` holder and add a `MO2_PYBIND11_SHARED_CPP_HOLDER`
|
||||
declaration in `pybind11_all.h`.
|
||||
|
||||
Trampoline can be defined directly in `wrappers.cpp`, and bindings in the appropriate
|
||||
function.
|
||||
See the existing classes for example.
|
||||
|
||||
**Important:**
|
||||
You need to make sure that `uibase` manipulates such classes through
|
||||
`std::shared_ptr<>` (unless those inherit `QObject`).
|
||||
Using `std::unique_ptr<>` is not possible since `std::unique_ptr<>` cannot have custom
|
||||
runtime-specified deleters.
|
||||
|
||||
## Things to remember
|
||||
|
||||
Here are a few things to remember when creating bindings:
|
||||
|
||||
- If a function has multiple overloads that can conflict in Python, the more complex
|
||||
one must be defined first as pybind11 will try calling them in order.
|
||||
- If a C++ function expect a `QString`, `QFileInfo` or `QDir` that represents a file or
|
||||
a directory, wrapping the function with `wrap_for_filepath` or `wrap_for_directory` is
|
||||
a good idea. This allows Python to call the function with `pathlib.Path`.
|
||||
- Most of the C++ function taking a reference to modify in C++ cannot be directly
|
||||
exposed in Python since Python cannot modify reference to simple type (e.g.
|
||||
`QString&` or `int&`).
|
||||
The best way to expose such function is to bind a lambda that returns a variant from
|
||||
Python, e.g.
|
||||
|
||||
```cpp
|
||||
// assume the C++ function is QString fn(QString& foo, QString const& bar, int& maz);
|
||||
|
||||
m.def("function", [](QString& foo, QString const& bar, int& maz) {
|
||||
// call the function
|
||||
auto ret = function(foo, bar, maz);
|
||||
|
||||
// make a tuple containing the return value (if there is one), and the modified
|
||||
// values passed by reference
|
||||
return std::make_tuple(ret, foo, maz);
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "deprecation.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
void show_deprecation_warning(std::string_view name, std::string_view message,
|
||||
bool show_once)
|
||||
{
|
||||
|
||||
// Contains the list of filename / line number for which a deprecation
|
||||
// warning has already been shown.
|
||||
static std::set<std::pair<std::string, int>> DeprecatedLines;
|
||||
|
||||
// Find the caller:
|
||||
auto inspect = py::module_::import("inspect");
|
||||
auto current_frame = inspect.attr("currentframe")();
|
||||
py::sequence callable_frame = inspect.attr("getouterframes")(current_frame, 2);
|
||||
auto last_frame = callable_frame[py::int_(-1)];
|
||||
auto filename = last_frame.attr("filename").cast<std::string>();
|
||||
auto function = last_frame.attr("function").cast<std::string>();
|
||||
auto lineno = last_frame.attr("lineno").cast<int>();
|
||||
|
||||
// Only show once if requested:
|
||||
if (show_once && DeprecatedLines.contains({filename, lineno})) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register the deprecation:
|
||||
DeprecatedLines.emplace(filename, lineno);
|
||||
|
||||
auto path = relative(std::filesystem::path(filename),
|
||||
QCoreApplication::applicationDirPath().toStdWString());
|
||||
|
||||
// Show the message:
|
||||
if (message.empty()) {
|
||||
MOBase::log::warn("[deprecated] {} in {} [{}:{}].", name, function,
|
||||
path.native(), lineno);
|
||||
}
|
||||
else {
|
||||
MOBase::log::warn("[deprecated] {} in {} [{}:{}]: {}", name, function,
|
||||
path.native(), lineno, message);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef PYTHONRUNNER_UTILS_H
|
||||
#define PYTHONRUNNER_UTILS_H
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
/**
|
||||
* @brief Show a deprecation warning.
|
||||
*
|
||||
* This methods will print a warning in MO2 log containing the location of
|
||||
* the call to the deprecated function. If show_once is true, the
|
||||
* deprecation warning will only be logged the first time the function is
|
||||
* called at this location.
|
||||
*
|
||||
* @param name Name of the deprecated function.
|
||||
* @param message Deprecation message.
|
||||
* @param show_once Only show the message once per call location.
|
||||
*/
|
||||
void show_deprecation_warning(std::string_view name, std::string_view message = "",
|
||||
bool show_once = true);
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma warning(disable : 4100)
|
||||
#pragma warning(disable : 4996)
|
||||
|
||||
#include <format>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
#include <pybind11/embed.h>
|
||||
|
||||
#include "pybind11_all.h"
|
||||
|
||||
#include "wrappers/pyfiletree.h"
|
||||
#include "wrappers/wrappers.h"
|
||||
|
||||
// TODO: remove these include (only for testing)
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QWidget>
|
||||
#include <iplugin.h>
|
||||
#include <iplugindiagnose.h>
|
||||
#include <ipluginfilemapper.h>
|
||||
#include <iplugingame.h>
|
||||
#include <iplugininstaller.h>
|
||||
#include <iplugininstallersimple.h>
|
||||
#include <ipluginlist.h>
|
||||
#include <ipluginmodpage.h>
|
||||
#include <ipluginpreview.h>
|
||||
#include <iplugintool.h>
|
||||
#include <isavegame.h>
|
||||
#include <isavegameinfowidget.h>
|
||||
|
||||
using namespace MOBase;
|
||||
namespace py = pybind11;
|
||||
|
||||
PYBIND11_MODULE(mobase, m)
|
||||
{
|
||||
using namespace mo2::python;
|
||||
|
||||
m.add_object("PyQt6", py::module_::import("PyQt6"));
|
||||
m.add_object("PyQt6.QtCore", py::module_::import("PyQt6.QtCore"));
|
||||
m.add_object("PyQt6.QtGui", py::module_::import("PyQt6.QtGui"));
|
||||
m.add_object("PyQt6.QtWidgets", py::module_::import("PyQt6.QtWidgets"));
|
||||
|
||||
// bindings
|
||||
//
|
||||
mo2::python::add_basic_bindings(m);
|
||||
mo2::python::add_wrapper_bindings(m);
|
||||
|
||||
// game features must be added before plugins
|
||||
mo2::python::add_game_feature_bindings(m);
|
||||
mo2::python::add_igamefeatures_classes(m);
|
||||
|
||||
mo2::python::add_plugins_bindings(m);
|
||||
|
||||
// widgets
|
||||
//
|
||||
py::module_ widgets = m.def_submodule("widgets");
|
||||
mo2::python::add_widget_bindings(widgets);
|
||||
|
||||
// utils
|
||||
//
|
||||
py::module_ utils = m.def_submodule("utils");
|
||||
mo2::python::add_utils_bindings(utils);
|
||||
|
||||
// functions
|
||||
//
|
||||
m.def("getFileVersion", wrap_for_filepath(&MOBase::getFileVersion),
|
||||
py::arg("filepath"));
|
||||
m.def("getProductVersion", wrap_for_filepath(&MOBase::getProductVersion),
|
||||
py::arg("executable"));
|
||||
m.def("getIconForExecutable", wrap_for_filepath(&MOBase::iconForExecutable),
|
||||
py::arg("executable"));
|
||||
|
||||
// typing stuff to be consistent with stubs and allow plugin developers to properly
|
||||
// type their code if they want
|
||||
{
|
||||
m.add_object("TypeVar", py::module_::import("typing").attr("TypeVar"));
|
||||
|
||||
auto s = m.attr("__dict__");
|
||||
|
||||
// expose MoVariant
|
||||
//
|
||||
// this needs to be defined, otherwise MoVariant is not found when actually
|
||||
// running plugins through MO2, making them crash (if plugins use MoVariant in
|
||||
// their own code)
|
||||
//
|
||||
m.add_object(
|
||||
"MoVariant",
|
||||
py::eval("None | bool | int | str | list[object] | dict[str, object]"));
|
||||
|
||||
// same thing for GameFeatureType
|
||||
//
|
||||
m.add_object("GameFeatureType", py::eval("TypeVar('GameFeatureType')", s));
|
||||
}
|
||||
|
||||
// private stuff for debugging/test
|
||||
py::module_ moprivate = m.def_submodule("private");
|
||||
|
||||
// expose a function to create a particular tree, only for debugging
|
||||
// purpose, not in mobase.
|
||||
mo2::python::add_make_tree_function(moprivate);
|
||||
moprivate.def("extract_plugins", &mo2::python::extract_plugins);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
#ifndef PYTHON_PYBIND11_ALL_H
|
||||
#define PYTHON_PYBIND11_ALL_H
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <QDir>
|
||||
|
||||
#include <pybind11/operators.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
#include <pybind11/stl/filesystem.h>
|
||||
|
||||
#include "pybind11_qt/pybind11_qt.h"
|
||||
|
||||
#include "pybind11_utils/functional.h"
|
||||
#include "pybind11_utils/shared_cpp_owner.h"
|
||||
#include "pybind11_utils/smart_variant_wrapper.h"
|
||||
|
||||
#include <game_feature.h>
|
||||
#include <isavegame.h>
|
||||
#include <pluginrequirements.h>
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <>
|
||||
struct smart_variant_converter<QString> {
|
||||
|
||||
static QString from(std::filesystem::path const& path)
|
||||
{
|
||||
return QString::fromStdWString(path.native());
|
||||
}
|
||||
|
||||
static QString from(QFileInfo const& fileInfo)
|
||||
{
|
||||
return fileInfo.filePath();
|
||||
}
|
||||
|
||||
static QString from(QDir const& dir) { return dir.path(); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct smart_variant_converter<std::filesystem::path> {
|
||||
|
||||
static std::filesystem::path from(QString const& value)
|
||||
{
|
||||
return {value.toStdWString()};
|
||||
}
|
||||
|
||||
static std::filesystem::path from(QFileInfo const& fileInfo)
|
||||
{
|
||||
return fileInfo.filesystemFilePath();
|
||||
}
|
||||
|
||||
static std::filesystem::path from(QDir const& dir)
|
||||
{
|
||||
return dir.filesystemPath();
|
||||
}
|
||||
};
|
||||
|
||||
// we do not need specialization for QFileInfo and QDir because both of them can
|
||||
// be constructed from std::filesystem::path and QString already
|
||||
|
||||
} // namespace detail
|
||||
|
||||
using FileWrapper = smart_variant<QString, std::filesystem::path, QFileInfo>;
|
||||
using DirectoryWrapper = smart_variant<QString, std::filesystem::path, QDir>;
|
||||
|
||||
// wrap the given function to accept FileWrapper (str | PathLike | QFileInfo) at the
|
||||
// given argument positions (or any valid positions if Is... is empty)
|
||||
//
|
||||
template <std::size_t... Is, class Fn>
|
||||
auto wrap_for_filepath(Fn&& fn)
|
||||
{
|
||||
return mo2::python::wrap_arguments<FileWrapper, Is...>(std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
// wrap the given function to accept DirectoryWrapper (str | PathLike | QDir)
|
||||
// at the given argument positions (or any valid positions if Is... is empty)
|
||||
//
|
||||
template <std::size_t... Is, class Fn>
|
||||
auto wrap_for_directory(Fn&& fn)
|
||||
{
|
||||
return mo2::python::wrap_arguments<DirectoryWrapper, Is...>(
|
||||
std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
// wrap a function-like object to return a FileWrapper instead of its return type,
|
||||
// useful to generate proper typing in Python
|
||||
//
|
||||
// note that QFileInfo has a __fspath__ in Python, so it is quite easy to convert
|
||||
// from "FileWrapper", a.k.a., str | os.PathLike | QFileInfo to Path by simply
|
||||
// calling Path() on the return type if necessary
|
||||
//
|
||||
// this should be combined with custom return-value in PYBIND11_OVERRIDE(_PURE), see
|
||||
// ISaveGame binding for an example
|
||||
//
|
||||
template <class Fn>
|
||||
auto wrap_return_for_filepath(Fn&& fn)
|
||||
{
|
||||
return mo2::python::wrap_return<FileWrapper>(std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
// similar to wrap_return_for_filepath, except it returns a DirectoryWrapper instead
|
||||
// of its return type
|
||||
//
|
||||
// this is much less practical than wrap_return_for_filepath since QDir does not
|
||||
// expose __fspath__, so more complex things need to be done in Python, which is why
|
||||
// this should be used carefully (e.g., should not be used if the return type is
|
||||
// already QDir)
|
||||
//
|
||||
template <class Fn>
|
||||
auto wrap_return_for_directory(Fn&& fn)
|
||||
{
|
||||
return mo2::python::wrap_return<DirectoryWrapper>(std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
// convert a QList to QStringList - QString must be constructible from QString
|
||||
//
|
||||
template <class T>
|
||||
QStringList toQStringList(QList<T> const& list)
|
||||
{
|
||||
static_assert(std::is_constructible_v<QString, T>,
|
||||
"QString must be constructible from T.");
|
||||
return {list.begin(), list.end()};
|
||||
}
|
||||
|
||||
// convert a QStringList to a QList - T must be constructible from QString
|
||||
//
|
||||
template <class T>
|
||||
QList<T> toQList(QStringList const& list)
|
||||
{
|
||||
static_assert(std::is_constructible_v<T, QString>,
|
||||
"T must be constructible from QString.");
|
||||
return {list.begin(), list.end()};
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IPluginRequirement)
|
||||
MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::ISaveGame)
|
||||
MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::GameFeature)
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,416 @@
|
||||
#include "wrappers.h"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include "../pybind11_all.h"
|
||||
|
||||
#include <ipluginlist.h>
|
||||
#include <isavegameinfowidget.h>
|
||||
|
||||
#include <bsainvalidation.h>
|
||||
#include <dataarchives.h>
|
||||
#include <gameplugins.h>
|
||||
#include <igamefeatures.h>
|
||||
#include <localsavegames.h>
|
||||
#include <moddatachecker.h>
|
||||
#include <moddatacontent.h>
|
||||
#include <savegameinfo.h>
|
||||
#include <scriptextender.h>
|
||||
#include <unmanagedmods.h>
|
||||
|
||||
#include "pyfiletree.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
using namespace MOBase;
|
||||
using namespace pybind11::literals;
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
class PyBSAInvalidation : public BSAInvalidation {
|
||||
public:
|
||||
bool isInvalidationBSA(const QString& bsaName) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, BSAInvalidation, isInvalidationBSA, bsaName);
|
||||
}
|
||||
void deactivate(MOBase::IProfile* profile) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, BSAInvalidation, deactivate, profile);
|
||||
}
|
||||
void activate(MOBase::IProfile* profile) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, BSAInvalidation, activate, profile);
|
||||
}
|
||||
bool prepareProfile(MOBase::IProfile* profile) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, BSAInvalidation, prepareProfile, profile);
|
||||
}
|
||||
};
|
||||
|
||||
class PyDataArchives : public DataArchives {
|
||||
public:
|
||||
QStringList vanillaArchives() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, DataArchives, vanillaArchives, );
|
||||
}
|
||||
QStringList archives(const MOBase::IProfile* profile) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, DataArchives, archives, profile);
|
||||
}
|
||||
void addArchive(MOBase::IProfile* profile, int index,
|
||||
const QString& archiveName) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, DataArchives, addArchive, profile, index,
|
||||
archiveName);
|
||||
}
|
||||
void removeArchive(MOBase::IProfile* profile,
|
||||
const QString& archiveName) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, DataArchives, removeArchive, profile,
|
||||
archiveName);
|
||||
}
|
||||
};
|
||||
|
||||
class PyGamePlugins : public GamePlugins {
|
||||
public:
|
||||
void writePluginLists(const MOBase::IPluginList* pluginList) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(void, GamePlugins, writePluginLists, pluginList);
|
||||
}
|
||||
void readPluginLists(MOBase::IPluginList* pluginList) override
|
||||
{
|
||||
// TODO: cannot update plugin list or create one from Python so this is
|
||||
// useless
|
||||
PYBIND11_OVERRIDE_PURE(void, GamePlugins, readPluginLists, pluginList);
|
||||
}
|
||||
QStringList getLoadOrder() override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, GamePlugins, getLoadOrder, );
|
||||
}
|
||||
bool lightPluginsAreSupported() override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, GamePlugins, lightPluginsAreSupported, );
|
||||
}
|
||||
bool mediumPluginsAreSupported() override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, GamePlugins, mediumPluginsAreSupported, );
|
||||
}
|
||||
};
|
||||
|
||||
class PyLocalSavegames : public LocalSavegames {
|
||||
public:
|
||||
MappingType mappings(const QDir& profileSaveDir) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(MappingType, LocalSavegames, mappings,
|
||||
profileSaveDir);
|
||||
}
|
||||
bool prepareProfile(MOBase::IProfile* profile) override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, LocalSavegames, prepareProfile, profile);
|
||||
}
|
||||
};
|
||||
|
||||
class PyModDataChecker : public ModDataChecker {
|
||||
public:
|
||||
CheckReturn
|
||||
dataLooksValid(std::shared_ptr<const MOBase::IFileTree> fileTree) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(CheckReturn, ModDataChecker, dataLooksValid,
|
||||
fileTree);
|
||||
}
|
||||
|
||||
std::shared_ptr<MOBase::IFileTree>
|
||||
fix(std::shared_ptr<MOBase::IFileTree> fileTree) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE(std::shared_ptr<MOBase::IFileTree>, ModDataChecker, fix,
|
||||
fileTree);
|
||||
}
|
||||
};
|
||||
|
||||
class PyModDataContent : public ModDataContent {
|
||||
public:
|
||||
std::vector<Content> getAllContents() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::vector<Content>, ModDataContent,
|
||||
getAllContents, );
|
||||
;
|
||||
}
|
||||
std::vector<int>
|
||||
getContentsFor(std::shared_ptr<const MOBase::IFileTree> fileTree) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::vector<int>, ModDataContent, getContentsFor,
|
||||
fileTree);
|
||||
}
|
||||
};
|
||||
|
||||
class PySaveGameInfo : public SaveGameInfo {
|
||||
public:
|
||||
MissingAssets getMissingAssets(MOBase::ISaveGame const& save) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(MissingAssets, SaveGameInfo, getMissingAssets,
|
||||
&save);
|
||||
}
|
||||
ISaveGameInfoWidget* getSaveGameWidget(QWidget* parent = 0) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(ISaveGameInfoWidget*, SaveGameInfo,
|
||||
getSaveGameWidget, parent);
|
||||
}
|
||||
};
|
||||
|
||||
class PyScriptExtender : public ScriptExtender {
|
||||
public:
|
||||
QString BinaryName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, binaryName, );
|
||||
}
|
||||
|
||||
QString PluginPath() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(DirectoryWrapper, ScriptExtender, pluginPath, );
|
||||
}
|
||||
|
||||
QString loaderName() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, loaderName, );
|
||||
}
|
||||
|
||||
QString loaderPath() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(FileWrapper, ScriptExtender, loaderPath, );
|
||||
}
|
||||
|
||||
QString savegameExtension() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, savegameExtension, );
|
||||
}
|
||||
|
||||
bool isInstalled() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(bool, ScriptExtender, isInstalled, );
|
||||
}
|
||||
|
||||
QString getExtenderVersion() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, ScriptExtender, getExtenderVersion, );
|
||||
}
|
||||
|
||||
WORD getArch() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(WORD, ScriptExtender, getArch, );
|
||||
}
|
||||
};
|
||||
|
||||
class PyUnmanagedMods : public UnmanagedMods {
|
||||
public:
|
||||
QStringList mods(bool onlyOfficial) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QStringList, UnmanagedMods, mods, onlyOfficial);
|
||||
}
|
||||
QString displayName(const QString& modName) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(QString, UnmanagedMods, displayName, modName);
|
||||
}
|
||||
QFileInfo referenceFile(const QString& modName) const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(FileWrapper, UnmanagedMods, referenceFile, modName);
|
||||
}
|
||||
QStringList secondaryFiles(const QString& modName) const override
|
||||
{
|
||||
return toQStringList([&] {
|
||||
PYBIND11_OVERRIDE_PURE(QList<FileWrapper>, UnmanagedMods,
|
||||
secondaryFiles, modName);
|
||||
}());
|
||||
}
|
||||
};
|
||||
|
||||
void add_game_feature_bindings(pybind11::module_ m)
|
||||
{
|
||||
// this is just to allow accepting GameFeature in function, we do not expose
|
||||
// anything from game feature to Python since typeInfo() is useless in Python
|
||||
//
|
||||
py::class_<GameFeature, std::shared_ptr<GameFeature>>(m, "GameFeature");
|
||||
|
||||
// BSAInvalidation
|
||||
|
||||
py::class_<BSAInvalidation, GameFeature, PyBSAInvalidation,
|
||||
std::shared_ptr<BSAInvalidation>>(m, "BSAInvalidation")
|
||||
.def(py::init<>())
|
||||
.def("isInvalidationBSA", &BSAInvalidation::isInvalidationBSA, "name"_a)
|
||||
.def("deactivate", &BSAInvalidation::deactivate, "profile"_a)
|
||||
.def("activate", &BSAInvalidation::activate, "profile"_a);
|
||||
|
||||
// DataArchives
|
||||
|
||||
py::class_<DataArchives, GameFeature, PyDataArchives,
|
||||
std::shared_ptr<DataArchives>>(m, "DataArchives")
|
||||
.def(py::init<>())
|
||||
.def("vanillaArchives", &DataArchives::vanillaArchives)
|
||||
.def("archives", &DataArchives::archives, "profile"_a)
|
||||
.def("addArchive", &DataArchives::addArchive, "profile"_a, "index"_a,
|
||||
"name"_a)
|
||||
.def("removeArchive", &DataArchives::removeArchive, "profile"_a, "name"_a);
|
||||
|
||||
// GamePlugins
|
||||
|
||||
py::class_<GamePlugins, GameFeature, PyGamePlugins,
|
||||
std::shared_ptr<GamePlugins>>(m, "GamePlugins")
|
||||
.def(py::init<>())
|
||||
.def("writePluginLists", &GamePlugins::writePluginLists, "plugin_list"_a)
|
||||
.def("readPluginLists", &GamePlugins::readPluginLists, "plugin_list"_a)
|
||||
.def("getLoadOrder", &GamePlugins::getLoadOrder)
|
||||
.def("lightPluginsAreSupported", &GamePlugins::lightPluginsAreSupported)
|
||||
.def("mediumPluginsAreSupported", &GamePlugins::mediumPluginsAreSupported);
|
||||
|
||||
// LocalSavegames
|
||||
|
||||
py::class_<LocalSavegames, GameFeature, PyLocalSavegames,
|
||||
std::shared_ptr<LocalSavegames>>(m, "LocalSavegames")
|
||||
.def(py::init<>())
|
||||
.def("mappings", &LocalSavegames::mappings, "profile_save_dir"_a)
|
||||
.def("prepareProfile", &LocalSavegames::prepareProfile, "profile"_a);
|
||||
|
||||
// ModDataChecker
|
||||
|
||||
py::class_<ModDataChecker, GameFeature, PyModDataChecker,
|
||||
std::shared_ptr<ModDataChecker>>
|
||||
pyModDataChecker(m, "ModDataChecker");
|
||||
|
||||
py::enum_<ModDataChecker::CheckReturn>(pyModDataChecker, "CheckReturn")
|
||||
.value("INVALID", ModDataChecker::CheckReturn::INVALID)
|
||||
.value("FIXABLE", ModDataChecker::CheckReturn::FIXABLE)
|
||||
.value("VALID", ModDataChecker::CheckReturn::VALID)
|
||||
.export_values();
|
||||
|
||||
pyModDataChecker.def(py::init<>())
|
||||
.def("dataLooksValid", &ModDataChecker::dataLooksValid, "filetree"_a)
|
||||
.def("fix", &ModDataChecker::fix, "filetree"_a);
|
||||
|
||||
// ModDataContent
|
||||
py::class_<ModDataContent, GameFeature, PyModDataContent,
|
||||
std::shared_ptr<ModDataContent>>
|
||||
pyModDataContent(m, "ModDataContent");
|
||||
|
||||
py::class_<ModDataContent::Content>(pyModDataContent, "Content")
|
||||
.def(py::init<int, QString, QString, bool>(), "id"_a, "name"_a, "icon"_a,
|
||||
"filter_only"_a = false)
|
||||
.def_property_readonly("id", &ModDataContent::Content::id)
|
||||
.def_property_readonly("name", &ModDataContent::Content::name)
|
||||
.def_property_readonly("icon", &ModDataContent::Content::icon)
|
||||
.def("isOnlyForFilter", &ModDataContent::Content::isOnlyForFilter);
|
||||
|
||||
pyModDataContent.def(py::init<>())
|
||||
.def("getAllContents", &ModDataContent::getAllContents)
|
||||
.def("getContentsFor", &ModDataContent::getContentsFor, "filetree"_a);
|
||||
|
||||
// SaveGameInfo
|
||||
|
||||
py::class_<SaveGameInfo, GameFeature, PySaveGameInfo,
|
||||
std::shared_ptr<SaveGameInfo>>(m, "SaveGameInfo")
|
||||
.def(py::init<>())
|
||||
.def("getMissingAssets", &SaveGameInfo::getMissingAssets, "save"_a)
|
||||
.def("getSaveGameWidget", &SaveGameInfo::getSaveGameWidget,
|
||||
py::return_value_policy::reference, "parent"_a);
|
||||
|
||||
// ScriptExtender
|
||||
|
||||
py::class_<ScriptExtender, GameFeature, PyScriptExtender,
|
||||
std::shared_ptr<ScriptExtender>>(m, "ScriptExtender")
|
||||
.def(py::init<>())
|
||||
.def("binaryName", &ScriptExtender::BinaryName)
|
||||
.def("pluginPath", wrap_return_for_directory(&ScriptExtender::PluginPath))
|
||||
.def("loaderName", &ScriptExtender::loaderName)
|
||||
.def("loaderPath", wrap_return_for_filepath(&ScriptExtender::loaderPath))
|
||||
.def("savegameExtension", &ScriptExtender::savegameExtension)
|
||||
.def("isInstalled", &ScriptExtender::isInstalled)
|
||||
.def("getExtenderVersion", &ScriptExtender::getExtenderVersion)
|
||||
.def("getArch", &ScriptExtender::getArch);
|
||||
|
||||
// UnmanagedMods
|
||||
|
||||
py::class_<UnmanagedMods, GameFeature, PyUnmanagedMods,
|
||||
std::shared_ptr<UnmanagedMods>>(m, "UnmanagedMods")
|
||||
.def(py::init<>())
|
||||
.def("mods", &UnmanagedMods::mods, "official_only"_a)
|
||||
.def("displayName", &UnmanagedMods::displayName, "mod_name"_a)
|
||||
.def("referenceFile",
|
||||
wrap_return_for_filepath(&UnmanagedMods::referenceFile), "mod_name"_a)
|
||||
.def(
|
||||
"secondaryFiles",
|
||||
[](UnmanagedMods* m, const QString& modName) -> QList<FileWrapper> {
|
||||
auto result = m->secondaryFiles(modName);
|
||||
return {result.begin(), result.end()};
|
||||
},
|
||||
"mod_name"_a);
|
||||
}
|
||||
|
||||
void add_igamefeatures_classes(py::module_ m)
|
||||
{
|
||||
py::class_<IGameFeatures>(m, "IGameFeatures")
|
||||
.def("registerFeature",
|
||||
py::overload_cast<QStringList const&, std::shared_ptr<GameFeature>,
|
||||
int, bool>(&IGameFeatures::registerFeature),
|
||||
"games"_a, "feature"_a, "priority"_a, "replace"_a = false)
|
||||
.def("registerFeature",
|
||||
py::overload_cast<MOBase::IPluginGame*, std::shared_ptr<GameFeature>,
|
||||
int, bool>(&IGameFeatures::registerFeature),
|
||||
"game"_a, "feature"_a, "priority"_a, "replace"_a = false)
|
||||
.def("registerFeature",
|
||||
py::overload_cast<std::shared_ptr<GameFeature>, int, bool>(
|
||||
&IGameFeatures::registerFeature),
|
||||
"feature"_a, "priority"_a, "replace"_a = false)
|
||||
.def("unregisterFeature", &IGameFeatures::unregisterFeature, "feature"_a)
|
||||
.def("unregisterFeatures", &unregister_feature, "feature_type"_a)
|
||||
.def("gameFeature", &extract_feature, "feature_type"_a,
|
||||
py ::return_value_policy::reference);
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
|
||||
namespace mo2::python {
|
||||
|
||||
class GameFeaturesHelper {
|
||||
template <class F, std::size_t... Is>
|
||||
static void helper(F&& f, std::index_sequence<Is...>)
|
||||
{
|
||||
(f(static_cast<
|
||||
std::tuple_element_t<Is, MOBase::details::BaseGameFeaturesP>>(
|
||||
nullptr)),
|
||||
...);
|
||||
}
|
||||
|
||||
public:
|
||||
// apply the function f on a null-pointer of type Feature* for each game
|
||||
// feature
|
||||
template <class F>
|
||||
static void apply(F&& f)
|
||||
{
|
||||
helper(f, std::make_index_sequence<
|
||||
std::tuple_size_v<MOBase::details::BaseGameFeaturesP>>{});
|
||||
}
|
||||
};
|
||||
|
||||
pybind11::object extract_feature(IGameFeatures const& gameFeatures,
|
||||
pybind11::object type)
|
||||
{
|
||||
py::object py_feature = py::none();
|
||||
GameFeaturesHelper::apply([&]<class Feature>(Feature*) {
|
||||
if (py::type::of<Feature>().is(type)) {
|
||||
py_feature = py::cast(gameFeatures.gameFeature<Feature>(),
|
||||
py::return_value_policy::reference);
|
||||
}
|
||||
});
|
||||
return py_feature;
|
||||
}
|
||||
|
||||
int unregister_feature(MOBase::IGameFeatures& gameFeatures, pybind11::object type)
|
||||
{
|
||||
int count = 0;
|
||||
GameFeaturesHelper::apply([&]<class Feature>(Feature*) {
|
||||
if (py::type::of<Feature>().is(type)) {
|
||||
count = gameFeatures.unregisterFeatures<Feature>();
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
} // namespace mo2::python
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user