From: "Alex Bennée" <alex.bennee@linaro.org>
To: Peter Maydell <peter.maydell@linaro.org>
Cc: Kevin Wolf <kwolf@redhat.com>,
qemu-block@nongnu.org, qemu-devel@nongnu.org,
Richard Henderson <richard.henderson@linaro.org>,
Markus Armbruster <armbru@redhat.com>,
Max Reitz <mreitz@redhat.com>, Greg Kurz <groug@kaod.org>,
Stefan Hajnoczi <stefanha@redhat.com>,
John Snow <jsnow@redhat.com>
Subject: Re: [PATCH v2 3/8] docs/sphinx: Add new hxtool Sphinx extension
Date: Fri, 24 Jan 2020 18:24:37 +0000 [thread overview]
Message-ID: <875zh0af16.fsf@linaro.org> (raw)
In-Reply-To: <20200124162606.8787-4-peter.maydell@linaro.org>
Peter Maydell <peter.maydell@linaro.org> writes:
> Some of our documentation includes sections which are created
> by assembling fragments of texinfo from a .hx source file into
> a .texi file, which is then included from qemu-doc.texi or
> qemu-img.texi.
>
> For Sphinx, rather than creating a file to include, the most natural
> way to handle this is to have a small custom Sphinx extension which
> reads the .hx file and process it. So instead of:
> * makefile produces foo.texi from foo.hx
> * qemu-doc.texi says '@include foo.texi'
> we have:
> * qemu-doc.rst says 'hxtool-doc:: foo.hx'
> * the Sphinx extension for hxtool has code that runs to handle that
> Sphinx directive which reads the .hx file and emits the appropriate
> documentation contents
>
> This is pretty much the same way the kerneldoc extension works right
> now. It also has the advantage that it should work for third-party
> services like readthedocs that expect to build the docs directly with
> sphinx rather than by invoking our makefiles.
>
> In this commit we implement the hxtool extension.
>
> Note that syntax errors in the rST fragments will be correctly
> reported to the user with the filename and line number within the
> hx file.
>
> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Tested-by: Alex Bennée <alex.bennee@linaro.org>
> ---
> docs/conf.py | 3 +-
> docs/sphinx/hxtool.py | 210 ++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 212 insertions(+), 1 deletion(-)
> create mode 100644 docs/sphinx/hxtool.py
>
> diff --git a/docs/conf.py b/docs/conf.py
> index 259c6049da7..ee7faa6b4e7 100644
> --- a/docs/conf.py
> +++ b/docs/conf.py
> @@ -54,7 +54,7 @@ needs_sphinx = '1.3'
> # Add any Sphinx extension module names here, as strings. They can be
> # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
> # ones.
> -extensions = ['kerneldoc', 'qmp_lexer']
> +extensions = ['kerneldoc', 'qmp_lexer', 'hxtool']
>
> # Add any paths that contain templates here, relative to this directory.
> templates_path = ['_templates']
> @@ -221,3 +221,4 @@ texinfo_documents = [
> # find everything.
> kerneldoc_bin = os.path.join(qemu_docdir, '../scripts/kernel-doc')
> kerneldoc_srctree = os.path.join(qemu_docdir, '..')
> +hxtool_srctree = os.path.join(qemu_docdir, '..')
> diff --git a/docs/sphinx/hxtool.py b/docs/sphinx/hxtool.py
> new file mode 100644
> index 00000000000..5d6736f3002
> --- /dev/null
> +++ b/docs/sphinx/hxtool.py
> @@ -0,0 +1,210 @@
> +# 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
> + )
--
Alex Bennée
next prev parent reply other threads:[~2020-01-24 18:25 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-01-24 16:25 [PATCH v2 0/8] qemu-img, qemu-trace-stap, virtfs-proxy-helper: convert to rST Peter Maydell
2020-01-24 16:25 ` [PATCH v2 1/8] Makefile: Ensure we don't run Sphinx in parallel for manpages Peter Maydell
2020-01-31 15:20 ` Alex Bennée
2020-01-24 16:26 ` [PATCH v2 2/8] hxtool: Support SRST/ERST directives Peter Maydell
2020-01-24 18:10 ` Alex Bennée
2020-01-27 8:23 ` Philippe Mathieu-Daudé
2020-01-24 16:26 ` [PATCH v2 3/8] docs/sphinx: Add new hxtool Sphinx extension Peter Maydell
2020-01-24 18:24 ` Alex Bennée [this message]
2020-01-24 16:26 ` [PATCH v2 4/8] qemu-img-cmds.hx: Add rST documentation fragments Peter Maydell
2020-01-24 16:26 ` [PATCH v2 5/8] qemu-img: Convert invocation documentation to rST Peter Maydell
2020-01-31 15:14 ` Alex Bennée
2020-01-24 16:26 ` [PATCH v2 6/8] qemu-img-cmds.hx: Remove texinfo document fragments Peter Maydell
2020-01-31 15:14 ` Alex Bennée
2020-01-24 16:26 ` [PATCH v2 7/8] scripts/qemu-trace-stap: Convert documentation to rST Peter Maydell
2020-01-31 15:15 ` Alex Bennée
2020-01-24 16:26 ` [PATCH v2 8/8] virtfs-proxy-helper: " Peter Maydell
2020-01-24 16:47 ` Greg Kurz
2020-01-31 11:44 ` [PATCH v2 0/8] qemu-img, qemu-trace-stap, virtfs-proxy-helper: convert " Peter Maydell
2020-02-03 11:01 ` Peter Maydell
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=875zh0af16.fsf@linaro.org \
--to=alex.bennee@linaro.org \
--cc=armbru@redhat.com \
--cc=groug@kaod.org \
--cc=jsnow@redhat.com \
--cc=kwolf@redhat.com \
--cc=mreitz@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=richard.henderson@linaro.org \
--cc=stefanha@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).