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 three
41 # states: reading the C code fragment, inside a texi fragment,
42 # or inside a rST fragment.
48 def serror(file, lnum
, errtext
):
49 """Raise an exception giving a user-friendly syntax error message"""
50 raise ExtensionError('%s line %d: syntax error: %s' % (file, lnum
, errtext
))
52 def parse_directive(line
):
53 """Return first word of line, if any"""
54 return re
.split('\W', line
)[0]
56 def parse_defheading(file, lnum
, line
):
57 """Handle a DEFHEADING directive"""
58 # The input should be "DEFHEADING(some string)", though note that
59 # the 'some string' could be the empty string. If the string is
60 # empty we ignore the directive -- these are used only to add
61 # blank lines in the plain-text content of the --help output.
63 # Return the heading text
64 match
= re
.match(r
'DEFHEADING\((.*)\)', line
)
66 serror(file, lnum
, "Invalid DEFHEADING line")
69 def parse_archheading(file, lnum
, line
):
70 """Handle an ARCHHEADING directive"""
71 # The input should be "ARCHHEADING(some string, other arg)",
72 # though note that the 'some string' could be the empty string.
73 # As with DEFHEADING, empty string ARCHHEADINGs will be ignored.
75 # Return the heading text
76 match
= re
.match(r
'ARCHHEADING\((.*),.*\)', line
)
78 serror(file, lnum
, "Invalid ARCHHEADING line")
81 class HxtoolDocDirective(Directive
):
82 """Extract rST fragments from the specified .hx file"""
84 optional_arguments
= 1
86 'hxfile': directives
.unchanged_required
91 env
= self
.state
.document
.settings
.env
92 hxfile
= env
.config
.hxtool_srctree
+ '/' + self
.arguments
[0]
94 # Tell sphinx of the dependency
95 env
.note_dependency(os
.path
.abspath(hxfile
))
98 # We build up lines of rST in this ViewList, which we will
99 # later put into a 'section' node.
104 with
open(hxfile
) as f
:
105 lines
= (l
.rstrip() for l
in f
)
106 for lnum
, line
in enumerate(lines
, 1):
107 directive
= parse_directive(line
)
109 if directive
== 'HXCOMM':
111 elif directive
== 'STEXI':
112 if state
== HxState
.RST
:
113 serror(hxfile
, lnum
, 'expected ERST, found STEXI')
114 elif state
== HxState
.TEXI
:
115 serror(hxfile
, lnum
, 'expected ETEXI, found STEXI')
118 elif directive
== 'ETEXI':
119 if state
== HxState
.RST
:
120 serror(hxfile
, lnum
, 'expected ERST, found ETEXI')
121 elif state
== HxState
.CTEXT
:
122 serror(hxfile
, lnum
, 'expected STEXI, found ETEXI')
124 state
= HxState
.CTEXT
125 elif directive
== 'SRST':
126 if state
== HxState
.RST
:
127 serror(hxfile
, lnum
, 'expected ERST, found SRST')
128 elif state
== HxState
.TEXI
:
129 serror(hxfile
, lnum
, 'expected ETEXI, found SRST')
132 elif directive
== 'ERST':
133 if state
== HxState
.TEXI
:
134 serror(hxfile
, lnum
, 'expected ETEXI, found ERST')
135 elif state
== HxState
.CTEXT
:
136 serror(hxfile
, lnum
, 'expected SRST, found ERST')
138 state
= HxState
.CTEXT
139 elif directive
== 'DEFHEADING' or directive
== 'ARCHHEADING':
140 if directive
== 'DEFHEADING':
141 heading
= parse_defheading(hxfile
, lnum
, line
)
143 heading
= parse_archheading(hxfile
, lnum
, line
)
146 # Put the accumulated rST into the previous node,
147 # and then start a fresh section with this heading.
149 if current_node
is None:
150 # We had some rST fragments before the first
151 # DEFHEADING. We don't have a section to put
152 # these in, so rather than magicing up a section,
153 # make it a syntax error.
155 'first DEFHEADING must precede all rST text')
156 self
.do_parse(rstlist
, current_node
)
158 if current_node
is not None:
159 node_list
.append(current_node
)
160 section_id
= 'hxtool-%d' % env
.new_serialno('hxtool')
161 current_node
= nodes
.section(ids
=[section_id
])
162 current_node
+= nodes
.title(heading
, heading
)
164 # Not a directive: put in output if we are in rST fragment
165 if state
== HxState
.RST
:
166 # Sphinx counts its lines from 0
167 rstlist
.append(line
, hxfile
, lnum
- 1)
169 if current_node
is None:
170 # We don't have multiple sections, so just parse the rst
171 # fragments into a dummy node so we can return the children.
172 current_node
= nodes
.section()
173 self
.do_parse(rstlist
, current_node
)
174 return current_node
.children
176 # Put the remaining accumulated rST into the last section, and
177 # return all the sections.
179 self
.do_parse(rstlist
, current_node
)
180 node_list
.append(current_node
)
183 # This is from kerneldoc.py -- it works around an API change in
184 # Sphinx between 1.6 and 1.7. Unlike kerneldoc.py, we use
185 # sphinx.util.nodes.nested_parse_with_titles() rather than the
186 # plain self.state.nested_parse(), and so we can drop the saving
187 # of title_styles and section_level that kerneldoc.py does,
188 # because nested_parse_with_titles() does that for us.
189 def do_parse(self
, result
, node
):
191 with
switch_source_input(self
.state
, result
):
192 nested_parse_with_titles(self
.state
, result
, node
)
194 save
= self
.state
.memo
.reporter
195 self
.state
.memo
.reporter
= AutodocReporter(result
, self
.state
.memo
.reporter
)
197 nested_parse_with_titles(self
.state
, result
, node
)
199 self
.state
.memo
.reporter
= save
202 """ Register hxtool-doc directive with Sphinx"""
203 app
.add_config_value('hxtool_srctree', None, 'env')
204 app
.add_directive('hxtool-doc', HxtoolDocDirective
)
207 version
= __version__
,
208 parallel_read_safe
= True,
209 parallel_write_safe
= True