Edit on GitHub

hexdoc.plugin

 1__all__ = [
 2    "HEXDOC_PROJECT_NAME",
 3    "BookPlugin",
 4    "BookPluginImpl",
 5    "DefaultRenderedTemplates",
 6    "HookReturn",
 7    "LoadTaggedUnionsImpl",
 8    "ModPlugin",
 9    "ModPluginImpl",
10    "ModPluginImplWithProps",
11    "ModPluginWithBook",
12    "PluginManager",
13    "PluginNotFoundError",
14    "UpdateContextImpl",
15    "UpdateTemplateArgsImpl",
16    "ValidateFormatTreeImpl",
17    "VersionedModPlugin",
18    "hookimpl",
19]
20
21import pluggy
22
23from .book_plugin import BookPlugin
24from .manager import (
25    PluginManager,
26    PluginNotFoundError,
27)
28from .mod_plugin import (
29    DefaultRenderedTemplates,
30    ModPlugin,
31    ModPluginWithBook,
32    VersionedModPlugin,
33)
34from .specs import (
35    HEXDOC_PROJECT_NAME,
36    BookPluginImpl,
37    LoadTaggedUnionsImpl,
38    ModPluginImpl,
39    ModPluginImplWithProps,
40    UpdateContextImpl,
41    UpdateTemplateArgsImpl,
42    ValidateFormatTreeImpl,
43)
44from .types import HookReturn
45
46hookimpl = pluggy.HookimplMarker(HEXDOC_PROJECT_NAME)
47"""Decorator for marking functions as hook implementations."""
HEXDOC_PROJECT_NAME = 'hexdoc'
class BookPlugin(abc.ABC, typing.Generic[~_Book]):
15class BookPlugin(ABC, Generic[_Book]):
16    @property
17    @abstractmethod
18    def modid(self) -> str:
19        """The modid of the mod whose book system this plugin implements."""
20
21    @abstractmethod
22    def load_book_data(
23        self,
24        book_id: ResourceLocation,
25        loader: ModResourceLoader,
26    ) -> tuple[ResourceLocation, JSONDict]:
27        """"""
28
29    @abstractmethod
30    def is_i18n_enabled(self, book_data: Mapping[str, Any]) -> bool:
31        """Given the raw book data, returns `True` if i18n is enabled for that book."""
32
33    @abstractmethod
34    def validate_book(
35        self,
36        book_data: Mapping[str, Any],
37        *,
38        context: ContextSource,
39    ) -> _Book:
40        """"""

Helper class that provides a standard way to create an ABC using inheritance.

modid: str
16    @property
17    @abstractmethod
18    def modid(self) -> str:
19        """The modid of the mod whose book system this plugin implements."""

The modid of the mod whose book system this plugin implements.

@abstractmethod
def load_book_data( self, book_id: hexdoc.core.ResourceLocation, loader: hexdoc.core.ModResourceLoader) -> tuple[hexdoc.core.ResourceLocation, dict[str, JsonValue]]:
21    @abstractmethod
22    def load_book_data(
23        self,
24        book_id: ResourceLocation,
25        loader: ModResourceLoader,
26    ) -> tuple[ResourceLocation, JSONDict]:
27        """"""
@abstractmethod
def is_i18n_enabled(self, book_data: Mapping[str, Any]) -> bool:
29    @abstractmethod
30    def is_i18n_enabled(self, book_data: Mapping[str, Any]) -> bool:
31        """Given the raw book data, returns `True` if i18n is enabled for that book."""

Given the raw book data, returns True if i18n is enabled for that book.

@abstractmethod
def validate_book( self, book_data: Mapping[str, Any], *, context: dict[str, typing.Any] | pydantic_core.core_schema.ValidationInfo | jinja2.runtime.Context) -> ~_Book:
33    @abstractmethod
34    def validate_book(
35        self,
36        book_data: Mapping[str, Any],
37        *,
38        context: ContextSource,
39    ) -> _Book:
40        """"""
class BookPluginImpl(hexdoc.plugin.specs.PluginImpl, typing.Protocol):
71class BookPluginImpl(PluginImpl, Protocol):
72    @staticmethod
73    def hexdoc_book_plugin() -> HookReturn[BookPlugin[Any]]:
74        """If your plugin represents a book system (like Patchouli), this must return an
75        instance of a subclass of `BookPlugin` with all abstract methods implemented."""
76        ...

Interface for an implementation of a hexdoc plugin hook.

These protocols are optional - they gives better type checking, but everything will work fine with a standard pluggy hook implementation.

@staticmethod
def hexdoc_book_plugin() -> Union[BookPlugin[Any], list[BookPlugin[Any]]]:
72    @staticmethod
73    def hexdoc_book_plugin() -> HookReturn[BookPlugin[Any]]:
74        """If your plugin represents a book system (like Patchouli), this must return an
75        instance of a subclass of `BookPlugin` with all abstract methods implemented."""
76        ...

If your plugin represents a book system (like Patchouli), this must return an instance of a subclass of BookPlugin with all abstract methods implemented.

DefaultRenderedTemplates = typing.Mapping[str | pathlib.Path, str | tuple[str, typing.Mapping[str, typing.Any]]]
HookReturn = typing.Union[~_T, list[~_T]]
class LoadTaggedUnionsImpl(hexdoc.plugin.specs.PluginImpl, typing.Protocol):
132class LoadTaggedUnionsImpl(PluginImpl, Protocol):
133    @staticmethod
134    def hexdoc_load_tagged_unions() -> HookReturn[Package]:
135        """Return the module(s) which contain your plugin's tagged union subtypes."""
136        ...

Interface for an implementation of a hexdoc plugin hook.

These protocols are optional - they gives better type checking, but everything will work fine with a standard pluggy hook implementation.

@staticmethod
def hexdoc_load_tagged_unions() -> Union[module, str, list[Union[module, str]]]:
133    @staticmethod
134    def hexdoc_load_tagged_unions() -> HookReturn[Package]:
135        """Return the module(s) which contain your plugin's tagged union subtypes."""
136        ...

Return the module(s) which contain your plugin's tagged union subtypes.

@dataclass(kw_only=True)
class ModPlugin(abc.ABC):
 29@dataclass(kw_only=True)
 30class ModPlugin(ABC):
 31    """Hexdoc plugin hooks that are tied to a specific Minecraft mod.
 32
 33    If you want to render a web book, subclass `ModPluginWithBook` instead.
 34
 35    Abstract methods are required. All other methods can optionally be implemented to
 36    override or add functionality to hexdoc.
 37
 38    Non-mod-specific hooks are implemented with normal Pluggy hooks instead.
 39    """
 40
 41    branch: str
 42    props: Properties | None = None
 43
 44    # required hooks
 45
 46    @property
 47    @abstractmethod
 48    def modid(self) -> str:
 49        """The modid of the Minecraft mod version that this plugin represents.
 50
 51        For example: `hexcasting`
 52        """
 53
 54    @property
 55    @abstractmethod
 56    def full_version(self) -> str:
 57        """The full PyPI version of this plugin.
 58
 59        This should generally return `your_plugin.__gradle_version__.FULL_VERSION`.
 60
 61        For example: `0.11.1.1.0rc7.dev20`
 62        """
 63
 64    @property
 65    @abstractmethod
 66    def plugin_version(self) -> str:
 67        """The hexdoc-specific component of this plugin's version number.
 68
 69        This should generally return `your_plugin.__version__.PY_VERSION`.
 70
 71        For example: `1.0.dev20`
 72        """
 73
 74    # optional hooks
 75
 76    @property
 77    def compat_minecraft_version(self) -> str | None:
 78        """The version of Minecraft supported by the mod that this plugin represents.
 79
 80        If no plugins implement this, models and validation for all Minecraft versions
 81        may be used. Currently, if two or more plugins provide different values, an
 82        error will be raised.
 83
 84        This should generally return `your_plugin.__gradle_version__.MINECRAFT_VERSION`.
 85
 86        For example: `1.20.1`
 87        """
 88        return None
 89
 90    @property
 91    def mod_version(self) -> str | None:
 92        """The Minecraft mod version that this plugin represents.
 93
 94        This should generally return `your_plugin.__gradle_version__.GRADLE_VERSION`.
 95
 96        For example: `0.11.1-7`
 97        """
 98        return None
 99
100    def resource_dirs(self) -> HookReturn[Package]:
101        """The module(s) that contain your plugin's Minecraft resources to be rendered.
102
103        For example: `your_plugin._export.generated`
104        """
105        return []
106
107    def jinja_template_root(self) -> HookReturn[tuple[Package, str]] | None:
108        """The module that contains the folder with your plugin's Jinja templates, and
109        the name of that folder.
110
111        For example: `your_plugin, "_templates"`
112        """
113        return None
114
115    def default_rendered_templates(self) -> DefaultRenderedTemplates:
116        """Extra templates to be rendered by default when your plugin is active.
117
118        The key is the output path, and the value is the template to import and render.
119        It may also be a tuple where the first item is the template and the second is
120        a dict to be merged with the arguments for that template.
121
122        This hook is not called if `props.template.render` is set, since that option
123        overrides all default templates.
124        """
125        return {}
126
127    def default_rendered_templates_v2(
128        self,
129        book: Any,
130        context: ContextSource,
131    ) -> DefaultRenderedTemplates:
132        """Like `default_rendered_templates`, but gets access to the book and context.
133
134        This is useful for dynamically generating multi-file output structures.
135        """
136        return {}
137
138    def update_jinja_env(self, env: SandboxedEnvironment) -> None:
139        """Modify the Jinja environment/configuration.
140
141        This is called after hexdoc is done setting up the Jinja environment but before
142        rendering the book.
143        """
144
145    def pre_render_site(
146        self,
147        props: Properties,
148        books: list[LoadedBookInfo],
149        env: SandboxedEnvironment,
150        output_dir: Path,
151    ) -> None:
152        """Called once, after hexdoc is done setting up the Jinja environment but before
153        rendering the book."""
154
155    def post_render_site(
156        self,
157        props: Properties,
158        books: list[LoadedBookInfo],
159        env: SandboxedEnvironment,
160        output_dir: Path,
161    ) -> None:
162        """Called once, at the end of `hexdoc build`."""
163
164    def pre_render_book(self, template_args: dict[str, Any], output_dir: Path) -> None:
165        """Called once per language, just before rendering the book for that
166        language."""
167
168    def post_render_book(self, template_args: dict[str, Any], output_dir: Path) -> None:
169        """Called once per language, after all book files for that language are
170        rendered."""
171
172    # utils
173
174    def site_path(self, versioned: bool):
175        if versioned:
176            return self.versioned_site_path
177        return self.latest_site_path
178
179    @property
180    def site_root(self) -> Path:
181        """Base path for all rendered web pages.
182
183        For example:
184        * URL: `https://gamma-delta.github.io/HexMod/v/0.11.1-7/1.0.dev20/en_us`
185        * value: `v`
186        """
187        return Path("v")
188
189    @property
190    def versioned_site_path(self) -> Path:
191        """Base path for the web pages for the current version.
192
193        For example:
194        * URL: `https://hexdoc.hexxy.media/book/v/1!0.1.0.dev0` (decoded)
195        * value: `book/v/1!0.1.0.dev0`
196        """
197        return self.site_root / self.full_version
198
199    @property
200    def latest_site_path(self) -> Path:
201        """Base path for the latest web pages for a given branch.
202
203        For example:
204        * URL: `https://gamma-delta.github.io/HexMod/v/latest/main/en_us`
205        * value: `v/latest/main`
206        """
207        return self.site_root / "latest" / self.branch
208
209    def asset_loader(
210        self,
211        loader: ModResourceLoader,
212        *,
213        site_url: URL,
214        asset_url: URL,
215        render_dir: Path,
216    ) -> HexdocAssetLoader:
217        # unfortunately, this is necessary to avoid some *real* ugly circular imports
218        from hexdoc.minecraft.assets import HexdocAssetLoader
219
220        return HexdocAssetLoader(
221            loader=loader,
222            site_url=site_url,
223            asset_url=asset_url,
224            render_dir=render_dir,
225        )
226
227    @property
228    def flags(self) -> dict[str, bool]:
229        """Set default values for Patchouli config flags.
230
231        Unlike the table in `hexdoc.toml`, this is available for use in dependents.
232        """
233        return {}

Hexdoc plugin hooks that are tied to a specific Minecraft mod.

If you want to render a web book, subclass ModPluginWithBook instead.

Abstract methods are required. All other methods can optionally be implemented to override or add functionality to hexdoc.

Non-mod-specific hooks are implemented with normal Pluggy hooks instead.

branch: str
props: hexdoc.core.Properties | None = None
modid: str
46    @property
47    @abstractmethod
48    def modid(self) -> str:
49        """The modid of the Minecraft mod version that this plugin represents.
50
51        For example: `hexcasting`
52        """

The modid of the Minecraft mod version that this plugin represents.

For example: hexcasting

full_version: str
54    @property
55    @abstractmethod
56    def full_version(self) -> str:
57        """The full PyPI version of this plugin.
58
59        This should generally return `your_plugin.__gradle_version__.FULL_VERSION`.
60
61        For example: `0.11.1.1.0rc7.dev20`
62        """

The full PyPI version of this plugin.

This should generally return your_plugin.__gradle_version__.FULL_VERSION.

For example: 0.11.1.1.0rc7.dev20

plugin_version: str
64    @property
65    @abstractmethod
66    def plugin_version(self) -> str:
67        """The hexdoc-specific component of this plugin's version number.
68
69        This should generally return `your_plugin.__version__.PY_VERSION`.
70
71        For example: `1.0.dev20`
72        """

The hexdoc-specific component of this plugin's version number.

This should generally return your_plugin.__version__.PY_VERSION.

For example: 1.0.dev20

compat_minecraft_version: str | None
76    @property
77    def compat_minecraft_version(self) -> str | None:
78        """The version of Minecraft supported by the mod that this plugin represents.
79
80        If no plugins implement this, models and validation for all Minecraft versions
81        may be used. Currently, if two or more plugins provide different values, an
82        error will be raised.
83
84        This should generally return `your_plugin.__gradle_version__.MINECRAFT_VERSION`.
85
86        For example: `1.20.1`
87        """
88        return None

The version of Minecraft supported by the mod that this plugin represents.

If no plugins implement this, models and validation for all Minecraft versions may be used. Currently, if two or more plugins provide different values, an error will be raised.

This should generally return your_plugin.__gradle_version__.MINECRAFT_VERSION.

For example: 1.20.1

mod_version: str | None
90    @property
91    def mod_version(self) -> str | None:
92        """The Minecraft mod version that this plugin represents.
93
94        This should generally return `your_plugin.__gradle_version__.GRADLE_VERSION`.
95
96        For example: `0.11.1-7`
97        """
98        return None

The Minecraft mod version that this plugin represents.

This should generally return your_plugin.__gradle_version__.GRADLE_VERSION.

For example: 0.11.1-7

def resource_dirs(self) -> Union[module, str, list[Union[module, str]]]:
100    def resource_dirs(self) -> HookReturn[Package]:
101        """The module(s) that contain your plugin's Minecraft resources to be rendered.
102
103        For example: `your_plugin._export.generated`
104        """
105        return []

The module(s) that contain your plugin's Minecraft resources to be rendered.

For example: your_plugin._export.generated

def jinja_template_root( self) -> Union[tuple[Union[module, str], str], list[tuple[Union[module, str], str]], NoneType]:
107    def jinja_template_root(self) -> HookReturn[tuple[Package, str]] | None:
108        """The module that contains the folder with your plugin's Jinja templates, and
109        the name of that folder.
110
111        For example: `your_plugin, "_templates"`
112        """
113        return None

The module that contains the folder with your plugin's Jinja templates, and the name of that folder.

For example: your_plugin, "_templates"

def default_rendered_templates(self) -> Mapping[str | pathlib.Path, str | tuple[str, Mapping[str, Any]]]:
115    def default_rendered_templates(self) -> DefaultRenderedTemplates:
116        """Extra templates to be rendered by default when your plugin is active.
117
118        The key is the output path, and the value is the template to import and render.
119        It may also be a tuple where the first item is the template and the second is
120        a dict to be merged with the arguments for that template.
121
122        This hook is not called if `props.template.render` is set, since that option
123        overrides all default templates.
124        """
125        return {}

Extra templates to be rendered by default when your plugin is active.

The key is the output path, and the value is the template to import and render. It may also be a tuple where the first item is the template and the second is a dict to be merged with the arguments for that template.

This hook is not called if props.template.render is set, since that option overrides all default templates.

def default_rendered_templates_v2( self, book: Any, context: dict[str, typing.Any] | pydantic_core.core_schema.ValidationInfo | jinja2.runtime.Context) -> Mapping[str | pathlib.Path, str | tuple[str, Mapping[str, Any]]]:
127    def default_rendered_templates_v2(
128        self,
129        book: Any,
130        context: ContextSource,
131    ) -> DefaultRenderedTemplates:
132        """Like `default_rendered_templates`, but gets access to the book and context.
133
134        This is useful for dynamically generating multi-file output structures.
135        """
136        return {}

Like default_rendered_templates, but gets access to the book and context.

This is useful for dynamically generating multi-file output structures.

def update_jinja_env(self, env: jinja2.sandbox.SandboxedEnvironment) -> None:
138    def update_jinja_env(self, env: SandboxedEnvironment) -> None:
139        """Modify the Jinja environment/configuration.
140
141        This is called after hexdoc is done setting up the Jinja environment but before
142        rendering the book.
143        """

Modify the Jinja environment/configuration.

This is called after hexdoc is done setting up the Jinja environment but before rendering the book.

def pre_render_site( self, props: hexdoc.core.Properties, books: list[hexdoc.cli.app.LoadedBookInfo], env: jinja2.sandbox.SandboxedEnvironment, output_dir: pathlib.Path) -> None:
145    def pre_render_site(
146        self,
147        props: Properties,
148        books: list[LoadedBookInfo],
149        env: SandboxedEnvironment,
150        output_dir: Path,
151    ) -> None:
152        """Called once, after hexdoc is done setting up the Jinja environment but before
153        rendering the book."""

Called once, after hexdoc is done setting up the Jinja environment but before rendering the book.

def post_render_site( self, props: hexdoc.core.Properties, books: list[hexdoc.cli.app.LoadedBookInfo], env: jinja2.sandbox.SandboxedEnvironment, output_dir: pathlib.Path) -> None:
155    def post_render_site(
156        self,
157        props: Properties,
158        books: list[LoadedBookInfo],
159        env: SandboxedEnvironment,
160        output_dir: Path,
161    ) -> None:
162        """Called once, at the end of `hexdoc build`."""

Called once, at the end of hexdoc build.

def pre_render_book( self, template_args: dict[str, typing.Any], output_dir: pathlib.Path) -> None:
164    def pre_render_book(self, template_args: dict[str, Any], output_dir: Path) -> None:
165        """Called once per language, just before rendering the book for that
166        language."""

Called once per language, just before rendering the book for that language.

def post_render_book( self, template_args: dict[str, typing.Any], output_dir: pathlib.Path) -> None:
168    def post_render_book(self, template_args: dict[str, Any], output_dir: Path) -> None:
169        """Called once per language, after all book files for that language are
170        rendered."""

Called once per language, after all book files for that language are rendered.

def site_path(self, versioned: bool):
174    def site_path(self, versioned: bool):
175        if versioned:
176            return self.versioned_site_path
177        return self.latest_site_path
site_root: pathlib.Path
179    @property
180    def site_root(self) -> Path:
181        """Base path for all rendered web pages.
182
183        For example:
184        * URL: `https://gamma-delta.github.io/HexMod/v/0.11.1-7/1.0.dev20/en_us`
185        * value: `v`
186        """
187        return Path("v")

Base path for all rendered web pages.

For example:

  • URL: https://gamma-delta.github.io/HexMod/v/0.11.1-7/1.0.dev20/en_us
  • value: v
versioned_site_path: pathlib.Path
189    @property
190    def versioned_site_path(self) -> Path:
191        """Base path for the web pages for the current version.
192
193        For example:
194        * URL: `https://hexdoc.hexxy.media/book/v/1!0.1.0.dev0` (decoded)
195        * value: `book/v/1!0.1.0.dev0`
196        """
197        return self.site_root / self.full_version

Base path for the web pages for the current version.

For example:

  • URL: https://hexdoc.hexxy.media/book/v/1!0.1.0.dev0 (decoded)
  • value: book/v/1!0.1.0.dev0
latest_site_path: pathlib.Path
199    @property
200    def latest_site_path(self) -> Path:
201        """Base path for the latest web pages for a given branch.
202
203        For example:
204        * URL: `https://gamma-delta.github.io/HexMod/v/latest/main/en_us`
205        * value: `v/latest/main`
206        """
207        return self.site_root / "latest" / self.branch

Base path for the latest web pages for a given branch.

For example:

  • URL: https://gamma-delta.github.io/HexMod/v/latest/main/en_us
  • value: v/latest/main
def asset_loader( self, loader: hexdoc.core.ModResourceLoader, *, site_url: yarl.URL, asset_url: yarl.URL, render_dir: pathlib.Path) -> hexdoc.minecraft.assets.HexdocAssetLoader:
209    def asset_loader(
210        self,
211        loader: ModResourceLoader,
212        *,
213        site_url: URL,
214        asset_url: URL,
215        render_dir: Path,
216    ) -> HexdocAssetLoader:
217        # unfortunately, this is necessary to avoid some *real* ugly circular imports
218        from hexdoc.minecraft.assets import HexdocAssetLoader
219
220        return HexdocAssetLoader(
221            loader=loader,
222            site_url=site_url,
223            asset_url=asset_url,
224            render_dir=render_dir,
225        )
flags: dict[str, bool]
227    @property
228    def flags(self) -> dict[str, bool]:
229        """Set default values for Patchouli config flags.
230
231        Unlike the table in `hexdoc.toml`, this is available for use in dependents.
232        """
233        return {}

Set default values for Patchouli config flags.

Unlike the table in hexdoc.toml, this is available for use in dependents.

class ModPluginImpl(hexdoc.plugin.specs.PluginImpl, typing.Protocol):
79class ModPluginImpl(PluginImpl, Protocol):
80    @staticmethod
81    def hexdoc_mod_plugin(branch: str) -> HookReturn[ModPlugin]:
82        """If your plugin represents a Minecraft mod, this must return an instance of a
83        subclass of `ModPlugin` or `ModPluginWithBook`, with all abstract methods
84        implemented."""
85        ...

Interface for an implementation of a hexdoc plugin hook.

These protocols are optional - they gives better type checking, but everything will work fine with a standard pluggy hook implementation.

@staticmethod
def hexdoc_mod_plugin( branch: str) -> Union[ModPlugin, list[ModPlugin]]:
80    @staticmethod
81    def hexdoc_mod_plugin(branch: str) -> HookReturn[ModPlugin]:
82        """If your plugin represents a Minecraft mod, this must return an instance of a
83        subclass of `ModPlugin` or `ModPluginWithBook`, with all abstract methods
84        implemented."""
85        ...

If your plugin represents a Minecraft mod, this must return an instance of a subclass of ModPlugin or ModPluginWithBook, with all abstract methods implemented.

class ModPluginImplWithProps(hexdoc.plugin.specs.PluginImpl, typing.Protocol):
88class ModPluginImplWithProps(PluginImpl, Protocol):
89    @staticmethod
90    def hexdoc_mod_plugin(branch: str, props: Properties) -> HookReturn[ModPlugin]:
91        """If your plugin represents a Minecraft mod, this must return an instance of a
92        subclass of `ModPlugin` or `ModPluginWithBook`, with all abstract methods
93        implemented."""
94        ...

Interface for an implementation of a hexdoc plugin hook.

These protocols are optional - they gives better type checking, but everything will work fine with a standard pluggy hook implementation.

@staticmethod
def hexdoc_mod_plugin( branch: str, props: hexdoc.core.Properties) -> Union[ModPlugin, list[ModPlugin]]:
89    @staticmethod
90    def hexdoc_mod_plugin(branch: str, props: Properties) -> HookReturn[ModPlugin]:
91        """If your plugin represents a Minecraft mod, this must return an instance of a
92        subclass of `ModPlugin` or `ModPluginWithBook`, with all abstract methods
93        implemented."""
94        ...

If your plugin represents a Minecraft mod, this must return an instance of a subclass of ModPlugin or ModPluginWithBook, with all abstract methods implemented.

class ModPluginWithBook(hexdoc.plugin.VersionedModPlugin):
256class ModPluginWithBook(VersionedModPlugin):
257    """Like `ModPlugin`, but with extra hooks to support rendering a web book."""
258
259    @abstractmethod
260    @override
261    def resource_dirs(self) -> HookReturn[Package]: ...
262
263    def site_book_path(self, lang: str, versioned: bool) -> Path:
264        if versioned:
265            return self.versioned_site_book_path(lang)
266        return self.latest_site_book_path(lang)
267
268    def versioned_site_book_path(self, lang: str) -> Path:
269        """Base path for the rendered web book for the current version.
270
271        For example:
272        * URL: `https://gamma-delta.github.io/HexMod/v/0.11.1-7/1.0.dev20/en_us`
273        * value: `v/0.11.1-7/1.0.dev20/en_us`
274        """
275        return self.versioned_site_path / lang
276
277    def latest_site_book_path(self, lang: str) -> Path:
278        """Base path for the latest rendered web book for a given branch.
279
280        For example:
281        * URL: `https://gamma-delta.github.io/HexMod/v/latest/main/en_us`
282        * value: `v/latest/main/en_us`
283        """
284        return self.latest_site_path / lang

Like ModPlugin, but with extra hooks to support rendering a web book.

@abstractmethod
@override
def resource_dirs(self) -> Union[module, str, list[Union[module, str]]]:
259    @abstractmethod
260    @override
261    def resource_dirs(self) -> HookReturn[Package]: ...

The module(s) that contain your plugin's Minecraft resources to be rendered.

For example: your_plugin._export.generated

def site_book_path(self, lang: str, versioned: bool) -> pathlib.Path:
263    def site_book_path(self, lang: str, versioned: bool) -> Path:
264        if versioned:
265            return self.versioned_site_book_path(lang)
266        return self.latest_site_book_path(lang)
def versioned_site_book_path(self, lang: str) -> pathlib.Path:
268    def versioned_site_book_path(self, lang: str) -> Path:
269        """Base path for the rendered web book for the current version.
270
271        For example:
272        * URL: `https://gamma-delta.github.io/HexMod/v/0.11.1-7/1.0.dev20/en_us`
273        * value: `v/0.11.1-7/1.0.dev20/en_us`
274        """
275        return self.versioned_site_path / lang

Base path for the rendered web book for the current version.

For example:

  • URL: https://gamma-delta.github.io/HexMod/v/0.11.1-7/1.0.dev20/en_us
  • value: v/0.11.1-7/1.0.dev20/en_us
def latest_site_book_path(self, lang: str) -> pathlib.Path:
277    def latest_site_book_path(self, lang: str) -> Path:
278        """Base path for the latest rendered web book for a given branch.
279
280        For example:
281        * URL: `https://gamma-delta.github.io/HexMod/v/latest/main/en_us`
282        * value: `v/latest/main/en_us`
283        """
284        return self.latest_site_path / lang

Base path for the latest rendered web book for a given branch.

For example:

  • URL: https://gamma-delta.github.io/HexMod/v/latest/main/en_us
  • value: v/latest/main/en_us
class PluginManager(hexdoc.utils.context.ValidationContext):
 97class PluginManager(ValidationContext):
 98    """Custom hexdoc plugin manager with helpers and stronger typing."""
 99
100    def __init__(self, branch: str, props: Properties, load: bool = True) -> None:
101        """Initialize the hexdoc plugin manager.
102
103        If `load` is true (the default), calls `init_entrypoints` and `init_mod_plugins`.
104        """
105        self.branch = branch
106        self.props = props
107        self.inner = pluggy.PluginManager(HEXDOC_PROJECT_NAME)
108        self.mod_plugins: dict[str, ModPlugin] = {}
109        self.book_plugins: dict[str, BookPlugin[Any]] = {}
110
111        self.inner.add_hookspecs(PluginSpec)
112        if load:
113            self.init_entrypoints()
114            self.init_plugins()
115
116    def init_entrypoints(self):
117        self.inner.load_setuptools_entrypoints(HEXDOC_PROJECT_NAME)
118        self.inner.check_pending()
119
120    def init_plugins(self):
121        self._init_book_plugins()
122        self._init_mod_plugins()
123
124    def _init_book_plugins(self):
125        caller = self._hook_caller(PluginSpec.hexdoc_book_plugin)
126        for plugin in flatten(caller.try_call()):
127            self.book_plugins[plugin.modid] = plugin
128
129    def _init_mod_plugins(self):
130        caller = self._hook_caller(PluginSpec.hexdoc_mod_plugin)
131        for plugin in flatten(
132            caller.try_call(
133                branch=self.branch,
134                props=self.props,
135            )
136        ):
137            self.mod_plugins[plugin.modid] = plugin
138
139    def register(self, plugin: Any, name: str | None = None):
140        self.inner.register(plugin, name)
141        self.init_plugins()
142
143    def book_plugin(self, modid: str):
144        plugin = self.book_plugins.get(modid)
145        if plugin is None:
146            raise ValueError(f"No BookPlugin registered for modid: {modid}")
147        return plugin
148
149    @overload
150    def mod_plugin(self, modid: str, book: Literal[True]) -> ModPluginWithBook: ...
151
152    @overload
153    def mod_plugin(self, modid: str, book: bool = False) -> ModPlugin: ...
154
155    def mod_plugin(self, modid: str, book: bool = False):
156        plugin = self.mod_plugins.get(modid)
157        if plugin is None:
158            raise ValueError(f"No ModPlugin registered for modid: {modid}")
159
160        if book and not isinstance(plugin, ModPluginWithBook):
161            raise ValueError(
162                f"ModPlugin registered for modid `{modid}`"
163                f" does not inherit from ModPluginWithBook: {plugin}"
164            )
165
166        return plugin
167
168    def mod_plugin_with_book(self, modid: str):
169        return self.mod_plugin(modid, book=True)
170
171    def minecraft_version(self) -> str | None:
172        versions = dict[str, str]()
173
174        for modid, plugin in self.mod_plugins.items():
175            version = plugin.compat_minecraft_version
176            if version is not None:
177                versions[modid] = version
178
179        match len(set(versions.values())):
180            case 0:
181                return None
182            case 1:
183                return versions.popitem()[1]
184            case n:
185                raise ValueError(
186                    f"Got {n} Minecraft versions, expected 1: "
187                    + ", ".join(
188                        f"{modid}={version}" for modid, version in versions.items()
189                    )
190                )
191
192    def validate_format_tree(
193        self,
194        tree: FormatTree,
195        macros: dict[str, str],
196        book_id: ResourceLocation,
197        i18n: I18n,
198        is_0_black: bool,
199        link_overrides: dict[str, str],
200    ):
201        caller = self._hook_caller(PluginSpec.hexdoc_validate_format_tree)
202        caller.try_call(
203            tree=tree,
204            macros=macros,
205            book_id=book_id,
206            i18n=i18n,
207            is_0_black=is_0_black,
208            link_overrides=link_overrides,
209        )
210        return tree
211
212    def update_context(self, context: dict[str, Any]) -> Iterator[ValidationContext]:
213        caller = self._hook_caller(PluginSpec.hexdoc_update_context)
214        if returns := caller.try_call(context=context):
215            yield from flatten(returns)
216
217    def update_jinja_env(self, modids: Sequence[str], env: SandboxedEnvironment):
218        for modid in modids:
219            plugin = self.mod_plugin(modid)
220            plugin.update_jinja_env(env)
221        return env
222
223    def pre_render_site(
224        self,
225        books: list[LoadedBookInfo],
226        env: SandboxedEnvironment,
227        output_dir: Path,
228        modids: Sequence[str],
229    ):
230        for modid in modids:
231            self.mod_plugin(modid).pre_render_site(self.props, books, env, output_dir)
232
233    def post_render_site(
234        self,
235        books: list[LoadedBookInfo],
236        env: SandboxedEnvironment,
237        output_dir: Path,
238        modids: Sequence[str],
239    ):
240        for modid in modids:
241            self.mod_plugin(modid).post_render_site(self.props, books, env, output_dir)
242
243    def update_template_args(self, template_args: dict[str, Any]):
244        caller = self._hook_caller(PluginSpec.hexdoc_update_template_args)
245        caller.try_call(template_args=template_args)
246        return template_args
247
248    def pre_render_book(
249        self,
250        template_args: dict[str, Any],
251        output_dir: Path,
252        modids: Sequence[str],
253    ):
254        for modid in modids:
255            self.mod_plugin(modid).pre_render_book(template_args, output_dir)
256
257    def post_render_book(
258        self,
259        template_args: dict[str, Any],
260        output_dir: Path,
261        modids: Sequence[str],
262    ):
263        for modid in modids:
264            self.mod_plugin(modid).post_render_book(template_args, output_dir)
265
266    def load_resources(self, modid: str) -> Iterator[ModuleType]:
267        plugin = self.mod_plugin(modid)
268        for package in flatten([plugin.resource_dirs()]):
269            yield import_package(package)
270
271    def load_tagged_unions(self) -> Iterator[ModuleType]:
272        yield from self._import_from_hook(PluginSpec.hexdoc_load_tagged_unions)
273
274    def load_jinja_templates(self, modids: Sequence[str]):
275        """modid -> PackageLoader"""
276        extra_modids = set(self.mod_plugins.keys()) - set(modids)
277
278        included = self._package_loaders_for(modids)
279        extra = self._package_loaders_for(extra_modids)
280
281        return included, extra
282
283    def _package_loaders_for(self, modids: Iterable[str]):
284        loaders = dict[str, BaseLoader]()
285
286        for modid in modids:
287            plugin = self.mod_plugin(modid)
288
289            result = plugin.jinja_template_root()
290            if not result:
291                continue
292
293            loaders[modid] = ChoiceLoader(
294                [
295                    PackageLoader(
296                        package_name=import_package(package).__name__,
297                        package_path=package_path,
298                    )
299                    for package, package_path in flatten([result])
300                ]
301            )
302
303        return loaders
304
305    def default_rendered_templates(
306        self,
307        modids: Iterable[str],
308        book: Any,
309        context: ContextSource,
310    ):
311        templates: DefaultRenderedTemplates = {}
312        for modid in modids:
313            plugin = self.mod_plugin(modid)
314            templates |= plugin.default_rendered_templates()
315            templates |= plugin.default_rendered_templates_v2(book, context)
316
317        result = dict[Path, tuple[str, Mapping[str, Any]]]()
318        for path, value in templates.items():
319            match value:
320                case str(template):
321                    args = dict[str, Any]()
322                case (template, args):
323                    pass
324            result[Path(path)] = (template, args)
325
326        return result
327
328    def _import_from_hook(
329        self,
330        __spec: Callable[_P, HookReturns[Package]],
331        *args: _P.args,
332        **kwargs: _P.kwargs,
333    ) -> Iterator[ModuleType]:
334        packages = self._hook_caller(__spec)(*args, **kwargs)
335        for package in flatten(packages):
336            yield import_package(package)
337
338    def load_flags(self) -> dict[str, bool]:
339        # https://vazkiimods.github.io/Patchouli/docs/patchouli-basics/config-gating
340        flags = {
341            "debug": False,
342            "advancements_disabled": False,
343            "testing_mode": False,
344        }
345
346        # first, resolve exported flags by OR
347        for modid, plugin in self.mod_plugins.items():
348            for flag, value in plugin.flags.items():
349                if flag in flags:
350                    resolved = flags[flag] = flags[flag] or value
351                    logger.debug(
352                        f"{modid} exports flag {flag}={value}, resolving to {resolved}"
353                    )
354                else:
355                    flags[flag] = value
356                    logger.debug(f"{modid} exports flag {flag}={value}")
357
358        # then, apply local overrides
359        flags |= self.props.flags
360
361        # any missing flags will default to True
362
363        logger.debug(f"Resolved flags: {flags}")
364        return flags
365
366    @overload
367    def _hook_caller(self, spec: Callable[_P, None]) -> _NoCallTypedHookCaller[_P]: ...
368
369    @overload
370    def _hook_caller(
371        self, spec: Callable[_P, _R | None]
372    ) -> TypedHookCaller[_P, _R]: ...
373
374    def _hook_caller(self, spec: Callable[_P, _R | None]) -> TypedHookCaller[_P, _R]:
375        caller = self.inner.hook.__dict__[spec.__name__]
376        return TypedHookCaller(None, caller)

Custom hexdoc plugin manager with helpers and stronger typing.

PluginManager( branch: str, props: hexdoc.core.Properties, load: bool = True)
100    def __init__(self, branch: str, props: Properties, load: bool = True) -> None:
101        """Initialize the hexdoc plugin manager.
102
103        If `load` is true (the default), calls `init_entrypoints` and `init_mod_plugins`.
104        """
105        self.branch = branch
106        self.props = props
107        self.inner = pluggy.PluginManager(HEXDOC_PROJECT_NAME)
108        self.mod_plugins: dict[str, ModPlugin] = {}
109        self.book_plugins: dict[str, BookPlugin[Any]] = {}
110
111        self.inner.add_hookspecs(PluginSpec)
112        if load:
113            self.init_entrypoints()
114            self.init_plugins()

Initialize the hexdoc plugin manager.

If load is true (the default), calls init_entrypoints and init_mod_plugins.

branch
props
inner
mod_plugins: dict[str, ModPlugin]
book_plugins: dict[str, BookPlugin[typing.Any]]
def init_entrypoints(self):
116    def init_entrypoints(self):
117        self.inner.load_setuptools_entrypoints(HEXDOC_PROJECT_NAME)
118        self.inner.check_pending()
def init_plugins(self):
120    def init_plugins(self):
121        self._init_book_plugins()
122        self._init_mod_plugins()
def register(self, plugin: Any, name: str | None = None):
139    def register(self, plugin: Any, name: str | None = None):
140        self.inner.register(plugin, name)
141        self.init_plugins()
def book_plugin(self, modid: str):
143    def book_plugin(self, modid: str):
144        plugin = self.book_plugins.get(modid)
145        if plugin is None:
146            raise ValueError(f"No BookPlugin registered for modid: {modid}")
147        return plugin
def mod_plugin(self, modid: str, book: bool = False):
155    def mod_plugin(self, modid: str, book: bool = False):
156        plugin = self.mod_plugins.get(modid)
157        if plugin is None:
158            raise ValueError(f"No ModPlugin registered for modid: {modid}")
159
160        if book and not isinstance(plugin, ModPluginWithBook):
161            raise ValueError(
162                f"ModPlugin registered for modid `{modid}`"
163                f" does not inherit from ModPluginWithBook: {plugin}"
164            )
165
166        return plugin
def mod_plugin_with_book(self, modid: str):
168    def mod_plugin_with_book(self, modid: str):
169        return self.mod_plugin(modid, book=True)
def minecraft_version(self) -> str | None:
171    def minecraft_version(self) -> str | None:
172        versions = dict[str, str]()
173
174        for modid, plugin in self.mod_plugins.items():
175            version = plugin.compat_minecraft_version
176            if version is not None:
177                versions[modid] = version
178
179        match len(set(versions.values())):
180            case 0:
181                return None
182            case 1:
183                return versions.popitem()[1]
184            case n:
185                raise ValueError(
186                    f"Got {n} Minecraft versions, expected 1: "
187                    + ", ".join(
188                        f"{modid}={version}" for modid, version in versions.items()
189                    )
190                )
def validate_format_tree( self, tree: hexdoc.patchouli.FormatTree, macros: dict[str, str], book_id: hexdoc.core.ResourceLocation, i18n: hexdoc.minecraft.I18n, is_0_black: bool, link_overrides: dict[str, str]):
192    def validate_format_tree(
193        self,
194        tree: FormatTree,
195        macros: dict[str, str],
196        book_id: ResourceLocation,
197        i18n: I18n,
198        is_0_black: bool,
199        link_overrides: dict[str, str],
200    ):
201        caller = self._hook_caller(PluginSpec.hexdoc_validate_format_tree)
202        caller.try_call(
203            tree=tree,
204            macros=macros,
205            book_id=book_id,
206            i18n=i18n,
207            is_0_black=is_0_black,
208            link_overrides=link_overrides,
209        )
210        return tree
def update_context( self, context: dict[str, typing.Any]) -> Iterator[hexdoc.utils.ValidationContext]:
212    def update_context(self, context: dict[str, Any]) -> Iterator[ValidationContext]:
213        caller = self._hook_caller(PluginSpec.hexdoc_update_context)
214        if returns := caller.try_call(context=context):
215            yield from flatten(returns)
def update_jinja_env( self, modids: Sequence[str], env: jinja2.sandbox.SandboxedEnvironment):
217    def update_jinja_env(self, modids: Sequence[str], env: SandboxedEnvironment):
218        for modid in modids:
219            plugin = self.mod_plugin(modid)
220            plugin.update_jinja_env(env)
221        return env
def pre_render_site( self, books: list[hexdoc.cli.app.LoadedBookInfo], env: jinja2.sandbox.SandboxedEnvironment, output_dir: pathlib.Path, modids: Sequence[str]):
223    def pre_render_site(
224        self,
225        books: list[LoadedBookInfo],
226        env: SandboxedEnvironment,
227        output_dir: Path,
228        modids: Sequence[str],
229    ):
230        for modid in modids:
231            self.mod_plugin(modid).pre_render_site(self.props, books, env, output_dir)
def post_render_site( self, books: list[hexdoc.cli.app.LoadedBookInfo], env: jinja2.sandbox.SandboxedEnvironment, output_dir: pathlib.Path, modids: Sequence[str]):
233    def post_render_site(
234        self,
235        books: list[LoadedBookInfo],
236        env: SandboxedEnvironment,
237        output_dir: Path,
238        modids: Sequence[str],
239    ):
240        for modid in modids:
241            self.mod_plugin(modid).post_render_site(self.props, books, env, output_dir)
def update_template_args(self, template_args: dict[str, typing.Any]):
243    def update_template_args(self, template_args: dict[str, Any]):
244        caller = self._hook_caller(PluginSpec.hexdoc_update_template_args)
245        caller.try_call(template_args=template_args)
246        return template_args
def pre_render_book( self, template_args: dict[str, typing.Any], output_dir: pathlib.Path, modids: Sequence[str]):
248    def pre_render_book(
249        self,
250        template_args: dict[str, Any],
251        output_dir: Path,
252        modids: Sequence[str],
253    ):
254        for modid in modids:
255            self.mod_plugin(modid).pre_render_book(template_args, output_dir)
def post_render_book( self, template_args: dict[str, typing.Any], output_dir: pathlib.Path, modids: Sequence[str]):
257    def post_render_book(
258        self,
259        template_args: dict[str, Any],
260        output_dir: Path,
261        modids: Sequence[str],
262    ):
263        for modid in modids:
264            self.mod_plugin(modid).post_render_book(template_args, output_dir)
def load_resources(self, modid: str) -> Iterator[module]:
266    def load_resources(self, modid: str) -> Iterator[ModuleType]:
267        plugin = self.mod_plugin(modid)
268        for package in flatten([plugin.resource_dirs()]):
269            yield import_package(package)
def load_tagged_unions(self) -> Iterator[module]:
271    def load_tagged_unions(self) -> Iterator[ModuleType]:
272        yield from self._import_from_hook(PluginSpec.hexdoc_load_tagged_unions)
def load_jinja_templates(self, modids: Sequence[str]):
274    def load_jinja_templates(self, modids: Sequence[str]):
275        """modid -> PackageLoader"""
276        extra_modids = set(self.mod_plugins.keys()) - set(modids)
277
278        included = self._package_loaders_for(modids)
279        extra = self._package_loaders_for(extra_modids)
280
281        return included, extra

modid -> PackageLoader

def default_rendered_templates( self, modids: Iterable[str], book: Any, context: dict[str, typing.Any] | pydantic_core.core_schema.ValidationInfo | jinja2.runtime.Context):
305    def default_rendered_templates(
306        self,
307        modids: Iterable[str],
308        book: Any,
309        context: ContextSource,
310    ):
311        templates: DefaultRenderedTemplates = {}
312        for modid in modids:
313            plugin = self.mod_plugin(modid)
314            templates |= plugin.default_rendered_templates()
315            templates |= plugin.default_rendered_templates_v2(book, context)
316
317        result = dict[Path, tuple[str, Mapping[str, Any]]]()
318        for path, value in templates.items():
319            match value:
320                case str(template):
321                    args = dict[str, Any]()
322                case (template, args):
323                    pass
324            result[Path(path)] = (template, args)
325
326        return result
def load_flags(self) -> dict[str, bool]:
338    def load_flags(self) -> dict[str, bool]:
339        # https://vazkiimods.github.io/Patchouli/docs/patchouli-basics/config-gating
340        flags = {
341            "debug": False,
342            "advancements_disabled": False,
343            "testing_mode": False,
344        }
345
346        # first, resolve exported flags by OR
347        for modid, plugin in self.mod_plugins.items():
348            for flag, value in plugin.flags.items():
349                if flag in flags:
350                    resolved = flags[flag] = flags[flag] or value
351                    logger.debug(
352                        f"{modid} exports flag {flag}={value}, resolving to {resolved}"
353                    )
354                else:
355                    flags[flag] = value
356                    logger.debug(f"{modid} exports flag {flag}={value}")
357
358        # then, apply local overrides
359        flags |= self.props.flags
360
361        # any missing flags will default to True
362
363        logger.debug(f"Resolved flags: {flags}")
364        return flags
class PluginNotFoundError(builtins.RuntimeError):
52class PluginNotFoundError(RuntimeError):
53    pass

Unspecified run-time error.

class UpdateContextImpl(hexdoc.plugin.specs.PluginImpl, typing.Protocol):
115class UpdateContextImpl(PluginImpl, Protocol):
116    @staticmethod
117    def hexdoc_update_context(context: dict[str, Any]) -> HookReturn[ValidationContext]:
118        """Modify the book validation context.
119
120        For example, Hex Casting uses this to add pattern data needed by pattern pages.
121        """
122        ...

Interface for an implementation of a hexdoc plugin hook.

These protocols are optional - they gives better type checking, but everything will work fine with a standard pluggy hook implementation.

@staticmethod
def hexdoc_update_context( context: dict[str, typing.Any]) -> Union[hexdoc.utils.ValidationContext, list[hexdoc.utils.ValidationContext]]:
116    @staticmethod
117    def hexdoc_update_context(context: dict[str, Any]) -> HookReturn[ValidationContext]:
118        """Modify the book validation context.
119
120        For example, Hex Casting uses this to add pattern data needed by pattern pages.
121        """
122        ...

Modify the book validation context.

For example, Hex Casting uses this to add pattern data needed by pattern pages.

class UpdateTemplateArgsImpl(hexdoc.plugin.specs.PluginImpl, typing.Protocol):
125class UpdateTemplateArgsImpl(PluginImpl, Protocol):
126    @staticmethod
127    def hexdoc_update_template_args(template_args: dict[str, Any]) -> None:
128        """Add extra template args (global variables for the Jinja templates)."""
129        ...

Interface for an implementation of a hexdoc plugin hook.

These protocols are optional - they gives better type checking, but everything will work fine with a standard pluggy hook implementation.

@staticmethod
def hexdoc_update_template_args(template_args: dict[str, typing.Any]) -> None:
126    @staticmethod
127    def hexdoc_update_template_args(template_args: dict[str, Any]) -> None:
128        """Add extra template args (global variables for the Jinja templates)."""
129        ...

Add extra template args (global variables for the Jinja templates).

class ValidateFormatTreeImpl(hexdoc.plugin.specs.PluginImpl, typing.Protocol):
 97class ValidateFormatTreeImpl(PluginImpl, Protocol):
 98    @staticmethod
 99    def hexdoc_validate_format_tree(
100        tree: FormatTree,
101        macros: dict[str, str],
102        book_id: ResourceLocation,
103        i18n: I18n,
104        is_0_black: bool,
105        link_overrides: dict[str, str],
106    ) -> None:
107        """This is called as the last step when a FormatTree (styled Patchouli text) is
108        generated. You can use this to modify or validate the text and styles.
109
110        For example, Hex Casting uses this to ensure all $(action) styles are in a link.
111        """
112        ...

Interface for an implementation of a hexdoc plugin hook.

These protocols are optional - they gives better type checking, but everything will work fine with a standard pluggy hook implementation.

@staticmethod
def hexdoc_validate_format_tree( tree: hexdoc.patchouli.FormatTree, macros: dict[str, str], book_id: hexdoc.core.ResourceLocation, i18n: hexdoc.minecraft.I18n, is_0_black: bool, link_overrides: dict[str, str]) -> None:
 98    @staticmethod
 99    def hexdoc_validate_format_tree(
100        tree: FormatTree,
101        macros: dict[str, str],
102        book_id: ResourceLocation,
103        i18n: I18n,
104        is_0_black: bool,
105        link_overrides: dict[str, str],
106    ) -> None:
107        """This is called as the last step when a FormatTree (styled Patchouli text) is
108        generated. You can use this to modify or validate the text and styles.
109
110        For example, Hex Casting uses this to ensure all $(action) styles are in a link.
111        """
112        ...

This is called as the last step when a FormatTree (styled Patchouli text) is generated. You can use this to modify or validate the text and styles.

For example, Hex Casting uses this to ensure all $(action) styles are in a link.

class VersionedModPlugin(hexdoc.plugin.ModPlugin):
236class VersionedModPlugin(ModPlugin):
237    """Like `ModPlugin`, but the versioned site path uses the plugin and mod version."""
238
239    @property
240    @abstractmethod
241    @override
242    def mod_version(self) -> str: ...
243
244    @property
245    @override
246    def versioned_site_path(self) -> Path:
247        """Base path for the web pages for the current version.
248
249        For example:
250        * URL: `https://gamma-delta.github.io/HexMod/v/0.11.1-7/1.0.dev20/en_us`
251        * value: `v/0.11.1-7/1.0.dev20`
252        """
253        return self.site_root / self.mod_version / self.plugin_version

Like ModPlugin, but the versioned site path uses the plugin and mod version.

mod_version: str
239    @property
240    @abstractmethod
241    @override
242    def mod_version(self) -> str: ...

The Minecraft mod version that this plugin represents.

This should generally return your_plugin.__gradle_version__.GRADLE_VERSION.

For example: 0.11.1-7

versioned_site_path: pathlib.Path
244    @property
245    @override
246    def versioned_site_path(self) -> Path:
247        """Base path for the web pages for the current version.
248
249        For example:
250        * URL: `https://gamma-delta.github.io/HexMod/v/0.11.1-7/1.0.dev20/en_us`
251        * value: `v/0.11.1-7/1.0.dev20`
252        """
253        return self.site_root / self.mod_version / self.plugin_version

Base path for the web pages for the current version.

For example:

  • URL: https://gamma-delta.github.io/HexMod/v/0.11.1-7/1.0.dev20/en_us
  • value: v/0.11.1-7/1.0.dev20
hookimpl = <pluggy._hooks.HookimplMarker object>

Decorator for marking functions as hook implementations.