55from sphinx .util .docutils import SphinxDirective , SphinxRole
66from sphinx .transforms import SphinxTransform
77
8- from .render import HostWrapper
8+ from .render import HostWrapper , Phase
99from .ctxnodes import pending_node
1010from .utils import find_current_section , Report , Reporter
1111
1919# ExtraContext ABC definitions
2020# ============================
2121
22- type ExtraContext = (
23- ParsingPhaseExtraContext
24- | ParsingPhaseExtraContext
25- | ResolvingPhaseExtraContext
26- | GlobalExtraContext
27- )
28- """Type alias of all available extra contexts."""
2922
23+ class _ExtraContext (ABC ):
24+ """Base class of extra context."""
3025
31- class ParsingPhaseExtraContext (ABC ):
26+ phase : ClassVar [Phase | None ] = None
27+
28+ @abstractmethod
29+ def generate (self , * args , ** kwargs ) -> Any : ...
30+
31+
32+ class ParsingPhaseExtraContext (_ExtraContext ):
3233 """Extra context generated during the :py:data:`~Phase.Parsing` phase.
3334
3435 The ``generate`` method receives the current directive or role being executed.
3536 """
3637
38+ phase = Phase .Parsing
39+
3740 @abstractmethod
3841 def generate (self , directive : SphinxDirective | SphinxRole ) -> Any : ...
3942
4043
41- class ParsedPhaseExtraContext (ABC ):
44+ class ParsedPhaseExtraContext (_ExtraContext ):
4245 """Extra context generated during the :py:data:`~Phase.Parsed` phase.
4346
4447 The ``generate`` method receives the current Sphinx transform.
4548 """
4649
50+ phase = Phase .Parsed
51+
4752 @abstractmethod
4853 def generate (self , transform : SphinxTransform ) -> Any : ...
4954
5055
51- class ResolvingPhaseExtraContext (ABC ):
56+ class ResolvingPhaseExtraContext (_ExtraContext ):
5257 """Extra context generated during the :py:data:`~Phase.Resolving` phase.
5358
5459 The ``generate`` method receives the current Sphinx transform.
5560 """
5661
62+ phase = Phase .Resolving
63+
5764 @abstractmethod
5865 def generate (self , transform : SphinxTransform ) -> Any : ...
5966
6067
61- class GlobalExtraContext (ABC ):
68+ class GlobalExtraContext (_ExtraContext ):
6269 """Extra context available in all phases.
6370
6471 The ``generate`` method receives the Sphinx build environment.
@@ -74,42 +81,34 @@ def generate(self, env: BuildEnvironment) -> Any: ...
7481
7582
7683class _ExtraContextRegistry :
77- ctxs : dict [str , ExtraContext ]
84+ ctxs : dict [str , _ExtraContext ]
7885
7986 def __init__ (self ) -> None :
8087 self .ctxs = {}
8188
82- def is_extra_context (self , ctx : Any ) -> bool :
83- return isinstance (
84- ctx ,
85- (
86- ParsingPhaseExtraContext ,
87- ParsingPhaseExtraContext ,
88- ResolvingPhaseExtraContext ,
89- GlobalExtraContext ,
90- ),
91- )
92-
93- def register (self , name : str , ctx : ExtraContext ) -> None :
89+ def register (self , name : str , ctx : _ExtraContext ) -> None :
9490 if name in self .ctxs :
9591 raise ValueError (f'Extra context "{ name } " already registered' )
96- if not self .is_extra_context (ctx ):
97- raise TypeError (
98- f'Invalid extra context instance "{ name } ", Expecting { ExtraContext } '
99- )
10092 self .ctxs [name ] = ctx
10193
102- def get (self , name : str ) -> ExtraContext | None :
94+ def get (self , name : str ) -> _ExtraContext | None :
10395 if name not in self .ctxs :
10496 return None
10597 return self .ctxs [name ]
10698
107- def get_names_by_phase (self , cls : type ) -> set [str ]:
108- return {name for name , ctx in self .ctxs .items () if isinstance (ctx , cls )}
109-
11099 def get_names (self ) -> set [str ]:
111100 return set (self .ctxs .keys ())
112101
102+ def get_names_at_phase (self , phase : Phase | None ) -> set [str ]:
103+ return {name for name , ctx in self .ctxs .items () if ctx .phase == phase }
104+
105+ def get_names_before_phase (self , phase : Phase | None ) -> set [str ]:
106+ return {
107+ name
108+ for name , ctx in self .ctxs .items ()
109+ if phase is None or ctx .phase is None or phase >= ctx .phase
110+ }
111+
113112
114113# Global registry instance.
115114_REGISTRY = _ExtraContextRegistry ()
@@ -133,8 +132,8 @@ def extra_context(name: str):
133132
134133 @extra_context('doc')
135134 class DocExtraContext(ParsingPhaseExtraContext):
136- def generate(self, directive ):
137- return proxy(HostWrapper(directive ).doctree)
135+ def generate(self, ctx ):
136+ return proxy(HostWrapper(ctx ).doctree)
138137
139138 :param name: The context name, used in templates via ``load_extra('name')``.
140139 """
@@ -169,13 +168,17 @@ def __init__(self, node: pending_node) -> None:
169168 Reporter (node ).append (self .report )
170169
171170 # Initialize todo with requested extra contexts, validate they exist
171+ total = _REGISTRY .get_names ()
172+ avail = _REGISTRY .get_names_before_phase (node .template .phase )
172173 requested = set (node .template .extra )
173- avail = _REGISTRY .get_names ()
174174 self .todo = requested & avail
175175
176176 # Report errors for non-existent contexts
177- if nonexist := requested - avail :
178- self .report .text (f'Extra contexts { nonexist } are not registered.' )
177+ if nonexist := requested - total :
178+ self .report .text (f'Extra contexts { nonexist } are non-exist.' )
179+ if nonavail := requested - nonexist :
180+ self .report .text (f'Extra contexts { nonavail } are not available '
181+ f'at pahse { node .template .phase } .' )
179182
180183 def on_anytime (self , env : BuildEnvironment ) -> None :
181184 self ._generate (GlobalExtraContext , lambda ctx : ctx .generate (env ))
@@ -189,9 +192,9 @@ def on_parsed(self, transform: SphinxTransform) -> None:
189192 def on_resolving (self , transform : SphinxTransform ) -> None :
190193 self ._generate (ResolvingPhaseExtraContext , lambda ctx : ctx .generate (transform ))
191194
192- def _generate (self , cls : type , gen : Callable [..., Any ]) -> None :
195+ def _generate (self , cls : type [ _ExtraContext ] , gen : Callable [..., Any ]) -> None :
193196 # Get all context names available for this phase
194- avail = _REGISTRY .get_names_by_phase (cls )
197+ avail = _REGISTRY .get_names_at_phase (cls . phase )
195198 # Find which ones are requested and not yet generated
196199 todo = avail & self .todo
197200
@@ -262,17 +265,4 @@ def generate(self, env: BuildEnvironment) -> Any:
262265 return proxy (env )
263266
264267
265- @extra_context ('docutils' )
266- class DocutilsExtraContext (GlobalExtraContext ):
267- @override
268- def generate (self , env : BuildEnvironment ) -> Any :
269- from docutils .parsers .rst .directives import _directives
270- from docutils .parsers .rst .roles import _roles
271-
272- return {
273- 'directives' : _directives ,
274- 'roles' : _roles ,
275- }
276-
277-
278268def setup (app : Sphinx ): ...
0 commit comments