You can extend the :doc:`dsl` by registering custom types, flags, and by-options through the :py:attr:`~sphinxnotes.render.Registry.data` attribute of :py:data:`sphinxnotes.render.REGISTRY`.
Use :py:meth:`~sphinxnotes.render.data.REGISTRY.add_type` method of :py:data:`sphinxnotes.render.REGISTRY` to add a new type:
>>> from sphinxnotes.render import REGISTRY, Field
>>>
>>> def parse_color(v: str):
... return tuple(int(x) for x in v.split(';'))
...
>>> def color_to_str(v):
... return ';'.join(str(x) for x in v)
...
>>> REGISTRY.data.add_type('color', tuple, parse_color, color_to_str)
>>> Field.from_dsl('color').parse('255;0;0')
(255, 0, 0)Use :py:meth:`~sphinxnotes.render.data.Registry.add_flag` method of :py:data:`sphinxnotes.render.REGISTRY` to add a new flag:
>>> from sphinxnotes.render import REGISTRY, Field
>>> REGISTRY.data.add_flag('unique', default=False)
>>> field = Field.from_dsl('int, unique')
>>> field.unique
TrueUse :py:meth:`~sphinxnotes.render.data.Registry.add_by_option` method of :py:data:`sphinxnotes.render.REGISTRY` to add a new by-option:
>>> from sphinxnotes.render import REGISTRY, Field
>>> REGISTRY.data.add_by_option('group', str)
>>> field = Field.from_dsl('str, group by size')
>>> field.group
'size'
>>> REGISTRY.data.add_by_option('index', str, store='append')
>>> field = Field.from_dsl('str, index by month, index by year')
>>> field.index
['month', 'year']Extra contexts are registered by a :py:deco:`sphinxnotes.render.extra_context` class decorator.
The decorated class must be a subclass of
:py:class:`~sphinxnotes.render.ExtraContext`. The generate() method
accepts the ExtraContextRequest as the first argument, plus any
positional and keyword arguments passed by the template via
load_extra().
.. literalinclude:: ../tests/roots/test-extra-context/conf.py :language: python :start-after: [literalinclude start] :end-before: [literalinclude end]
.. dropdown:: :file:`cat.json` .. literalinclude:: ../tests/roots/test-extra-context/cat.json
.. example::
.. data.render::
{{ load_extra('cat').name }}
To accept custom parameters in your extra context, add *args and
**kwargs to the generate() method signature:
.. literalinclude:: ../tests/roots/test-extra-context-params/conf.py :language: python :start-after: [literalinclude start] :end-before: [literalinclude end]
.. example::
.. data.render::
{% set docs = load_extra('all_docs', 3) %}
{% for doc in docs %}
- :doc:`{{ doc }}`
{% endfor %}
Template filters are registered by a :py:deco:`sphinxnotes.render.filter` function decorator.
.. literalinclude:: ../tests/roots/test-filter-example/conf.py :language: python :start-after: [literalinclude catify-start] :end-before: [literalinclude catify-end]
.. example::
.. data.render::
{{ "Hello world" | catify }}
If your filter needs access to the Sphinx build environment
:py:class:`sphinx.environment.BuildEnvironment`
(e.g., to access configuration or document metadata), use pass_build_env=True:
.. literalinclude:: ../tests/roots/test-filter-example/conf.py :language: python :start-after: [literalinclude author-start] :end-before: [literalinclude author-end]
.. example::
.. data.render::
{{ "author: Hello world" | format_author }}
Tip
Before reading this documentation, please refer to :external+sphinx:doc:`development/tutorials/extending_syntax`. See how to extend :py:class:`SphinxDirective` and :py:class:`SphinxRole`.
All of the classes listed in :ref:`api-directives` are subclassed from the
internal sphinxnotes.render.Pipeline class, which is responsible to generate
the dedicated :py:class:`node <sphinxnotes.render.pending_node>` that
carries a :ref:`context` and a :py:class:`~sphinxnotes.render.Template`.
At the appropriate :ref:`render-phases`, the node will be rendered into markup text, usually reStructuredText. The rendered text is then parsed again by Sphinx and inserted into the document.
.. seealso::
- :doc:`tmpl` for template variables, phases, and extra context
- :doc:`dsl` for the field description language used by
:py:class:`~sphinxnotes.render.Field` and
:py:class:`~sphinxnotes.render.Schema`
- Implementations of :parsed_literal:`sphinxnotes-render.ext__`
and :parsed_literal:`sphinxnotes-any__`.
__ https://github.com/sphinx-notes/render/tree/master/src/sphinxnotes/render/ext
__ https://github.com/sphinx-notes/any
Now we have a quick example to help you get Started.
:external+sphinx:doc:`Create a Sphinx documentation <tutorial/getting-started>`
with the following conf.py:
.. literalinclude:: ../tests/roots/test-base-context-directive-example/conf.py
This is the smallest useful extension built on top of sphinxnotes.render:
- it defines a mimi-dedicated directive by subclassing :py:class:`~sphinxnotes.render.BaseContextDirective`
- it returns a :py:class:`~sphinxnotes.render.ResolvedContext` object from
current_context() - it returns a :py:class:`~sphinxnotes.render.Template` from
current_template() - the template is rendered in the default :py:data:`~sphinxnotes.render.Phase.Parsing` phase
Now use the directive in your document:
.. example:: .. mimi::
BaseDataDefineDirective is higher level of API than BaseContextDirective.
You no longer need to implement the current_context methods; instead,
implement the :py:meth:`~sphinxnotes.render.BaseDataDefineDirective.current_schema`
method.
Here's an example:
.. literalinclude:: ../tests/roots/test-base-data-define-directive-example/conf.py
Key differences from BaseContextDirective:
The directive automatically generates :py:class:`~sphinxnotes.render.RawData` (from directive's arguments, options, and content, by method :py:meth:`~sphinxnotes.render.BaseDataDefineDirective.current_raw_data`).
The generated RawData are parsed to :py:class:`~sphinxnotes.render.ParsedData` according to the :py:class:`~sphinxnotes.render.Schema` returned from :py:meth:`~sphinxnotes.render.BaseDataDefineDirective.current_schema` method.
Tip
Internally, the
ParsedDatais returned bycurrent_context, so we do not need to implement it.The the fields of schema are generated from :doc:`dsl` which restricted the
colormust be an space-separated list, andbirthmust be a integer.The
current_templatestill returns a Jinja template, but it uses more fancy syntax.
Use the directive in your document:
.. example::
.. cat2:: mimi
:color: black and brown
:birth: 2025
I like fish!
StrictDataDefineDirective is an even higher-level API built on top of
BaseDataDefineDirective. It automatically handles SphinxDirective's members
from your :py:class:`~sphinxnotes.render.Schema`, so you don't need to manually
set:
required_arguments/optional_arguments- derived fromSchema.nameoption_spec- derived fromSchema.attrshas_content- derived fromSchema.content
You no longer need to manually create subclasses, simply pass schema and
template to :py:meth:`~sphinxnotes.render.StrictDataDefineDirective.derive`
method:
.. literalinclude:: ../tests/roots/test-strict-data-define-directive-example/conf.py
Use the directive in your document:
.. example::
.. cat3:: mimi
:color: black and brown
:birth: 2025
I like fish!