2020PAT_EXPLICIT_TILE = re .compile (r'^(?P<label>.+?)\s*(?<!\x00)<(?P<target>.*?)>$' , re .DOTALL )
2121PAT_WHITESPACE = re .compile (r'^\x20*' )
2222PAT_BLOCK_HAS_ARGUMENT = re .compile (r'^\x20*\.\.\x20[^\s]+::\s*\S+' )
23- SPECIAL_DIRECTIVES = {'code-block' , 'include' , 'only' }
2423
2524
2625@checked
@@ -38,6 +37,10 @@ class LegacyTabsDefinition(nodes.Node):
3837 tabs : List [LegacyTabDefinition ]
3938
4039
40+ def option_bool (argument : str ) -> Any :
41+ return docutils .parsers .rst .directives .choice (argument , ('true' , 'false' , None ))
42+
43+
4144class directive_argument (docutils .nodes .General , docutils .nodes .TextElement ):
4245 pass
4346
@@ -48,6 +51,10 @@ def __init__(self, name: str) -> None:
4851 self ['name' ] = name
4952
5053
54+ class code (docutils .nodes .General , docutils .nodes .FixedTextElement ):
55+ pass
56+
57+
5158class role (docutils .nodes .General , docutils .nodes .Inline , docutils .nodes .Element ):
5259 def __init__ (self , name : str , rawtext : str , text : str , lineno : int ) -> None :
5360 super (role , self ).__init__ ()
@@ -131,6 +138,22 @@ def parse_options(block_text: str) -> Dict[str, str]:
131138 return kv
132139
133140
141+ def parse_linenos (term : str , max_val : int ) -> List [Tuple [int , int ]]:
142+ """Parse a comma-delimited list of line numbers and ranges."""
143+ results : List [Tuple [int , int ]] = []
144+ for term in (term for term in term .split (',' ) if term .strip ()):
145+ parts = term .split ('-' , 1 )
146+ lower = int (parts [0 ])
147+ higher = int (parts [1 ]) if len (parts ) == 2 else lower
148+
149+ if lower < 0 or lower > max_val or higher < 0 or higher > max_val or lower > higher :
150+ raise ValueError (f'Invalid line number specification: { term } ' )
151+
152+ results .append ((lower , higher ))
153+
154+ return results
155+
156+
134157class Directive (docutils .parsers .rst .Directive ):
135158 optional_arguments = 1
136159 final_argument_whitespace = True
@@ -163,7 +186,7 @@ def run(self) -> List[docutils.nodes.Node]:
163186 node .append (argument )
164187
165188 # Parse the content
166- if self .name in SPECIAL_DIRECTIVES :
189+ if self .name in { 'include' , 'only' } :
167190 raw = docutils .nodes .FixedTextElement ()
168191 raw .document = self .state .document
169192 raw .source , raw .line = source , line
@@ -195,9 +218,8 @@ def prepare_viewlist(text: str, ignore: int = 1) -> List[str]:
195218class TabsDirective (Directive ):
196219 option_spec = {
197220 'tabset' : str ,
198- 'hidden' : bool
221+ 'hidden' : option_bool
199222 }
200- has_content = True
201223
202224 def run (self ) -> List [docutils .nodes .Node ]:
203225 # Transform the old YAML-based syntax into the new pure-rst syntax.
@@ -262,6 +284,38 @@ def make_tab_node(self, source: str, child: LegacyTabDefinition) -> docutils.nod
262284 return node
263285
264286
287+ class CodeDirective (Directive ):
288+ required_arguments = 1
289+ optional_arguments = 0
290+ option_spec = {
291+ 'copyable' : option_bool ,
292+ 'emphasize-lines' : str
293+ }
294+
295+ def run (self ) -> List [docutils .nodes .Node ]:
296+ source , line = self .state_machine .get_source_and_line (self .lineno )
297+ copyable = 'copyable' not in self .options or self .options ['copyable' ] == 'true'
298+
299+ try :
300+ n_lines = len (self .content )
301+ emphasize_lines = parse_linenos (self .options .get ('emphasize-lines' , '' ), n_lines )
302+ except ValueError as err :
303+ error_node = self .state .document .reporter .error (
304+ str (err ),
305+ line = self .lineno )
306+ return [error_node ]
307+
308+ value = '\n ' .join (self .content )
309+ node = code (value , value )
310+ node ['name' ] = 'code'
311+ node ['lang' ] = self .arguments [0 ]
312+ node ['copyable' ] = copyable
313+ node ['emphasize_lines' ] = emphasize_lines
314+ node .document = self .state .document
315+ node .source , node .line = source , line
316+ return [node ]
317+
318+
265319def handle_role (typ : str , rawtext : str , text : str ,
266320 lineno : int , inliner : object ,
267321 options : Dict [str , object ] = {},
@@ -275,6 +329,9 @@ def lookup_directive(directive_name: str, language_module: object,
275329 if directive_name .startswith ('tabs' ):
276330 return TabsDirective , []
277331
332+ if directive_name in {'code-block' , 'sourcecode' }:
333+ return CodeDirective , []
334+
278335 return Directive , []
279336
280337
0 commit comments