diff --git a/.gitignore b/.gitignore index faa97aa..cdea324 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,10 @@ .mypy_cache __pycache__ .vscode +**/*.egg-info # The 'bin/' directory: bin docs/build docs/mobase.py -docs/source/api \ No newline at end of file +docs/source/api diff --git a/main.py b/main.py deleted file mode 100644 index fd84d87..0000000 --- a/main.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- encoding: utf-8 -*- - -import argparse -import logging -from pathlib import Path -from typing import cast - -import black -import isort - -from generator import LOGGER -from generator.loader import load_mobase -from generator.mtypes import Class, Function, PyType -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() - -logging.basicConfig() -LOGGER.setLevel(logging.WARNING) - -if args.verbose: - LOGGER.setLevel(logging.INFO) - -output_path = cast(Path, args.output) - -# 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 - - # ignore the private module - if name == "private": - continue - - # for now, ignore this since it is a submodule and we - # not handle them - if name == "widgets": - continue - - # IPlugin is not the real object - if name == "IPlugin": - continue - - objects.append((name, getattr(mobase, name))) - -# enum first, and then alphabetical, should be fine with the __future__ import -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) - - # the __future__ import must be at the beginning - writer.print_imports([("__future__", ["annotations"])]) - writer.print_version(settings.mobase["__version__"]) # type: ignore - writer.print_imports( - [ - "abc", - ("enum", ["Enum"]), - ("pathlib", ["Path"]), - ( - "typing", - [ - "Dict", - "Iterator", - "List", - "Tuple", - "Union", - "Any", - "Optional", - "Callable", - "overload", - "Sequence", - "Set", - "TypeVar", - "Type", - ], - ), - "PyQt6.QtCore", - "PyQt6.QtGui", - "PyQt6.QtWidgets", - ] - ) - - # Needs to define the MVariant and GameFeatureType type: - writer._print(f"MoVariant = {PyType.MO_VARIANT}") - writer._print(f"FileWrapper = {PyType.FILE_WRAPPER}") - writer._print(f"DirectoryWrapper = {PyType.DIRECTORY_WRAPPER}") - 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( - output_path, - fast=False, - mode=black.Mode(is_pyi=args.output.name.endswith("pyi")), - write_back=black.WriteBack.YES, -) -isort.api.sort_file(output_path) diff --git a/setup.cfg b/setup.cfg index 544fcfb..be2b54e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,7 +34,7 @@ deps = types-PyYAML isort commands = - black generator main.py --check --diff - flake8 generator main.py - mypy generator main.py - isort -c generator main.py + black src --check --diff + flake8 src + mypy src + isort -c src diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..31880a5 --- /dev/null +++ b/setup.py @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- + +from setuptools import find_namespace_packages, setup + +install_requires = ["black", "isort"] + +dev_requires = [ + "black", + "flake8-black", + "flake8", + "types-chardet", +] + +setup( + name="mo2-stubs-generator", + version="1.0.0", + package_dir={"": "src"}, + packages=find_namespace_packages(where="src", include=["mo2.*"]), + author="Holt59", + author_email="capelle.mikael@gmail", + description="Python stubs generator for mobase (MO2 Python API).", + long_description=open("README.md").read(), + url="https://github.com/ModOrganizer2/pystubs-generation", + classifiers=[ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + ], + license="MIT", + install_requires=install_requires, + extras_require={"dev": dev_requires}, + entry_points={ + "console_scripts": ["mo2-stubs-generator=mo2.stubs.generator.__main__:main"], + }, +) diff --git a/generator/__init__.py b/src/mo2/stubs/generator/__init__.py similarity index 100% rename from generator/__init__.py rename to src/mo2/stubs/generator/__init__.py diff --git a/src/mo2/stubs/generator/__main__.py b/src/mo2/stubs/generator/__main__.py new file mode 100644 index 0000000..00a8566 --- /dev/null +++ b/src/mo2/stubs/generator/__main__.py @@ -0,0 +1,200 @@ +# -*- encoding: utf-8 -*- + +import argparse +import logging +from pathlib import Path +from typing import cast + +import black +import isort + +from . import LOGGER +from .loader import load_mobase +from .mtypes import Class, Function, PyType +from .parser import is_enum +from .register import MOBASE_REGISTER +from .utils import Settings, clean_class +from .writer import Writer + + +def main(): + + 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() + + logging.basicConfig() + LOGGER.setLevel(logging.WARNING) + + if args.verbose: + LOGGER.setLevel(logging.INFO) + + output_path = cast(Path, args.output) + + # 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 + + # ignore the private module + if name == "private": + continue + + # for now, ignore this since it is a submodule and we + # not handle them + if name == "widgets": + continue + + # IPlugin is not the real object + if name == "IPlugin": + continue + + objects.append((name, getattr(mobase, name))) + + # enum first, and then alphabetical, should be fine with the __future__ import + 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) + + # the __future__ import must be at the beginning + writer.print_imports([("__future__", ["annotations"])]) + writer.print_version(settings.mobase["__version__"]) # type: ignore + writer.print_imports( + [ + "abc", + ("enum", ["Enum"]), + ("pathlib", ["Path"]), + ( + "typing", + [ + "Dict", + "Iterator", + "List", + "Tuple", + "Union", + "Any", + "Optional", + "Callable", + "overload", + "Sequence", + "Set", + "TypeVar", + "Type", + ], + ), + "PyQt6.QtCore", + "PyQt6.QtGui", + "PyQt6.QtWidgets", + ] + ) + + # Needs to define the MVariant and GameFeatureType type: + writer._print(f"MoVariant = {PyType.MO_VARIANT}") + writer._print(f"FileWrapper = {PyType.FILE_WRAPPER}") + writer._print(f"DirectoryWrapper = {PyType.DIRECTORY_WRAPPER}") + 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( + output_path, + fast=False, + mode=black.Mode(is_pyi=args.output.name.endswith("pyi")), + write_back=black.WriteBack.YES, + ) + isort.api.sort_file(output_path) + + +if __name__ == "__main__": + main() diff --git a/generator/loader.py b/src/mo2/stubs/generator/loader.py similarity index 97% rename from generator/loader.py rename to src/mo2/stubs/generator/loader.py index 63b0d8a..54946db 100644 --- a/generator/loader.py +++ b/src/mo2/stubs/generator/loader.py @@ -33,7 +33,7 @@ def load_mobase(path: Path): # We need to add plugins/data to sys.path, mainly for PyQt6 sys.path.insert(1, path.joinpath("plugins", "plugin_python", "libs").as_posix()) - import mobase + import mobase # type: ignore return mobase diff --git a/generator/mtypes.py b/src/mo2/stubs/generator/mtypes.py similarity index 100% rename from generator/mtypes.py rename to src/mo2/stubs/generator/mtypes.py diff --git a/generator/parser.py b/src/mo2/stubs/generator/parser.py similarity index 100% rename from generator/parser.py rename to src/mo2/stubs/generator/parser.py diff --git a/generator/register.py b/src/mo2/stubs/generator/register.py similarity index 100% rename from generator/register.py rename to src/mo2/stubs/generator/register.py diff --git a/generator/utils.py b/src/mo2/stubs/generator/utils.py similarity index 100% rename from generator/utils.py rename to src/mo2/stubs/generator/utils.py diff --git a/generator/writer.py b/src/mo2/stubs/generator/writer.py similarity index 100% rename from generator/writer.py rename to src/mo2/stubs/generator/writer.py