193 lines
7.7 KiB
Python
193 lines
7.7 KiB
Python
|
# coding=utf-8
|
||
|
#
|
||
|
# QEMU hxtool .hx file parsing extension
|
||
|
#
|
||
|
# Copyright (c) 2020 Linaro
|
||
|
#
|
||
|
# This work is licensed under the terms of the GNU GPLv2 or later.
|
||
|
# See the COPYING file in the top-level directory.
|
||
|
"""hxtool is a Sphinx extension that implements the hxtool-doc directive"""
|
||
|
|
||
|
# The purpose of this extension is to read fragments of rST
|
||
|
# from .hx files, and insert them all into the current document.
|
||
|
# The rST fragments are delimited by SRST/ERST lines.
|
||
|
# The conf.py file must set the hxtool_srctree config value to
|
||
|
# the root of the QEMU source tree.
|
||
|
# Each hxtool-doc:: directive takes one argument which is the
|
||
|
# path of the .hx file to process, relative to the source tree.
|
||
|
|
||
|
import os
|
||
|
import re
|
||
|
from enum import Enum
|
||
|
|
||
|
from docutils import nodes
|
||
|
from docutils.statemachine import ViewList
|
||
|
from docutils.parsers.rst import directives, Directive
|
||
|
from sphinx.errors import ExtensionError
|
||
|
from sphinx.util.nodes import nested_parse_with_titles
|
||
|
import sphinx
|
||
|
|
||
|
# Sphinx up to 1.6 uses AutodocReporter; 1.7 and later
|
||
|
# use switch_source_input. Check borrowed from kerneldoc.py.
|
||
|
Use_SSI = sphinx.__version__[:3] >= '1.7'
|
||
|
if Use_SSI:
|
||
|
from sphinx.util.docutils import switch_source_input
|
||
|
else:
|
||
|
from sphinx.ext.autodoc import AutodocReporter
|
||
|
|
||
|
__version__ = '1.0'
|
||
|
|
||
|
# We parse hx files with a state machine which may be in one of two
|
||
|
# states: reading the C code fragment, or inside a rST fragment.
|
||
|
class HxState(Enum):
|
||
|
CTEXT = 1
|
||
|
RST = 2
|
||
|
|
||
|
def serror(file, lnum, errtext):
|
||
|
"""Raise an exception giving a user-friendly syntax error message"""
|
||
|
raise ExtensionError('%s line %d: syntax error: %s' % (file, lnum, errtext))
|
||
|
|
||
|
def parse_directive(line):
|
||
|
"""Return first word of line, if any"""
|
||
|
return re.split('\W', line)[0]
|
||
|
|
||
|
def parse_defheading(file, lnum, line):
|
||
|
"""Handle a DEFHEADING directive"""
|
||
|
# The input should be "DEFHEADING(some string)", though note that
|
||
|
# the 'some string' could be the empty string. If the string is
|
||
|
# empty we ignore the directive -- these are used only to add
|
||
|
# blank lines in the plain-text content of the --help output.
|
||
|
#
|
||
|
# Return the heading text. We strip out any trailing ':' for
|
||
|
# consistency with other headings in the rST documentation.
|
||
|
match = re.match(r'DEFHEADING\((.*?):?\)', line)
|
||
|
if match is None:
|
||
|
serror(file, lnum, "Invalid DEFHEADING line")
|
||
|
return match.group(1)
|
||
|
|
||
|
def parse_archheading(file, lnum, line):
|
||
|
"""Handle an ARCHHEADING directive"""
|
||
|
# The input should be "ARCHHEADING(some string, other arg)",
|
||
|
# though note that the 'some string' could be the empty string.
|
||
|
# As with DEFHEADING, empty string ARCHHEADINGs will be ignored.
|
||
|
#
|
||
|
# Return the heading text. We strip out any trailing ':' for
|
||
|
# consistency with other headings in the rST documentation.
|
||
|
match = re.match(r'ARCHHEADING\((.*?):?,.*\)', line)
|
||
|
if match is None:
|
||
|
serror(file, lnum, "Invalid ARCHHEADING line")
|
||
|
return match.group(1)
|
||
|
|
||
|
class HxtoolDocDirective(Directive):
|
||
|
"""Extract rST fragments from the specified .hx file"""
|
||
|
required_argument = 1
|
||
|
optional_arguments = 1
|
||
|
option_spec = {
|
||
|
'hxfile': directives.unchanged_required
|
||
|
}
|
||
|
has_content = False
|
||
|
|
||
|
def run(self):
|
||
|
env = self.state.document.settings.env
|
||
|
hxfile = env.config.hxtool_srctree + '/' + self.arguments[0]
|
||
|
|
||
|
# Tell sphinx of the dependency
|
||
|
env.note_dependency(os.path.abspath(hxfile))
|
||
|
|
||
|
state = HxState.CTEXT
|
||
|
# We build up lines of rST in this ViewList, which we will
|
||
|
# later put into a 'section' node.
|
||
|
rstlist = ViewList()
|
||
|
current_node = None
|
||
|
node_list = []
|
||
|
|
||
|
with open(hxfile) as f:
|
||
|
lines = (l.rstrip() for l in f)
|
||
|
for lnum, line in enumerate(lines, 1):
|
||
|
directive = parse_directive(line)
|
||
|
|
||
|
if directive == 'HXCOMM':
|
||
|
pass
|
||
|
elif directive == 'SRST':
|
||
|
if state == HxState.RST:
|
||
|
serror(hxfile, lnum, 'expected ERST, found SRST')
|
||
|
else:
|
||
|
state = HxState.RST
|
||
|
elif directive == 'ERST':
|
||
|
if state == HxState.CTEXT:
|
||
|
serror(hxfile, lnum, 'expected SRST, found ERST')
|
||
|
else:
|
||
|
state = HxState.CTEXT
|
||
|
elif directive == 'DEFHEADING' or directive == 'ARCHHEADING':
|
||
|
if directive == 'DEFHEADING':
|
||
|
heading = parse_defheading(hxfile, lnum, line)
|
||
|
else:
|
||
|
heading = parse_archheading(hxfile, lnum, line)
|
||
|
if heading == "":
|
||
|
continue
|
||
|
# Put the accumulated rST into the previous node,
|
||
|
# and then start a fresh section with this heading.
|
||
|
if len(rstlist) > 0:
|
||
|
if current_node is None:
|
||
|
# We had some rST fragments before the first
|
||
|
# DEFHEADING. We don't have a section to put
|
||
|
# these in, so rather than magicing up a section,
|
||
|
# make it a syntax error.
|
||
|
serror(hxfile, lnum,
|
||
|
'first DEFHEADING must precede all rST text')
|
||
|
self.do_parse(rstlist, current_node)
|
||
|
rstlist = ViewList()
|
||
|
if current_node is not None:
|
||
|
node_list.append(current_node)
|
||
|
section_id = 'hxtool-%d' % env.new_serialno('hxtool')
|
||
|
current_node = nodes.section(ids=[section_id])
|
||
|
current_node += nodes.title(heading, heading)
|
||
|
else:
|
||
|
# Not a directive: put in output if we are in rST fragment
|
||
|
if state == HxState.RST:
|
||
|
# Sphinx counts its lines from 0
|
||
|
rstlist.append(line, hxfile, lnum - 1)
|
||
|
|
||
|
if current_node is None:
|
||
|
# We don't have multiple sections, so just parse the rst
|
||
|
# fragments into a dummy node so we can return the children.
|
||
|
current_node = nodes.section()
|
||
|
self.do_parse(rstlist, current_node)
|
||
|
return current_node.children
|
||
|
else:
|
||
|
# Put the remaining accumulated rST into the last section, and
|
||
|
# return all the sections.
|
||
|
if len(rstlist) > 0:
|
||
|
self.do_parse(rstlist, current_node)
|
||
|
node_list.append(current_node)
|
||
|
return node_list
|
||
|
|
||
|
# This is from kerneldoc.py -- it works around an API change in
|
||
|
# Sphinx between 1.6 and 1.7. Unlike kerneldoc.py, we use
|
||
|
# sphinx.util.nodes.nested_parse_with_titles() rather than the
|
||
|
# plain self.state.nested_parse(), and so we can drop the saving
|
||
|
# of title_styles and section_level that kerneldoc.py does,
|
||
|
# because nested_parse_with_titles() does that for us.
|
||
|
def do_parse(self, result, node):
|
||
|
if Use_SSI:
|
||
|
with switch_source_input(self.state, result):
|
||
|
nested_parse_with_titles(self.state, result, node)
|
||
|
else:
|
||
|
save = self.state.memo.reporter
|
||
|
self.state.memo.reporter = AutodocReporter(result, self.state.memo.reporter)
|
||
|
try:
|
||
|
nested_parse_with_titles(self.state, result, node)
|
||
|
finally:
|
||
|
self.state.memo.reporter = save
|
||
|
|
||
|
def setup(app):
|
||
|
""" Register hxtool-doc directive with Sphinx"""
|
||
|
app.add_config_value('hxtool_srctree', None, 'env')
|
||
|
app.add_directive('hxtool-doc', HxtoolDocDirective)
|
||
|
|
||
|
return dict(
|
||
|
version = __version__,
|
||
|
parallel_read_safe = True,
|
||
|
parallel_write_safe = True
|
||
|
)
|