77"""
88
99from __future__ import annotations
10- from typing import cast
10+ from typing import cast , override
11+ from abc import abstractmethod
1112
1213from docutils import nodes
1314from 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
9095class 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
172259class 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
216304def 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 )
0 commit comments