Skip to content

Commit 3fcb730

Browse files
committed
Add type annotations, minor clean-ups
- Add a module docstring - Use the snake_case convention for node classes - Make :group: a required option - declare parallel_write_safe = True
1 parent e2359b7 commit 3fcb730

File tree

2 files changed

+44
-18
lines changed

2 files changed

+44
-18
lines changed

Doc/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@
2727
'c_annotations',
2828
'changes',
2929
'glossary_search',
30+
'grammar_snippet',
3031
'lexers',
3132
'pydoc_topics',
3233
'pyspecific',
3334
'sphinx.ext.coverage',
3435
'sphinx.ext.doctest',
3536
'sphinx.ext.extlinks',
36-
'grammar_snippet',
3737
]
3838

3939
# Skip if downstream redistributors haven't installed them

Doc/tools/extensions/grammar_snippet.py

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
"""Support for documenting Python's grammar."""
2+
3+
from __future__ import annotations
4+
15
import re
6+
from typing import TYPE_CHECKING
27

38
from docutils import nodes
49
from docutils.parsers.rst import directives
@@ -7,21 +12,38 @@
712
from sphinx.util.docutils import SphinxDirective
813
from sphinx.util.nodes import make_id
914

15+
if TYPE_CHECKING:
16+
from collections.abc import Sequence
17+
from typing import Any
18+
19+
from docutils.nodes import Node
20+
from sphinx.application import Sphinx
21+
from sphinx.util.typing import ExtensionMetadata
1022

11-
class SnippetStringNode(nodes.inline):
23+
24+
class snippet_string_node(nodes.inline): # noqa: N801 (snake_case is fine)
1225
"""Node for a string literal in a grammar snippet."""
1326

14-
def __init__(self) -> None:
27+
def __init__(
28+
self,
29+
rawsource: str = '',
30+
text: str = '',
31+
*children: Node,
32+
**attributes: Any,
33+
) -> None:
34+
super().__init__(rawsource, text, *children, **attributes)
1535
# Use the Pygments highlight class for `Literal.String.Other`
16-
super().__init__(classes=['sx'])
36+
self['classes'].append('sx')
1737

1838

1939
class GrammarSnippetBase(SphinxDirective):
2040
"""Common functionality for GrammarSnippetDirective & CompatProductionList."""
2141

2242
# The option/argument handling is left to the individual classes.
2343

24-
def make_grammar_snippet(self, options, content):
44+
def make_grammar_snippet(
45+
self, options: dict[str, Any], content: Sequence[str]
46+
) -> list[nodes.paragraph]:
2547
"""Create a literal block from options & content."""
2648

2749
group_name = options['group']
@@ -65,22 +87,20 @@ def make_grammar_snippet(self, options, content):
6587
last_pos = match.end()
6688

6789
# Handle matches
68-
groupdict = {
90+
group_dict = {
6991
name: content
7092
for name, content in match.groupdict().items()
7193
if content is not None
7294
}
73-
match groupdict:
95+
match group_dict:
7496
case {'rule_name': name}:
7597
literal += self.make_link_target_for_token(
7698
group_name, name
7799
)
78100
case {'rule_ref': ref_text}:
79101
literal += token_xrefs(ref_text, group_name)
80102
case {'single_quoted': name} | {'double_quoted': name}:
81-
string_node = SnippetStringNode()
82-
string_node += nodes.Text(name)
83-
literal += string_node
103+
literal += snippet_string_node('', name)
84104
case _:
85105
raise ValueError('unhandled match')
86106
literal += nodes.Text(line[last_pos:] + '\n')
@@ -93,8 +113,10 @@ def make_grammar_snippet(self, options, content):
93113

94114
return [node]
95115

96-
def make_link_target_for_token(self, group_name, name):
97-
"""Return a literal node which is a link target for the given token"""
116+
def make_link_target_for_token(
117+
self, group_name: str, name: str
118+
) -> addnodes.literal_strong:
119+
"""Return a literal node which is a link target for the given token."""
98120
name_node = addnodes.literal_strong()
99121

100122
# Cargo-culted magic to make `name_node` a link target
@@ -138,20 +160,20 @@ class GrammarSnippetDirective(GrammarSnippetBase):
138160

139161
has_content = True
140162
option_spec = {
141-
'group': directives.unchanged,
163+
'group': directives.unchanged_required,
142164
}
143165

144166
# We currently ignore arguments.
145167
required_arguments = 0
146168
optional_arguments = 1
147169
final_argument_whitespace = True
148170

149-
def run(self):
171+
def run(self) -> list[nodes.paragraph]:
150172
return self.make_grammar_snippet(self.options, self.content)
151173

152174

153175
class CompatProductionList(GrammarSnippetBase):
154-
"""Create grammar snippets from ReST productionlist syntax
176+
"""Create grammar snippets from reST productionlist syntax
155177
156178
This is intended to be a transitional directive, used while we switch
157179
from productionlist to grammar-snippet.
@@ -165,7 +187,7 @@ class CompatProductionList(GrammarSnippetBase):
165187
final_argument_whitespace = True
166188
option_spec = {}
167189

168-
def run(self):
190+
def run(self) -> list[nodes.paragraph]:
169191
# The "content" of a productionlist is actually the first and only
170192
# argument. The first line is the group; the rest is the content lines.
171193
lines = self.arguments[0].splitlines()
@@ -185,9 +207,13 @@ def run(self):
185207
return self.make_grammar_snippet(options, content)
186208

187209

188-
def setup(app):
210+
def setup(app: Sphinx) -> ExtensionMetadata:
189211
app.add_directive('grammar-snippet', GrammarSnippetDirective)
190212
app.add_directive_to_domain(
191213
'std', 'productionlist', CompatProductionList, override=True
192214
)
193-
return {'version': '1.0', 'parallel_read_safe': True}
215+
return {
216+
'version': '1.0',
217+
'parallel_read_safe': True,
218+
'parallel_write_safe': True,
219+
}

0 commit comments

Comments
 (0)