Skip to content

Commit 7e785f3

Browse files
committed
just save
1 parent 5f27fd4 commit 7e785f3

5 files changed

Lines changed: 133 additions & 74 deletions

File tree

src/sphinxnotes/data/data.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,22 @@ class RawData:
215215
attrs: dict[str, str]
216216
content: str | None
217217

218+
@dataclass(frozen=True)
219+
class PendingData:
220+
raw: RawData
221+
schema: Schema
222+
223+
def parse(self) -> Data:
224+
return self.schema.parse(self.raw)
225+
218226

219227
@dataclass
220228
class Data:
221229
name: Value
222230
attrs: dict[str, Value]
223231
content: Value
224232

225-
def ascontext(self, lift_attrs: bool = True) -> dict[str, Any]:
233+
def ascontext(self) -> dict[str, Any]:
226234
"""
227235
Convert Data to a dict for usage of Jinja2 context.
228236
@@ -236,10 +244,9 @@ def ascontext(self, lift_attrs: bool = True) -> dict[str, Any]:
236244
the variable name is taken by ``Data.name``.
237245
"""
238246
ctx = asdict(self)
239-
if lift_attrs:
240-
for k, v in self.attrs:
241-
if k not in ctx:
242-
ctx[k] = v
247+
for k, v in self.attrs.items():
248+
if k not in ctx:
249+
ctx[k] = v
243250
return ctx
244251

245252

src/sphinxnotes/data/render.py

Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"""
1010

1111
from __future__ import annotations
12+
from os import wait
1213
from typing import TYPE_CHECKING, override, final
1314
from abc import abstractmethod, ABC
1415
from dataclasses import dataclass
@@ -20,12 +21,13 @@
2021
from sphinx.transforms import SphinxTransform
2122
from sphinx.transforms.post_transforms import ReferencesResolver
2223

23-
from .data import Field, Schema, RawData, Data
24-
from .template import Template, Phase
25-
from . import extra_contexts
24+
from .data import Field, Schema, RawData, Data, PendingData
25+
from .template import Template, Phase, Context
26+
from . import extractx
2627
from . import utils
2728

2829
if TYPE_CHECKING:
30+
from typing import Any
2931
from sphinx.application import Sphinx
3032
from sphinx.environment import BuildEnvironment
3133
from .template import MarkupParser
@@ -34,7 +36,6 @@
3436

3537

3638
# Possible caller of :meth:`pending_node.render`.
37-
# TODO: support Any?
3839
type Caller = SphinxDirective | SphinxRole | SphinxTransform
3940

4041

@@ -97,7 +98,7 @@ class BaseNode(nodes.Element): ...
9798

9899

99100
class RenderedNode(BaseNode):
100-
data: Data
101+
data: Data # FIXME
101102
external_name: nodes.Node | None
102103
external_content: list[nodes.Node]
103104

@@ -108,57 +109,53 @@ class rendered_node(RenderedNode, nodes.container): ...
108109
class rendered_inline_node(RenderedNode, nodes.inline): ...
109110

110111

111-
class pending_node(BaseNode, nodes.Invisible, nodes.Element):
112-
data: RawData
112+
class pending_node(BaseNode, nodes.Invisible):
113+
#: The context.
114+
ctx: Context
115+
#: Template for rendering the context.
113116
template: Template
114-
schema: Schema
117+
#: Extra context, as a supplement to data.
118+
extra: list[dict[str, Any]]
115119

116120
inline: bool = False
117121
need_external_name: bool = False
118122
need_external_content: bool = False
119123

124+
# extra ctx: markup(before paring) relation(just after ReferencesResolver)
125+
# all: env config doctree
126+
127+
def __init__(self, data: Context, tmpl: Template, rawsource='', *children, **attributes):
128+
super().__init__(rawsource, *children, **attributes)
129+
self.ctx = data
130+
self.template = tmpl
131+
self.extra = []
132+
133+
120134
def render(self, caller: Caller, replace: bool = False) -> RenderedNode:
121135
rendered = rendered_inline_node() if self.inline else rendered_node()
122136
rendered.update_all_atts(self)
123137

124138
_caller = _Caller(caller)
125-
# Resolve external name and content.
126-
if not self.data.name and self.need_external_name:
127-
if ns := self._resolve_external_name(_caller.parent_node):
128-
self.data.name = ns.astext()
129-
rendered.external_name = ns
130-
if not self.data.content and self.need_external_content:
131-
if ns := self._resolve_external_content():
132-
self.data.content = '\n\n'.join([x.astext() for x in ns])
133-
rendered.external_content = ns
134-
135-
try:
136-
data = self.schema.parse(self.data)
137-
except ValueError as e:
138-
if isinstance(caller, SphinxDirective):
139-
raise caller.error(str(e))
140-
else:
141-
raise e
142-
# raise self._caller_exception_hanlder(caller, e)
143-
else:
144-
rendered.data = data
145139

146-
rendered += self.template.render(
147-
_caller.markup_parser,
148-
data,
149-
{
150-
**extra_contexts.sphinx(caller),
151-
**extra_contexts.doctree(self),
152-
**extra_contexts.markup(self),
153-
},
154-
)
140+
if isinstance(self.ctx, PendingData):
141+
self._resolve_external(self.ctx.raw, _caller)
142+
143+
rendered += self.template.render(_caller.markup_parser, self.ctx)
155144
# TODO: check tmpl schema line no
156145

157146
if replace:
158147
self.replace_self(rendered)
159148

160149
return rendered
161150

151+
def _resolve_external(self, data: RawData, caller: _Caller) -> None:
152+
if not data.name and self.need_external_name:
153+
if ns := self._resolve_external_name(caller.parent_node):
154+
data.name = ns.astext()
155+
if not data.content and self.need_external_content:
156+
if ns := self._resolve_external_content():
157+
data.content = '\n\n'.join([x.astext() for x in ns])
158+
162159
def _resolve_external_name(
163160
self, caller_parent: nodes.Element | None
164161
) -> nodes.Node | None:
@@ -217,8 +214,7 @@ def build_pending_node(self) -> pending_node:
217214

218215
self.process_raw_data(data)
219216

220-
pending = pending_node()
221-
pending.data, pending.template, pending.schema = data, tmpl, schema
217+
pending = pending_node(PendingData(data, schema), tmpl)
222218
self.process_pending_node(pending)
223219

224220
return pending
@@ -368,7 +364,7 @@ def _insert_parsed_hook(app, docname, content):
368364

369365
class _ResolvingHook(SphinxTransform):
370366
# After resolving pending_xref.
371-
default_priority = (ReferencesResolver.default_priority or 10) + 1
367+
default_priority = (ReferencesResolver.default_priority or 10) + 5
372368

373369
def apply(self, **kwargs):
374370
logger.warning(f'running resolving hook for doc {self.env.docname}...')

src/sphinxnotes/data/template.py

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
from sphinx.util import logging
1010
from sphinx.application import Sphinx
1111
from sphinx.builders import Builder
12-
1312
from jinja2.sandbox import SandboxedEnvironment
13+
from jinja2 import StrictUndefined, DebugUndefined
1414

15-
from .data import Data
15+
from .data import Data, PendingData
1616
from .utils import Reporter
1717

1818
logger = logging.getLogger(__name__)
@@ -34,6 +34,7 @@ def option_spec(cls, arg):
3434
choice = directives.choice(arg, [x.value for x in cls])
3535
return cls[choice.title()]
3636

37+
type Context = PendingData | Data | dict[str, Any]
3738

3839
@dataclass
3940
class Template(object):
@@ -42,31 +43,74 @@ class Template(object):
4243
debug: bool
4344

4445
def render(
45-
self, parser: MarkupParser, data: Data, extractx: dict[str, Any]
46+
self, parser: MarkupParser, ctx: Context, extractxs: list[Context] = []
4647
) -> list[nodes.Node]:
47-
ctx = data.ascontext()
48-
ctx.update(**extractx)
49-
text = self._render(ctx)
5048

49+
mainctx = self._resolve(ctx)
50+
finalctx = mainctx.copy()
51+
52+
dropped_keys = set()
53+
for extra in extractxs:
54+
for k, v in self._resolve(extra):
55+
if k in mainctx:
56+
dropped_keys.add(k)
57+
continue
58+
finalctx[k] = v
59+
60+
text = self._render(mainctx)
5161
ns = parser(text)
5262

5363
if self.debug:
5464
reporter = Reporter('Template debug report')
55-
reporter.append_text('Data:')
56-
reporter.append_code(pformat(data), lang='python')
57-
reporter.append_text(
65+
66+
reporter.text('Data:')
67+
reporter.code(pformat(ctx), lang='python')
68+
69+
reporter.text('Main context:')
70+
reporter.code(pformat(mainctx), lang='python')
71+
72+
reporter.text('Extra context keys:')
73+
reporter.list(set(finalctx.keys()) - set(mainctx.keys()))
74+
75+
reporter.text('Dropped extra conetxt keys:')
76+
reporter.list(dropped_keys)
77+
78+
reporter.text(
5879
f'Template (phase: {self.phase}, debug: {self.debug}):'
5980
)
60-
reporter.append_code(self.text, lang='jinja')
61-
reporter.append_text('Rendered ndoes:')
62-
reporter.append_code('\n'.join(n.pformat() for n in ns), lang='xml')
81+
reporter.code(self.text, lang='jinja')
82+
83+
reporter.text('Rendered ndoes:')
84+
reporter.code('\n'.join(n.pformat() for n in ns), lang='xml')
6385

6486
ns.append(reporter)
6587

6688
return ns
6789

90+
91+
def _resolve(self, ctx: Context) -> dict[str, Any]:
92+
if isinstance(ctx, PendingData):
93+
return ctx.parse().ascontext()
94+
elif isinstance(ctx, Data):
95+
return ctx.ascontext()
96+
elif isinstance(ctx, dict):
97+
return ctx
98+
99+
68100
def _render(self, ctx: dict[str, Any]) -> str:
69-
return _JinjaEnv().from_string(self.text).render(ctx)
101+
extensions=[
102+
'jinja2.ext.loopcontrols', # enable {% break %}, {% continue %}
103+
]
104+
if self.debug:
105+
extensions.append('jinja2.ext.debug')
106+
107+
env = _JinjaEnv(
108+
undefined=DebugUndefined if self.debug else StrictUndefined,
109+
extensions=extensions,
110+
)
111+
# TODO: cache jinja env
112+
113+
return env.from_string(self.text).render(ctx)
70114

71115

72116
class _JinjaEnv(SandboxedEnvironment):
@@ -103,13 +147,8 @@ def is_safe_attribute(self, obj, attr, value=None):
103147
return False
104148
return super().is_safe_attribute(obj, attr, value)
105149

106-
def __init__(self):
107-
super().__init__(
108-
extensions=[
109-
'jinja2.ext.loopcontrols', # enable {% break %}, {% continue %}
110-
]
111-
)
112-
logger.warning
150+
def __init__(self, *args, **kwargs):
151+
super().__init__(*args, **kwargs)
113152
for name, factory in self._filter_factories.items():
114153
self.filters[name] = factory(self._builder.env)
115154

src/sphinxnotes/data/utils/__init__.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
from typing import TypeVar, cast, Literal
1+
from __future__ import annotations
2+
from typing import TYPE_CHECKING, TypeVar, cast
23

34
from docutils import nodes
45
from docutils.frontend import get_default_settings
56
from docutils.parsers.rst import Parser
67
from docutils.parsers.rst.states import Struct
78
from docutils.utils import new_document
89
from sphinx.util import logging
9-
from sphinx.util.docutils import SphinxRole
10+
11+
if TYPE_CHECKING:
12+
from typing import Literal, Iterable
13+
from sphinx.util.docutils import SphinxRole
1014

1115
logger = logging.getLogger(__name__)
1216

@@ -84,15 +88,28 @@ def __init__(
8488
level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR'] = 'DEBUG',
8589
) -> None:
8690
super().__init__(title + ':', type=level, level=2, source='')
87-
# logger.warning(f'creating a new report: {title}')
8891

89-
def append_text(self, text: str) -> None:
90-
self += nodes.paragraph(text, text)
91-
# logger.warning(f'report append text: {text}')
92+
def report(self, node: nodes.Node) -> None:
93+
self += node
94+
# TODO: print log here
95+
96+
def text(self, text: str) -> None:
97+
self.report(nodes.paragraph(text, text))
9298

93-
def append_code(self, code: str, lang: str | None = None) -> None:
99+
def code(self, code: str, lang: str | None = None) -> None:
94100
blk = nodes.literal_block(code, code)
95101
if lang:
96102
blk['language'] = lang
97-
self += blk
98-
# logger.warning(f'report append code: {code}')
103+
self.report(blk)
104+
105+
def list(self, lines: Iterable[str]) -> None:
106+
bullet_list = nodes.bullet_list(bullet='*')
107+
108+
for line in lines:
109+
list_item = nodes.list_item()
110+
para = nodes.paragraph()
111+
para += nodes.Text(line)
112+
list_item += para
113+
bullet_list += list_item
114+
115+
self.report(bullet_list)

0 commit comments

Comments
 (0)