mirror of
https://github.com/ModOrganizer2/python-plugins-doc.git
synced 2026-07-27 14:07:44 -07:00
Deploying to master from @ 2a06eab0a6601add904e7a8e9705cc575d4efda5 🚀
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
# Generated files:
|
||||
.mypy_cache
|
||||
__pycache__
|
||||
.vscode
|
||||
|
||||
# The 'bin/' directory:
|
||||
bin
|
||||
docs/build
|
||||
docs/mobase.py
|
||||
docs/source/api
|
||||
@@ -0,0 +1,7 @@
|
||||
Copyright 2020 Mikaël Capelle
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,141 @@
|
||||
# Mod Organizer 2 - Python stubs generation
|
||||
|
||||
This little project can be used to generate python stubs (`.pyi` file) for the MO2 python
|
||||
interface `mobase`.
|
||||
|
||||
## Using the stubs
|
||||
|
||||
You can install the `stubs` with `pip`:
|
||||
|
||||
```bash
|
||||
pip install mobase-stubs
|
||||
```
|
||||
|
||||
This will install the latest stubs on PyPi which usually corresponds to the latest release of
|
||||
MO2.
|
||||
You can install stubs for a specific version of MO2:
|
||||
|
||||
```bash
|
||||
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 underlying `QWidget` or `QObject`. Some things might not work as
|
||||
expected with these class (e.g., `isintance(myObject, QObject)` will return `False`), which is why a
|
||||
`_object()` and `_widget()` method is also provided.
|
||||
|
||||
## Generating the stubs
|
||||
|
||||
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 `python38.dll` in
|
||||
your MO2 installation path, then you need **python 3.8**.
|
||||
|
||||
To generate the stubs, you can run:
|
||||
|
||||
```
|
||||
# Change the output folder to whatever you want:
|
||||
python main.py -c configs\config-2.3.yml -o stubs\setup\mobase-stubs\__init__.pyi ${MO2_INSTALL_PATH}
|
||||
```
|
||||
|
||||
Where `${MO2_INSTALL_PATH}` is the path to your MO2 installation (the one containing `ModOrganizer.exe`).
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
If you do not specify a `-o` option, output will go to `stdout`, so you should redirect.
|
||||
Warning and "critical" messages are printed to `stderr`.
|
||||
|
||||
A few options are available for `main.py`:
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-o OUTPUT, --output OUTPUT
|
||||
output file (output to stdout if not specified)
|
||||
-v, --verbose verbose mode (all logs go to stderr)
|
||||
-c CONFIG, --config CONFIG
|
||||
configuration file
|
||||
```
|
||||
|
||||
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 outputed.
|
||||
For instance, here is the output with the current `config-2.3.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.3.yml`).
|
||||
|
||||
## Configuration file
|
||||
|
||||
The configuration file contains information for the stubs that cannot be deduced by `main` (or are too
|
||||
complex to deduce), and the documentation for everything.
|
||||
|
||||
## Uploading the stubs to pypi
|
||||
|
||||
The upload of the stubs to https://pypi.org/project/mobase-stubs/ has to be done manually. Here
|
||||
are the steps:
|
||||
|
||||
1. Check the version of the stubs:
|
||||
- The version is specified in the configuration file. If you need to modify the stubs of the
|
||||
current version, you need to add a `.postX` after the version since PyPi does not allow
|
||||
re-upload of the same release.
|
||||
2. Generate the stubs using the procedure above. You should generate the stubs under `stubs/setup/mobase-stubs/__init__.pyi`.
|
||||
3. If necessary, update the dependencies in `setup.py` (`PyQt5-stubs` version and python version).
|
||||
4. Go to `stubs/setup` and run:
|
||||
|
||||
```bash
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload dist/*
|
||||
```
|
||||
|
||||
You need to set the environment variables `TWINE_USERNAME` and `TWINE_PASSWORD` to appropriate values
|
||||
before running `twine upload`.
|
||||
|
||||
## Extras — Starts a python interpreter with `mobase`
|
||||
|
||||
It is possible to start a (i)python interpret with `mobase` imported by running:
|
||||
|
||||
```
|
||||
python -im generator.loader ${MO2_INSTALL_PATH}
|
||||
```
|
||||
|
||||
This has no real usage except for MO2 developers since most classes from the `mobase` module cannot be instantiated.
|
||||
|
||||
# License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020, Holt59.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
See [LICENSE](LICENSE).
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
sphinx-rtd-theme
|
||||
sphinx-autodoc-typehints
|
||||
sphinx-automodapi
|
||||
PyQt5
|
||||
@@ -0,0 +1,70 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# 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.3rc1/"))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = "MO2 Python Plugin API"
|
||||
copyright = "2020, Holt59"
|
||||
author = "Holt59"
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = "2.3rc1"
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx_autodoc_typehints",
|
||||
"sphinx.ext.napoleon",
|
||||
# "sphinx.ext.autosummary",
|
||||
"sphinx_automodapi.automodapi",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = []
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
html_theme_options = {
|
||||
# Toc options
|
||||
"collapse_navigation": False,
|
||||
"titles_only": False,
|
||||
}
|
||||
html_sidebars = {
|
||||
"**": ["globaltoc.html", "relations.html", "sourcelink.html", "searchbox.html"]
|
||||
}
|
||||
|
||||
# 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"]
|
||||
@@ -0,0 +1,53 @@
|
||||
FAQ
|
||||
===
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
1. Why is MO2 throwing an exception when I try to create a type inheriting one of MO2 class?
|
||||
............................................................................................
|
||||
|
||||
This often happens if you forget to call ``super().__init__()`` with the right arguments.
|
||||
Even if the list of arguments is empty (as in the example), it must be called due to a "bug"
|
||||
in ``boost::python``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MySaveGame(mobase.ISaveGame):
|
||||
def __init__(self):
|
||||
super().__init__() # Mandatory!
|
||||
|
||||
2. How can I be sure to implement all the required methods when creating a plugin?
|
||||
..................................................................................
|
||||
|
||||
It is kind of annoying to create a MO2 python plugin that kind of works and have it crash
|
||||
at some point simply because there plugin is missing a function implementation.
|
||||
You can have ``mypy`` warn you for such issue by typing the ``createPlugin`` or ``createPlugins``
|
||||
functions:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import typing
|
||||
|
||||
# A plugin that is missing something (e.g. display()).
|
||||
class MyPlugin(mobase.IPluginTool): ...
|
||||
|
||||
# If you type-hint `createPlugin` by adding `-> mobase.IPlugin`,
|
||||
# `mypy` will warn you that `MyPlugin` is an incomplete class.
|
||||
def createPlugin() -> mobase.IPlugin:
|
||||
return MyPlugin()
|
||||
|
||||
# You can also type-hint `createPlugins`:
|
||||
def createPlugins() -> typing.List[mobase.IPlugin]:
|
||||
return [MyPlugin()]
|
||||
|
||||
|
||||
3. Why are my ``isinstance(x, QObject)`` and ``isinstance(y, QWidget)`` not working?
|
||||
....................................................................................
|
||||
|
||||
Some classes from ``mobase`` are said to inherit ``QObject`` and ``QWidget`` in the
|
||||
stubs, such as :class:`mobase.IDownloadManager`.
|
||||
This is not the case in practice due to limitation in Python and ``boost::python``.
|
||||
The bases are in the stubs to allow for auto-completion and typing, which is then
|
||||
simulated in the real ``mobase`` by overriding ``__getattr__`` and delegating to
|
||||
the underlying ``QObject`` or ``QWidget``.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,30 @@
|
||||
.. MO2 Python Plugin API documentation master file, created by
|
||||
sphinx-quickstart on Wed Jul 15 19:36:59 2020.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to MO2 Python Plugin API's documentation!
|
||||
=================================================
|
||||
|
||||
Plugins are a way to extend Mod Organizers functionality. You can write plugins in either C++ or Python,
|
||||
theoretically with either being as powerful as the other.
|
||||
This documentation is dedicated to writting MO2 **Python** plugins.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
setup-tools
|
||||
plugin-types
|
||||
writing-plugins
|
||||
faq
|
||||
mobase
|
||||
|
||||
|
||||
|
||||
.. Indices and tables
|
||||
.. ==================
|
||||
|
||||
.. * :ref:`genindex`
|
||||
.. * :ref:`modindex`
|
||||
.. * :ref:`search`
|
||||
@@ -0,0 +1,11 @@
|
||||
``mobase`` API
|
||||
==============
|
||||
|
||||
.. currentmodule:: mobase
|
||||
|
||||
.. automodapi:: mobase
|
||||
:no-inheritance-diagram:
|
||||
:no-heading:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
@@ -0,0 +1,7 @@
|
||||
mobase
|
||||
======
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
mobase
|
||||
@@ -0,0 +1,150 @@
|
||||
.. _type-of-plugins:
|
||||
|
||||
Type of Plugins
|
||||
===============
|
||||
|
||||
Plugins are passive, that is: they react to events emitted by the core application or extend
|
||||
an existing functionality (like adding support for additional types of installers). Plugins
|
||||
should integrate with Mod Organizers concepts. While you could write an INI editor that works with
|
||||
the global INI files instead of the profile specific ones, what would be the point?
|
||||
|
||||
Depending on where/how the plugin integrates with MO you need to write a different type of plugin,
|
||||
in practice this means you need to implement a different interface (inherit from a different base
|
||||
plugin class from ``mobase``). As mentioned above, plugins are passive: the plugin type decides how/when
|
||||
MO makes requests to/invokes your plugin.
|
||||
All plugins however gain access to MOs own plugin interface so all plugins get to make the same requests
|
||||
to MO.
|
||||
|
||||
Installers
|
||||
----------
|
||||
|
||||
| **Interface:** :class:`IPluginInstaller<mobase.IPluginInstaller>`, :class:`IPluginInstallerSimple<mobase.IPluginInstallerSimple>`,
|
||||
:class:`IPluginInstallerCustom<mobase.IPluginInstallerCustom>`
|
||||
| **Examples:** ``installer_bain``, ``installer_bundle``, ``installer_fomod``, ``installer_ncc``, ``installer_quick``, ``installer_manual``.
|
||||
|
||||
An installer is invoked when the user tries to install a mod, either by double clicking in the download
|
||||
view or through the "Install Mod.." button or "Reinstall mod" item from the mod lists context menu.
|
||||
There are actually two ways to write an installer, *simple* or *complex*:
|
||||
|
||||
- With *simple* installers, MO does the unpacking of the file but this works only with standard archive formats.
|
||||
The plugin can then select the files and folders that requires extraction, and where to extract them.
|
||||
- *Complex* installers are more flexible but require a bit more work.
|
||||
|
||||
Previewers
|
||||
----------
|
||||
|
||||
| **Interfaces:** :class:`IPluginPreview<mobase.IPluginPreview>`
|
||||
| **Examples:** ``preview_base``
|
||||
|
||||
These plugins add support for previewing files in the data pane.
|
||||
Right now all image formats supported by Qt are implemented (including `.dds`) but no audio files and
|
||||
no 3d mesh formats.
|
||||
|
||||
Mod Page
|
||||
--------
|
||||
|
||||
*WIP*
|
||||
|
||||
| **Interfaces:** :class:`IPluginModPage<mobase.IPluginModPage>`
|
||||
| **Examples:** ``page_tesalliance``
|
||||
|
||||
Mod Page plugins implement interfaces to modding communities where mods can be downloaded, checked
|
||||
for updates and so on.
|
||||
This interface is not finished and some of the bits that are do not actually get used. The goal is
|
||||
that the whole Nexus integration can be implemented through this interface and can then be removed
|
||||
from the core application. This is a task for the distant future, unless someone wants to volunteer.
|
||||
|
||||
Game
|
||||
----
|
||||
|
||||
| **Interfaces:** :class:`IPluginGame<mobase.IPluginGame>`
|
||||
| **Examples:** ``game_oblivion``, ``game_fallout3``, ``game_falloutnv``, ``game_skyrim``, ...
|
||||
|
||||
These plugins (shall eventually) implement all the game specific features and further game plugins are
|
||||
able to add support for further games.
|
||||
The plugin is also responsible to help MO determine if (and where) the game is installed in the first place.
|
||||
Since supporting a game properly requires extensions in many places of the UI.
|
||||
To allow this without creating one huge plugin interface that involves every aspect of MO, game plugins
|
||||
expose a *feature list*.
|
||||
The list of possible features can be found in the "game_features" project and each feature can itself be
|
||||
considered a plugin interface.
|
||||
|
||||
As an example for a game feature take BSA invalidation: If the game requires BSA invalidation it will implement
|
||||
this feature.
|
||||
Wherever the core can support BSA invalidation it will query whether the current game has this feature and if so
|
||||
query the implementation on specifics (like "How should the invalidation BSA be called" and "what's the right bsa version").
|
||||
Of course, the goal is for feature interfaces to be as generic as possible without limiting usefulness.
|
||||
|
||||
**Note:** The :class:`IPluginGame<mobase.IPluginGame>` interface is complex, and mostly designed for Gamebryo games. If you plan
|
||||
on writting a game plugin for another type of games, you might be interested by a simpler interface and might
|
||||
want to check out the `Basic Games <https://github.com/ModOrganizer2/modorganizer-basic_games>`_ meta-plugin.
|
||||
|
||||
|
||||
Tool
|
||||
----
|
||||
|
||||
| **Interfaces:** :class:`IPluginTool<mobase.IPluginTool>`
|
||||
| **Examples:** ``tool_configurator``, ``tool_inieditor``, ``fnistool``
|
||||
|
||||
This is the simplest of plugin interfaces. Such plugins simply place an icon inside the tools submenu and get
|
||||
invoked when the user clicks it. They are expected to have a user interface of some sort.
|
||||
These are almost like independent applications except they can access all Mod Organizer interfaces like querying
|
||||
and modifying the current profile, mod list, load order, use MO to install mods and so on.
|
||||
A tool plugin can (and should!) integrate its UI as a window inside MO and thus doesn't have to initialize a
|
||||
windows application itself.
|
||||
|
||||
Proxies
|
||||
-------
|
||||
|
||||
| **Interfaces:** :class:`IPluginProxy<mobase.IPluginProxy>`
|
||||
| **Examples:** ``plugin_python``
|
||||
|
||||
Proxy Plugins expose the plugin api to foreign languages. This is what allows you to write plugins using python
|
||||
in the first place.
|
||||
The python proxy is easily the most complicated plugin and requires constant updating so if you're considering
|
||||
writing a Haskell plugin because that is your programming language of choice, I am fairly certain learning python
|
||||
is easier than writing the haskell proxy. Just saying.
|
||||
And no, you can not write a proxy for a third language in Python, do not be silly.
|
||||
|
||||
*Free Plugins*
|
||||
--------------
|
||||
|
||||
| **Interfaces:** :class:`IPlugin<mobase.IPlugin>`
|
||||
| **Examples:** ``check_fnis``, ``bsa_extractor``, ``diagnose_basic``, ``tool_inibakery``
|
||||
|
||||
"Free" plugins implement none of the interfaces and thus initially do not integrate with MO at all.
|
||||
They are initialized by MO and get access to the MO interface.
|
||||
This makes sense if you only want to implement one of the extension interfaces (see below) or register handlers
|
||||
for events.
|
||||
|
||||
Extension Interfaces
|
||||
--------------------
|
||||
|
||||
In Python, these interfaces are similar to other plugin types, but in C++, those do not inherit ``IPlugin`` so
|
||||
that plugins can implement one or more of these interfaces, in addition to a normal plugin type.
|
||||
|
||||
Diagnose
|
||||
........
|
||||
|
||||
| **Interfaces:** :class:`IPluginDiagnose<mobase.IPluginDiagnose>`
|
||||
| **Examples:** ``diagnose_basic``, ``installer_ncc``, ``plugin_python``, ``script_extender_plugin_checker``
|
||||
|
||||
This interface lets the plugin report issues that are then listed in the "Problems" icon in the main window.
|
||||
If possible the plugin can also provide an automatic or guided fix to the problem.
|
||||
The ``diagnose_basic`` plugin does nothing but analyze the MO installation and report problems it discovers (like
|
||||
*"There are files in your overwrite directory."*) but usually a plugin will want to report issues relevant for its
|
||||
own operation.
|
||||
For instance, ``installer_ncc`` requires a specific version of .NET and will report a problem if it is not installed.
|
||||
This should always be the prefered way to communicate problems the user has to fix but should never be used for problems
|
||||
he cannot fix (i.e. *"This plugin does not work with this game."*).
|
||||
An empty problem list should always be achievable.
|
||||
|
||||
File Mappings
|
||||
.............
|
||||
|
||||
| **Interfaces:** :class:`IPluginFileMapper<mobase.IPluginFileMapper>`
|
||||
| **Examples:** ``tool_inibakery``, ``game_gamebryo``
|
||||
|
||||
This interface allows plugins to add virtual file (or directory) links to the virtual file system in addition to the
|
||||
mod files.
|
||||
Profile-local save games, ini-files and load-orders are all implemented this way in MO2.
|
||||
@@ -0,0 +1,135 @@
|
||||
Setting up the environment
|
||||
==========================
|
||||
|
||||
This page will get you started for creating a MO2 python plugin. If you have troubles getting
|
||||
everything setup, feel free to come by our discord server at `https://discord.gg/cYwdcxj <https://discord.gg/cYwdcxj>`_.
|
||||
|
||||
Required tools
|
||||
--------------
|
||||
|
||||
It is possible to write a MO2 python plugin using any text editor, but this guide will focus on
|
||||
`Visual Studio Code <https://code.visualstudio.com/>`_.
|
||||
This guide assumes that:
|
||||
|
||||
- You have `Visual Studio Code <https://code.visualstudio.com/>`_ installed with the
|
||||
`Python extension <https://marketplace.visualstudio.com/items?itemName=ms-python.python>`_.
|
||||
- You have Python installed: https://www.python.org/downloads/.
|
||||
|
||||
- It is recommended but not mandatory to use the Python version that is used by MO2.
|
||||
You can check the ``pythonXX.dll`` in the MO2 installation folder to find the Python version used by MO2 (``python38.dll`` means Python 3.8).
|
||||
- You obviously need a valid MO2 installation: https://github.com/modorganizer2/modorganizer/releases
|
||||
|
||||
- This guide is written for MO2 ≥ 2.3.0.
|
||||
|
||||
**Note:** In the following, I will refer to the MO2 installation directory as ``$MO2DIR``. So if you
|
||||
installed MO2 at ``C:\MO2`` and you are asked to copy a file to ``$MO2DIR/plugins``, it refers to ``C:\MO2\plugins``.
|
||||
|
||||
Preparation
|
||||
-----------
|
||||
|
||||
**Note:** This part is optional but highly recommended if you want a proper environment to work with.
|
||||
Everything here is written to be as simple as possible but you can of course adapt it to your preferences: use a python virtual
|
||||
environment, use workspace settings instead of global ones, etc.
|
||||
|
||||
1. Get the ``mobase`` stubs
|
||||
...........................
|
||||
|
||||
``mobase`` is the MO2 Python module. The module is written in C++ and thus cannot be read directly by tools such
|
||||
as ``flake8`` or ``mypy``.
|
||||
Instead, we provide `stubs <https://stackoverflow.com/questions/59051631/what-is-the-use-of-stub-files-pyi-in-python>`_
|
||||
which can be used for auto-completion or type-checking.
|
||||
|
||||
The stubs for ``mobase`` are available at https://github.com/ModOrganizer2/pystubs-generation/tree/master/stubs.
|
||||
You want to download the ``mobase.pyi`` file in the folder corresponding to your MO2 version and put it under ``$MO2DIR/plugins/data``.
|
||||
|
||||
**Note:** It is possible to put the stubs in a different location, but we are going to use ``$MO2DIR/plugins/data`` for PyQt5,
|
||||
so we might as well use it for the stubs.
|
||||
|
||||
2. Configure Visual Studio Code for ``mobase``
|
||||
..............................................
|
||||
|
||||
We are going to configure Visual Studio Code to have auto-completion and linting (error and type checking)
|
||||
for the MO2 Python module.
|
||||
Open ``settings.json`` (Ctrl+Shift+P, then "Open Settings (JSON)"), and add the following entries:
|
||||
|
||||
.. code-block:: json-object
|
||||
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.mypyEnabled": true,
|
||||
"python.linting.flake8Enabled": true,
|
||||
"python.autoComplete.extraPaths": [
|
||||
"$MO2DIR\\plugins\\data",
|
||||
]
|
||||
|
||||
3. Configure ``mypy`` to find the ``mobase`` stubs
|
||||
..................................................
|
||||
|
||||
There are multiply way to configure ``mypy``:
|
||||
|
||||
1. You can create a ``mypy.ini`` file somewhere containing:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[mypy]
|
||||
mypy_path = $MO2DIR\plugins\data
|
||||
|
||||
And then add the following to ``settings.json`` (with the correct path):
|
||||
|
||||
.. code-block:: json-object
|
||||
|
||||
"python.linting.mypyArgs": [
|
||||
"--config-file=path-to-mypy.ini",
|
||||
]
|
||||
|
||||
2. You can set the ``MYPYPATH`` environment variable to ``$MO2DIR\plugins\data`` (this requires
|
||||
restarting VS code).
|
||||
|
||||
Testing the setup
|
||||
-----------------
|
||||
|
||||
Create a new Python file in Visual Studio Code, and paste the following content (if you create
|
||||
the file in ``$MO2DIR\plugins``, do not forget to delete it after since it is not a valid
|
||||
plugin):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import mobase
|
||||
|
||||
|
||||
class MyPlugin(mobase.IPluginTool):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def init(self, organizer: mobase.IOrganizer) -> bool:
|
||||
organizer.dow # (1)
|
||||
return True
|
||||
|
||||
def name(self) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def createPlugin() -> mobase.IPlugin:
|
||||
return MyPlugin()
|
||||
|
||||
|
||||
If your setup is valid, here is what you should have.
|
||||
|
||||
- You should see the following (with the errors underlined):
|
||||
|
||||
.. image:: images/check-setup-1.png
|
||||
|
||||
- If you press ``Ctrl+Space`` after ``.down`` (1), you should see the following
|
||||
meaning that auto-complete is working:
|
||||
|
||||
.. image:: images/check-setup-2.png
|
||||
|
||||
- In the error window (click |error-window| on the status bar), you should see
|
||||
the 3 following errors:
|
||||
|
||||
.. image:: images/check-setup-3.png
|
||||
|
||||
If everything is as above, you can delete the test file and move on to writting
|
||||
your own plugin!
|
||||
|
||||
|
||||
.. |error-window| image:: images/error-window.png
|
||||
@@ -0,0 +1,201 @@
|
||||
Writing Plugins
|
||||
===============
|
||||
|
||||
Getting started
|
||||
---------------
|
||||
|
||||
Now that you know which type of plugin you need, you can start writing your own plugin.
|
||||
There is two way to write a Python plugin:
|
||||
|
||||
- :ref:`single-file-plugin`: You use a single ``.py`` file that you put directly in the ``plugins``
|
||||
folder of the MO2 installation.
|
||||
- :ref:`module-plugin`: You create a Python package (folder) with a ``__init__.py`` file that you
|
||||
put in the ``plugins`` folder of the MO2 installation.
|
||||
|
||||
Most examples of plugins will be module plugins.
|
||||
|
||||
.. _single-file-plugin:
|
||||
|
||||
Single file plugins
|
||||
...................
|
||||
|
||||
Prior to version 2.3, this was the only way of creating a Python plugin. You simply need
|
||||
to create a ``myplugin.py`` file in the ``plugins`` folder of Mod Organizer 2 with a content
|
||||
similar to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import mobase
|
||||
|
||||
class MyPlugin(...):
|
||||
...
|
||||
|
||||
def createPlugin() -> mobase.IPlugin:
|
||||
return MyPlugin()
|
||||
|
||||
We will see later on how to create the actual ``MyPlugin`` class.
|
||||
The ``createPlugin`` function is the function that is called by Mod Organizer 2 to instantiate
|
||||
the plugin.
|
||||
|
||||
You can also provide multiple plugins by using ``createPlugins`` instead of ``createPlugin``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from typing import List
|
||||
|
||||
import mobase
|
||||
|
||||
class MyPlugin1(...):
|
||||
...
|
||||
|
||||
class MyPlugin2(...):
|
||||
...
|
||||
|
||||
|
||||
def createPlugins() -> List[mobase.IPlugin]:
|
||||
return [MyPlugin1(), MyPlugin2()]
|
||||
|
||||
**Note:** If you provide neither ``createPlugin()`` nor ``createPlugins``, MO2 will display
|
||||
an error message in the logs.
|
||||
|
||||
**Note:** If you add a return type-hint to ``createPlugin()`` or ``createPlugins`` (``->``), ``mypy``
|
||||
will type-check the function and warn you if one of your plugins is invalid, e.g. if you
|
||||
forgot to implement a required method.
|
||||
|
||||
If you need to provide other files with your ``.py`` (assets or other Python files), you can
|
||||
put them in the ``plugins/data``, but this is deprecated since MO2 2.3, and you should instead
|
||||
create a Python module plugin.
|
||||
|
||||
.. _module-plugin:
|
||||
|
||||
Module Plugins
|
||||
..............
|
||||
|
||||
Module plugins were introduced in MO2 2.3 and are shipped as whole folder containg a python module.
|
||||
The minimum content of the folder is a ``__init__.py`` file with ``createPlugin`` or ``createPlugins``
|
||||
function.
|
||||
|
||||
A minimal module plugin could be as follows:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
plugins/ # MO2 plugins folder
|
||||
myplugin/
|
||||
__init__.py
|
||||
plugin.py
|
||||
|
||||
In ``plugin.py``, you could define your plugin:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# plugin.py
|
||||
|
||||
import mobase
|
||||
|
||||
class MyPlugin(...):
|
||||
...
|
||||
|
||||
And in ``__init__.py``, you should write ``createPlugin``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# __init__.py
|
||||
|
||||
import mobase # For type-checking createPlugin().
|
||||
|
||||
from .plugin import MyPlugin # Always use relative import:
|
||||
|
||||
def createPlugin() -> mobase.IPlugin:
|
||||
return MyPlugin()
|
||||
|
||||
Similar to single-file plugins, you can expose ``createPlugins`` instead of ``createPlugin``
|
||||
to instantiate multiple plugins.
|
||||
|
||||
**Note:** The name of the folder does not have to be a valid python package, and you should
|
||||
always use relative imports within the module (``import .xxx``) instead of absolute ones.
|
||||
|
||||
Writing the plugin
|
||||
------------------
|
||||
|
||||
``IPlugin`` interface
|
||||
.....................
|
||||
|
||||
In the code snippets above, the ``MyPlugin`` class was not implemented.
|
||||
Depending on the :ref:`type of plugins<type-of-plugins>` that you want to create, you will
|
||||
need to extend a different class.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MyTool(mobase.IPluginTool): # Create a Tool plugin
|
||||
...
|
||||
|
||||
class MyPreview(mobase.IPluginPreview): # Create a preview plugin
|
||||
...
|
||||
|
||||
Each plugin class has its own abstract methods that you need to implement but all the classes
|
||||
also extend ``IPlugin``, so you need to implement the methods from :class:`IPlugin<mobase.IPlugin>`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from typing import List
|
||||
|
||||
import mobase
|
||||
|
||||
class MyPlugin(...): # The base class depends on the actual type of plugin
|
||||
|
||||
_organizer: mobase.IOrganizer
|
||||
|
||||
def __init__(self):
|
||||
super().__init__() # You need to call this manually.
|
||||
|
||||
def init(self, organizer: mobase.IOrganizer):
|
||||
self._organizer = organizer
|
||||
return True
|
||||
|
||||
def name(self) -> str:
|
||||
return ""
|
||||
|
||||
def author(self) -> str:
|
||||
return "Tannin"
|
||||
|
||||
def description(self) -> str:
|
||||
return self._tr("Gives a friendly greeting")
|
||||
|
||||
def version(self) -> mobase.VersionInfo:
|
||||
return mobase.VersionInfo(1, 0, 0, mobase.ReleaseType.FINAL)
|
||||
|
||||
def isActive(self) -> bool:
|
||||
return self._organizer.pluginSetting(self.name(), "enabled")
|
||||
|
||||
def settings(self) -> List[mobase.PluginSetting]:
|
||||
return [
|
||||
mobase.PluginSetting("enabled", "enable this plugin", True)
|
||||
]
|
||||
|
||||
Most of these are pretty simple to understand:
|
||||
|
||||
- ``name``: Returns the name of the plugin. The name of the plugin is used to
|
||||
fetch settings, and in many places, so this should not change between versions.
|
||||
- ``author``: Returns the name of the plugin author (you!).
|
||||
- ``description``: Returns the description of the plugin.
|
||||
- ``version``: Returns the version of the plugin. See :class:`VersionInfo<mobase.VersionInfo>` for
|
||||
more details.
|
||||
- ``isActive``: Returns ``True`` if the plugin is active, ``False`` otherwise. This
|
||||
usually returns ``True``, unless you want to check for something to dynamically
|
||||
enable the plugin. You can also use a plugin setting to allow users to disable
|
||||
your plugins.
|
||||
- ``settings``: Returns the list of settings (that user can modify) for this plugin.
|
||||
Settings can be ``int```, ``bool``, ``str`` or list of ``str``. Here we indicate
|
||||
that we have a "enabled" setting that user could use to disable the plugin (and
|
||||
we use it in ``isActive``).
|
||||
|
||||
|
||||
The ``__init__`` method is the normal Python constructor for our plugin, called when doing
|
||||
``MyPlugin()``.
|
||||
You should always call ``super().__init__()`` explicitly when extending MO2 classes (due to
|
||||
a "bug" in ``boost.python``).
|
||||
|
||||
The ``init`` method is called by MO2 to initialize the plugin. The given argument, ``organizer``,
|
||||
is an instance of :class:`IOrganizer<mobase.IOrganizer>` which is the class used to interface with MO2.
|
||||
Here, we use it in the ``isActive()`` method to retrieve the "enabled" setting for our plugin.
|
||||
See :class:`IOrganizer<mobase.IOrganizer>` for more details.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user