Skip to content

Commit 230e44b

Browse files
committed
update
1 parent 47e36a0 commit 230e44b

3 files changed

Lines changed: 130 additions & 40 deletions

File tree

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ sphinxnotes-data
88
FooBar
99
======
1010

11-
.. data:autodef::
11+
.. data:auto::
1212
:color:
1313
:size: 1
1414

src/sphinxnotes/data/__init__.py

Lines changed: 123 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"""
88

99
from __future__ import annotations
10-
from typing import cast
10+
from typing import cast, override
11+
from abc import abstractmethod
1112

1213
from docutils import nodes
1314
from docutils.parsers.rst import directives
@@ -41,7 +42,8 @@ def render(
4142
self, caller: SphinxDirective | SphinxTransform, replace: bool = False
4243
) -> rendered_data_node:
4344
if self.need_external_data:
44-
self._complete_external_data()
45+
parent = self.parent or caller.state.parent
46+
self._complete_external_data(parent)
4547

4648
try:
4749
parsed = self.schema.parse(self.data)
@@ -71,20 +73,23 @@ def render(
7173

7274
return rendered
7375

74-
def _complete_external_data(self):
76+
def _complete_external_data(self, parent: nodes.Element):
77+
# NOTE: Do not use self.parent here, the pending_data_node may be just
78+
# created and haven't inserted to doctree.
7579
if not self.data.name:
76-
if title := find_titular_node_upward(self.parent):
80+
if title := find_titular_node_upward(parent):
7781
self.data.name = title.astext()
7882

79-
if not self.data.content and self.parent:
83+
if not self.data.content:
8084
contnodes = []
81-
for i, child in enumerate(self.parent):
85+
for i, child in enumerate(parent):
8286
if child == self:
8387
contnodes = self.parent[i:]
8488
self.data.content = '\n\n'.join([n.astext() for n in contnodes])
8589

8690

87-
class rendered_data_node(DataNode, nodes.container): ...
91+
class rendered_data_node(DataNode, nodes.container):
92+
anchor: nodes.Element | None
8893

8994

9095
class TemplateDirective(SphinxDirective):
@@ -104,7 +109,7 @@ def run(self) -> list[nodes.Node]:
104109
return []
105110

106111

107-
class DataSchemaDirective(FreeStyleDirective):
112+
class SchemaDirective(FreeStyleDirective):
108113
optional_arguments = 1
109114
option_spec = FreeStyleOptionSpec()
110115
has_content = True
@@ -121,52 +126,134 @@ def run(self) -> list[nodes.Node]:
121126
return []
122127

123128

124-
class DefineDirective(SphinxDirective):
125-
optional_arguments = 1
126-
has_content = True
129+
class BaseDataDirective(SphinxDirective):
130+
131+
"""Methods to be overrided."""
132+
133+
@abstractmethod
134+
def current_template(self) -> Template: ...
135+
136+
@abstractmethod
137+
def current_schema(self) -> Schema: ...
138+
139+
def process_data(self, data: Data) -> None: ...
140+
141+
def process_pending_node(self, n: pending_data_node) -> None: ...
142+
143+
def process_rendered_node(self, n: rendered_data_node) -> None: ...
144+
145+
def run(self) -> list[nodes.Node]:
146+
pending = self.build_pending_node()
147+
if pending.template.phase != Phase.Parsing:
148+
return [pending}
149+
return [self.render_pending_node(pending)]
150+
151+
"""Methods used internal."""
127152

128-
def _extract_data(self) -> Data:
129-
return Data(
153+
def build_pending_node(self) -> pending_data_node:
154+
data = Data(
130155
self.arguments[0] if self.arguments else None,
131156
self.options.copy(),
132157
'\n'.join(self.content) if self.has_content else None,
133158
)
159+
self.process_data(data)
160+
161+
tmpl = self.current_template()
162+
schema = self.current_schema()
163+
164+
n = pending_data_node()
165+
self.set_source_info(n)
166+
n.data, n.template, n.schema = data, tmpl, schema
167+
self.process_pending_node(n)
168+
169+
return n
170+
171+
def render_pending_node(self, pending: pending_data_node) -> rendered_data_node:
172+
rendered = pending.render(self)
173+
self.process_rendered_node(rendered)
174+
return rendered
175+
134176

177+
class FreeDataDirective(BaseDataDirective, FreeStyleDirective):
178+
optional_arguments = 1
179+
has_content = True
180+
181+
@override
135182
def current_template(self) -> Template:
136183
tmpl = self.env.current_document.get(TEMPLATE_KEY, Template.default())
137184
return cast(Template, tmpl)
138185

186+
@override
139187
def current_schema(self) -> Schema:
140188
schema = self.env.current_document.get(SCHEMA_KEY, Schema.default())
141189
return cast(Schema, schema)
142190

143-
def new_pending_node(self) -> pending_data_node:
144-
data = self._extract_data()
145-
tmpl = self.current_template()
146-
schema = self.current_schema()
147191

148-
n = pending_data_node()
149-
self.set_source_info(n)
150-
n.data, n.template, n.schema = data, tmpl, schema
192+
class AutoDataDirective(FreeDataDirective):
193+
@override
194+
def process_pending_node(self, n: pending_data_node) -> None:
195+
n.need_external_data = True
151196

152-
return n
153197

154-
def run(self) -> list[nodes.Node]:
155-
n = self.new_pending_node()
156-
if n.template.phase != Phase.Parsing:
157-
return [n]
198+
class FixedDataDirective(BaseDataDirective):
199+
final_argument_whitespace = True
158200

159-
return [n.render(self)]
201+
schema: Schema
202+
template: Template
160203

204+
@classmethod
205+
def derive(cls, name: str, tmpl: Template, schema: Schema) -> type[FixedDataDirective]:
206+
"""Generate an AnyDirective child class for describing object."""
207+
base_classes = []
208+
base_classes.append(FixedDataDirective)
209+
210+
if not schema.name:
211+
required_arguments = 0
212+
optional_arguments = 0
213+
elif schema.name.required:
214+
required_arguments = 1
215+
optional_arguments = 0
216+
else:
217+
required_arguments = 0
218+
optional_arguments = 1
161219

162-
class FreeDefineDirective(DefineDirective, FreeStyleDirective): ...
220+
if isinstance(schema.attrs, Field):
221+
base_classes.append(FreeStyleDirective)
222+
if schema.attrs.required:
223+
option_spec = FreeStyleOptionSpec(directives.unchanged_required)
224+
else:
225+
option_spec = FreeStyleOptionSpec(directives.unchanged)
226+
else:
227+
option_spec = {}
228+
for name, field in schema.attrs.items():
229+
if field.required:
230+
option_spec[name] = directives.unchanged_required
231+
else:
232+
option_spec[name] = directives.unchanged
233+
234+
has_content = schema.content is not None
235+
236+
# Generate directive class
237+
return type(
238+
'Any%sDirective' % name.title(),
239+
tuple(base_classes),
240+
{
241+
'schema': schema,
242+
'template': tmpl,
243+
'has_content': has_content,
244+
'required_arguments': required_arguments,
245+
'optional_arguments': optional_arguments,
246+
'option_spec': option_spec,
247+
},
248+
)
163249

250+
@override
251+
def current_template(self) -> Template:
252+
return self.template
164253

165-
class AutoDefineDirective(DefineDirective, FreeStyleDirective):
166-
def new_pending_node(self) -> pending_data_node:
167-
n = super().new_pending_node()
168-
n.need_external_data = True
169-
return n
254+
@override
255+
def current_schema(self) -> Schema:
256+
return self.schema
170257

171258

172259
class ParsedHook(SphinxDirective):
@@ -210,16 +297,17 @@ def apply(self, **kwargs):
210297
if pending.template.phase != Phase.Resolving:
211298
continue
212299

300+
# TODO: deal with ValueError
213301
pending.render(self, replace=True)
214302

215303

216304
def setup(app: Sphinx):
217305
meta.pre_setup(app)
218306

219307
app.add_directive('data:tmpl', TemplateDirective, False)
220-
app.add_directive('data:schema', DataSchemaDirective, False)
221-
app.add_directive('data:def', FreeDefineDirective, False)
222-
app.add_directive('data:autodef', AutoDefineDirective, False)
308+
app.add_directive('data:schema', SchemaDirective, False)
309+
app.add_directive('data:def', FreeDataDirective, False)
310+
app.add_directive('data:auto', AutoDataDirective, False)
223311
app.add_directive('data:parsed-hook', ParsedHook, False)
224312

225313
app.connect('source-read', on_source_read)

src/sphinxnotes/data/context_proxy.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
from types import MappingProxyType
55

66
from docutils import nodes
7-
87
from sphinx.util import logging
98
from sphinx.config import Config as SphinxConfig
109

10+
from .utils import find_first_child
11+
12+
1113
logger = logging.getLogger(__name__)
1214

1315

@@ -98,7 +100,7 @@ def __str__(self) -> str:
98100
class NodeWithTitle(Node):
99101
@proxy_property
100102
def title(self) -> Node | None:
101-
return self._obj.next_node(nodes.Titular)
103+
return find_first_child(self._obj, nodes.Titular) # type: ignore
102104

103105

104106
@dataclass(frozen=True)
@@ -128,7 +130,7 @@ def sections(self) -> tuple[Section, ...]:
128130

129131

130132
@dataclass(frozen=True)
131-
class ConfigProxy(Proxy):
133+
class Config(Proxy):
132134
_obj: SphinxConfig
133135

134136

@@ -139,7 +141,7 @@ class ConfigProxy(Proxy):
139141
SPECIFIC_TYPE_REGISTRY: dict[type, type[Proxy]] = {
140142
nodes.document: Document,
141143
nodes.section: Section,
142-
SphinxConfig: ConfigProxy,
144+
SphinxConfig: Config,
143145
}
144146

145147

0 commit comments

Comments
 (0)