99"""
1010
1111from __future__ import annotations
12+ from dataclasses import dataclass
1213from typing import TYPE_CHECKING , override , cast , TypeVar
1314from pprint import pformat
1415
15- from sphinxnotes .data .data import PendingData
16-
17- from .indexers import LiteralIndexer , PathIndexer , YearIndexer , MonthIndexer
18-
1916from docutils import nodes
17+ from docutils .parsers .rst import directives
2018from sphinx import addnodes
2119from sphinx .domains import Domain , ObjType , Index , IndexEntry
2220from sphinx .roles import XRefRole
2321from sphinx .util import logging
2422from sphinx .util .nodes import make_id , make_refnode
2523from sphinx .errors import ExtensionError
2624from sphinxnotes .data import (
25+ Phase ,
2726 PlainValue ,
2827 RawData ,
2928 Template ,
3029 ValueWrapper ,
3130 ParsedData ,
3231 Schema ,
32+ PendingContext ,
33+ ResolvedContext ,
3334 pending_node ,
35+ BaseContextDirective ,
36+ UnparsedData ,
3437 StrictDataDefineDirective ,
3538)
3639from sphinxnotes .data .utils import (
5255 get_object_refs ,
5356)
5457from .utils import strip_rst_markups
58+ from .indexers import LiteralIndexer , PathIndexer , YearIndexer , MonthIndexer
5559
5660if TYPE_CHECKING :
5761 from typing import Iterator , Iterable
@@ -169,7 +173,7 @@ def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]:
169173 for (objtype , objid ), (docname , anchor , _ ) in self .data ['objects' ].items ():
170174 yield objid , objid , objtype , docname , anchor , 1
171175
172- """Publish methods."""
176+ """Public methods."""
173177
174178 @classmethod
175179 def add_objtype (cls , objtype : str , typedef : ObjTypeDef ) -> None :
@@ -179,7 +183,7 @@ def add_objtype(cls, objtype: str, typedef: ObjTypeDef) -> None:
179183 cls .schemas [objtype ] = schema
180184 cls .templates [objtype ] = tmpls
181185
182- # Create a directive for documenting object.
186+ # Create directive ``.. objtype::`` for documenting object.
183187 if typedef .auto :
184188 cls .directives [objtype ] = AutoObjDefineDirective .derive (
185189 objtype ,
@@ -193,6 +197,9 @@ def add_objtype(cls, objtype: str, typedef: ObjTypeDef) -> None:
193197 tmpls .obj ,
194198 )
195199
200+ # Create directive ``.. objtype+embed::`` for embedding object.
201+ cls .directives [objtype + '+embed' ] = ObjEmbedDirective
202+
196203 def mkrole (reftype : RefType ):
197204 """Create and register role for referencing object."""
198205 role = XRefRole (
@@ -265,21 +272,34 @@ def note_object(
265272
266273 return otherobj
267274
275+ def get_rendered_object (self , objtype : str , objid : PlainValue ) -> list [nodes .Node ]:
276+ docname , anchor , _ = self .objects [objtype , objid ]
277+ doctree = self .env .get_doctree (docname )
278+ ahrnode = doctree .ids [anchor ]
279+ if isinstance (ahrnode , addnodes .desc_signature ):
280+ return [ahrnode .parent ]
281+ assert False # TODO: mark obj start and objend
282+
268283 """Methods for inernal use"""
269284
270- def _get_index_anchor (self , reftype : str , refval : str , location = None ) -> tuple [str , str ]:
285+ def _get_index_anchor (
286+ self , reftype : str , refval : str , location = None
287+ ) -> tuple [str , str ]:
271288 """
272289 Return the docname and anchor name of index page. Can be used for ``make_refnode()``.
273290
274291 .. warning:: This is no public API of sphinx and may broken in future version.
275292 """
276293 domain = self .name
277294 index = self .indices_for_reftype [reftype ]
278- try :
295+ try :
279296 anchor = index .indexer .anchor (refval )
280297 except Exception as e :
281298 anchor = ''
282- logger .warning (f'failed to convert { repr (refval )} (xref target) to anchor: { e } ' , location = location )
299+ logger .warning (
300+ f'failed to convert { repr (refval )} (xref target) to anchor: { e } ' ,
301+ location = location ,
302+ )
283303
284304 return f'{ domain } -{ index .name } ' , anchor
285305
@@ -318,21 +338,21 @@ def _build_objdesc(self, pending: pending_node, rendered: list[nodes.Node]) -> N
318338 <desc_content>
319339 <nodes...> # the original ``ns``
320340 """
321- domain , objtype = self .get_any_domain_and_type ()
341+ domain , objtype = self .get_domain_and_type ()
322342
323343 if (hdrtmpl := domain .templates [objtype ].header ) is None :
324344 # No header template available, no need to generate objdesc.
325345 return
326346 if (
327- isinstance (pending .data , ParsedData )
328- and pending .data .name is None
347+ isinstance (pending .ctx , ParsedData )
348+ and pending .ctx .name is None
329349 and '{{ name }}' in hdrtmpl .text
330350 ):
331351 # HACK: do not generate signode when name is not given.
332352 return
333353
334354 # Queue a rendering for header, and setup anchor when rendering done.
335- hdrnode = pending_node (pending .data , hdrtmpl , inline = True )
355+ hdrnode = pending_node (pending .ctx , hdrtmpl , inline = True )
336356 hdrnode .hook_rendered_nodes (self ._setup_signode_anchor )
337357 self .queue_pending_node (hdrnode )
338358
@@ -351,28 +371,28 @@ def _setup_signode_anchor(
351371 ) -> None :
352372 ahrnode = find_parent (pending , addnodes .desc_signature )
353373 assert ahrnode
354- obj = pending .data
374+ obj = pending .ctx
355375 assert isinstance (obj , ParsedData )
356376
357377 self .setup_anchor (ahrnode , obj )
358378
359379 """Helpers methods for self and subclasses."""
360380
361- def get_any_domain_and_type (self ) -> tuple [ObjDomain , str ]:
381+ def get_domain_and_type (self ) -> tuple [ObjDomain , str ]:
362382 domainname , _ , objtype = self .name .partition (':' )
363383 _domain = self .env .get_domain (domainname )
364384 return cast (ObjDomain , _domain ), objtype
365385
366386 def update_domain_atts (self , node : nodes .Element ):
367387 """Attach domain related info to node."""
368- domain , objtype = self .get_any_domain_and_type ()
388+ domain , objtype = self .get_domain_and_type ()
369389 node ['domain' ] = domain .name
370390 # 'desctype' is a backwards compatible attribute
371391 node ['objtype' ] = node ['desctype' ] = objtype
372392 node ['classes' ].extend ([domain .name , objtype ])
373393
374394 def setup_anchor (self , ahrnode : nodes .Element , obj : Object ) -> None :
375- domain , objtype = self .get_any_domain_and_type ()
395+ domain , objtype = self .get_domain_and_type ()
376396
377397 objids = get_object_uniq_ids (self .schema , obj )
378398 ahrterm = ValueWrapper (objids [0 ]).as_str () if objids else None
@@ -426,10 +446,10 @@ def derive(
426446 def process_pending_node (self , n : pending_node ) -> bool :
427447 if (
428448 n .template == self .template
429- and isinstance (n .data , PendingData )
430- and self ._require_external_header (n .data .schema , n .data .raw )
449+ and isinstance (n .ctx , UnparsedData )
450+ and self ._require_external_header (n .ctx .schema , n .ctx .raw )
431451 ):
432- n .hook_raw_data (self ._resolve_external_header )
452+ n .hook_pending_context (self ._resolve_external_header )
433453 return super (StrictDataDefineDirective , self ).process_pending_node (n )
434454
435455 return super ().process_pending_node (n )
@@ -453,8 +473,10 @@ def _require_external_header(self, schema: Schema, data: RawData) -> bool:
453473 return False
454474 return ValueWrapper (val ).as_str () == '_'
455475
456- def _resolve_external_header (self , pending : pending_node , raw : RawData ) -> None :
457- domain , objtype = self .get_any_domain_and_type ()
476+ def _resolve_external_header (
477+ self , pending : pending_node , ctx : PendingContext
478+ ) -> None :
479+ domain , objtype = self .get_domain_and_type ()
458480
459481 if (hdrtmpl := domain .templates [objtype ].header ) is None :
460482 # No header template available, no need to generate objdesc.
@@ -468,14 +490,16 @@ def _resolve_external_header(self, pending: pending_node, raw: RawData) -> None:
468490 set (['any' , domain .name , 'any-header' , objtype + '-header' ])
469491 )
470492
493+ raw = cast (UnparsedData , ctx ).raw
494+
471495 if raw .name is None :
472496 raw .name = title .astext ()
473497 else :
474498 # HACK: See also _require_external_header.
475499 # TODO: Introduce a new extra context?
476500 raw .name = raw .name .replace ('_' , title .astext (), count = 1 )
477501
478- pending_title = pending_node (pending .data , hdrtmpl , inline = True )
502+ pending_title = pending_node (pending .ctx , hdrtmpl , inline = True )
479503 pending_title .hook_rendered_nodes (self ._setup_external_anchor )
480504 self .queue_pending_node (pending_title )
481505
@@ -486,11 +510,69 @@ def _resolve_external_header(self, pending: pending_node, raw: RawData) -> None:
486510 def _setup_external_anchor (
487511 self , pending : pending_node , rendered : list [nodes .Node ]
488512 ) -> None :
489- assert isinstance (pending .data , ParsedData )
490- self .setup_anchor (pending .parent , pending .data )
513+ assert isinstance (pending .ctx , ParsedData )
514+ self .setup_anchor (pending .parent , pending .ctx )
515+
516+
517+ @dataclass
518+ class PendingObject (PendingContext ):
519+ domain : ObjDomain
520+ objtype : str
521+ objid : str
522+
523+ @override
524+ def resolve (self ) -> ResolvedContext :
525+ _ , _ , obj = self .domain .objects [self .objtype , self .objid ]
526+ return obj
527+
528+ def __hash__ (self ) -> int :
529+ return hash ((self .domain .name , self .objtype , self .objid ))
530+
491531
532+ class ObjEmbedDirective (BaseContextDirective ):
533+ required_arguments = 1
534+ optional_arguments = 0
535+ option_spec = {
536+ 'debug' : directives .flag ,
537+ }
538+ has_content = True
539+
540+ def get_domain_and_type (self ) -> tuple [ObjDomain , str ]:
541+ domainname , _ , dirname = self .name .partition (':' )
542+ objtype , _ , _ = dirname .partition ('+' )
543+ _domain = self .env .get_domain (domainname )
544+ return cast (ObjDomain , _domain ), objtype
545+
546+ def get_rendered_objects (self ) -> list [nodes .Node ]:
547+ domain , objtype = self .get_domain_and_type ()
548+ objid = self .arguments [0 ]
549+ return domain .get_rendered_object (objtype , objid )
550+
551+ """Methods that override from parent."""
552+
553+ @override
554+ def current_context (self ) -> PendingContext | ResolvedContext :
555+ domain , objtype = self .get_domain_and_type ()
556+ objid = self .arguments [0 ]
557+ return PendingObject (domain , objtype , objid )
558+
559+ @override
560+ def current_template (self ) -> Template :
561+ if self .content :
562+ return Template (
563+ '\n ' .join (self .content ), Phase .Resolving , 'debug' in self .options
564+ )
492565
493- # =================
566+ domain , objtype = self .get_domain_and_type ()
567+ tmpl = domain .templates [objtype ].embed
568+ if tmpl :
569+ return tmpl
570+
571+ self .assert_has_content ()
572+ assert False
573+
574+
575+ # ==================
494576# Index implemention
495577# ==================
496578#
0 commit comments