Edit on GitHub

hexdoc.core.properties

  1from __future__ import annotations
  2
  3import logging
  4from collections import defaultdict
  5from functools import cached_property
  6from pathlib import Path
  7from typing import Annotated, Any, Literal, Self, Sequence
  8
  9from pydantic import Field, PrivateAttr, field_validator, model_validator
 10from pydantic.json_schema import (
 11    DEFAULT_REF_TEMPLATE,
 12    GenerateJsonSchema,
 13    SkipJsonSchema,
 14)
 15from typing_extensions import override
 16from yarl import URL
 17
 18from hexdoc.model.base import HexdocSettings
 19from hexdoc.model.strip_hidden import StripHiddenModel
 20from hexdoc.utils import (
 21    TRACE,
 22    PydanticOrderedSet,
 23    RelativePath,
 24    ValidationContext,
 25    git_root,
 26    load_toml_with_placeholders,
 27    relative_path_root,
 28)
 29from hexdoc.utils.deserialize.toml import GenerateJsonSchemaTOML
 30from hexdoc.utils.types import PydanticURL
 31
 32from .resource import ResourceLocation
 33from .resource_dir import ResourceDir
 34
 35logger = logging.getLogger(__name__)
 36
 37JINJA_NAMESPACE_ALIASES = {
 38    "patchouli": "hexdoc",
 39}
 40
 41
 42class EnvironmentVariableProps(HexdocSettings):
 43    # default Actions environment variables
 44    github_repository: str
 45    github_sha: str
 46
 47    # set by CI
 48    github_pages_url: PydanticURL
 49
 50    # for putting books somewhere other than the site root
 51    hexdoc_subdirectory: str | None = None
 52
 53    # optional for debugging
 54    debug_githubusercontent: PydanticURL | None = None
 55
 56    @property
 57    def asset_url(self) -> URL:
 58        if self.debug_githubusercontent is not None:
 59            return URL(str(self.debug_githubusercontent))
 60
 61        return (
 62            URL("https://raw.githubusercontent.com")
 63            / self.github_repository
 64            / self.github_sha
 65        )
 66
 67    @property
 68    def source_url(self) -> URL:
 69        return (
 70            URL("https://github.com")
 71            / self.github_repository
 72            / "tree"
 73            / self.github_sha
 74        )
 75
 76    @property
 77    def repo_owner(self):
 78        return self._github_repository_parts[0]
 79
 80    @property
 81    def repo_name(self):
 82        return self._github_repository_parts[1]
 83
 84    @property
 85    def _github_repository_parts(self):
 86        owner, repo_name = self.github_repository.split("/", maxsplit=1)
 87        return owner, repo_name
 88
 89    @model_validator(mode="after")
 90    def _append_subdirectory(self) -> Self:
 91        if self.hexdoc_subdirectory:
 92            self.github_pages_url /= self.hexdoc_subdirectory
 93        return self
 94
 95
 96class TemplateProps(StripHiddenModel, validate_assignment=True):
 97    static_dir: RelativePath | None = None
 98    icon: RelativePath | None = None
 99    include: PydanticOrderedSet[str]
100
101    render_from: PydanticOrderedSet[str] = Field(None, validate_default=False)  # type: ignore
102    """List of modids to include default rendered templates from.
103
104    If not provided, defaults to `self.include`.
105    """
106    render: dict[Path, str] = Field(default_factory=dict)
107    extend_render: dict[Path, str] = Field(default_factory=dict)
108
109    redirect: tuple[Path, str] | None = (Path("index.html"), "redirect.html.jinja")
110    """filename, template"""
111
112    args: dict[str, Any]
113
114    _was_render_set: bool = PrivateAttr(False)
115
116    @property
117    def override_default_render(self):
118        return self._was_render_set
119
120    @field_validator("include", "render_from", mode="after")
121    @classmethod
122    def _resolve_aliases(cls, values: PydanticOrderedSet[str] | None):
123        if values:
124            for alias, replacement in JINJA_NAMESPACE_ALIASES.items():
125                if alias in values:
126                    values.remove(alias)
127                    values.add(replacement)
128        return values
129
130    @model_validator(mode="after")
131    def _set_default_render_from(self):
132        if self.render_from is None:  # pyright: ignore[reportUnnecessaryComparison]
133            self.render_from = self.include
134        return self
135
136
137# TODO: support item/block override
138class PNGTextureOverride(StripHiddenModel):
139    url: PydanticURL
140    pixelated: bool
141
142
143class TextureTextureOverride(StripHiddenModel):
144    texture: ResourceLocation
145    """The id of an image texture (eg. `minecraft:textures/item/stick.png`)."""
146
147
148class TexturesProps(StripHiddenModel):
149    enabled: bool = True
150    """Set to False to disable texture rendering."""
151    strict: bool = True
152    """Set to False to print some errors instead of throwing them."""
153    missing: set[ResourceLocation] | Literal["*"] = Field(default_factory=set)
154    override: dict[
155        ResourceLocation,
156        PNGTextureOverride | TextureTextureOverride,
157    ] = Field(default_factory=dict)
158
159
160class LangProps(StripHiddenModel):
161    """Configuration for a specific book language."""
162
163    quiet: bool = False
164    """If `True`, do not log warnings for missing translations.
165
166    Using this option for the default language is not recommended.
167    """
168    ignore_errors: bool = False
169    """If `True`, log fatal errors for this language instead of failing entirely.
170
171    Using this option for the default language is not recommended.
172    """
173
174
175class BaseProperties(StripHiddenModel, ValidationContext):
176    env: SkipJsonSchema[EnvironmentVariableProps]
177    props_dir: SkipJsonSchema[Path]
178
179    @classmethod
180    def load(cls, path: Path) -> Self:
181        return cls.load_data(
182            props_dir=path.parent,
183            data=load_toml_with_placeholders(path),
184        )
185
186    @classmethod
187    def load_data(cls, props_dir: Path, data: dict[str, Any]) -> Self:
188        props_dir = props_dir.resolve()
189
190        with relative_path_root(props_dir):
191            env = EnvironmentVariableProps.model_getenv()
192            props = cls.model_validate(
193                data
194                | {
195                    "env": env,
196                    "props_dir": props_dir,
197                },
198            )
199
200        logger.log(TRACE, props)
201        return props
202
203    @override
204    @classmethod
205    def model_json_schema(
206        cls,
207        by_alias: bool = True,
208        ref_template: str = DEFAULT_REF_TEMPLATE,
209        schema_generator: type[GenerateJsonSchema] = GenerateJsonSchemaTOML,
210        mode: Literal["validation", "serialization"] = "validation",
211    ) -> dict[str, Any]:
212        return super().model_json_schema(by_alias, ref_template, schema_generator, mode)
213
214
215class Properties(BaseProperties):
216    """Pydantic model for `hexdoc.toml` / `properties.toml`."""
217
218    modid: str
219
220    book_type: str = "patchouli"
221    """Modid of the `hexdoc.plugin.BookPlugin` to use when loading this book."""
222
223    # TODO: make another properties type without book_id
224    book_id: ResourceLocation | None = Field(alias="book", default=None)
225    extra_books: list[ResourceLocation] = Field(default_factory=list)
226
227    default_lang: str = "en_us"
228    default_branch: str = "main"
229
230    is_0_black: bool = False
231    """If true, the style `$(0)` changes the text color to black; otherwise it resets
232    the text color to the default."""
233
234    resource_dirs: Sequence[ResourceDir]
235    export_dir: RelativePath | None = None
236
237    entry_id_blacklist: set[ResourceLocation] = Field(default_factory=set)
238
239    macros: dict[str, str] = Field(default_factory=dict)
240    link_overrides: dict[str, str] = Field(default_factory=dict)
241
242    textures: TexturesProps = Field(default_factory=TexturesProps)
243
244    template: TemplateProps | None = None
245
246    lang: defaultdict[
247        str,
248        Annotated[LangProps, Field(default_factory=LangProps)],
249    ] = Field(default_factory=lambda: defaultdict(LangProps))
250    """Per-language configuration. The key should be the language code, eg. `en_us`."""
251
252    extra: dict[str, Any] = Field(default_factory=dict)
253
254    def mod_loc(self, path: str) -> ResourceLocation:
255        """Returns a ResourceLocation with self.modid as the namespace."""
256        return ResourceLocation(self.modid, path)
257
258    @property
259    def prerender_dir(self):
260        return self.cache_dir / "prerender"
261
262    @property
263    def cache_dir(self):
264        return self.repo_root / ".hexdoc"
265
266    @cached_property
267    def repo_root(self):
268        return git_root(self.props_dir)
logger = <Logger hexdoc.core.properties (WARNING)>
JINJA_NAMESPACE_ALIASES = {'patchouli': 'hexdoc'}
class EnvironmentVariableProps(hexdoc.model.base.HexdocSettings):
43class EnvironmentVariableProps(HexdocSettings):
44    # default Actions environment variables
45    github_repository: str
46    github_sha: str
47
48    # set by CI
49    github_pages_url: PydanticURL
50
51    # for putting books somewhere other than the site root
52    hexdoc_subdirectory: str | None = None
53
54    # optional for debugging
55    debug_githubusercontent: PydanticURL | None = None
56
57    @property
58    def asset_url(self) -> URL:
59        if self.debug_githubusercontent is not None:
60            return URL(str(self.debug_githubusercontent))
61
62        return (
63            URL("https://raw.githubusercontent.com")
64            / self.github_repository
65            / self.github_sha
66        )
67
68    @property
69    def source_url(self) -> URL:
70        return (
71            URL("https://github.com")
72            / self.github_repository
73            / "tree"
74            / self.github_sha
75        )
76
77    @property
78    def repo_owner(self):
79        return self._github_repository_parts[0]
80
81    @property
82    def repo_name(self):
83        return self._github_repository_parts[1]
84
85    @property
86    def _github_repository_parts(self):
87        owner, repo_name = self.github_repository.split("/", maxsplit=1)
88        return owner, repo_name
89
90    @model_validator(mode="after")
91    def _append_subdirectory(self) -> Self:
92        if self.hexdoc_subdirectory:
93            self.github_pages_url /= self.hexdoc_subdirectory
94        return self

Base class for settings, allowing values to be overridden by environment variables.

This is useful in production for secrets you do not wish to save in code, it plays nicely with docker(-compose), Heroku and any 12 factor app design.

All the below attributes can be set via model_config.

Args: _case_sensitive: Whether environment and CLI variable names should be read with case-sensitivity. Defaults to None. _nested_model_default_partial_update: Whether to allow partial updates on nested model default object fields. Defaults to False. _env_prefix: Prefix for all environment variables. Defaults to None. _env_file: The env file(s) to load settings values from. Defaults to Path(''), which means that the value from model_config['env_file'] should be used. You can also pass None to indicate that environment variables should not be loaded from an env file. _env_file_encoding: The env file encoding, e.g. 'latin-1'. Defaults to None. _env_ignore_empty: Ignore environment variables where the value is an empty string. Default to False. _env_nested_delimiter: The nested env values delimiter. Defaults to None. _env_nested_max_split: The nested env values maximum nesting. Defaults to None, which means no limit. _env_parse_none_str: The env string value that should be parsed (e.g. "null", "void", "None", etc.) into None type(None). Defaults to None type(None), which means no parsing should occur. _env_parse_enums: Parse enum field names to values. Defaults to None., which means no parsing should occur. _cli_prog_name: The CLI program name to display in help text. Defaults to None if _cli_parse_args is None. Otherwse, defaults to sys.argv[0]. _cli_parse_args: The list of CLI arguments to parse. Defaults to None. If set to True, defaults to sys.argv[1:]. _cli_settings_source: Override the default CLI settings source with a user defined instance. Defaults to None. _cli_parse_none_str: The CLI string value that should be parsed (e.g. "null", "void", "None", etc.) into None type(None). Defaults to _env_parse_none_str value if set. Otherwise, defaults to "null" if _cli_avoid_json is False, and "None" if _cli_avoid_json is True. _cli_hide_none_type: Hide None values in CLI help text. Defaults to False. _cli_avoid_json: Avoid complex JSON objects in CLI help text. Defaults to False. _cli_enforce_required: Enforce required fields at the CLI. Defaults to False. _cli_use_class_docs_for_groups: Use class docstrings in CLI group help text instead of field descriptions. Defaults to False. _cli_exit_on_error: Determines whether or not the internal parser exits with error info when an error occurs. Defaults to True. _cli_prefix: The root parser command line arguments prefix. Defaults to "". _cli_flag_prefix_char: The flag prefix character to use for CLI optional arguments. Defaults to '-'. _cli_implicit_flags: Whether bool fields should be implicitly converted into CLI boolean flags. (e.g. --flag, --no-flag). Defaults to False. _cli_ignore_unknown_args: Whether to ignore unknown CLI args and parse only known ones. Defaults to False. _cli_kebab_case: CLI args use kebab case. Defaults to False. _secrets_dir: The secret files directory or a sequence of directories. Defaults to None.

github_repository: str
github_sha: str
github_pages_url: typing.Annotated[yarl.URL, GetPydanticSchema(get_pydantic_core_schema=<function <lambda> at 0x7f29b2e59760>, get_pydantic_json_schema=None)]
hexdoc_subdirectory: str | None
debug_githubusercontent: Optional[Annotated[yarl.URL, GetPydanticSchema(get_pydantic_core_schema=<function <lambda> at 0x7f29b2e59760>, get_pydantic_json_schema=None)]]
asset_url: yarl.URL
57    @property
58    def asset_url(self) -> URL:
59        if self.debug_githubusercontent is not None:
60            return URL(str(self.debug_githubusercontent))
61
62        return (
63            URL("https://raw.githubusercontent.com")
64            / self.github_repository
65            / self.github_sha
66        )
source_url: yarl.URL
68    @property
69    def source_url(self) -> URL:
70        return (
71            URL("https://github.com")
72            / self.github_repository
73            / "tree"
74            / self.github_sha
75        )
repo_owner
77    @property
78    def repo_owner(self):
79        return self._github_repository_parts[0]
repo_name
81    @property
82    def repo_name(self):
83        return self._github_repository_parts[1]
model_config = {'extra': 'allow', 'arbitrary_types_allowed': True, 'validate_default': True, 'case_sensitive': False, 'env_prefix': '', 'nested_model_default_partial_update': False, 'env_file': '.env', 'env_file_encoding': None, 'env_ignore_empty': False, 'env_nested_delimiter': None, 'env_nested_max_split': None, 'env_parse_none_str': None, 'env_parse_enums': None, 'cli_prog_name': None, 'cli_parse_args': None, 'cli_parse_none_str': None, 'cli_hide_none_type': False, 'cli_avoid_json': False, 'cli_enforce_required': False, 'cli_use_class_docs_for_groups': False, 'cli_exit_on_error': True, 'cli_prefix': '', 'cli_flag_prefix_char': '-', 'cli_implicit_flags': False, 'cli_ignore_unknown_args': False, 'cli_kebab_case': False, 'json_file': None, 'json_file_encoding': None, 'yaml_file': None, 'yaml_file_encoding': None, 'toml_file': None, 'secrets_dir': None, 'protected_namespaces': ('model_validate', 'model_dump', 'settings_customise_sources'), 'enable_decoding': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class TemplateProps(hexdoc.model.strip_hidden.StripHiddenModel):
 97class TemplateProps(StripHiddenModel, validate_assignment=True):
 98    static_dir: RelativePath | None = None
 99    icon: RelativePath | None = None
100    include: PydanticOrderedSet[str]
101
102    render_from: PydanticOrderedSet[str] = Field(None, validate_default=False)  # type: ignore
103    """List of modids to include default rendered templates from.
104
105    If not provided, defaults to `self.include`.
106    """
107    render: dict[Path, str] = Field(default_factory=dict)
108    extend_render: dict[Path, str] = Field(default_factory=dict)
109
110    redirect: tuple[Path, str] | None = (Path("index.html"), "redirect.html.jinja")
111    """filename, template"""
112
113    args: dict[str, Any]
114
115    _was_render_set: bool = PrivateAttr(False)
116
117    @property
118    def override_default_render(self):
119        return self._was_render_set
120
121    @field_validator("include", "render_from", mode="after")
122    @classmethod
123    def _resolve_aliases(cls, values: PydanticOrderedSet[str] | None):
124        if values:
125            for alias, replacement in JINJA_NAMESPACE_ALIASES.items():
126                if alias in values:
127                    values.remove(alias)
128                    values.add(replacement)
129        return values
130
131    @model_validator(mode="after")
132    def _set_default_render_from(self):
133        if self.render_from is None:  # pyright: ignore[reportUnnecessaryComparison]
134            self.render_from = self.include
135        return self

Base model which removes all keys starting with _ before validation.

static_dir: Optional[Annotated[pathlib.Path, AfterValidator(func=<function <lambda> at 0x7f29b39d79c0>)]]
icon: Optional[Annotated[pathlib.Path, AfterValidator(func=<function <lambda> at 0x7f29b39d79c0>)]]

List of modids to include default rendered templates from.

If not provided, defaults to self.include.

render: dict[pathlib.Path, str]
extend_render: dict[pathlib.Path, str]
redirect: tuple[pathlib.Path, str] | None

filename, template

args: dict[str, typing.Any]
override_default_render
117    @property
118    def override_default_render(self):
119        return self._was_render_set
def model_post_init(self: pydantic.main.BaseModel, context: Any, /) -> None:
337def init_private_attributes(self: BaseModel, context: Any, /) -> None:
338    """This function is meant to behave like a BaseModel method to initialise private attributes.
339
340    It takes context as an argument since that's what pydantic-core passes when calling it.
341
342    Args:
343        self: The BaseModel instance.
344        context: The context.
345    """
346    if getattr(self, '__pydantic_private__', None) is None:
347        pydantic_private = {}
348        for name, private_attr in self.__private_attributes__.items():
349            default = private_attr.get_default()
350            if default is not PydanticUndefined:
351                pydantic_private[name] = default
352        object_setattr(self, '__pydantic_private__', pydantic_private)

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that's what pydantic-core passes when calling it.

Args: self: The BaseModel instance. context: The context.

class PNGTextureOverride(hexdoc.model.strip_hidden.StripHiddenModel):
139class PNGTextureOverride(StripHiddenModel):
140    url: PydanticURL
141    pixelated: bool

Base model which removes all keys starting with _ before validation.

url: typing.Annotated[yarl.URL, GetPydanticSchema(get_pydantic_core_schema=<function <lambda> at 0x7f29b2e59760>, get_pydantic_json_schema=None)]
pixelated: bool
class TextureTextureOverride(hexdoc.model.strip_hidden.StripHiddenModel):
144class TextureTextureOverride(StripHiddenModel):
145    texture: ResourceLocation
146    """The id of an image texture (eg. `minecraft:textures/item/stick.png`)."""

Base model which removes all keys starting with _ before validation.

The id of an image texture (eg. minecraft:textures/item/stick.png).

class TexturesProps(hexdoc.model.strip_hidden.StripHiddenModel):
149class TexturesProps(StripHiddenModel):
150    enabled: bool = True
151    """Set to False to disable texture rendering."""
152    strict: bool = True
153    """Set to False to print some errors instead of throwing them."""
154    missing: set[ResourceLocation] | Literal["*"] = Field(default_factory=set)
155    override: dict[
156        ResourceLocation,
157        PNGTextureOverride | TextureTextureOverride,
158    ] = Field(default_factory=dict)

Base model which removes all keys starting with _ before validation.

enabled: bool

Set to False to disable texture rendering.

strict: bool

Set to False to print some errors instead of throwing them.

missing: Union[set[hexdoc.core.ResourceLocation], Literal['*']]
class LangProps(hexdoc.model.strip_hidden.StripHiddenModel):
161class LangProps(StripHiddenModel):
162    """Configuration for a specific book language."""
163
164    quiet: bool = False
165    """If `True`, do not log warnings for missing translations.
166
167    Using this option for the default language is not recommended.
168    """
169    ignore_errors: bool = False
170    """If `True`, log fatal errors for this language instead of failing entirely.
171
172    Using this option for the default language is not recommended.
173    """

Configuration for a specific book language.

quiet: bool

If True, do not log warnings for missing translations.

Using this option for the default language is not recommended.

ignore_errors: bool

If True, log fatal errors for this language instead of failing entirely.

Using this option for the default language is not recommended.

class BaseProperties(hexdoc.model.strip_hidden.StripHiddenModel, hexdoc.utils.context.ValidationContext):
176class BaseProperties(StripHiddenModel, ValidationContext):
177    env: SkipJsonSchema[EnvironmentVariableProps]
178    props_dir: SkipJsonSchema[Path]
179
180    @classmethod
181    def load(cls, path: Path) -> Self:
182        return cls.load_data(
183            props_dir=path.parent,
184            data=load_toml_with_placeholders(path),
185        )
186
187    @classmethod
188    def load_data(cls, props_dir: Path, data: dict[str, Any]) -> Self:
189        props_dir = props_dir.resolve()
190
191        with relative_path_root(props_dir):
192            env = EnvironmentVariableProps.model_getenv()
193            props = cls.model_validate(
194                data
195                | {
196                    "env": env,
197                    "props_dir": props_dir,
198                },
199            )
200
201        logger.log(TRACE, props)
202        return props
203
204    @override
205    @classmethod
206    def model_json_schema(
207        cls,
208        by_alias: bool = True,
209        ref_template: str = DEFAULT_REF_TEMPLATE,
210        schema_generator: type[GenerateJsonSchema] = GenerateJsonSchemaTOML,
211        mode: Literal["validation", "serialization"] = "validation",
212    ) -> dict[str, Any]:
213        return super().model_json_schema(by_alias, ref_template, schema_generator, mode)

Base model which removes all keys starting with _ before validation.

env: typing.Annotated[EnvironmentVariableProps, SkipJsonSchema()]
props_dir: typing.Annotated[pathlib.Path, SkipJsonSchema()]
@classmethod
def load(cls, path: pathlib.Path) -> Self:
180    @classmethod
181    def load(cls, path: Path) -> Self:
182        return cls.load_data(
183            props_dir=path.parent,
184            data=load_toml_with_placeholders(path),
185        )
@classmethod
def load_data(cls, props_dir: pathlib.Path, data: dict[str, typing.Any]) -> Self:
187    @classmethod
188    def load_data(cls, props_dir: Path, data: dict[str, Any]) -> Self:
189        props_dir = props_dir.resolve()
190
191        with relative_path_root(props_dir):
192            env = EnvironmentVariableProps.model_getenv()
193            props = cls.model_validate(
194                data
195                | {
196                    "env": env,
197                    "props_dir": props_dir,
198                },
199            )
200
201        logger.log(TRACE, props)
202        return props
@override
@classmethod
def model_json_schema( cls, by_alias: bool = True, ref_template: str = '#/$defs/{model}', schema_generator: type[pydantic.json_schema.GenerateJsonSchema] = <class 'hexdoc.utils.deserialize.toml.GenerateJsonSchemaTOML'>, mode: Literal['validation', 'serialization'] = 'validation') -> dict[str, typing.Any]:
204    @override
205    @classmethod
206    def model_json_schema(
207        cls,
208        by_alias: bool = True,
209        ref_template: str = DEFAULT_REF_TEMPLATE,
210        schema_generator: type[GenerateJsonSchema] = GenerateJsonSchemaTOML,
211        mode: Literal["validation", "serialization"] = "validation",
212    ) -> dict[str, Any]:
213        return super().model_json_schema(by_alias, ref_template, schema_generator, mode)

Generates a JSON schema for a model class.

Args: by_alias: Whether to use attribute aliases or not. ref_template: The reference template. schema_generator: To override the logic used to generate the JSON schema, as a subclass of GenerateJsonSchema with your desired modifications mode: The mode in which to generate the schema.

Returns: The JSON schema for the given model class.

class Properties(BaseProperties):
216class Properties(BaseProperties):
217    """Pydantic model for `hexdoc.toml` / `properties.toml`."""
218
219    modid: str
220
221    book_type: str = "patchouli"
222    """Modid of the `hexdoc.plugin.BookPlugin` to use when loading this book."""
223
224    # TODO: make another properties type without book_id
225    book_id: ResourceLocation | None = Field(alias="book", default=None)
226    extra_books: list[ResourceLocation] = Field(default_factory=list)
227
228    default_lang: str = "en_us"
229    default_branch: str = "main"
230
231    is_0_black: bool = False
232    """If true, the style `$(0)` changes the text color to black; otherwise it resets
233    the text color to the default."""
234
235    resource_dirs: Sequence[ResourceDir]
236    export_dir: RelativePath | None = None
237
238    entry_id_blacklist: set[ResourceLocation] = Field(default_factory=set)
239
240    macros: dict[str, str] = Field(default_factory=dict)
241    link_overrides: dict[str, str] = Field(default_factory=dict)
242
243    textures: TexturesProps = Field(default_factory=TexturesProps)
244
245    template: TemplateProps | None = None
246
247    lang: defaultdict[
248        str,
249        Annotated[LangProps, Field(default_factory=LangProps)],
250    ] = Field(default_factory=lambda: defaultdict(LangProps))
251    """Per-language configuration. The key should be the language code, eg. `en_us`."""
252
253    extra: dict[str, Any] = Field(default_factory=dict)
254
255    def mod_loc(self, path: str) -> ResourceLocation:
256        """Returns a ResourceLocation with self.modid as the namespace."""
257        return ResourceLocation(self.modid, path)
258
259    @property
260    def prerender_dir(self):
261        return self.cache_dir / "prerender"
262
263    @property
264    def cache_dir(self):
265        return self.repo_root / ".hexdoc"
266
267    @cached_property
268    def repo_root(self):
269        return git_root(self.props_dir)

Pydantic model for hexdoc.toml / properties.toml.

modid: str
book_type: str

Modid of the hexdoc.plugin.BookPlugin to use when loading this book.

extra_books: list[hexdoc.core.ResourceLocation]
default_lang: str
default_branch: str
is_0_black: bool

If true, the style $(0) changes the text color to black; otherwise it resets the text color to the default.

resource_dirs: Sequence[hexdoc.core.PathResourceDir | hexdoc.core.resource_dir.PatchouliBooksResourceDir | hexdoc.core.PluginResourceDir | hexdoc.core.resource_dir.GlobResourceDir]
export_dir: Optional[Annotated[pathlib.Path, AfterValidator(func=<function <lambda> at 0x7f29b39d79c0>)]]
entry_id_blacklist: set[hexdoc.core.ResourceLocation]
macros: dict[str, str]
textures: TexturesProps
template: TemplateProps | None
lang: collections.defaultdict[str, typing.Annotated[LangProps, FieldInfo(annotation=NoneType, required=False, default_factory=LangProps)]]

Per-language configuration. The key should be the language code, eg. en_us.

extra: dict[str, typing.Any]
def mod_loc(self, path: str) -> hexdoc.core.ResourceLocation:
255    def mod_loc(self, path: str) -> ResourceLocation:
256        """Returns a ResourceLocation with self.modid as the namespace."""
257        return ResourceLocation(self.modid, path)

Returns a ResourceLocation with self.modid as the namespace.

prerender_dir
259    @property
260    def prerender_dir(self):
261        return self.cache_dir / "prerender"
cache_dir
263    @property
264    def cache_dir(self):
265        return self.repo_root / ".hexdoc"
repo_root
267    @cached_property
268    def repo_root(self):
269        return git_root(self.props_dir)