From 1e64a589a9ba2d491c2900fc2f87942fe3cf111b Mon Sep 17 00:00:00 2001 From: object-Object Date: Tue, 5 Sep 2023 01:26:00 -0400 Subject: [PATCH] Use pluggy for fetching mod version --- .vscode/launch.json | 2 +- doc/src/hexdoc/hexcasting/hooks.py | 7 ++++ doc/src/hexdoc/plugin/__init__.py | 5 +++ doc/src/hexdoc/plugin/helpers.py | 10 ++++++ doc/src/hexdoc/plugin/hookspecs.py | 8 +++++ doc/src/hexdoc/scripts/hexdoc.py | 45 +++++++++++++++++++------ doc/src/hexdoc/scripts/hexdoc_merge.py | 6 ++-- doc/src/hexdoc/utils/compat.py | 8 ++--- doc/src/hexdoc/utils/resource_loader.py | 7 ++-- pyproject.toml | 19 +++++------ 10 files changed, 84 insertions(+), 33 deletions(-) create mode 100644 doc/src/hexdoc/hexcasting/hooks.py create mode 100644 doc/src/hexdoc/plugin/__init__.py create mode 100644 doc/src/hexdoc/plugin/helpers.py create mode 100644 doc/src/hexdoc/plugin/hookspecs.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 9ff0c3b0..be85c780 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,7 +16,7 @@ "name": "Python: Generate Docs", "type": "python", "request": "launch", - "module": "hexdoc.hexdoc", + "module": "hexdoc.scripts.hexdoc", "args": [ "doc/properties.toml", "-o", "out", "--lang", "en_us", ], diff --git a/doc/src/hexdoc/hexcasting/hooks.py b/doc/src/hexdoc/hexcasting/hooks.py new file mode 100644 index 00000000..6565e5cc --- /dev/null +++ b/doc/src/hexdoc/hexcasting/hooks.py @@ -0,0 +1,7 @@ +from hexdoc.__gradle_version__ import GRADLE_VERSION +from hexdoc.plugin import hookimpl + + +@hookimpl +def hexdoc_mod_version(): + return GRADLE_VERSION diff --git a/doc/src/hexdoc/plugin/__init__.py b/doc/src/hexdoc/plugin/__init__.py new file mode 100644 index 00000000..5e114faf --- /dev/null +++ b/doc/src/hexdoc/plugin/__init__.py @@ -0,0 +1,5 @@ +__all__ = ["hookimpl"] + +from pluggy import HookimplMarker + +hookimpl = HookimplMarker("hexdoc") diff --git a/doc/src/hexdoc/plugin/helpers.py b/doc/src/hexdoc/plugin/helpers.py new file mode 100644 index 00000000..2f37ef62 --- /dev/null +++ b/doc/src/hexdoc/plugin/helpers.py @@ -0,0 +1,10 @@ +from pluggy import PluginManager + + +def name_hook_caller(pm: PluginManager, method_name: str, plugin_name: str): + return pm.subset_hook_caller( + name=method_name, + remove_plugins=( + plugin for name, plugin in pm.list_name_plugin() if name != plugin_name + ), + ) diff --git a/doc/src/hexdoc/plugin/hookspecs.py b/doc/src/hexdoc/plugin/hookspecs.py new file mode 100644 index 00000000..d23df209 --- /dev/null +++ b/doc/src/hexdoc/plugin/hookspecs.py @@ -0,0 +1,8 @@ +from pluggy import HookspecMarker + +hookspec = HookspecMarker("hexdoc") + + +@hookspec(firstresult=True) +def hexdoc_mod_version(): + """Return the mod version (aka `GRADLE_VERSION`) from `__gradle_version__.py`.""" diff --git a/doc/src/hexdoc/scripts/hexdoc.py b/doc/src/hexdoc/scripts/hexdoc.py index 50eabba0..a07608a9 100644 --- a/doc/src/hexdoc/scripts/hexdoc.py +++ b/doc/src/hexdoc/scripts/hexdoc.py @@ -6,6 +6,7 @@ import shutil import sys from argparse import ArgumentParser from pathlib import Path +from types import NoneType from typing import Self, Sequence from jinja2 import ( @@ -16,13 +17,16 @@ from jinja2 import ( Template, ) from jinja2.sandbox import SandboxedEnvironment +from pluggy import PluginManager from pydantic import model_validator -from hexdoc.__gradle_version__ import GRADLE_VERSION from hexdoc.hexcasting.hex_book import load_hex_book from hexdoc.minecraft import I18n from hexdoc.patchouli import Book +from hexdoc.plugin import hookspecs +from hexdoc.plugin.helpers import name_hook_caller from hexdoc.utils import HexdocModel, ModResourceLoader, Properties +from hexdoc.utils.deserialize import cast_or_raise, isinstance_or_raise from hexdoc.utils.jinja_extensions import IncludeRawExtension, hexdoc_block, hexdoc_wrap from hexdoc.utils.path import write_to_path @@ -113,15 +117,15 @@ class SitemapMarker(HexdocModel): def main(args: Args | None = None) -> None: + # set stdout to utf-8 so printing to pipe or redirect doesn't break on Windows + # (common windows L) + cast_or_raise(sys.stdout, io.TextIOWrapper).reconfigure(encoding="utf-8") + cast_or_raise(sys.stderr, io.TextIOWrapper).reconfigure(encoding="utf-8") + # allow passing Args for test cases, but parse by default if args is None: args = Args.parse_args() - # set stdout to utf-8 so printing to pipe or redirect doesn't break on Windows - # (common windows L) - assert isinstance(sys.stdout, io.TextIOWrapper) - sys.stdout.reconfigure(encoding="utf-8") - # set up logging logging.basicConfig( style="{", @@ -130,18 +134,34 @@ def main(args: Args | None = None) -> None: ) logger = logging.getLogger(__name__) + # Properties is the main config file for hexdoc props = Properties.load(args.properties_file) logger.debug(props) + # load plugins + # load entry points for props.modid first to make sure its version is added last + pm = PluginManager("hexdoc") + pm.add_hookspecs(hookspecs) + pm.load_setuptools_entrypoints("hexdoc") + pm.check_pending() + + # get the current mod version + version = name_hook_caller(pm, "hexdoc_mod_version", props.modid)() + assert isinstance_or_raise(version, (str, NoneType)) + if version is None: + raise ValueError(f"Missing hexdoc_mod_version hookimpl for {props.modid}") + + print(f"Building docs for {props.modid} {version}") + # just list the languages and exit if args.list_langs: - with ModResourceLoader.load_all(props, export=False) as loader: + with ModResourceLoader.load_all(props, version, export=False) as loader: langs = sorted(I18n.list_all(loader)) print(json.dumps(langs)) return # load everything - with ModResourceLoader.clean_and_load_all(props) as loader: + with ModResourceLoader.clean_and_load_all(props, version) as loader: books = dict[str, Book]() if args.lang: @@ -181,7 +201,8 @@ def main(args: Args | None = None) -> None: if args.export_only: return - # set up Jinja environment + # set up Jinja + env = SandboxedEnvironment( # search order: template_dirs, template_packages loader=ChoiceLoader( @@ -204,6 +225,8 @@ def main(args: Args | None = None) -> None: template = env.get_template(props.template.main) + # render everything + assert (output_dir := args.output_dir) if args.clean: shutil.rmtree(output_dir, ignore_errors=True) @@ -212,11 +235,11 @@ def main(args: Args | None = None) -> None: render_books(props, books, template, output_dir, "latest") if args.is_release: - render_books(props, books, template, output_dir, GRADLE_VERSION) + render_books(props, books, template, output_dir, version) # the default book should be the latest released version if args.update_latest and args.is_release: - render_books(props, books, template, output_dir, GRADLE_VERSION, is_root=True) + render_books(props, books, template, output_dir, version, is_root=True) def render_books( diff --git a/doc/src/hexdoc/scripts/hexdoc_merge.py b/doc/src/hexdoc/scripts/hexdoc_merge.py index 59f44db8..79106af6 100644 --- a/doc/src/hexdoc/scripts/hexdoc_merge.py +++ b/doc/src/hexdoc/scripts/hexdoc_merge.py @@ -6,7 +6,6 @@ from typing import Self, Sequence from pydantic import Field, TypeAdapter -from hexdoc.__gradle_version__ import GRADLE_VERSION from hexdoc.utils import DEFAULT_CONFIG, HexdocModel from hexdoc.utils.path import write_to_path @@ -57,10 +56,11 @@ def main(): args = Args.parse_args() # ensure at least the default language was built successfully - if args.is_release: - assert_version_exists(args.src, GRADLE_VERSION) if args.update_latest: assert_version_exists(args.src, "latest") + # TODO: figure out how to do this with pluggy + # if args.is_release: + # assert_version_exists(args.src, GRADLE_VERSION) args.dst.mkdir(parents=True, exist_ok=True) diff --git a/doc/src/hexdoc/utils/compat.py b/doc/src/hexdoc/utils/compat.py index f7c2319e..1708fc4b 100644 --- a/doc/src/hexdoc/utils/compat.py +++ b/doc/src/hexdoc/utils/compat.py @@ -2,7 +2,7 @@ from enum import Enum from functools import total_ordering from typing import Any, Callable, Self -from hexdoc.__gradle_version__ import GRADLE_VERSION +from hexdoc.__gradle_version__ import GRADLE_VERSION as HEX_VERSION @total_ordering @@ -14,9 +14,9 @@ class HexVersion(Enum): @classmethod def get(cls): for version in cls: - if GRADLE_VERSION.startswith(version.value): + if HEX_VERSION.startswith(version.value): return version - raise ValueError(f"Unknown mod version: {GRADLE_VERSION}") + raise ValueError(f"Unknown mod version: {HEX_VERSION}") @classmethod def check(cls, version: Self | Callable[[Self], bool] | bool, typ: type[Any] | str): @@ -33,7 +33,7 @@ class HexVersion(Enum): if not isinstance(typ, str): typ = typ.__name__ - raise ValueError(f"{typ} is not supported in {GRADLE_VERSION}") + raise ValueError(f"{typ} is not supported in {HEX_VERSION}") @property def _sort_by(self): diff --git a/doc/src/hexdoc/utils/resource_loader.py b/doc/src/hexdoc/utils/resource_loader.py index 961f6637..0cb3998f 100644 --- a/doc/src/hexdoc/utils/resource_loader.py +++ b/doc/src/hexdoc/utils/resource_loader.py @@ -9,7 +9,6 @@ from typing import Callable, Literal, Self, TypeVar, overload from pydantic.dataclasses import dataclass -from hexdoc.__gradle_version__ import GRADLE_VERSION from hexdoc.utils.deserialize import JSONDict, decode_json_dict from hexdoc.utils.model import DEFAULT_CONFIG, HexdocModel, ValidationContext from hexdoc.utils.path import strip_suffixes, write_to_path @@ -45,19 +44,21 @@ class ModResourceLoader: def clean_and_load_all( cls, props: Properties, + version: str, *, export: bool = True, ): # clear the export dir so we start with a clean slate if props.export_dir and export: subprocess.run(["git", "clean", "-fdX", props.export_dir]) - return cls.load_all(props, export=export) + return cls.load_all(props, version, export=export) @classmethod @contextmanager def load_all( cls, props: Properties, + version: str, *, export: bool = True, ) -> Iterator[Self]: @@ -78,7 +79,7 @@ class ModResourceLoader: loader.export( path=HexdocMetadata.path(props.modid), data=HexdocMetadata( - book_url=f"{props.url}/v/{GRADLE_VERSION}", + book_url=f"{props.url}/v/{version}", ).model_dump_json(), ) diff --git a/pyproject.toml b/pyproject.toml index 432c4845..73307164 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["hatchling"] +requires = ["hatchling", "hatch-gradle-version>=0.6.0"] build-backend = "hatchling.build" # project metadata @@ -8,7 +8,7 @@ build-backend = "hatchling.build" name = "hexdoc" dynamic = ["version"] authors = [ - { name="object-Object", email="object@objectobject.ca" }, + { name="object-Object" }, { name="Alwinfy" }, ] readme = "doc/README.md" @@ -17,18 +17,15 @@ classifiers = [ ] requires-python = ">=3.11" dependencies = [ - "hexdoc[build]", # required optional dependency i guess "typing_extensions>=4.6.1", "importlib_resources>=6.0.1", "pydantic>=2.3.0", "Jinja2>=3.1.2", "pyjson5>=1.6.3", + "pluggy>=1.3.0", ] [project.optional-dependencies] -build = [ # need these for build AND runtime - "hatch-gradle-version>=0.6.0", -] dev = [ "black==23.7.0", "isort==5.12.0", @@ -52,9 +49,6 @@ py-path = "doc/src/hexdoc/__version__.py" # directory inclusion [tool.hatch.build] -require-runtime-features = [ - "build", -] artifacts = [ "/doc/src/hexdoc/_export/generated", "/doc/src/hexdoc/__gradle_version__.py", @@ -73,6 +67,9 @@ packages = [ # hexdoc entry points +[project.entry-points.hexdoc] +hexcasting = "hexdoc.hexcasting.hooks" + [project.entry-points."hexdoc.export"] hexcasting = "hexdoc._export:__resources__" @@ -110,8 +107,8 @@ known_first_party = ["hexdoc"] pythonVersion = "3.11" pythonPlatform = "All" -include = [ - "doc/src/hexdoc", +extraPaths = [ + "doc/src", ] # mostly we use strict mode