11"""NOTE: this will eventually be moved out of core"""
22from contextlib import contextmanager
33import json
4+ import sys
45from typing import List
56
67import yaml
78
89from docutils import nodes
910from docutils .frontend import OptionParser
1011
11- # from docutils.languages import get_language
12- # from docutils.parsers.rst import directives, Directive, DirectiveError, roles
12+ from docutils .languages import get_language
13+ from docutils .parsers .rst import roles # directives, Directive, DirectiveError, roles
1314from docutils .parsers .rst import Parser as RSTParser
1415
1516# from docutils.parsers.rst.directives.misc import Include
16- # from docutils.parsers.rst.states import RSTStateMachine, Body, Inliner
17+ from docutils .parsers .rst .states import Inliner # RSTStateMachine, Body
18+
1719# from docutils.statemachine import StringList
18- from docutils .utils import new_document , Reporter # noqa
20+ from docutils .utils import new_document , Reporter
1921
2022from markdown_it .token import Token , nest_tokens
23+ from markdown_it .utils import AttrDict
24+ from markdown_it .common .utils import escapeHtml
2125
2226
2327def make_document (source_path = "notset" ) -> nodes .document :
@@ -31,20 +35,41 @@ class DocRenderer:
3135
3236 def __init__ (self , options = None , env = None ):
3337 self .options = options or {}
34- self .env = env or {}
38+ self .env = env or AttrDict ()
3539 self .rules = {
3640 k : v
3741 for k , v in self .__class__ .__dict__ .items ()
3842 if k .startswith ("render_" ) and k != "render_children"
3943 }
4044 self .document = make_document ()
45+ self .reporter = self .document .reporter # type: Reporter
4146 self .current_node = self .document
47+ self .language_module = self .document .settings .language_code # type: str
48+ get_language (self .language_module )
49+ # TODO merge these with self.env?
4250 self .config = {}
4351 self ._level_to_elem = {0 : self .document }
4452
45- def run_render (self , tokens : List [Token ]):
53+ def run_render (self , tokens : List [Token ], env : AttrDict ):
54+ """Run the render on a token stream.
55+
56+ :param tokens: the token stream
57+ :param env: the environment sandbox associated with the tokens,
58+ containing additional metadata like reference info
59+ """
60+ self .env = env
61+ last_map = None
62+ # propagate line number down to inline elements
63+ for token in tokens :
64+ if token .map :
65+ last_map = token .map
66+ elif last_map :
67+ token .meta ["parent_line" ] = last_map [0 ]
68+ for child in token .children or []:
69+ child .meta ["parent_line" ] = last_map [0 ]
4670 tokens = nest_tokens (tokens )
4771 for i , token in enumerate (tokens ):
72+ # skip hidden?
4873 if f"render_{ token .type } " in self .rules :
4974 self .rules [f"render_{ token .type } " ](self , token )
5075 else :
@@ -113,7 +138,7 @@ def renderInlineAsText(self, tokens: List[Token]) -> str:
113138
114139 return result
115140
116- # ### render methods for tokens
141+ # ### render methods for commonmark tokens
117142
118143 def render_paragraph_open (self , token ):
119144 para = nodes .paragraph ("" )
@@ -133,6 +158,12 @@ def render_bullet_list_open(self, token):
133158 with self .current_node_context (list_node , append = True ):
134159 self .render_children (token )
135160
161+ def render_ordered_list_open (self , token ):
162+ list_node = nodes .enumerated_list ()
163+ self .add_line_and_source_path (list_node , token )
164+ with self .current_node_context (list_node , append = True ):
165+ self .render_children (token )
166+
136167 def render_list_item_open (self , token ):
137168 item_node = nodes .list_item ()
138169 self .add_line_and_source_path (item_node , token )
@@ -220,6 +251,7 @@ def render_heading_open(self, token):
220251 def render_link_open (self , token ):
221252 # TODO I think this is maybe already handled at this point?
222253 # refuri = escape_url(http://github.com/token.target)
254+ # TODO identify cross-references
223255 refuri = target = token .attrGet ("href" )
224256 ref_node = nodes .reference (target , target , refuri = refuri )
225257 self .add_line_and_source_path (ref_node , token )
@@ -240,6 +272,8 @@ def render_image(self, token):
240272
241273 self .current_node .append (img_node )
242274
275+ # ### render methods for plugin tokens
276+
243277 def render_front_matter (self , token ):
244278 """Pass document front matter data
245279
@@ -267,6 +301,45 @@ def render_front_matter(self, token):
267301 docinfo = dict_to_docinfo (data )
268302 self .current_node .append (docinfo )
269303
304+ def render_math_inline (self , token ):
305+ content = token .content
306+ node = nodes .math (content , content )
307+ self .add_line_and_source_path (node , token )
308+ self .current_node .append (node )
309+
310+ def render_math_block (self , token ):
311+ content = token .content
312+ node = nodes .math_block (content , content , nowrap = False , number = None )
313+ self .add_line_and_source_path (node , token )
314+ self .current_node .append (node )
315+
316+ def render_footnote_ref (self , token ):
317+ """Footnote references are added as auto-numbered,
318+ .i.e. `[^a]` is read as rST `[#a]_`
319+ """
320+ # TODO we now also have ^[a] the inline version (currently disabled)
321+ # that would be rendered here
322+ target = token .meta ["label" ]
323+ refnode = nodes .footnote_reference ("[^{}]" .format (target ))
324+ self .add_line_and_source_path (refnode , token )
325+ refnode ["auto" ] = 1
326+ refnode ["refname" ] = target
327+ # refnode += nodes.Text(token.target)
328+ self .document .note_autofootnote_ref (refnode )
329+ self .document .note_footnote_ref (refnode )
330+ self .current_node .append (refnode )
331+
332+ def render_footnote_reference_open (self , token ):
333+ target = token .meta ["label" ]
334+ footnote = nodes .footnote ()
335+ self .add_line_and_source_path (footnote , token )
336+ footnote ["names" ].append (target )
337+ footnote ["auto" ] = 1
338+ self .document .note_autofootnote (footnote )
339+ self .document .note_explicit_target (footnote , footnote )
340+ with self .current_node_context (footnote , append = True ):
341+ self .render_children (token )
342+
270343 def render_myst_block_break (self , token ):
271344 block_break = nodes .comment (token .content , token .content )
272345 block_break ["classes" ] += ["block_break" ]
@@ -282,14 +355,33 @@ def render_myst_target(self, token):
282355 self .document .note_explicit_target (target , self .current_node )
283356 self .current_node .append (target )
284357
285- def render_myst_role (self , token ):
358+ def render_myst_line_comment (self , token ):
359+ self .current_node .append (nodes .comment (token .content , token .content ))
286360
361+ def render_myst_role (self , token ):
287362 name = token .meta ["name" ]
288- # TODO representing as literal for place-holder
289- content = f":{ name } :`{ token .content } `"
290- node = nodes .literal (content , content )
291- self .add_line_and_source_path (node , token )
292- self .current_node .append (node )
363+ text = escapeHtml (token .content ) # TODO check this
364+ rawsource = f":{ name } :`{ token .content } `"
365+ lineno = token .meta .get ("parent_line" , 0 )
366+ role_func , messages = roles .role (
367+ name , self .language_module , lineno , self .reporter
368+ )
369+ inliner = MockInliner (self , lineno )
370+ if role_func :
371+ nodes , messages2 = role_func (name , rawsource , text , lineno , inliner )
372+ # return nodes, messages + messages2
373+ self .current_node += nodes
374+ else :
375+ message = self .reporter .error (
376+ 'Unknown interpreted text role "{}".' .format (name ), line = lineno
377+ )
378+ problematic = inliner .problematic (text , rawsource , message )
379+ self .current_node += problematic
380+
381+ # # TODO representing as literal for place-holder
382+ # node = nodes.literal(rawsource, rawsource)
383+ # self.add_line_and_source_path(node, token)
384+ # self.current_node.append(node)
293385
294386 # def render_table_open(self, token):
295387 # # print(token)
@@ -326,3 +418,48 @@ def dict_to_docinfo(data):
326418 field_node += nodes .field_body (value , nodes .Text (value , value ))
327419 docinfo += field_node
328420 return docinfo
421+
422+
423+ class MockingError (Exception ):
424+ """An exception to signal an error during mocking of docutils components."""
425+
426+
427+ class MockInliner :
428+ """A mock version of `docutils.parsers.rst.states.Inliner`.
429+
430+ This is parsed to role functions.
431+ """
432+
433+ def __init__ (self , renderer : DocRenderer , lineno : int ):
434+ self ._renderer = renderer
435+ self .document = renderer .document
436+ self .reporter = renderer .document .reporter
437+ if not hasattr (self .reporter , "get_source_and_line" ):
438+ # TODO this is called by some roles,
439+ # but I can't see how that would work in RST?
440+ self .reporter .get_source_and_line = lambda l : (self .document ["source" ], l )
441+ self .parent = renderer .current_node
442+ self .language = renderer .language_module
443+ self .rfc_url = "rfc%d.html"
444+
445+ def problematic (self , text : str , rawsource : str , message : nodes .system_message ):
446+ msgid = self .document .set_id (message , self .parent )
447+ problematic = nodes .problematic (rawsource , rawsource , refid = msgid )
448+ prbid = self .document .set_id (problematic )
449+ message .add_backref (prbid )
450+ return problematic
451+
452+ # TODO add parse method
453+
454+ def __getattr__ (self , name ):
455+ """This method is only be called if the attribute requested has not
456+ been defined. Defined attributes will not be overridden.
457+ """
458+ # TODO use document.reporter mechanism?
459+ if hasattr (Inliner , name ):
460+ msg = "{cls} has not yet implemented attribute '{name}'" .format (
461+ cls = type (self ).__name__ , name = name
462+ )
463+ raise MockingError (msg ).with_traceback (sys .exc_info ()[2 ])
464+ msg = "{cls} has no attribute {name}" .format (cls = type (self ).__name__ , name = name )
465+ raise MockingError (msg ).with_traceback (sys .exc_info ()[2 ])
0 commit comments