Edit on GitHub

hexdoc.jinja

 1__all__ = [
 2    "IncludeRawExtension",
 3    "hexdoc_item",
 4    "hexdoc_localize",
 5    "hexdoc_smart_var",
 6    "hexdoc_texture",
 7    "hexdoc_wrap",
 8]
 9
10from .extensions import IncludeRawExtension
11from .filters import (
12    hexdoc_item,
13    hexdoc_localize,
14    hexdoc_smart_var,
15    hexdoc_texture,
16    hexdoc_wrap,
17)
class IncludeRawExtension(jinja2.ext.Extension):
 9class IncludeRawExtension(Extension):
10    tags = {"include_raw"}
11
12    def parse(self, parser: Parser) -> nodes.Node | list[nodes.Node]:
13        lineno = parser.stream.expect("name:include_raw").lineno
14        template = parser.parse_expression()
15        result = self.call_method("_render", [template], lineno=lineno)
16        return nodes.Output([result], lineno=lineno)
17
18    def _render(self, filename: str) -> Markup:
19        assert self.environment.loader is not None
20        source = self.environment.loader.get_source(self.environment, filename)
21        return Markup(source[0])

Extensions can be used to add extra functionality to the Jinja template system at the parser level. Custom extensions are bound to an environment but may not store environment specific data on self. The reason for this is that an extension can be bound to another environment (for overlays) by creating a copy and reassigning the environment attribute.

As extensions are created by the environment they cannot accept any arguments for configuration. One may want to work around that by using a factory function, but that is not possible as extensions are identified by their import name. The correct way to configure the extension is storing the configuration values on the environment. Because this way the environment ends up acting as central configuration storage the attributes may clash which is why extensions have to ensure that the names they choose for configuration are not too generic. prefix for example is a terrible name, fragment_cache_prefix on the other hand is a good name as includes the name of the extension (fragment cache).

tags = {'include_raw'}
def parse( self, parser: jinja2.parser.Parser) -> jinja2.nodes.Node | list[jinja2.nodes.Node]:
12    def parse(self, parser: Parser) -> nodes.Node | list[nodes.Node]:
13        lineno = parser.stream.expect("name:include_raw").lineno
14        template = parser.parse_expression()
15        result = self.call_method("_render", [template], lineno=lineno)
16        return nodes.Output([result], lineno=lineno)

If any of the tags matched this method is called with the parser as first argument. The token the parser stream is pointing at is the name token that matched. This method has to return one or a list of multiple nodes.

identifier: ClassVar[str] = 'IncludeRawExtension'
@pass_context
@make_jinja_exceptions_suck_a_bit_less
def hexdoc_item( context: jinja2.runtime.Context, id: str | hexdoc.core.ResourceLocation | hexdoc.core.ItemStack) -> hexdoc.minecraft.assets.ItemWithTexture:
 97@pass_context
 98@make_jinja_exceptions_suck_a_bit_less
 99def hexdoc_item(
100    context: Context,
101    id: str | ResourceLocation | ItemStack,
102) -> ItemWithTexture:
103    return ItemWithTexture.model_validate(
104        id,
105        context=cast(dict[str, Any], context),  # lie
106    )
@make_jinja_exceptions_suck_a_bit_less
def hexdoc_localize( key: str, *, do_format: bool, props: hexdoc.core.Properties, book_id: hexdoc.core.ResourceLocation, i18n: hexdoc.minecraft.I18n, macros: dict[str, str], pm: hexdoc.plugin.PluginManager):
55@make_jinja_exceptions_suck_a_bit_less
56def hexdoc_localize(
57    key: str,
58    *,
59    do_format: bool,
60    props: Properties,
61    book_id: ResourceLocation,
62    i18n: I18n,
63    macros: dict[str, str],
64    pm: PluginManager,
65):
66    # get the localized value from i18n
67    localized = i18n.localize(key)
68
69    if not do_format:
70        return Markup(localized.value)
71
72    # construct a FormatTree from the localized value (to allow using patchi styles)
73    formatted = FormatTree.format(
74        localized.value,
75        book_id=book_id,
76        i18n=i18n,
77        macros=macros,
78        is_0_black=props.is_0_black,
79        pm=pm,
80        link_overrides=props.link_overrides,
81    )
82    return formatted
@pass_context
@make_jinja_exceptions_suck_a_bit_less
def hexdoc_smart_var(context: jinja2.runtime.Context, value: Any):
109@pass_context
110@make_jinja_exceptions_suck_a_bit_less
111def hexdoc_smart_var(context: Context, value: Any):
112    """Smart template argument filter.
113
114    If `value` is of the form `{"variable": str(ref)}`, returns the value of the
115    template variable called `ref`.
116
117    Otherwise, returns `value` unchanged.
118    """
119
120    match value:
121        case {**items} if len(items) != 1:
122            return value
123        case {"variable": str(ref)}:
124            return context.resolve(ref)
125        case _:
126            return value

Smart template argument filter.

If value is of the form {"variable": str(ref)}, returns the value of the template variable called ref.

Otherwise, returns value unchanged.

@pass_context
@make_jinja_exceptions_suck_a_bit_less
def hexdoc_texture( context: jinja2.runtime.Context, id: str | hexdoc.core.ResourceLocation) -> str:
86@pass_context
87@make_jinja_exceptions_suck_a_bit_less
88def hexdoc_texture(context: Context, id: str | ResourceLocation) -> str:
89    texture = validate_texture(
90        id,
91        context=context,
92        model_type=PNGTexture,
93    )
94    return str(texture.url)
@make_jinja_exceptions_suck_a_bit_less
def hexdoc_wrap(value: str, *args: str):
44@make_jinja_exceptions_suck_a_bit_less
45def hexdoc_wrap(value: str, *args: str):
46    tag, *attributes = args
47    if attributes:
48        attributes = " " + " ".join(attributes)
49    else:
50        attributes = ""
51    return Markup(f"<{tag}{attributes}>{Markup.escape(value)}</{tag}>")