3
# QEMU hxtool .hx file parsing extension
5
# Copyright (c) 2020 Linaro
7
# This work is licensed under the terms of the GNU GPLv2 or later.
8
# See the COPYING file in the top-level directory.
9
"""hxtool is a Sphinx extension that implements the hxtool-doc directive"""
11
# The purpose of this extension is to read fragments of rST
12
# from .hx files, and insert them all into the current document.
13
# The rST fragments are delimited by SRST/ERST lines.
14
# The conf.py file must set the hxtool_srctree config value to
15
# the root of the QEMU source tree.
16
# Each hxtool-doc:: directive takes one argument which is the
17
# path of the .hx file to process, relative to the source tree.
23
from docutils import nodes
24
from docutils.statemachine import ViewList
25
from docutils.parsers.rst import directives, Directive
26
from sphinx.errors import ExtensionError
27
from sphinx.util.docutils import switch_source_input
28
from sphinx.util.nodes import nested_parse_with_titles
34
# We parse hx files with a state machine which may be in one of two
35
# states: reading the C code fragment, or inside a rST fragment.
40
def serror(file, lnum, errtext):
41
"""Raise an exception giving a user-friendly syntax error message"""
42
raise ExtensionError('%s line %d: syntax error: %s' % (file, lnum, errtext))
44
def parse_directive(line):
45
"""Return first word of line, if any"""
46
return re.split(r'\W', line)[0]
48
def parse_defheading(file, lnum, line):
49
"""Handle a DEFHEADING directive"""
50
# The input should be "DEFHEADING(some string)", though note that
51
# the 'some string' could be the empty string. If the string is
52
# empty we ignore the directive -- these are used only to add
53
# blank lines in the plain-text content of the --help output.
55
# Return the heading text. We strip out any trailing ':' for
56
# consistency with other headings in the rST documentation.
57
match = re.match(r'DEFHEADING\((.*?):?\)', line)
59
serror(file, lnum, "Invalid DEFHEADING line")
62
def parse_archheading(file, lnum, line):
63
"""Handle an ARCHHEADING directive"""
64
# The input should be "ARCHHEADING(some string, other arg)",
65
# though note that the 'some string' could be the empty string.
66
# As with DEFHEADING, empty string ARCHHEADINGs will be ignored.
68
# Return the heading text. We strip out any trailing ':' for
69
# consistency with other headings in the rST documentation.
70
match = re.match(r'ARCHHEADING\((.*?):?,.*\)', line)
72
serror(file, lnum, "Invalid ARCHHEADING line")
75
def parse_srst(file, lnum, line):
76
"""Handle an SRST directive"""
77
# The input should be either "SRST", or "SRST(label)".
78
match = re.match(r'SRST(\((.*?)\))?', line)
80
serror(file, lnum, "Invalid SRST line")
83
class HxtoolDocDirective(Directive):
84
"""Extract rST fragments from the specified .hx file"""
86
optional_arguments = 1
88
'hxfile': directives.unchanged_required
93
env = self.state.document.settings.env
94
hxfile = env.config.hxtool_srctree + '/' + self.arguments[0]
96
# Tell sphinx of the dependency
97
env.note_dependency(os.path.abspath(hxfile))
100
# We build up lines of rST in this ViewList, which we will
101
# later put into a 'section' node.
106
with open(hxfile) as f:
107
lines = (l.rstrip() for l in f)
108
for lnum, line in enumerate(lines, 1):
109
directive = parse_directive(line)
111
if directive == 'HXCOMM':
113
elif directive == 'SRST':
114
if state == HxState.RST:
115
serror(hxfile, lnum, 'expected ERST, found SRST')
118
label = parse_srst(hxfile, lnum, line)
120
rstlist.append("", hxfile, lnum - 1)
121
# Build label as _DOCNAME-HXNAME-LABEL
122
hx = os.path.splitext(os.path.basename(hxfile))[0]
123
refline = ".. _" + env.docname + "-" + hx + \
125
rstlist.append(refline, hxfile, lnum - 1)
126
elif directive == 'ERST':
127
if state == HxState.CTEXT:
128
serror(hxfile, lnum, 'expected SRST, found ERST')
130
state = HxState.CTEXT
131
elif directive == 'DEFHEADING' or directive == 'ARCHHEADING':
132
if directive == 'DEFHEADING':
133
heading = parse_defheading(hxfile, lnum, line)
135
heading = parse_archheading(hxfile, lnum, line)
138
# Put the accumulated rST into the previous node,
139
# and then start a fresh section with this heading.
141
if current_node is None:
142
# We had some rST fragments before the first
143
# DEFHEADING. We don't have a section to put
144
# these in, so rather than magicing up a section,
145
# make it a syntax error.
147
'first DEFHEADING must precede all rST text')
148
self.do_parse(rstlist, current_node)
150
if current_node is not None:
151
node_list.append(current_node)
152
section_id = 'hxtool-%d' % env.new_serialno('hxtool')
153
current_node = nodes.section(ids=[section_id])
154
current_node += nodes.title(heading, heading)
156
# Not a directive: put in output if we are in rST fragment
157
if state == HxState.RST:
158
# Sphinx counts its lines from 0
159
rstlist.append(line, hxfile, lnum - 1)
161
if current_node is None:
162
# We don't have multiple sections, so just parse the rst
163
# fragments into a dummy node so we can return the children.
164
current_node = nodes.section()
165
self.do_parse(rstlist, current_node)
166
return current_node.children
168
# Put the remaining accumulated rST into the last section, and
169
# return all the sections.
171
self.do_parse(rstlist, current_node)
172
node_list.append(current_node)
175
# This is from kerneldoc.py -- it works around an API change in
176
# Sphinx between 1.6 and 1.7. Unlike kerneldoc.py, we use
177
# sphinx.util.nodes.nested_parse_with_titles() rather than the
178
# plain self.state.nested_parse(), and so we can drop the saving
179
# of title_styles and section_level that kerneldoc.py does,
180
# because nested_parse_with_titles() does that for us.
181
def do_parse(self, result, node):
182
with switch_source_input(self.state, result):
183
nested_parse_with_titles(self.state, result, node)
187
""" Register hxtool-doc directive with Sphinx"""
188
app.add_config_value('hxtool_srctree', None, 'env')
189
app.add_directive('hxtool-doc', HxtoolDocDirective)
192
version = __version__,
193
parallel_read_safe = True,
194
parallel_write_safe = True