1+ import re
2+ from docutils import nodes
3+ from docutils .parsers .rst import directives
4+
5+ from sphinx import addnodes
6+ from sphinx .util .docutils import SphinxDirective
7+ from sphinx .util .nodes import make_id
8+
19
210class GrammarSnippetDirective (SphinxDirective ):
311 """Transform a grammar-snippet directive to a Sphinx productionlist
@@ -6,50 +14,45 @@ class GrammarSnippetDirective(SphinxDirective):
614
715 .. grammar-snippet:: file
816 :group: python-grammar
9- :generated-by: Tools/peg_generator/docs_generator.py
1017
1118 file: (NEWLINE | statement)*
1219
13- into something like:
14-
15- .. productionlist:: python-grammar
16- file: (NEWLINE | statement)*
20+ into something similar to Sphinx productionlist, but better suited
21+ for our needs:
22+ - Instead of `::=`, use a colon, as in `Grammar/python.gram`
23+ - Show the listing almost as is, with no auto-aligment.
24+ The only special character is the backtick, which marks tokens.
1725
18- The custom directive is needed because Sphinx's ` productionlist` does
19- not support options .
26+ Unlike Sphinx's productionlist, this directive supports options.
27+ The "group" must be given as an option .
2028 """
2129 has_content = True
2230 option_spec = {
2331 'group' : directives .unchanged ,
24- 'generated-by' : directives .unchanged ,
25- 'diagrams' : directives .unchanged ,
2632 }
2733
28- # Arguments are used by the tool that generates grammar-snippet,
29- # this Directive ignores them.
30- required_arguments = 1
31- optional_arguments = 0
34+ # We currently ignore arguments.
35+ required_arguments = 0
36+ optional_arguments = 1
3237 final_argument_whitespace = True
3338
3439 def run (self ):
3540 group_name = self .options ['group' ]
3641
37- rawsource = '''
3842 # Docutils elements have a `rawsource` attribute that is supposed to be
3943 # set to the original ReST source.
4044 # Sphinx does the following with it:
4145 # - if it's empty, set it to `self.astext()`
4246 # - if it matches `self.astext()` when generating the output,
4347 # apply syntax highlighting (which is based on the plain-text content
4448 # and thus discards internal formatting, like references).
45- # To get around this, we set it to this fake (and very non-empty)
46- # string!
47- '''
49+ # To get around this, we set it to this non-empty string:
50+ rawsource = 'You should not see this.'
4851
4952 literal = nodes .literal_block (
5053 rawsource ,
5154 '' ,
52- # TODO: Use a dedicated CSS class here and for strings,
55+ # TODO: Use a dedicated CSS class here and for strings.
5356 # and add it to the theme too
5457 classes = ['highlight' ],
5558 )
@@ -87,7 +90,9 @@ def run(self):
8790 name_node = addnodes .literal_strong ()
8891
8992 # Cargo-culted magic to make `name_node` a link target
90- # similar to Sphinx `production`:
93+ # similar to Sphinx `production`.
94+ # This needs to be the same as what Sphinx does
95+ # to avoid breaking existing links.
9196 domain = self .env .domains ['std' ]
9297 obj_name = f"{ group_name } :{ name } "
9398 prefix = f'grammar-token-{ group_name } '
@@ -116,19 +121,13 @@ def run(self):
116121 raise ValueError ('unhandled match' )
117122 literal += nodes .Text (line [last_pos :] + '\n ' )
118123
119-
120124 node = nodes .paragraph (
121125 '' , '' ,
122126 literal ,
123127 )
124128
125- content = StringList ()
126- for rule_name in self .options ['diagrams' ].split ():
127- content .append ('' , source = __file__ )
128- content .append (f'``{ rule_name } ``:' , source = __file__ )
129- content .append ('' , source = __file__ )
130- content .append (f'.. image:: diagrams/{ rule_name } .svg' , source = __file__ )
131-
132- self .state .nested_parse (content , 0 , node )
133-
134129 return [node ]
130+
131+ def setup (app ):
132+ app .add_directive ('grammar-snippet' , GrammarSnippetDirective )
133+ return {'version' : '1.0' , 'parallel_read_safe' : True }
0 commit comments