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
.nodes
import nested_parse_with_titles
30 # Sphinx up to 1.6 uses AutodocReporter; 1.7 and later
31 # use switch_source_input. Check borrowed from kerneldoc.py.
32 Use_SSI
= sphinx
.__version
__[:3] >= '1.7'
34 from sphinx
.util
.docutils
import switch_source_input
36 from sphinx
.ext
.autodoc
import AutodocReporter
40 # We parse hx files with a state machine which may be in one of two
41 # states: reading the C code fragment, or inside a rST fragment.
46 def serror(file, lnum
, errtext
):
47 """Raise an exception giving a user-friendly syntax error message"""
48 raise ExtensionError('%s line %d: syntax error: %s' % (file, lnum
, errtext
))
50 def parse_directive(line
):
51 """Return first word of line, if any"""
52 return re
.split(r
'\W', line
)[0]
54 def parse_defheading(file, lnum
, line
):
55 """Handle a DEFHEADING directive"""
56 # The input should be "DEFHEADING(some string)", though note that
57 # the 'some string' could be the empty string. If the string is
58 # empty we ignore the directive -- these are used only to add
59 # blank lines in the plain-text content of the --help output.
61 # Return the heading text. We strip out any trailing ':' for
62 # consistency with other headings in the rST documentation.
63 match
= re
.match(r
'DEFHEADING\((.*?):?\)', line
)
65 serror(file, lnum
, "Invalid DEFHEADING line")
68 def parse_archheading(file, lnum
, line
):
69 """Handle an ARCHHEADING directive"""
70 # The input should be "ARCHHEADING(some string, other arg)",
71 # though note that the 'some string' could be the empty string.
72 # As with DEFHEADING, empty string ARCHHEADINGs will be ignored.
74 # Return the heading text. We strip out any trailing ':' for
75 # consistency with other headings in the rST documentation.
76 match
= re
.match(r
'ARCHHEADING\((.*?):?,.*\)', line
)
78 serror(file, lnum
, "Invalid ARCHHEADING line")
81 def parse_srst(file, lnum
, line
):
82 """Handle an SRST directive"""
83 # The input should be either "SRST", or "SRST(label)".
84 match
= re
.match(r
'SRST(\((.*?)\))?', line
)
86 serror(file, lnum
, "Invalid SRST line")
89 class HxtoolDocDirective(Directive
):
90 """Extract rST fragments from the specified .hx file"""
92 optional_arguments
= 1
94 'hxfile': directives
.unchanged_required
99 env
= self
.state
.document
.settings
.env
100 hxfile
= env
.config
.hxtool_srctree
+ '/' + self
.arguments
[0]
102 # Tell sphinx of the dependency
103 env
.note_dependency(os
.path
.abspath(hxfile
))
105 state
= HxState
.CTEXT
106 # We build up lines of rST in this ViewList, which we will
107 # later put into a 'section' node.
112 with
open(hxfile
) as f
:
113 lines
= (l
.rstrip() for l
in f
)
114 for lnum
, line
in enumerate(lines
, 1):
115 directive
= parse_directive(line
)
117 if directive
== 'HXCOMM':
119 elif directive
== 'SRST':
120 if state
== HxState
.RST
:
121 serror(hxfile
, lnum
, 'expected ERST, found SRST')
124 label
= parse_srst(hxfile
, lnum
, line
)
126 rstlist
.append("", hxfile
, lnum
- 1)
127 # Build label as _DOCNAME-HXNAME-LABEL
128 hx
= os
.path
.splitext(os
.path
.basename(hxfile
))[0]
129 refline
= ".. _" + env
.docname
+ "-" + hx
+ \
131 rstlist
.append(refline
, hxfile
, lnum
- 1)
132 elif directive
== 'ERST':
133 if state
== HxState
.CTEXT
:
134 serror(hxfile
, lnum
, 'expected SRST, found ERST')
136 state
= HxState
.CTEXT
137 elif directive
== 'DEFHEADING' or directive
== 'ARCHHEADING':
138 if directive
== 'DEFHEADING':
139 heading
= parse_defheading(hxfile
, lnum
, line
)
141 heading
= parse_archheading(hxfile
, lnum
, line
)
144 # Put the accumulated rST into the previous node,
145 # and then start a fresh section with this heading.
147 if current_node
is None:
148 # We had some rST fragments before the first
149 # DEFHEADING. We don't have a section to put
150 # these in, so rather than magicing up a section,
151 # make it a syntax error.
153 'first DEFHEADING must precede all rST text')
154 self
.do_parse(rstlist
, current_node
)
156 if current_node
is not None:
157 node_list
.append(current_node
)
158 section_id
= 'hxtool-%d' % env
.new_serialno('hxtool')
159 current_node
= nodes
.section(ids
=[section_id
])
160 current_node
+= nodes
.title(heading
, heading
)
162 # Not a directive: put in output if we are in rST fragment
163 if state
== HxState
.RST
:
164 # Sphinx counts its lines from 0
165 rstlist
.append(line
, hxfile
, lnum
- 1)
167 if current_node
is None:
168 # We don't have multiple sections, so just parse the rst
169 # fragments into a dummy node so we can return the children.
170 current_node
= nodes
.section()
171 self
.do_parse(rstlist
, current_node
)
172 return current_node
.children
174 # Put the remaining accumulated rST into the last section, and
175 # return all the sections.
177 self
.do_parse(rstlist
, current_node
)
178 node_list
.append(current_node
)
181 # This is from kerneldoc.py -- it works around an API change in
182 # Sphinx between 1.6 and 1.7. Unlike kerneldoc.py, we use
183 # sphinx.util.nodes.nested_parse_with_titles() rather than the
184 # plain self.state.nested_parse(), and so we can drop the saving
185 # of title_styles and section_level that kerneldoc.py does,
186 # because nested_parse_with_titles() does that for us.
187 def do_parse(self
, result
, node
):
189 with
switch_source_input(self
.state
, result
):
190 nested_parse_with_titles(self
.state
, result
, node
)
192 save
= self
.state
.memo
.reporter
193 self
.state
.memo
.reporter
= AutodocReporter(result
, self
.state
.memo
.reporter
)
195 nested_parse_with_titles(self
.state
, result
, node
)
197 self
.state
.memo
.reporter
= save
200 """ Register hxtool-doc directive with Sphinx"""
201 app
.add_config_value('hxtool_srctree', None, 'env')
202 app
.add_directive('hxtool-doc', HxtoolDocDirective
)
205 version
= __version__
,
206 parallel_read_safe
= True,
207 parallel_write_safe
= True