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('\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 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
== 'SRST':
112 if state
== HxState
.RST
:
113 serror(hxfile
, lnum
, 'expected ERST, found SRST')
116 elif directive
== 'ERST':
117 if state
== HxState
.CTEXT
:
118 serror(hxfile
, lnum
, 'expected SRST, found ERST')
120 state
= HxState
.CTEXT
121 elif directive
== 'DEFHEADING' or directive
== 'ARCHHEADING':
122 if directive
== 'DEFHEADING':
123 heading
= parse_defheading(hxfile
, lnum
, line
)
125 heading
= parse_archheading(hxfile
, lnum
, line
)
128 # Put the accumulated rST into the previous node,
129 # and then start a fresh section with this heading.
131 if current_node
is None:
132 # We had some rST fragments before the first
133 # DEFHEADING. We don't have a section to put
134 # these in, so rather than magicing up a section,
135 # make it a syntax error.
137 'first DEFHEADING must precede all rST text')
138 self
.do_parse(rstlist
, current_node
)
140 if current_node
is not None:
141 node_list
.append(current_node
)
142 section_id
= 'hxtool-%d' % env
.new_serialno('hxtool')
143 current_node
= nodes
.section(ids
=[section_id
])
144 current_node
+= nodes
.title(heading
, heading
)
146 # Not a directive: put in output if we are in rST fragment
147 if state
== HxState
.RST
:
148 # Sphinx counts its lines from 0
149 rstlist
.append(line
, hxfile
, lnum
- 1)
151 if current_node
is None:
152 # We don't have multiple sections, so just parse the rst
153 # fragments into a dummy node so we can return the children.
154 current_node
= nodes
.section()
155 self
.do_parse(rstlist
, current_node
)
156 return current_node
.children
158 # Put the remaining accumulated rST into the last section, and
159 # return all the sections.
161 self
.do_parse(rstlist
, current_node
)
162 node_list
.append(current_node
)
165 # This is from kerneldoc.py -- it works around an API change in
166 # Sphinx between 1.6 and 1.7. Unlike kerneldoc.py, we use
167 # sphinx.util.nodes.nested_parse_with_titles() rather than the
168 # plain self.state.nested_parse(), and so we can drop the saving
169 # of title_styles and section_level that kerneldoc.py does,
170 # because nested_parse_with_titles() does that for us.
171 def do_parse(self
, result
, node
):
173 with
switch_source_input(self
.state
, result
):
174 nested_parse_with_titles(self
.state
, result
, node
)
176 save
= self
.state
.memo
.reporter
177 self
.state
.memo
.reporter
= AutodocReporter(result
, self
.state
.memo
.reporter
)
179 nested_parse_with_titles(self
.state
, result
, node
)
181 self
.state
.memo
.reporter
= save
184 """ Register hxtool-doc directive with Sphinx"""
185 app
.add_config_value('hxtool_srctree', None, 'env')
186 app
.add_directive('hxtool-doc', HxtoolDocDirective
)
189 version
= __version__
,
190 parallel_read_safe
= True,
191 parallel_write_safe
= True