sphinxnotes-render 1.0b4__tar.gz → 1.0b5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.cruft.json +1 -1
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.gitignore +1 -0
- {sphinxnotes_render-1.0b4/src/sphinxnotes_render.egg-info → sphinxnotes_render-1.0b5}/PKG-INFO +1 -1
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/api.rst +11 -19
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/conf.py +1 -1
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/conf.rst +0 -2
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/ext.rst +2 -6
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/tmpl.rst +8 -22
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/usage.rst +0 -6
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/__init__.py +6 -10
- sphinxnotes_render-1.0b5/src/sphinxnotes/render/ctx.py +23 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/ctxnodes.py +64 -47
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/data.py +5 -6
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/ext/adhoc.py +2 -11
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/ext/derive.py +0 -2
- sphinxnotes_render-1.0b5/src/sphinxnotes/render/ext/extractx.py +89 -0
- sphinxnotes_render-1.0b5/src/sphinxnotes/render/extractx.py +98 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/jinja.py +8 -28
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/pipeline.py +3 -13
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/sources.py +4 -4
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/template.py +6 -9
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5/src/sphinxnotes_render.egg-info}/PKG-INFO +1 -1
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes_render.egg-info/SOURCES.txt +5 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/conftest.py +3 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-extra-context/conf.py +4 -4
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-extra-context/index.rst +0 -1
- sphinxnotes_render-1.0b5/tests/roots/test-extra-context-rebuild/conf.py +1 -0
- sphinxnotes_render-1.0b5/tests/roots/test-extra-context-rebuild/index.rst +11 -0
- sphinxnotes_render-1.0b5/tests/test_ctx.py +60 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/test_e2e.py +11 -0
- sphinxnotes_render-1.0b5/tests/test_extractx.py +36 -0
- sphinxnotes_render-1.0b5/tests/test_jinja.py +12 -0
- sphinxnotes_render-1.0b4/src/sphinxnotes/render/ctx.py +0 -69
- sphinxnotes_render-1.0b4/src/sphinxnotes/render/ext/extractx.py +0 -81
- sphinxnotes_render-1.0b4/src/sphinxnotes/render/extractx.py +0 -208
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.github/workflows/lint.yml +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.github/workflows/pages.yml +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.github/workflows/pypi.yml +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.github/workflows/release.yml +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.github/workflows/test.yml +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/.pre-commit-config.yaml +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/LICENSE +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/MANIFEST.in +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/Makefile +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/README.rst +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/Makefile +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/_images/.gitkeep +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/_static/.gitkeep +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/_static/custom.css +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/_static/sphinx-notes.png +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/changelog.rst +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/dsl.rst +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/index.rst +3 -3
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/docs/make.bat +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/pyproject.toml +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/ruff.toml +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/setup.cfg +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/ext/__init__.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/ext/filters.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/markup.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/meta.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/py.typed +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/utils/__init__.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/utils/ctxproxy.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes/render/utils/freestyle.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes_render.egg-info/dependency_links.txt +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes_render.egg-info/requires.txt +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/src/sphinxnotes_render.egg-info/top_level.txt +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/__init__.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-base-context-directive-example/conf.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-base-context-directive-example/index.rst +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-base-data-define-directive-example/conf.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-base-data-define-directive-example/index.rst +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-data-define/conf.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-data-define/index.rst +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-derive/conf.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-derive/index.rst +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-extra-context/cat.json +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-filter-example/conf.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-filter-example/index.rst +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-strict-data-define-directive-example/conf.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-strict-data-define-directive-example/index.rst +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-strictdir-card/conf.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/roots/test-strictdir-card/index.rst +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/test_always_pass.py +0 -0
- {sphinxnotes_render-1.0b4 → sphinxnotes_render-1.0b5}/tests/test_data.py +0 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"full_name": "sphinxnotes-render",
|
|
10
10
|
"author": "Shengyu Zhang",
|
|
11
11
|
"description": "Define, constrain, and render data in Sphinx documentation",
|
|
12
|
-
"version": "1.
|
|
12
|
+
"version": "1.0b4",
|
|
13
13
|
"github_owner": "sphinx-notes",
|
|
14
14
|
"github_repo": "data",
|
|
15
15
|
"pypi_name": "sphinxnotes-render",
|
|
@@ -55,22 +55,22 @@ Context refers to the dynamic content of a Jinja template. It can be:
|
|
|
55
55
|
Our dedicated data type (:py:class:`sphinxnotes.render.ParsedData`), or any
|
|
56
56
|
Python ``dict``.
|
|
57
57
|
|
|
58
|
-
:py:class:`~sphinxnotes.render.
|
|
58
|
+
:py:class:`~sphinxnotes.render.UnresolvedContext`:
|
|
59
59
|
Context that is not yet available. For example, it may contain
|
|
60
60
|
:py:class:`unparsed data <sphinxnotes.render.RawData>`,
|
|
61
61
|
remote data, and more.
|
|
62
62
|
|
|
63
|
-
:py:class:`
|
|
63
|
+
:py:class:`UnresolvedContext` can be resolved to
|
|
64
64
|
:py:class:`~sphinxnotes.render.ResolvedContext` by calling
|
|
65
|
-
:py:meth:`~sphinxnotes.render.
|
|
65
|
+
:py:meth:`~sphinxnotes.render.UnresolvedContext.resolve`.
|
|
66
66
|
|
|
67
67
|
.. autotype:: sphinxnotes.render.ResolvedContext
|
|
68
68
|
|
|
69
|
-
.. autoclass:: sphinxnotes.render.
|
|
69
|
+
.. autoclass:: sphinxnotes.render.UnresolvedContext
|
|
70
70
|
:members: resolve
|
|
71
71
|
|
|
72
|
-
``
|
|
73
|
-
|
|
72
|
+
``UnresolvedContext`` Implementations
|
|
73
|
+
-------------------------------------
|
|
74
74
|
|
|
75
75
|
.. autoclass:: sphinxnotes.render.UnparsedData
|
|
76
76
|
:show-inheritance:
|
|
@@ -92,24 +92,16 @@ Extra Context
|
|
|
92
92
|
=============
|
|
93
93
|
|
|
94
94
|
See :doc:`tmpl` for built-in extra-context names such as ``doc`` and
|
|
95
|
-
``
|
|
95
|
+
``env``, plus usage examples.
|
|
96
96
|
|
|
97
97
|
.. autodecorator:: sphinxnotes.render.extra_context
|
|
98
98
|
|
|
99
|
-
.. autoclass:: sphinxnotes.render.
|
|
100
|
-
:members:
|
|
99
|
+
.. autoclass:: sphinxnotes.render.ExtraContext
|
|
100
|
+
:members: generate
|
|
101
101
|
:undoc-members:
|
|
102
102
|
|
|
103
|
-
.. autoclass:: sphinxnotes.render.
|
|
104
|
-
:members:
|
|
105
|
-
:undoc-members:
|
|
106
|
-
|
|
107
|
-
.. autoclass:: sphinxnotes.render.ResolvingPhaseExtraContext
|
|
108
|
-
:members: phase, generate
|
|
109
|
-
:undoc-members:
|
|
110
|
-
|
|
111
|
-
.. autoclass:: sphinxnotes.render.GlobalExtraContext
|
|
112
|
-
:members: phase, generate
|
|
103
|
+
.. autoclass:: sphinxnotes.render.ExtraContextRequest
|
|
104
|
+
:members:
|
|
113
105
|
:undoc-members:
|
|
114
106
|
|
|
115
107
|
Filters
|
|
@@ -14,7 +14,7 @@ author = 'Shengyu Zhang'
|
|
|
14
14
|
copyright = "2025, " + author
|
|
15
15
|
|
|
16
16
|
# The full version, including alpha/beta/rc tags
|
|
17
|
-
version = release = '1.
|
|
17
|
+
version = release = '1.0b4'
|
|
18
18
|
|
|
19
19
|
# -- General configuration ---------------------------------------------------
|
|
20
20
|
|
|
@@ -25,7 +25,5 @@ The extension provides the following configuration:
|
|
|
25
25
|
- ``text`` (str): the Jinja2 template text.
|
|
26
26
|
- ``on`` (str, optional): same as :rst:dir:`data.template:on`
|
|
27
27
|
- ``debug`` (bool, optional): same as :rst:dir:`data.template:debug`
|
|
28
|
-
- ``extra`` (list[str], optional): same as :rst:dir:`data.template:extra`
|
|
29
28
|
|
|
30
29
|
See :ref:`usage-custom-directive` for example.
|
|
31
|
-
|
|
@@ -69,11 +69,8 @@ Extending Extra Contexts
|
|
|
69
69
|
Extra contexts are registered by a
|
|
70
70
|
:py:deco:`sphinxnotes.render.extra_context` class decorator.
|
|
71
71
|
|
|
72
|
-
The decorated class must be
|
|
73
|
-
:py:class:`~sphinxnotes.render.
|
|
74
|
-
:py:class:`~sphinxnotes.render.ParsedPhaseExtraContext`,
|
|
75
|
-
:py:class:`~sphinxnotes.render.ResolvingPhaseExtraContext`,
|
|
76
|
-
:py:class:`~sphinxnotes.render.GlobalExtraContext`.
|
|
72
|
+
The decorated class must be a subclass of
|
|
73
|
+
:py:class:`~sphinxnotes.render.ExtraContext`.
|
|
77
74
|
|
|
78
75
|
.. literalinclude:: ../tests/roots/test-extra-context/conf.py
|
|
79
76
|
:language: python
|
|
@@ -88,7 +85,6 @@ The decorated class must be one of the following classes:
|
|
|
88
85
|
:style: grid
|
|
89
86
|
|
|
90
87
|
.. data.render::
|
|
91
|
-
:extra: cat
|
|
92
88
|
|
|
93
89
|
{{ load_extra('cat').name }}
|
|
94
90
|
|
|
@@ -158,20 +158,13 @@ sources (such as Sphinx application, JSON file, and etc.). Unlike main context
|
|
|
158
158
|
which comes from the directive/role itself, extra context lets you fetch data
|
|
159
159
|
that was prepared beforehand.
|
|
160
160
|
|
|
161
|
-
Extra contexts are
|
|
162
|
-
|
|
163
|
-
``load_extra()`` function:
|
|
164
|
-
|
|
165
|
-
The way of declaring extra context is vary depending on the extension you use.
|
|
166
|
-
For ``sphinxnotes.render.ext`` extension, :rst:dir:`data.template:extra`,
|
|
167
|
-
:rst:dir:`data.render:extra` and the ``templat.extra`` field of
|
|
168
|
-
:confval:`render_ext_data_define_directives` are for this.
|
|
161
|
+
Extra contexts are generated on demand. Load them in the template using the
|
|
162
|
+
``load_extra()`` function.
|
|
169
163
|
|
|
170
164
|
.. example::
|
|
171
165
|
:style: grid
|
|
172
166
|
|
|
173
167
|
.. data.render::
|
|
174
|
-
:extra: doc
|
|
175
168
|
|
|
176
169
|
{% set doc = load_extra('doc') %}
|
|
177
170
|
|
|
@@ -191,7 +184,6 @@ The following extra contexts are available:
|
|
|
191
184
|
:style: grid
|
|
192
185
|
|
|
193
186
|
.. data.render::
|
|
194
|
-
:extra: app
|
|
195
187
|
|
|
196
188
|
{% set app = load_extra('app') %}
|
|
197
189
|
|
|
@@ -207,7 +199,6 @@ The following extra contexts are available:
|
|
|
207
199
|
:style: grid
|
|
208
200
|
|
|
209
201
|
.. data.render::
|
|
210
|
-
:extra: env
|
|
211
202
|
|
|
212
203
|
{% set env = load_extra('env') %}
|
|
213
204
|
|
|
@@ -215,7 +206,7 @@ The following extra contexts are available:
|
|
|
215
206
|
documents found.
|
|
216
207
|
|
|
217
208
|
``markup``
|
|
218
|
-
:Phase: parsing
|
|
209
|
+
:Phase: :term:`parsing`
|
|
219
210
|
|
|
220
211
|
Information about the current directive or role invocation, such as its
|
|
221
212
|
type, name, source text, and line number.
|
|
@@ -224,7 +215,6 @@ The following extra contexts are available:
|
|
|
224
215
|
:style: grid
|
|
225
216
|
|
|
226
217
|
.. data.render::
|
|
227
|
-
:extra: markup
|
|
228
218
|
|
|
229
219
|
{%
|
|
230
220
|
set m = load_extra('markup')
|
|
@@ -238,22 +228,22 @@ The following extra contexts are available:
|
|
|
238
228
|
{% endfor %}
|
|
239
229
|
|
|
240
230
|
``section``
|
|
241
|
-
:Phase:
|
|
231
|
+
:Phase: :term:`parsed` and :term:`resolving`
|
|
242
232
|
|
|
243
233
|
A proxy to the current :py:class:`docutils.nodes.section` node, when one
|
|
244
|
-
exists.
|
|
234
|
+
exists. This extra context is not available during the parsing phase.
|
|
245
235
|
|
|
246
236
|
.. example::
|
|
247
237
|
:style: grid
|
|
248
238
|
|
|
249
239
|
.. data.render::
|
|
250
|
-
:
|
|
240
|
+
:on: parsed
|
|
251
241
|
|
|
252
|
-
|
|
242
|
+
Section Title:
|
|
253
243
|
"{{ load_extra('section').title }}"
|
|
254
244
|
|
|
255
245
|
``doc``
|
|
256
|
-
:Phase:
|
|
246
|
+
:Phase: all
|
|
257
247
|
|
|
258
248
|
A proxy to the current :py:class:`docutils.notes.document` node.
|
|
259
249
|
|
|
@@ -261,7 +251,6 @@ The following extra contexts are available:
|
|
|
261
251
|
:style: grid
|
|
262
252
|
|
|
263
253
|
.. data.render::
|
|
264
|
-
:extra: doc
|
|
265
254
|
|
|
266
255
|
Document title:
|
|
267
256
|
"{{ load_extra('doc').title }}".
|
|
@@ -331,7 +320,6 @@ Each template has a render phase that determines when it is processed:
|
|
|
331
320
|
|
|
332
321
|
.. data.render::
|
|
333
322
|
:on: parsing
|
|
334
|
-
:extra: doc env
|
|
335
323
|
|
|
336
324
|
{% set doc = load_extra('doc') %}
|
|
337
325
|
{% set env = load_extra('env') %}
|
|
@@ -354,7 +342,6 @@ Each template has a render phase that determines when it is processed:
|
|
|
354
342
|
|
|
355
343
|
.. data.render::
|
|
356
344
|
:on: parsed
|
|
357
|
-
:extra: doc env
|
|
358
345
|
|
|
359
346
|
{% set doc = load_extra('doc') %}
|
|
360
347
|
{% set env = load_extra('env') %}
|
|
@@ -378,7 +365,6 @@ Each template has a render phase that determines when it is processed:
|
|
|
378
365
|
|
|
379
366
|
.. data.render::
|
|
380
367
|
:on: resolving
|
|
381
|
-
:extra: doc env
|
|
382
368
|
|
|
383
369
|
{% set doc = load_extra('doc') %}
|
|
384
370
|
{% set env = load_extra('env') %}
|
|
@@ -57,11 +57,6 @@ Directives
|
|
|
57
57
|
|
|
58
58
|
Enable :ref:`debug report <debug>` for template rendering.
|
|
59
59
|
|
|
60
|
-
.. rst:directive:option:: extra
|
|
61
|
-
:type: space separted list
|
|
62
|
-
|
|
63
|
-
List of :ref:`extra-context` to be used in the template.
|
|
64
|
-
|
|
65
60
|
The content of the directive should be Jinja2 Template, please refer to
|
|
66
61
|
::doc:`tmpl`.
|
|
67
62
|
|
|
@@ -116,7 +111,6 @@ Directives
|
|
|
116
111
|
|
|
117
112
|
.. rst:directive:option:: on
|
|
118
113
|
.. rst:directive:option:: debug
|
|
119
|
-
.. rst:directive:option:: extra
|
|
120
114
|
|
|
121
115
|
The options of this directive are same to :rst:dir:`data.template`.
|
|
122
116
|
|
|
@@ -22,14 +22,12 @@ from .data import (
|
|
|
22
22
|
Schema,
|
|
23
23
|
)
|
|
24
24
|
from .template import Phase, Template
|
|
25
|
-
from .ctx import
|
|
25
|
+
from .ctx import UnresolvedContext, ResolvedContext
|
|
26
26
|
from .ctxnodes import pending_node
|
|
27
27
|
from .extractx import (
|
|
28
28
|
extra_context,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
ResolvingPhaseExtraContext,
|
|
32
|
-
GlobalExtraContext,
|
|
29
|
+
ExtraContext,
|
|
30
|
+
ExtraContextRequest,
|
|
33
31
|
)
|
|
34
32
|
from .pipeline import BaseContextRole, BaseContextDirective
|
|
35
33
|
from .sources import (
|
|
@@ -56,12 +54,10 @@ __all__ = [
|
|
|
56
54
|
'Schema',
|
|
57
55
|
'Phase',
|
|
58
56
|
'Template',
|
|
59
|
-
'
|
|
57
|
+
'UnresolvedContext',
|
|
60
58
|
'ResolvedContext',
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'ResolvingPhaseExtraContext',
|
|
64
|
-
'GlobalExtraContext',
|
|
59
|
+
'ExtraContext',
|
|
60
|
+
'ExtraContextRequest',
|
|
65
61
|
'extra_context',
|
|
66
62
|
'pending_node',
|
|
67
63
|
'BaseContextRole',
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module wraps the :py:mod:`sphinxnotes.render.data` module into context
|
|
3
|
+
suitable for use with Jinja templates.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
from typing import Any
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from collections.abc import Hashable
|
|
10
|
+
from .data import ParsedData
|
|
11
|
+
|
|
12
|
+
type ResolvedContext = ParsedData | dict[str, Any]
|
|
13
|
+
"""Resolved context types used by template rendering."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UnresolvedContext(ABC, Hashable):
|
|
17
|
+
"""An abstract representation of context that will be resolved later."""
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def resolve(self) -> ResolvedContext:
|
|
21
|
+
"""This method will be called when rendering to get the available
|
|
22
|
+
:py:type:`ResolvedContext`."""
|
|
23
|
+
...
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from typing import TYPE_CHECKING, override
|
|
3
|
+
import pickle
|
|
3
4
|
from pprint import pformat
|
|
4
5
|
|
|
5
6
|
from docutils import nodes
|
|
6
7
|
from docutils.parsers.rst.states import Inliner
|
|
7
8
|
|
|
8
|
-
from .
|
|
9
|
+
from .data import ValueWrapper, ParsedData
|
|
10
|
+
from .template import Template, Phase
|
|
9
11
|
from .ctx import (
|
|
10
|
-
|
|
11
|
-
PendingContext,
|
|
12
|
-
PendingContextStorage,
|
|
12
|
+
UnresolvedContext,
|
|
13
13
|
ResolvedContext,
|
|
14
14
|
)
|
|
15
|
+
from .extractx import ExtraContextRequest, extra_context_loader, extra_context_names
|
|
15
16
|
from .markup import MarkupRenderer
|
|
16
17
|
from .jinja import TemplateRenderer
|
|
17
18
|
from .utils import (
|
|
@@ -21,7 +22,7 @@ from .utils import (
|
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
if TYPE_CHECKING:
|
|
24
|
-
from typing import
|
|
25
|
+
from typing import Callable, Any
|
|
25
26
|
from .markup import Host
|
|
26
27
|
from .ctx import ResolvedContext
|
|
27
28
|
|
|
@@ -30,25 +31,19 @@ class pending_node(nodes.Element):
|
|
|
30
31
|
"""A docutils node to be rendered."""
|
|
31
32
|
|
|
32
33
|
# The context to be rendered by Jinja template.
|
|
33
|
-
ctx:
|
|
34
|
-
# The extra context as supplement to ctx.
|
|
35
|
-
extra: dict[str, Any]
|
|
34
|
+
ctx: UnresolvedContext | ResolvedContext
|
|
36
35
|
#: Jinja template for rendering the context.
|
|
37
36
|
template: Template
|
|
38
37
|
#: Whether rendering to inline nodes.
|
|
39
38
|
inline: bool
|
|
40
39
|
#: Whether the rendering pipeline is finished (failed is also finished).
|
|
41
40
|
rendered: bool
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
#:
|
|
45
|
-
#: NOTE: ``PendingContextStorage`` holds Unpicklable data (``PendingContext``)
|
|
46
|
-
#: but it is doesn't matters :-), cause pickle doesn't deal with ClassVar.
|
|
47
|
-
_PENDING_CONTEXTS: ClassVar[PendingContextStorage] = PendingContextStorage()
|
|
41
|
+
#: Stored pickling error for later-phase unresolved context.
|
|
42
|
+
_ctx_pickle_error: Exception | None
|
|
48
43
|
|
|
49
44
|
def __init__(
|
|
50
45
|
self,
|
|
51
|
-
ctx:
|
|
46
|
+
ctx: UnresolvedContext | ResolvedContext,
|
|
52
47
|
tmpl: Template,
|
|
53
48
|
inline: bool = False,
|
|
54
49
|
rawsource='',
|
|
@@ -56,18 +51,20 @@ class pending_node(nodes.Element):
|
|
|
56
51
|
**attributes,
|
|
57
52
|
) -> None:
|
|
58
53
|
super().__init__(rawsource, *children, **attributes)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
self._ctx_pickle_error = None
|
|
55
|
+
if isinstance(ctx, UnresolvedContext) and tmpl.phase != Phase.Parsing:
|
|
56
|
+
try:
|
|
57
|
+
pickle.dumps(ctx)
|
|
58
|
+
except Exception as exc:
|
|
59
|
+
self._ctx_pickle_error = exc
|
|
60
|
+
self.ctx = ctx
|
|
64
61
|
self.template = tmpl
|
|
65
62
|
self.inline = inline
|
|
66
63
|
self.rendered = False
|
|
67
64
|
|
|
68
65
|
# Init hook lists.
|
|
69
|
-
self.
|
|
70
|
-
self.
|
|
66
|
+
self._unresolved_context_hooks = []
|
|
67
|
+
self._resolved_context_hooks = []
|
|
71
68
|
self._markup_text_hooks = []
|
|
72
69
|
self._rendered_nodes_hooks = []
|
|
73
70
|
|
|
@@ -75,7 +72,7 @@ class pending_node(nodes.Element):
|
|
|
75
72
|
"""
|
|
76
73
|
The core function for rendering context and template to docutils nodes.
|
|
77
74
|
|
|
78
|
-
1.
|
|
75
|
+
1. UnresolvedContext -> ResolvedContext
|
|
79
76
|
2. TemplateRenderer.render(ResolvedContext) -> Markup Text (``str``)
|
|
80
77
|
3. MarkupRenderer.render(Markup Text) -> doctree Nodes (list[nodes.Node])
|
|
81
78
|
"""
|
|
@@ -97,48 +94,55 @@ class pending_node(nodes.Element):
|
|
|
97
94
|
return report
|
|
98
95
|
return Report('Render Report', 'ERROR', source=self.source, line=self.line)
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
report.text(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
self += report
|
|
110
|
-
return None
|
|
97
|
+
if self._ctx_pickle_error is not None:
|
|
98
|
+
report = err_report()
|
|
99
|
+
report.text(
|
|
100
|
+
f'UnresolvedContext used by {self.template.phase} phase templates '
|
|
101
|
+
'must be picklable:'
|
|
102
|
+
)
|
|
103
|
+
report.exception(self._ctx_pickle_error)
|
|
104
|
+
self += report
|
|
105
|
+
return None
|
|
111
106
|
|
|
112
|
-
|
|
107
|
+
# 1. Prepare context for Jinja template.
|
|
108
|
+
if isinstance(self.ctx, UnresolvedContext):
|
|
109
|
+
pdata = self.ctx
|
|
110
|
+
report.text('Unresolved context:')
|
|
113
111
|
report.code(pformat(pdata), lang='python')
|
|
114
112
|
|
|
115
|
-
for hook in self.
|
|
113
|
+
for hook in self._unresolved_context_hooks:
|
|
116
114
|
hook(self, pdata)
|
|
117
115
|
|
|
118
116
|
try:
|
|
119
117
|
ctx = self.ctx = pdata.resolve()
|
|
120
118
|
except Exception as e:
|
|
121
119
|
report = err_report()
|
|
122
|
-
report.text('Failed to resolve
|
|
120
|
+
report.text('Failed to resolve unresolved context:')
|
|
123
121
|
report.exception(e)
|
|
124
122
|
self += report
|
|
125
123
|
return None
|
|
126
124
|
else:
|
|
127
125
|
ctx = self.ctx
|
|
128
126
|
|
|
129
|
-
for hook in self.
|
|
127
|
+
for hook in self._resolved_context_hooks:
|
|
130
128
|
hook(self, ctx)
|
|
131
129
|
|
|
132
130
|
report.text(f'Resolved context (type: {type(ctx)}):')
|
|
133
131
|
report.code(pformat(ctx), lang='python')
|
|
134
|
-
report.text('Extra context (only keys):')
|
|
135
|
-
report.code(pformat(list(self.extra.keys())), lang='python')
|
|
136
132
|
report.text(f'Template (phase: {self.template.phase}):')
|
|
137
133
|
report.code(self.template.text, lang='jinja')
|
|
138
134
|
|
|
135
|
+
extractx_req = ExtraContextRequest(self.template.phase, self, host.env, host)
|
|
136
|
+
report.text('Available extra context names:')
|
|
137
|
+
report.code(pformat(sorted(extra_context_names())), lang='python')
|
|
138
|
+
|
|
139
139
|
# 2. Render the template and context to markup text.
|
|
140
140
|
try:
|
|
141
|
-
markup = TemplateRenderer(self.template.text).render(
|
|
141
|
+
markup = TemplateRenderer(self.template.text).render(
|
|
142
|
+
ctx,
|
|
143
|
+
globals={'load_extra': extra_context_loader(extractx_req)},
|
|
144
|
+
debug=self.template.debug,
|
|
145
|
+
)
|
|
142
146
|
except Exception as e:
|
|
143
147
|
report = err_report()
|
|
144
148
|
report.text('Failed to render Jinja template:')
|
|
@@ -221,21 +225,21 @@ class pending_node(nodes.Element):
|
|
|
221
225
|
|
|
222
226
|
"""Hooks for processing render intermediate products."""
|
|
223
227
|
|
|
224
|
-
type
|
|
228
|
+
type UnresolvedContextHook = Callable[[pending_node, UnresolvedContext], None]
|
|
225
229
|
type ResolvedContextHook = Callable[[pending_node, ResolvedContext], None]
|
|
226
230
|
type MarkupTextHook = Callable[[pending_node, str], str]
|
|
227
231
|
type RenderedNodesHook = Callable[[pending_node, list[nodes.Node]], None]
|
|
228
232
|
|
|
229
|
-
|
|
230
|
-
|
|
233
|
+
_unresolved_context_hooks: list[UnresolvedContextHook]
|
|
234
|
+
_resolved_context_hooks: list[ResolvedContextHook]
|
|
231
235
|
_markup_text_hooks: list[MarkupTextHook]
|
|
232
236
|
_rendered_nodes_hooks: list[RenderedNodesHook]
|
|
233
237
|
|
|
234
|
-
def
|
|
235
|
-
self.
|
|
238
|
+
def hook_unresolved_context(self, hook: UnresolvedContextHook) -> None:
|
|
239
|
+
self._unresolved_context_hooks.append(hook)
|
|
236
240
|
|
|
237
241
|
def hook_resolved_context(self, hook: ResolvedContextHook) -> None:
|
|
238
|
-
self.
|
|
242
|
+
self._resolved_context_hooks.append(hook)
|
|
239
243
|
|
|
240
244
|
def hook_markup_text(self, hook: MarkupTextHook) -> None:
|
|
241
245
|
self._markup_text_hooks.append(hook)
|
|
@@ -259,3 +263,16 @@ class pending_node(nodes.Element):
|
|
|
259
263
|
def deepcopy(self) -> Any:
|
|
260
264
|
# NOTE: Same to :meth:`copy`.
|
|
261
265
|
return self.copy()
|
|
266
|
+
|
|
267
|
+
@override
|
|
268
|
+
def astext(self) -> str:
|
|
269
|
+
ctx = self.ctx
|
|
270
|
+
if isinstance(ctx, UnresolvedContext):
|
|
271
|
+
try:
|
|
272
|
+
ctx = ctx.resolve()
|
|
273
|
+
except Exception:
|
|
274
|
+
return ''
|
|
275
|
+
if isinstance(ctx, ParsedData):
|
|
276
|
+
return ValueWrapper(ctx.content).as_str() or ''
|
|
277
|
+
else:
|
|
278
|
+
return ''
|
|
@@ -14,8 +14,6 @@ import re
|
|
|
14
14
|
from dataclasses import dataclass, asdict, field as dataclass_field
|
|
15
15
|
from ast import literal_eval
|
|
16
16
|
|
|
17
|
-
from .utils import Unpicklable
|
|
18
|
-
|
|
19
17
|
if TYPE_CHECKING:
|
|
20
18
|
from typing import Any, Callable, Generator, Self
|
|
21
19
|
|
|
@@ -287,7 +285,7 @@ class ParsedData:
|
|
|
287
285
|
|
|
288
286
|
|
|
289
287
|
@dataclass
|
|
290
|
-
class Field
|
|
288
|
+
class Field:
|
|
291
289
|
#: Type of element.
|
|
292
290
|
etype: type = str
|
|
293
291
|
#: Type of container (if the field holds multiple values).
|
|
@@ -374,8 +372,9 @@ class Field(Unpicklable):
|
|
|
374
372
|
raise ValueError(f"Failed to parse '{rawval}' as {self.etype}: {e}") from e
|
|
375
373
|
|
|
376
374
|
def __getattr__(self, name: str) -> Value:
|
|
377
|
-
|
|
378
|
-
|
|
375
|
+
flags = self.__dict__.get('flags')
|
|
376
|
+
if flags is not None and name in flags:
|
|
377
|
+
return flags[name]
|
|
379
378
|
raise AttributeError(name)
|
|
380
379
|
|
|
381
380
|
|
|
@@ -491,7 +490,7 @@ class DSLParser:
|
|
|
491
490
|
|
|
492
491
|
|
|
493
492
|
@dataclass(frozen=True)
|
|
494
|
-
class Schema
|
|
493
|
+
class Schema:
|
|
495
494
|
name: Field | None
|
|
496
495
|
attrs: dict[str, Field] | Field
|
|
497
496
|
content: Field | None
|
|
@@ -33,7 +33,7 @@ if TYPE_CHECKING:
|
|
|
33
33
|
from types import ModuleType
|
|
34
34
|
from docutils.utils import Reporter
|
|
35
35
|
from sphinx.util.typing import RoleFunction
|
|
36
|
-
from .. import
|
|
36
|
+
from .. import UnresolvedContext, ResolvedContext
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
# Keys of env.temp_data.
|
|
@@ -50,19 +50,15 @@ class TemplateDefineDirective(SphinxDirective):
|
|
|
50
50
|
option_spec = {
|
|
51
51
|
'on': phase_option_spec,
|
|
52
52
|
'debug': directives.flag,
|
|
53
|
-
'extra': directives.unchanged,
|
|
54
53
|
}
|
|
55
54
|
has_content = True
|
|
56
55
|
|
|
57
56
|
@override
|
|
58
57
|
def run(self) -> list[nodes.Node]:
|
|
59
|
-
extra = self.options.get('extra', '')
|
|
60
|
-
|
|
61
58
|
self.env.temp_data[TEMPLATE_KEY] = Template(
|
|
62
59
|
'\n'.join(self.content),
|
|
63
60
|
phase=self.options.get('on', Phase.default()),
|
|
64
61
|
debug='debug' in self.options,
|
|
65
|
-
extra=extra.split() if extra else [],
|
|
66
62
|
)
|
|
67
63
|
|
|
68
64
|
return []
|
|
@@ -135,24 +131,19 @@ class DataRenderDirective(BaseContextDirective):
|
|
|
135
131
|
option_spec = {
|
|
136
132
|
'on': phase_option_spec,
|
|
137
133
|
'debug': directives.flag,
|
|
138
|
-
'extra': directives.unchanged,
|
|
139
134
|
}
|
|
140
135
|
has_content = True
|
|
141
136
|
|
|
142
137
|
@override
|
|
143
|
-
def current_context(self) ->
|
|
138
|
+
def current_context(self) -> UnresolvedContext | ResolvedContext:
|
|
144
139
|
return {}
|
|
145
140
|
|
|
146
141
|
@override
|
|
147
142
|
def current_template(self) -> Template:
|
|
148
|
-
extra_str = self.options.get('extra', '')
|
|
149
|
-
extra_list = extra_str.split() if extra_str else []
|
|
150
|
-
|
|
151
143
|
return Template(
|
|
152
144
|
'\n'.join(self.content),
|
|
153
145
|
phase=self.options.get('on', Phase.default()),
|
|
154
146
|
debug='debug' in self.options,
|
|
155
|
-
extra=extra_list,
|
|
156
147
|
)
|
|
157
148
|
|
|
158
149
|
|
|
@@ -31,7 +31,6 @@ DATA_DEFINE_DIRECTIVE = DictSchema(
|
|
|
31
31
|
Optional('on', default='parsing'): Or('parsing', 'parsed', 'resolving'),
|
|
32
32
|
'text': str,
|
|
33
33
|
Optional('debug', default=False): bool,
|
|
34
|
-
Optional('extra', default=[]): list,
|
|
35
34
|
},
|
|
36
35
|
}
|
|
37
36
|
)
|
|
@@ -50,7 +49,6 @@ def _validate_directive_define(d: dict, config: Config) -> tuple[Schema, Templat
|
|
|
50
49
|
text=tmpldef['text'],
|
|
51
50
|
phase=Phase[tmpldef['on'].title()],
|
|
52
51
|
debug=tmpldef['debug'],
|
|
53
|
-
extra=tmpldef['extra'],
|
|
54
52
|
)
|
|
55
53
|
|
|
56
54
|
return schema, template
|