123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- # 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 three
- # states: reading the C code fragment, inside a texi fragment,
- # or inside a rST fragment.
- class HxState(Enum):
- CTEXT = 1
- TEXI = 2
- RST = 3
- 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
- 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
- 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 == 'STEXI':
- if state == HxState.RST:
- serror(hxfile, lnum, 'expected ERST, found STEXI')
- elif state == HxState.TEXI:
- serror(hxfile, lnum, 'expected ETEXI, found STEXI')
- else:
- state = HxState.TEXI
- elif directive == 'ETEXI':
- if state == HxState.RST:
- serror(hxfile, lnum, 'expected ERST, found ETEXI')
- elif state == HxState.CTEXT:
- serror(hxfile, lnum, 'expected STEXI, found ETEXI')
- else:
- state = HxState.CTEXT
- elif directive == 'SRST':
- if state == HxState.RST:
- serror(hxfile, lnum, 'expected ERST, found SRST')
- elif state == HxState.TEXI:
- serror(hxfile, lnum, 'expected ETEXI, found SRST')
- else:
- state = HxState.RST
- elif directive == 'ERST':
- if state == HxState.TEXI:
- serror(hxfile, lnum, 'expected ETEXI, found ERST')
- elif 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
- )
|