mirror of
https://github.com/ModOrganizer2/pystubs-generation.git
synced 2026-07-27 14:07:13 -07:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca2ca66de8 |
@@ -1,38 +1,36 @@
|
||||
name: Build Documentation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.11
|
||||
- uses: abatilo/actions-poetry@v2
|
||||
- name: Install
|
||||
run: |
|
||||
poetry install
|
||||
- name: Install libgl1
|
||||
run: sudo apt install -y libgl1 libegl1 libglib2.0-0 libxkbcommon0 libdbus-1-3
|
||||
- name: Copy stubs
|
||||
run: cp stubs/2.5.0/mobase-stubs/__init__.pyi docs/mobase.py
|
||||
- name: Build
|
||||
run: poetry run sphinx-build -b html docs/source docs/build
|
||||
env:
|
||||
PYTHONPATH: docs
|
||||
- if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
name: Deploy Documentation
|
||||
uses: JamesIves/github-pages-deploy-action@v4
|
||||
with:
|
||||
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
||||
repository-name: ModOrganizer2/python-plugins-doc
|
||||
branch: master
|
||||
folder: docs/build
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
# Standard drop-in approach that should work for most people.
|
||||
- uses: ammaraskar/sphinx-action@master
|
||||
env:
|
||||
PYTHONPATH: .
|
||||
with:
|
||||
pre-build-command: "apt-get update -y && apt-get install -y libgl1-mesa-glx && cp stubs/2.4.0/mobase.pyi docs/mobase.py"
|
||||
docs-folder: "docs/"
|
||||
|
||||
- name: Install SSH Client 🔑
|
||||
uses: webfactory/ssh-agent@v0.4.1
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
SSH: true
|
||||
REPOSITORY_NAME: ModOrganizer2/python-plugins-doc
|
||||
BRANCH: master
|
||||
FOLDER: docs/build/html
|
||||
|
||||
@@ -5,20 +5,20 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
checks:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.11
|
||||
- uses: abatilo/actions-poetry@v2
|
||||
- name: Install
|
||||
run: |
|
||||
poetry install
|
||||
- name: Lint
|
||||
run: |
|
||||
poetry run black src --check --diff
|
||||
poetry run isort -c src
|
||||
poetry run mypy src
|
||||
poetry run ruff src
|
||||
poetry run pyright src
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
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
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
name: Check Documentation
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
# Standard drop-in approach that should work for most people.
|
||||
- uses: ammaraskar/sphinx-action@master
|
||||
env:
|
||||
PYTHONPATH: .
|
||||
with:
|
||||
pre-build-command: "apt-get update -y && apt-get install -y libgl1-mesa-glx && cp stubs/2.4.0/mobase.pyi docs/mobase.py"
|
||||
docs-folder: "docs/"
|
||||
@@ -1,57 +1,40 @@
|
||||
# This workflows will upload a Python Package using Twine when a release is created
|
||||
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
||||
|
||||
name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI
|
||||
name: Upload Python Package
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["*"]
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build distribution 📦
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Replace string
|
||||
uses: frabert/replace-string-action@v1.1
|
||||
id: version
|
||||
with:
|
||||
string: ${{ github.ref_name }}
|
||||
pattern: "v?([0-9][.][0-9][.][0-9]).*"
|
||||
replace-with: "$1"
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.11
|
||||
- uses: abatilo/actions-poetry@v2
|
||||
- name: Build
|
||||
run: |
|
||||
cd stubs/setup
|
||||
mkdir mobase-stubs
|
||||
cp -r ../${{ steps.version.outputs.replaced }}/mobase-stubs/* mobase-stubs/
|
||||
sed -i 's/__version__ = ".*"/__version__ = "${{ github.ref_name }}"/' mobase-stubs/__init__.pyi
|
||||
TAG=${{ github.ref_name }}
|
||||
poetry version ${TAG#v}
|
||||
poetry build
|
||||
- name: Store the distribution packages
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: stubs/setup/dist/
|
||||
deploy:
|
||||
|
||||
publish-to-pypi:
|
||||
name: Publish Python 🐍 distribution 📦 to PyPI
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
||||
|
||||
steps:
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
- name: Publish distribution 📦 to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: Replace string
|
||||
uses: frabert/replace-string-action@v1.1
|
||||
id: version
|
||||
with:
|
||||
string: ${{ github.event.release.tag_name }}
|
||||
pattern: "v?([0-9][.][0-9][.][0-9]).*"
|
||||
replace-with: "$1"
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel twine
|
||||
- name: Build and publish
|
||||
env:
|
||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
run: |
|
||||
cd stubs/setup
|
||||
cp ../${{ steps.version.outputs.replaced }}/mobase.pyi mobase-stubs/__init__.pyi
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload dist/*
|
||||
|
||||
+1
-2
@@ -2,10 +2,9 @@
|
||||
.mypy_cache
|
||||
__pycache__
|
||||
.vscode
|
||||
**/*.egg-info
|
||||
|
||||
# The 'bin/' directory:
|
||||
bin
|
||||
docs/build
|
||||
docs/mobase.py
|
||||
docs/source/api
|
||||
docs/source/api
|
||||
@@ -16,12 +16,23 @@ MO2.
|
||||
You can install stubs for a specific version of MO2:
|
||||
|
||||
```bash
|
||||
pip install mobase-stubs==2.5.*
|
||||
pip install mobase-stubs==2.3.2.*
|
||||
```
|
||||
|
||||
If you want development stubs, you can install them this way:
|
||||
```bash
|
||||
# Clone this repository:
|
||||
git clone https://github.com/ModOrganizer2/pystubs-generation.git
|
||||
|
||||
# Install the stubs:
|
||||
cd pystubs-generation/stubs/setup
|
||||
pip install .
|
||||
```
|
||||
|
||||
Some words of warning:
|
||||
|
||||
- The stubs are as correct as possible, but some errors are expected.
|
||||
- If you see a `InterfaceNotImplemented` class anywhere in the stubs, it means that
|
||||
a proper interface is currently not available.
|
||||
- Some classes are said (in the stubs) to inherit `QWidget` or `QObject`. This is true
|
||||
on the C++ side but NOT on the python side. The inheritance is only added to help with
|
||||
auto-completion since these classes also override `__getattr__` to dispatch to the
|
||||
@@ -33,39 +44,35 @@ Some words of warning:
|
||||
|
||||
The stubs are generated using python by parsing the `mobase` module.
|
||||
You need the version of python that matches your current MO2 installation: e.g., if you
|
||||
have a `python310.dll` in your MO2 installation path, then you need **Python 3.10**.
|
||||
have a `python38.dll` in your MO2 installation path, then you need **Python 3.8**.
|
||||
|
||||
To generate the stubs, you can run:
|
||||
|
||||
```bash
|
||||
# install the package
|
||||
poetry install
|
||||
|
||||
# change the output folder to whatever you want
|
||||
mo2-stubs-generator -c configs/config-2.4.yml -o mobase-stubs ${MO2_INSTALL_PATH}
|
||||
```
|
||||
# Change the output folder to whatever you want:
|
||||
python main.py -c configs\config-2.4.yml ${MO2_INSTALL_PATH}
|
||||
```
|
||||
|
||||
Where `${MO2_INSTALL_PATH}` is the path to your MO2 installation (the one
|
||||
containing `ModOrganizer.exe`).
|
||||
Where `${MO2_INSTALL_PATH}` is the path to your MO2 installation (the one containing `ModOrganizer.exe`).
|
||||
|
||||
The stubs are generated under `stubs/setup/mobase-stubs` by default, you
|
||||
can change the output file by using the `-o` option.
|
||||
The stubs under `stubs/setup/mobase-stubs` should not be committed as these are
|
||||
generated from the version stubs under `stubs/${VERSION}/mobase-stubs`.
|
||||
The stubs are generated under `stubs/setup/mobase-stubs/__init__.pyi`, you
|
||||
can change the output file by using the `-o` option
|
||||
The latest stubs are kept under `stubs/setup/mobase-stubs/__init__.pyi`,
|
||||
and when a new version is released, the stubs are backed-up under
|
||||
`stubs/x.y.z/mobase.pyi`.
|
||||
|
||||
A few options are available for `mo2-stubs-generator`:
|
||||
A few options are available for `main.py`:
|
||||
|
||||
```bash
|
||||
$ mo2-stubs-generator --help
|
||||
usage: stubs generator for the MO2 python interface [-h] [-o OUTPUT] [-v] [-c CONFIG] INSTALL_DIR
|
||||
```
|
||||
usage: Stubs generator for the MO2 python interface [-h] [-o OUTPUT] [-v] [-c CONFIG] INSTALL_DIR
|
||||
|
||||
positional arguments:
|
||||
INSTALL_DIR installation directory of Mod Organizer 2
|
||||
|
||||
options:
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-o OUTPUT, --output OUTPUT
|
||||
output folder (default stubs/setup/mobase-stubs)
|
||||
output file (default stubs/setup/mobase-stubs/__init__.pyi)
|
||||
-v, --verbose verbose mode (all logs go to stderr)
|
||||
-c CONFIG, --config CONFIG
|
||||
configuration file
|
||||
@@ -74,7 +81,19 @@ options:
|
||||
The stubs generator will try hard to find a valid stubs for all classes
|
||||
and methods of `mobase`.
|
||||
A lot of information is available through the `-v` options. Without it,
|
||||
only conversions or fixes considered "strange" will be shown.
|
||||
only conversions or fixes
|
||||
considered "strange" will be shown.
|
||||
For instance, here is the output with the current `config-2.4.yml` file:
|
||||
|
||||
```
|
||||
WARNING: Replacing IOrganizer::FileInfo with FileInfo.
|
||||
WARNING: Replacing IOrganizer::FileInfo with FileInfo.
|
||||
WARNING: Replacing IPluginInstaller::EInstallResult with InstallResult.
|
||||
WARNING: Replacing IPluginInstaller::EInstallResult with InstallResult.
|
||||
```
|
||||
|
||||
As you can see, only a few types were manually fixed (specified in
|
||||
`config-2.4.yml`).
|
||||
|
||||
## Configuration file
|
||||
|
||||
@@ -83,37 +102,20 @@ deduced by `main` (or are too complex to deduce), and the documentation for ever
|
||||
|
||||
## Uploading the stubs to pypi
|
||||
|
||||
The upload of the stubs to [https://pypi.org/project/mobase-stubs/](https://pypi.org/project/mobase-stubs/)
|
||||
should be done automatically when a new Github tag is pushed.
|
||||
The upload of the stubs to https://pypi.org/project/mobase-stubs/ should be
|
||||
done automatically when a new Github release is made.
|
||||
|
||||
## Extras — Using `mobase` in a Python interpreter
|
||||
## Extras — Starts a python interpreter with `mobase`
|
||||
|
||||
It is possible to start a (i)python interpreter with `mobase` imported by running
|
||||
It is possible to start a (i)python interpret with `mobase` imported by running:
|
||||
|
||||
```bash
|
||||
python -i -m mo2.stubs.generator.loader ${MO2_INSTALL_PATH}
|
||||
```
|
||||
python -im generator.loader ${MO2_INSTALL_PATH}
|
||||
```
|
||||
|
||||
You can also import `mobase` in your code using the following (after installing
|
||||
this package):
|
||||
This has no real usage except for MO2 developers since most classes from the `mobase` module cannot be instantiated.
|
||||
|
||||
```python
|
||||
from mo2.stubs.generator import load_mobase
|
||||
|
||||
mobase = load_mobase(MO2_INSTALL_PATH)
|
||||
|
||||
# the above will probably not give you type-completion in your IDE or typing, so
|
||||
# you can use the following (if the stubs are installed)
|
||||
load_mobase(MO2_INSTALL_PATH)
|
||||
import mobase
|
||||
import mobase.widgets
|
||||
```
|
||||
|
||||
|
||||
**Note:** Most classes in `mobase` cannot be instantiated, so this is mostly intended
|
||||
for MO2 developers.
|
||||
|
||||
## License
|
||||
# License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
|
||||
+44
-160
@@ -1,14 +1,24 @@
|
||||
---
|
||||
# version of the configuration
|
||||
version: 2
|
||||
version: 1
|
||||
|
||||
# version of the stubs - this is overridden when publishing
|
||||
__version__: "2.5.0"
|
||||
# This is the list of type to replace:
|
||||
replacements:
|
||||
Organizer::FileInfo: FileInfo
|
||||
IOrganizer::FileInfo: FileInfo
|
||||
IPluginInstaller::EInstallResult: InstallResult
|
||||
GuessedValue< QString>: GuessedString
|
||||
|
||||
# List of names to ignores:
|
||||
ignores:
|
||||
- toPyQt
|
||||
|
||||
# This is the root of the mobase module and will contain everything
|
||||
# related to functions / classes, including their documentation.
|
||||
mobase:
|
||||
|
||||
# Version of the stubs.
|
||||
__version__: 2.5.0.dev0
|
||||
|
||||
getFileVersion:
|
||||
__doc__: Retrieve the file version of the given executable.
|
||||
args:
|
||||
@@ -255,7 +265,7 @@ mobase:
|
||||
type: str
|
||||
desc: Full path to the file.
|
||||
origins:
|
||||
type: list[str]
|
||||
type: List[str]
|
||||
desc: |
|
||||
List of origins containing providing this file. The first origin in the list
|
||||
is the highest priority one (actually providing the file).
|
||||
@@ -319,7 +329,7 @@ mobase:
|
||||
__doc__: |
|
||||
The parent tree containing this entry, or a `None` if this entry is the root
|
||||
or the parent tree is unreachable.
|
||||
type: IFileTree | None
|
||||
type: Optional["IFileTree"]
|
||||
|
||||
path:
|
||||
__doc__: |
|
||||
@@ -356,8 +366,6 @@ mobase:
|
||||
returns:
|
||||
lightPluginsAreSupported:
|
||||
returns: True if light plugins are supported, False otherwise.
|
||||
overridePluginsAreSupported:
|
||||
returns: True if override plugins are supported, False otherwise.
|
||||
readPluginLists:
|
||||
__doc__:
|
||||
args:
|
||||
@@ -665,7 +673,7 @@ mobase:
|
||||
__doc__: |
|
||||
The entry at the given location, or `None` if the entry was not found or
|
||||
was not of the correct type.
|
||||
type: IFileTree | FileTreeEntry | None
|
||||
type: Optional[Union["IFileTree", "FileTreeEntry"]]
|
||||
|
||||
insert:
|
||||
__doc__: |
|
||||
@@ -1200,7 +1208,7 @@ mobase:
|
||||
IModRepositoryBridge:
|
||||
|
||||
__bases__:
|
||||
- PyQt6.QtCore.QObject
|
||||
- PyQt5.QtCore.QObject
|
||||
|
||||
signals[]:
|
||||
descriptionAvailable:
|
||||
@@ -1228,7 +1236,7 @@ mobase:
|
||||
type: MoVariant
|
||||
desc: The data that was included in the request.
|
||||
result_data:
|
||||
type: dict[str, MoVariant]
|
||||
type: Dict[str, MoVariant]
|
||||
desc: The data included in the response.
|
||||
|
||||
filesAvailable:
|
||||
@@ -1244,7 +1252,7 @@ mobase:
|
||||
type: MoVariant
|
||||
desc: The data that was included in the request.
|
||||
result_data:
|
||||
type: list[ModRepositoryFileInfo]
|
||||
type: List[ModRepositoryFileInfo]
|
||||
desc: List of file information objects.
|
||||
|
||||
fileInfoAvailable:
|
||||
@@ -1271,7 +1279,7 @@ mobase:
|
||||
type: MoVariant
|
||||
desc: The data that was included in the request.
|
||||
result_data:
|
||||
type: dict[str, MoVariant]
|
||||
type: Dict[str, MoVariant]
|
||||
desc: The data included in the response.
|
||||
|
||||
downloadURLsAvailable:
|
||||
@@ -1293,7 +1301,7 @@ mobase:
|
||||
type: MoVariant
|
||||
desc: The data that was included in the request.
|
||||
result_data:
|
||||
type: dict[str, MoVariant]
|
||||
type: Dict[str, MoVariant]
|
||||
desc: The data included in the response.
|
||||
|
||||
endorsementsAvailable:
|
||||
@@ -1364,7 +1372,7 @@ mobase:
|
||||
type: MoVariant
|
||||
desc: The data that was included in the request.
|
||||
error:
|
||||
type: PyQt6.QtNetwork.QNetworkReply.NetworkError
|
||||
type: PyQt5.QtNetwork.QNetworkReply.NetworkError
|
||||
desc: The actual error.
|
||||
message:
|
||||
type: str
|
||||
@@ -1806,7 +1814,6 @@ mobase:
|
||||
returns: True if the plugin was initialized correctly, False otherwise.
|
||||
|
||||
enabledByDefault:
|
||||
abstract: false
|
||||
__doc__: Check whether this plugin should be enabled by default.
|
||||
returns: True if this plugin should be enabled by default, False otherwise.
|
||||
|
||||
@@ -1992,7 +1999,7 @@ mobase:
|
||||
abstract: false
|
||||
returns:
|
||||
__doc__: A mapping from feature type to actual game features.
|
||||
type: dict[Type[GameFeatureType], GameFeatureType]
|
||||
type: Dict[Type[GameFeatureType], GameFeatureType]
|
||||
|
||||
gameDirectory:
|
||||
returns: The directory containing the game installation.
|
||||
@@ -2026,9 +2033,6 @@ mobase:
|
||||
The name of the launcher executable to run (relative to the game folder), or an
|
||||
empty string if there is no launcher.
|
||||
|
||||
getSupportURL:
|
||||
returns: An URL for the support page of this game.
|
||||
|
||||
iniFiles:
|
||||
returns: |
|
||||
The list of INI files this game uses. The first file in the list should be the
|
||||
@@ -2094,14 +2098,6 @@ mobase:
|
||||
savesDirectory:
|
||||
returns: The directory where save games are stored.
|
||||
|
||||
secondaryDataDirectories:
|
||||
__doc__: |
|
||||
Retrieve the list of secondary data directories. Each directories should be
|
||||
assigned a unique name that differs from "data" which is the name of the main
|
||||
data directory returned by dataDirectory().
|
||||
|
||||
returns: A mapping from unique name to secondary data directories.
|
||||
|
||||
setGamePath:
|
||||
__doc__: |
|
||||
Set the path to the managed game.
|
||||
@@ -2226,6 +2222,13 @@ mobase:
|
||||
args:
|
||||
parent: The parent widget.
|
||||
|
||||
IPluginInstallerCustom:
|
||||
__doc__: |
|
||||
Custom installer for mods. Custom installers receive the archive name and have to go
|
||||
from there. They have to be able to extract the archive themselves.
|
||||
|
||||
Example of such installers are the external NCC installer or the OMOD installer.
|
||||
|
||||
_manager:
|
||||
abstract: false
|
||||
returns: The installation manager.
|
||||
@@ -2234,13 +2237,6 @@ mobase:
|
||||
abstract: false
|
||||
returns: The parent widget.
|
||||
|
||||
IPluginInstallerCustom:
|
||||
__doc__: |
|
||||
Custom installer for mods. Custom installers receive the archive name and have to go
|
||||
from there. They have to be able to extract the archive themselves.
|
||||
|
||||
Example of such installers are the external NCC installer or the OMOD installer.
|
||||
|
||||
install:
|
||||
__doc__: |
|
||||
Install the given archive.
|
||||
@@ -2281,6 +2277,14 @@ mobase:
|
||||
representing the archive and can modify what to install and where by editing this structure.
|
||||
Actually extracting the archive is handled by the manager.
|
||||
|
||||
_manager:
|
||||
abstract: false
|
||||
returns: The installation manager.
|
||||
|
||||
_parentWidget:
|
||||
abstract: false
|
||||
returns: The parent widget.
|
||||
|
||||
install:
|
||||
__doc__: |
|
||||
Install a mod from an archive filetree.
|
||||
@@ -2798,7 +2802,7 @@ mobase:
|
||||
filetree: The tree to try to fix. Can be modified during the process.
|
||||
returns:
|
||||
__doc__: The fixed tree, or a null pointer if the tree could not be fixed.
|
||||
type: IFileTree | None
|
||||
type: Optional["IFileTree"]
|
||||
|
||||
ModDataContent:
|
||||
__doc__: |
|
||||
@@ -2915,7 +2919,7 @@ mobase:
|
||||
type: int
|
||||
desc:
|
||||
fileTime:
|
||||
type: PyQt6.QtCore.QDateTime
|
||||
type: PyQt5.QtCore.QDateTime
|
||||
desc:
|
||||
gameName:
|
||||
type: str
|
||||
@@ -3008,17 +3012,17 @@ mobase:
|
||||
parent: The parent widget.
|
||||
returns:
|
||||
__doc__: A SaveGameInfoWidget to display information about save game.
|
||||
type: ISaveGameInfoWidget | None
|
||||
type: Optional["ISaveGameInfoWidget"]
|
||||
|
||||
ScriptExtender:
|
||||
__doc__:
|
||||
__abstract__: true
|
||||
|
||||
binaryName:
|
||||
BinaryName:
|
||||
__doc__:
|
||||
returns: The name of the script extender binary.
|
||||
|
||||
pluginPath:
|
||||
PluginPath:
|
||||
__doc__:
|
||||
returns: The script extender plugin path, relative to the data folder.
|
||||
|
||||
@@ -3147,123 +3151,3 @@ mobase:
|
||||
|
||||
scheme:
|
||||
returns: The version scheme in effect for this VersionInfo.
|
||||
|
||||
mobase.widgets:
|
||||
|
||||
TaskDialog:
|
||||
__doc__: Customizable choice dialog.
|
||||
|
||||
__init__:
|
||||
__doc__: Construct a new TaskDialog.
|
||||
|
||||
args:
|
||||
parent: Parent widget of the dialog.
|
||||
title: Title of the dialog.
|
||||
main: Header of the dialog (big text at the top).
|
||||
content: Main message of the dialog (text below main).
|
||||
details: Details for the dialog, initially collapsed (bottom of the dialog).
|
||||
icon: Icon for the dialog.
|
||||
buttons: List of buttons for the dialog.
|
||||
remember: Remember the choice for this dialog.
|
||||
|
||||
addButton:
|
||||
__doc__: Add a custom button to this TaskDialog.
|
||||
|
||||
args:
|
||||
button: Button to add to the dialog.
|
||||
|
||||
addContent:
|
||||
__doc__: |
|
||||
Add a custom widget content to this TaskDialog. Widget content are put between
|
||||
content and buttons (above buttons).
|
||||
|
||||
args:
|
||||
widget: Widget to add.
|
||||
|
||||
exec:
|
||||
__doc__: |
|
||||
Display this dialog and wait for user-interaction to return. This is a blocking
|
||||
function.
|
||||
|
||||
returns: |
|
||||
The button clicked by the user. Without custom buttons, this return Ok,
|
||||
otherwise it returns the button set in the TaskDialogButton.
|
||||
|
||||
setContent:
|
||||
__doc__: Set the top-level message of this dialog.
|
||||
|
||||
args:
|
||||
content: Top-level message to set.
|
||||
|
||||
setDetails:
|
||||
__doc__: |
|
||||
Set the details for this TaskDialog.
|
||||
|
||||
The details are hidden by default and the user can display them by clicking
|
||||
the "Details" button at the bottom of the TaskDialog.
|
||||
|
||||
args:
|
||||
details: Details content to display. Can be a multi-line string.
|
||||
|
||||
setIcon:
|
||||
__doc__: Set the icon of the dialog.
|
||||
|
||||
args:
|
||||
icon: Icon of the dialog.
|
||||
|
||||
setMain:
|
||||
__doc__: |
|
||||
Set the main message of the dialog. The main message is displayed at the top of
|
||||
the dialog in large font.
|
||||
|
||||
args:
|
||||
main: Main message of the dialog.
|
||||
|
||||
setRemember:
|
||||
__doc__: Configure the dialog to remember user-choice.
|
||||
|
||||
args:
|
||||
action:
|
||||
file:
|
||||
|
||||
setTitle:
|
||||
__doc__: Set the title of the dialog.
|
||||
|
||||
args:
|
||||
title: Title of the dialog.
|
||||
|
||||
setWidth:
|
||||
__doc__: Set the width of the dialog.
|
||||
|
||||
args:
|
||||
width: Width of the dialog.
|
||||
|
||||
|
||||
TaskDialogButton:
|
||||
__doc__: Special button to be used inside TaskDialog widgets.
|
||||
|
||||
__init__.1:
|
||||
__doc__: Create a TaskDialogButton.
|
||||
|
||||
args:
|
||||
text: Label of the button.
|
||||
description: Description of the button.
|
||||
button: Value returned by TaskDialog.exec() if this button is clicked.
|
||||
|
||||
__init__.2:
|
||||
__doc__: Create a TaskDialogButton without description.
|
||||
|
||||
args:
|
||||
text: Label of the button.
|
||||
button: Value returned by TaskDialog.exec() if this button is clicked.
|
||||
|
||||
properties[]:
|
||||
text:
|
||||
type: str
|
||||
desc: Label of the button.
|
||||
description:
|
||||
type: str
|
||||
desc: Description of the button.
|
||||
button:
|
||||
type: PyQt6.QtWidgets.QMessageBox.StandardButton
|
||||
desc: Value returned by TaskDialog.exec() if this button is clicked.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
@@ -1,4 +1,4 @@
|
||||
sphinx-rtd-theme
|
||||
sphinx-autodoc-typehints
|
||||
sphinx-automodapi
|
||||
PyQt6
|
||||
PyQt5
|
||||
+10
-2
@@ -10,13 +10,21 @@
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../../stubs/2.4.0/"))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = "MO2 Python Plugin API"
|
||||
copyright = "2023, Holt59"
|
||||
copyright = "2020, Holt59"
|
||||
author = "Holt59"
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = "2.3rc1"
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
@@ -61,5 +69,5 @@ html_favicon = "mo2.ico"
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
# html_static_path = ["_static"]
|
||||
html_static_path = ["_static"]
|
||||
html_extra_path = [".nojekyll"]
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
logging.basicConfig(stream=sys.stderr, format="%(levelname)s: %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.WARNING)
|
||||
@@ -0,0 +1,94 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load_module(name: str, path: Path):
|
||||
# Create the loader:
|
||||
loader = importlib.machinery.ExtensionFileLoader( # type: ignore
|
||||
name, path.as_posix()
|
||||
)
|
||||
|
||||
# Extract the spec:
|
||||
spec = importlib.util.spec_from_loader(name, loader)
|
||||
|
||||
# Create the module and execute it?
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
if module is None:
|
||||
raise ImportError(f"Failed to import module {name} from {path}.")
|
||||
|
||||
loader.exec_module(module)
|
||||
|
||||
return module
|
||||
|
||||
|
||||
def load_mobase(path: Path, moprivate: bool = False):
|
||||
"""
|
||||
Load the mobase from the given MO2 installation path and
|
||||
returns it.
|
||||
|
||||
Args:
|
||||
path: Path to the MO2 installation (folder containing the ModOrganizer.exe).
|
||||
moprivate: If True, the moprivate module will also be loaded and returned
|
||||
alongside mobase.
|
||||
|
||||
Returns: The mobase module.
|
||||
"""
|
||||
|
||||
# We need absolute path for loading DLL and modules:
|
||||
path = path.resolve()
|
||||
|
||||
# Adding to PATH environment variable for python < 3.8 and
|
||||
# via os.add_dll_directory (python >= 3.8).
|
||||
# See: https://stackoverflow.com/a/58632354/2666289
|
||||
if sys.version_info < (3, 8):
|
||||
os.environ["PATH"] = os.pathsep.join(
|
||||
[str(path), str(path.joinpath("dlls")), os.environ.get("PATH", "")]
|
||||
)
|
||||
else:
|
||||
os.add_dll_directory(str(path)) # type: ignore[attr-defined]
|
||||
os.add_dll_directory(str(path.joinpath("dlls"))) # type: ignore[attr-defined]
|
||||
|
||||
# We need to add plugins/data to sys.path, mainly for PyQt5
|
||||
sys.path.insert(1, path.joinpath("plugins", "data").as_posix())
|
||||
|
||||
mobase = load_module("mobase", path.joinpath("plugins", "data", "pythonrunner.dll"))
|
||||
|
||||
if not moprivate:
|
||||
return mobase
|
||||
|
||||
moprivate = load_module(
|
||||
"moprivate", path.joinpath("plugins", "data", "pythonrunner.dll")
|
||||
)
|
||||
|
||||
return mobase, moprivate
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
"Load mobase python module from MO2 installation directory"
|
||||
)
|
||||
parser.add_argument(
|
||||
"install_dir",
|
||||
metavar="INSTALL_DIR",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="installation directory of Mod Organizer 2",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p", "--private", action="store_true", help="also load the moprivate module"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.private:
|
||||
mobase, moprivate = load_mobase(args.install_dir, moprivate=True)
|
||||
else:
|
||||
mobase = load_mobase(args.install_dir)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from . import logger
|
||||
from .mtypes import Class, CType, Function, Type
|
||||
|
||||
|
||||
class MobaseRegister:
|
||||
"""
|
||||
Class that register classes.
|
||||
"""
|
||||
|
||||
objects: Dict[str, Union[Class, List[Function]]]
|
||||
|
||||
def __init__(self):
|
||||
self.raw_objects: Dict[str, Union[type]] = OrderedDict()
|
||||
self.objects = {}
|
||||
|
||||
self._cpptypes = {}
|
||||
self.cpp2py = {}
|
||||
|
||||
def add_object(self, name, object):
|
||||
self.raw_objects[name] = object
|
||||
|
||||
def make_object(
|
||||
self, name: str, e: Optional[type] = None
|
||||
) -> Union["Class", List["Function"]]:
|
||||
"""
|
||||
Construct a Function, Class or Enum for the given object.
|
||||
|
||||
Args:
|
||||
name: The name of the object to inspect.
|
||||
e: The object to inspect, or None to fetch it from the underlying list.
|
||||
|
||||
Returns:
|
||||
A Class object for the given type, or a list of function overloads.
|
||||
"""
|
||||
from .parser import is_enum, make_class, make_enum, make_functions
|
||||
|
||||
if e is None:
|
||||
e = self.raw_objects[name]
|
||||
|
||||
if name not in self.raw_objects:
|
||||
self.raw_objects[name] = e
|
||||
|
||||
if name not in self.objects:
|
||||
if is_enum(e):
|
||||
self.objects[name] = make_enum(name, e)
|
||||
elif isinstance(e, type):
|
||||
self.objects[name] = make_class(name, e, self)
|
||||
elif callable(e):
|
||||
self.objects[name] = make_functions(name, e)
|
||||
|
||||
return self.objects[name]
|
||||
|
||||
def get_object(self, name: str):
|
||||
"""
|
||||
Retrieve the object if the given name. Fails if no object with this
|
||||
name exists (if `make_object(name, ...)` has never been called).
|
||||
|
||||
Args:
|
||||
name: Name of the object to retrieve.
|
||||
|
||||
Returns:
|
||||
The object with the given name.
|
||||
"""
|
||||
return self.objects[name]
|
||||
|
||||
def register_type(self, ptype: "Type", ctype: "CType"):
|
||||
"""Register an equivalence between a python name and a C++ name.
|
||||
|
||||
Args:
|
||||
python_name: Name of the Python class.
|
||||
cpp_name: Name of the C++ class.
|
||||
"""
|
||||
# Register the const equivalent for smart pointers:
|
||||
cname = ctype.name
|
||||
if ctype.is_smart_pointer():
|
||||
if cname.find(" const >") != -1:
|
||||
c2name = cname.replace(" const >", ">")
|
||||
if c2name in self._cpptypes:
|
||||
ptype = self.cpp2py[c2name]
|
||||
# Not the const, replace the const one:
|
||||
else:
|
||||
c2name = cname.replace(">", " const >")
|
||||
if c2name in self._cpptypes and self.cpp2py[c2name].is_object():
|
||||
self._cpptypes[c2name] = ctype
|
||||
self.cpp2py[c2name] = ptype
|
||||
logger.warning(
|
||||
"Replace registration {} [c++] with {} [python] using {}"
|
||||
" information.".format(c2name, ptype.name, cname) # noqa: E501
|
||||
)
|
||||
|
||||
if cname not in MOBASE_REGISTER.cpp2py:
|
||||
self._cpptypes[cname] = ctype
|
||||
self.cpp2py[cname] = ptype
|
||||
logger.info("Registered {} [c++] as {} [python].".format(cname, ptype.name))
|
||||
|
||||
@property
|
||||
def py2cpp(self):
|
||||
result = {v.name: [] for v in self.cpp2py.values()}
|
||||
for k in self.cpp2py:
|
||||
result[self.cpp2py[k].name].append(self._cpptypes[k])
|
||||
return result
|
||||
|
||||
|
||||
MOBASE_REGISTER = MobaseRegister()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,40 +1,24 @@
|
||||
import logging
|
||||
from typing import Any, Iterable, TextIO, TypeGuard
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from .mtypes import Class, Enum, Function, Method, Property, PyTyping
|
||||
from typing import List, TextIO, Tuple, Union
|
||||
|
||||
from . import logger
|
||||
from .mtypes import Class, Enum, Function, Method, Property
|
||||
from .utils import Settings
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
|
||||
|
||||
def is_list_of_functions(e: Any | Iterable[Any]) -> TypeGuard[list[Function]]:
|
||||
if not isinstance(e, list):
|
||||
return False
|
||||
return all(isinstance(x, Function) for x in e)
|
||||
|
||||
|
||||
class Writer:
|
||||
|
||||
_output: TextIO
|
||||
_settings: Settings
|
||||
|
||||
def __init__(self, package: str, output: TextIO, settings: Settings):
|
||||
self._package = package.split(".")
|
||||
def __init__(self, output: TextIO, settings: Settings):
|
||||
self._output = output
|
||||
self._settings = settings
|
||||
|
||||
def _fix_typing(self, value: str) -> str:
|
||||
for pkg in self._package:
|
||||
value = value.replace(pkg + ".", "")
|
||||
return value
|
||||
|
||||
def _print(
|
||||
self,
|
||||
*values: object,
|
||||
sep: str | None = " ",
|
||||
end: str | None = "\n",
|
||||
flush: bool = False,
|
||||
) -> None:
|
||||
print(*values, sep=sep, end=end, flush=flush, file=self._output)
|
||||
def _print(self, *args, **kwargs):
|
||||
kwargs["file"] = self._output
|
||||
print(*args, **kwargs)
|
||||
|
||||
def _print_doc(self, doc: str, indent: str):
|
||||
"""
|
||||
@@ -52,7 +36,7 @@ class Writer:
|
||||
self._print('__version__ = "{}"'.format(version))
|
||||
self._print()
|
||||
|
||||
def print_imports(self, imports: list[str | tuple[str, list[str]]]):
|
||||
def print_imports(self, imports: List[Union[str, Tuple[str, List[str]]]]):
|
||||
"""
|
||||
Print the given imports.
|
||||
"""
|
||||
@@ -71,12 +55,12 @@ class Writer:
|
||||
if fn.has_overloads():
|
||||
self._print("{}@overload".format(indent))
|
||||
|
||||
sig_return_type = ""
|
||||
srtype = ""
|
||||
if not fn.ret.type.is_none():
|
||||
sig_return_type = " -> " + self._fix_typing(fn.ret.type.typing())
|
||||
else:
|
||||
sig_return_type = " -> None"
|
||||
srtype = " -> " + fn.ret.type.typing(self._settings)
|
||||
|
||||
fargs = fn.args
|
||||
largs: List[str] = []
|
||||
if isinstance(fn, Method):
|
||||
if fn.is_static():
|
||||
self._print("{}@staticmethod".format(indent))
|
||||
@@ -84,19 +68,17 @@ class Writer:
|
||||
if fn.is_abstract():
|
||||
self._print("{}@abc.abstractmethod".format(indent))
|
||||
|
||||
python_args: list[str] = []
|
||||
for arg in fn.args:
|
||||
tmp = "{}: {}".format(arg.name, self._fix_typing(arg.type.typing()))
|
||||
largs.insert(0, "self")
|
||||
fargs = fargs[1:]
|
||||
|
||||
for i, arg in enumerate(fargs):
|
||||
tmp = "{}: {}".format(arg.name, arg.type.typing(self._settings))
|
||||
if arg.has_default_value():
|
||||
tmp += " = {}".format(arg.value)
|
||||
python_args.append(tmp)
|
||||
largs.append(tmp)
|
||||
sargs = ", ".join(largs)
|
||||
|
||||
self._print(
|
||||
"{}def {}({}){}:".format(
|
||||
indent, fn.name, ", ".join(python_args), sig_return_type
|
||||
),
|
||||
end="",
|
||||
)
|
||||
self._print("{}def {}({}){}:".format(indent, fn.name, sargs, srtype), end="")
|
||||
|
||||
# Add the documentation, if any:
|
||||
doc = ""
|
||||
@@ -111,12 +93,9 @@ class Writer:
|
||||
if any(arg.doc for arg in args):
|
||||
doc += "\nArgs:\n"
|
||||
for arg in args:
|
||||
arg_doc_list = arg.doc.strip().split("\n")
|
||||
arg_doc = "\n".join(
|
||||
[arg_doc_list[0]]
|
||||
+ [" " + line_doc for line_doc in arg_doc_list[1:]]
|
||||
)
|
||||
doc += " " + arg.name + ": " + arg_doc + "\n"
|
||||
adocl = arg.doc.strip().split("\n")
|
||||
adoc = "\n".join([adocl[0]] + [" " + ldoc for ldoc in adocl[1:]])
|
||||
doc += " " + arg.name + ": " + adoc + "\n"
|
||||
|
||||
if not fn.ret.type.is_none() and fn.ret.doc:
|
||||
doc += "\nReturns:\n " + fn.ret.doc.strip() + "\n"
|
||||
@@ -126,7 +105,7 @@ class Writer:
|
||||
for rai in fn.raises:
|
||||
doc += (
|
||||
" "
|
||||
+ self._fix_typing(rai.type.typing())
|
||||
+ rai.type.typing(self._settings)
|
||||
+ ": "
|
||||
+ rai.doc.strip()
|
||||
+ "\n"
|
||||
@@ -148,7 +127,7 @@ class Writer:
|
||||
"""
|
||||
|
||||
if prop.type.is_object() or prop.type.is_any():
|
||||
LOGGER.warning(
|
||||
logger.warning(
|
||||
"Property {}.{} does not have a specified type.".format(
|
||||
cls.name, prop.name
|
||||
)
|
||||
@@ -157,14 +136,14 @@ class Writer:
|
||||
self._print("{}@property".format(indent))
|
||||
self._print(
|
||||
"{}def {}(self) -> {}: ...".format(
|
||||
indent, prop.name, self._fix_typing(prop.type.typing())
|
||||
indent, prop.name, prop.type.typing(self._settings)
|
||||
)
|
||||
)
|
||||
if not prop.is_read_only():
|
||||
self._print("{}@{}.setter".format(indent, prop.name))
|
||||
self._print(
|
||||
"{}def {}(self, arg0: {}): ...".format(
|
||||
indent, prop.name, self._fix_typing(prop.type.typing())
|
||||
indent, prop.name, prop.type.typing(self._settings)
|
||||
)
|
||||
)
|
||||
self._print()
|
||||
@@ -176,10 +155,7 @@ class Writer:
|
||||
|
||||
bc = ""
|
||||
if cls.bases or cls.is_abstract():
|
||||
bases: list[str] = [
|
||||
bc.canonical_name if bc.package.startswith("mobase") else bc.full_name
|
||||
for bc in cls.bases
|
||||
]
|
||||
bases = [str(bc) for bc in cls.bases]
|
||||
if cls.is_abstract() and not any(bc.is_abstract() for bc in cls.bases):
|
||||
bases.insert(0, "abc.ABC")
|
||||
bc = "(" + ", ".join(bases) + ")"
|
||||
@@ -203,8 +179,8 @@ class Writer:
|
||||
self._print()
|
||||
|
||||
# Inner classes:
|
||||
for inner_class in cls.inner_classes:
|
||||
self.print_class(inner_class, indent=indent + " ")
|
||||
for iclass in cls.inner_classes:
|
||||
self.print_class(iclass, indent=indent + " ")
|
||||
self._print()
|
||||
|
||||
# Constants:
|
||||
@@ -215,7 +191,7 @@ class Writer:
|
||||
|
||||
typing = ""
|
||||
if constant.type is not None:
|
||||
typing = ": {}".format(self._fix_typing(constant.type.typing()))
|
||||
typing = ": {}".format(constant.type.typing(self._settings))
|
||||
|
||||
# Note: We do not print the value, we use ...
|
||||
self._print(
|
||||
@@ -241,7 +217,6 @@ class Writer:
|
||||
cls.methods,
|
||||
key=lambda m: (m.name != "__init__", not m.is_special(), m.name),
|
||||
)
|
||||
|
||||
for method in methods:
|
||||
self.print_function(method, indent=indent + " ")
|
||||
|
||||
@@ -252,17 +227,3 @@ class Writer:
|
||||
if isinstance(cls, Enum):
|
||||
self._print()
|
||||
self._print()
|
||||
|
||||
def print_typing(self, typ: PyTyping):
|
||||
self._print(f"{typ.name} = {typ.typing}")
|
||||
|
||||
def print_object(self, e: object):
|
||||
if isinstance(e, Class):
|
||||
self.print_class(e)
|
||||
|
||||
elif is_list_of_functions(e):
|
||||
for fn in e:
|
||||
self.print_function(fn)
|
||||
|
||||
elif isinstance(e, PyTyping):
|
||||
self.print_typing(e)
|
||||
@@ -0,0 +1,169 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import black
|
||||
|
||||
from generator import logger
|
||||
from generator.loader import load_mobase
|
||||
from generator.mtypes import Class, Function, Type
|
||||
from generator.parser import is_enum
|
||||
from generator.register import MOBASE_REGISTER
|
||||
from generator.utils import Settings, clean_class
|
||||
from generator.writer import Writer
|
||||
|
||||
parser = argparse.ArgumentParser("Stubs generator for the MO2 python interface")
|
||||
parser.add_argument(
|
||||
"install_dir",
|
||||
metavar="INSTALL_DIR",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="installation directory of Mod Organizer 2",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
type=Path,
|
||||
default="stubs/setup/mobase-stubs/__init__.pyi",
|
||||
help="output file (default stubs/setup/mobase-stubs/__init__.pyi)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", action="store_true", help="verbose mode (all logs go to stderr)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
type=argparse.FileType("r"),
|
||||
default=None,
|
||||
help="configuration file",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
# Load settings from the configuration:
|
||||
settings: Settings = Settings(register=MOBASE_REGISTER)
|
||||
if args.config is not None:
|
||||
settings = Settings(MOBASE_REGISTER, args.config)
|
||||
|
||||
# Parse mobase:
|
||||
|
||||
# Load mobase (cannot simply do "import mobase"):
|
||||
mobase = load_mobase(Path(args.install_dir))
|
||||
|
||||
# List of objects:
|
||||
objects = []
|
||||
|
||||
for name in dir(mobase):
|
||||
if name.startswith("__"):
|
||||
continue
|
||||
|
||||
if name in settings.ignore_names:
|
||||
continue
|
||||
|
||||
# We do not want the real MoVariant.
|
||||
if name == "MoVariant":
|
||||
continue
|
||||
|
||||
# For now, ignore this since it is a submodule and we
|
||||
# not handle them.
|
||||
if name == "widgets":
|
||||
continue
|
||||
|
||||
objects.append((name, getattr(mobase, name)))
|
||||
|
||||
# Enum first, and then alphabetical. Might cause issue with base classes, so
|
||||
# maybe create a kind of dependency...
|
||||
# For argument or return types, this should not be an issue since we quote
|
||||
# everything from mobase.
|
||||
objects = sorted(
|
||||
objects, key=lambda e: (isinstance(e[1], type), not is_enum(e[1]), e[0])
|
||||
)
|
||||
|
||||
for n, o in objects:
|
||||
MOBASE_REGISTER.add_object(n, o)
|
||||
|
||||
# Process everything:
|
||||
for n, o in objects:
|
||||
|
||||
# Create the corresponding object:
|
||||
c = MOBASE_REGISTER.make_object(n, o)
|
||||
|
||||
if isinstance(c, Class):
|
||||
|
||||
# Clean the class (e.g., remove duplicates methods due to wrappers):
|
||||
clean_class(c, settings)
|
||||
|
||||
# Path the class using the configuration:
|
||||
settings.patch_class(c)
|
||||
|
||||
elif isinstance(c, list) and isinstance(c[0], Function):
|
||||
settings.patch_functions(c)
|
||||
|
||||
else:
|
||||
logger.critical(
|
||||
"Cannot generated stubs for {}, unsupported object type.".format(n)
|
||||
)
|
||||
|
||||
# Write everything:
|
||||
with open(args.output, "w") as output:
|
||||
|
||||
writer = Writer(output, settings)
|
||||
writer.print_version(settings.mobase["__version__"]) # type: ignore
|
||||
writer.print_imports(
|
||||
[
|
||||
"abc",
|
||||
("enum", ["Enum"]),
|
||||
(
|
||||
"typing",
|
||||
[
|
||||
"Dict",
|
||||
"Iterator",
|
||||
"List",
|
||||
"Tuple",
|
||||
"Union",
|
||||
"Any",
|
||||
"Optional",
|
||||
"Callable",
|
||||
"overload",
|
||||
"TypeVar",
|
||||
"Type",
|
||||
],
|
||||
),
|
||||
"PyQt5.QtCore",
|
||||
"PyQt5.QtGui",
|
||||
"PyQt5.QtWidgets",
|
||||
]
|
||||
)
|
||||
|
||||
# Needs to define the MVariant and GameFeatureType type:
|
||||
writer._print("MoVariant = {}".format(Type.MO_VARIANT))
|
||||
writer._print('GameFeatureType = TypeVar("GameFeatureType")')
|
||||
writer._print()
|
||||
|
||||
# This is a class to represent interface not implemented:
|
||||
writer.print_class(Class("InterfaceNotImplemented", [], []))
|
||||
writer._print()
|
||||
|
||||
for n, o in objects:
|
||||
|
||||
# Get the corresponding object:
|
||||
c = MOBASE_REGISTER.get_object(n)
|
||||
|
||||
if isinstance(c, Class):
|
||||
writer.print_class(c)
|
||||
|
||||
elif isinstance(c, list) and isinstance(c[0], Function):
|
||||
for fn in c:
|
||||
writer.print_function(fn)
|
||||
|
||||
black.format_file_in_place(
|
||||
args.output,
|
||||
fast=False,
|
||||
mode=black.Mode(is_pyi=args.output.name.endswith("pyi")),
|
||||
write_back=black.WriteBack.YES,
|
||||
)
|
||||
@@ -1,61 +0,0 @@
|
||||
[tool.poetry]
|
||||
name = "mo2-pystubs-generation"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Holt59 <capelle.mikael@gmail.com>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
packages = [{ include = "mo2", from = "src" }]
|
||||
|
||||
[tool.poetry.scripts]
|
||||
mo2-stubs-generator = "mo2.stubs.generator.__main__:main"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
pyqt6 = "^6.5.2"
|
||||
pyyaml = "^6.0.1"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^23.9.1"
|
||||
mypy = "^1.5.1"
|
||||
pyright = "^1.1.327"
|
||||
isort = "^5.12.0"
|
||||
ruff = "^0.0.290"
|
||||
flake8 = "^6.1.0"
|
||||
flake8-black = "^0.3.6"
|
||||
flake8-pyproject = "^1.2.3"
|
||||
types-pyyaml = "^6.0.12.11"
|
||||
|
||||
|
||||
[tool.poetry.group.doc.dependencies]
|
||||
sphinx-rtd-theme = "^1.3.0"
|
||||
sphinx-autodoc-typehints = "^1.24.0"
|
||||
sphinx-automodapi = "^0.16.0"
|
||||
sphinx = "^7.2.6"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.flake8]
|
||||
max-line-length = 88
|
||||
extend-ignore = ["E203"]
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
multi_line_output = 3
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py311"
|
||||
|
||||
[tool.mypy]
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
namespace_packages = true
|
||||
|
||||
[tool.pyright]
|
||||
# reportMissingTypeStubs = true
|
||||
# reportUntypedBaseClass = false
|
||||
typeCheckingMode = "strict"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user