docs/sphinx: remove special parsing for freeform sections

Remove the QAPI doc section heading syntax, use plain rST section
headings instead.

Tests and documentation are updated to match.

Interestingly, Plain rST headings work fine before this patch, except
for over- and underlining with '=', which the doc parser rejected as
invalid QAPI doc section heading in free-form comments.

Signed-off-by: John Snow <jsnow@redhat.com>
Message-ID: <20250618165353.1980365-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Add more detail to commit message]
Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
John Snow 2025-06-18 12:53:52 -04:00 committed by Markus Armbruster
parent 8d789c8cdb
commit 6c10778826
48 changed files with 173 additions and 106 deletions

View File

@ -876,25 +876,35 @@ structuring content.
Headings and subheadings Headings and subheadings
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~
A free-form documentation comment containing a line which starts with Free-form documentation does not start with ``@SYMBOL`` and can contain
some ``=`` symbols and then a space defines a section heading:: arbitrary rST markup. Headings can be marked up using the standard rST
syntax::
## ##
# = This is a top level heading # *************************
# This is a level 2 heading
# *************************
# #
# This is a free-form comment which will go under the # This is a free-form comment which will go under the
# top level heading. # top level heading.
## ##
## ##
# == This is a second level heading # This is a third level heading
# ==============================
#
# Level 4
# _______
#
# Level 5
# ^^^^^^^
#
# Level 6
# """""""
## ##
A heading line must be the first line of the documentation Level 1 headings are reserved for use by the generated documentation
comment block. page itself, leaving level 2 as the highest level that should be used.
Section headings must always be correctly nested, so you can only
define a third-level heading inside a second-level heading, and so on.
Documentation markup Documentation markup

View File

@ -11,7 +11,9 @@
# later. See the COPYING file in the top-level directory. # later. See the COPYING file in the top-level directory.
## ##
# = Firmware # ********
# Firmware
# ********
## ##
{ 'pragma': { { 'pragma': {

View File

@ -10,7 +10,9 @@
# later. See the COPYING file in the top-level directory. # later. See the COPYING file in the top-level directory.
## ##
# = vhost user backend discovery & capabilities # *******************************************
# vhost user backend discovery & capabilities
# *******************************************
## ##
## ##

View File

@ -399,44 +399,9 @@ def visit_module(self, path: str) -> None:
self.ensure_blank_line() self.ensure_blank_line()
def visit_freeform(self, doc: QAPIDoc) -> None: def visit_freeform(self, doc: QAPIDoc) -> None:
# TODO: Once the old qapidoc transformer is deprecated, freeform
# sections can be updated to pure rST, and this transformed removed.
#
# For now, translate our micro-format into rST. Code adapted
# from Peter Maydell's freeform().
assert len(doc.all_sections) == 1, doc.all_sections assert len(doc.all_sections) == 1, doc.all_sections
body = doc.all_sections[0] body = doc.all_sections[0]
text = self.reformat_arobase(body.text) self.add_lines(self.reformat_arobase(body.text), doc.info)
info = doc.info
if re.match(r"=+ ", text):
# Section/subsection heading (if present, will always be the
# first line of the block)
(heading, _, text) = text.partition("\n")
(leader, _, heading) = heading.partition(" ")
# Implicit +1 for heading in the containing .rst doc
level = len(leader) + 1
# https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#sections
markers = ' #*=_^"'
overline = level <= 2
marker = markers[level]
self.ensure_blank_line()
# This credits all 2 or 3 lines to the single source line.
if overline:
self.add_line(marker * len(heading), info)
self.add_line(heading, info)
self.add_line(marker * len(heading), info)
self.ensure_blank_line()
# Eat blank line(s) and advance info
trimmed = text.lstrip("\n")
text = trimmed
info = info.next_line(len(text) - len(trimmed) + 1)
self.add_lines(text, info)
self.ensure_blank_line() self.ensure_blank_line()
def visit_entity(self, ent: QAPISchemaDefinition) -> None: def visit_entity(self, ent: QAPISchemaDefinition) -> None:

View File

@ -6,7 +6,9 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
## ##
# = ACPI # ****
# ACPI
# ****
## ##
## ##

View File

@ -7,7 +7,9 @@
# See the COPYING file in the top-level directory. # See the COPYING file in the top-level directory.
## ##
# = Audio # *****
# Audio
# *****
## ##
## ##

View File

@ -2,7 +2,9 @@
# vim: filetype=python # vim: filetype=python
## ##
# = User authorization # ******************
# User authorization
# ******************
## ##
## ##

View File

@ -2,7 +2,8 @@
# vim: filetype=python # vim: filetype=python
## ##
# == Block core (VM unrelated) # Block core (VM unrelated)
# =========================
## ##
{ 'include': 'common.json' } { 'include': 'common.json' }

View File

@ -2,7 +2,8 @@
# vim: filetype=python # vim: filetype=python
## ##
# == Block device exports # Block device exports
# ====================
## ##
{ 'include': 'sockets.json' } { 'include': 'sockets.json' }

View File

@ -2,13 +2,16 @@
# vim: filetype=python # vim: filetype=python
## ##
# = Block devices # *************
# Block devices
# *************
## ##
{ 'include': 'block-core.json' } { 'include': 'block-core.json' }
## ##
# == Additional block stuff (VM related) # Additional block stuff (VM related)
# ===================================
## ##
## ##

View File

@ -3,7 +3,9 @@
# #
## ##
# = Character devices # *****************
# Character devices
# *****************
## ##
{ 'include': 'sockets.json' } { 'include': 'sockets.json' }

View File

@ -2,7 +2,9 @@
# vim: filetype=python # vim: filetype=python
## ##
# = Common data types # *****************
# Common data types
# *****************
## ##
## ##

View File

@ -2,7 +2,9 @@
# vim: filetype=python # vim: filetype=python
## ##
# = Compatibility policy # ********************
# Compatibility policy
# ********************
## ##
## ##

View File

@ -3,7 +3,9 @@
# #
## ##
# = QMP monitor control # *******************
# QMP monitor control
# *******************
## ##
## ##

View File

@ -3,7 +3,9 @@
# #
## ##
# = Cryptography # ************
# Cryptography
# ************
## ##
## ##

View File

@ -5,7 +5,9 @@
# See the COPYING file in the top-level directory. # See the COPYING file in the top-level directory.
## ##
# = Cryptography devices # ********************
# Cryptography devices
# ********************
## ##
## ##

View File

@ -2,7 +2,9 @@
# vim: filetype=python # vim: filetype=python
## ##
# = CXL devices # ***********
# CXL devices
# ***********
## ##
## ##

View File

@ -5,7 +5,9 @@
# See the COPYING file in the top-level directory. # See the COPYING file in the top-level directory.
## ##
# = Dump guest memory # *****************
# Dump guest memory
# *****************
## ##
## ##

View File

@ -5,7 +5,9 @@
# See the COPYING file in the top-level directory. # See the COPYING file in the top-level directory.
## ##
# = eBPF Objects # ************
# eBPF Objects
# ************
# #
# eBPF object is an ELF binary that contains the eBPF program and eBPF # eBPF object is an ELF binary that contains the eBPF program and eBPF
# map description(BTF). Overall, eBPF object should contain the # map description(BTF). Overall, eBPF object should contain the

View File

@ -2,7 +2,9 @@
# vim: filetype=python # vim: filetype=python
## ##
# = QMP errors # **********
# QMP errors
# **********
## ##
## ##

View File

@ -10,7 +10,9 @@
# See the COPYING file in the top-level directory. # See the COPYING file in the top-level directory.
## ##
# = QMP introspection # *****************
# QMP introspection
# *****************
## ##
## ##

View File

@ -2,7 +2,9 @@
# vim: filetype=python # vim: filetype=python
## ##
# = Background jobs # ***************
# Background jobs
# ***************
## ##
## ##

View File

@ -5,7 +5,9 @@
# See the COPYING file in the top-level directory. # See the COPYING file in the top-level directory.
## ##
# = Common machine types # ********************
# Common machine types
# ********************
## ##
## ##

View File

@ -5,7 +5,9 @@
# See the COPYING file in the top-level directory. # See the COPYING file in the top-level directory.
## ##
# = Machines # ********
# Machines
# ********
## ##
{ 'include': 'common.json' } { 'include': 'common.json' }

View File

@ -3,7 +3,9 @@
# #
## ##
# = Migration # *********
# Migration
# *********
## ##
{ 'include': 'common.json' } { 'include': 'common.json' }

View File

@ -3,7 +3,9 @@
# #
## ##
# = Miscellanea # ***********
# Miscellanea
# ***********
## ##
{ 'include': 'common.json' } { 'include': 'common.json' }

View File

@ -3,7 +3,9 @@
# #
## ##
# = Net devices # ***********
# Net devices
# ***********
## ##
{ 'include': 'sockets.json' } { 'include': 'sockets.json' }

View File

@ -6,7 +6,9 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
## ##
# = PCI # ***
# PCI
# ***
## ##
## ##

View File

@ -1,7 +1,9 @@
# -*- Mode: Python -*- # -*- Mode: Python -*-
# vim: filetype=python # vim: filetype=python
## ##
# = Introduction # ************
# Introduction
# ************
# #
# This manual describes the commands and events supported by the QEMU # This manual describes the commands and events supported by the QEMU
# Monitor Protocol (QMP). # Monitor Protocol (QMP).

View File

@ -5,7 +5,9 @@
# See the COPYING file in the top-level directory. # See the COPYING file in the top-level directory.
## ##
# = Device infrastructure (qdev) # ****************************
# Device infrastructure (qdev)
# ****************************
## ##
{ 'include': 'qom.json' } { 'include': 'qom.json' }

View File

@ -10,7 +10,9 @@
{ 'include': 'crypto.json' } { 'include': 'crypto.json' }
## ##
# = QEMU Object Model (QOM) # ***********************
# QEMU Object Model (QOM)
# ***********************
## ##
## ##

View File

@ -3,7 +3,9 @@
# #
## ##
# = Record/replay # *************
# Record/replay
# *************
## ##
{ 'include': 'common.json' } { 'include': 'common.json' }

View File

@ -2,7 +2,9 @@
# vim: filetype=python # vim: filetype=python
## ##
# = Rocker switch device # ********************
# Rocker switch device
# ********************
## ##
## ##

View File

@ -3,7 +3,9 @@
# #
## ##
# = VM run state # ************
# VM run state
# ************
## ##
## ##

View File

@ -2,7 +2,9 @@
# vim: filetype=python # vim: filetype=python
## ##
# = Socket data types # *****************
# Socket data types
# *****************
## ##
## ##

View File

@ -9,7 +9,9 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
## ##
# = Statistics # **********
# Statistics
# **********
## ##
## ##

View File

@ -3,7 +3,9 @@
# #
## ##
# = TPM (trusted platform module) devices # *************************************
# TPM (trusted platform module) devices
# *************************************
## ##
## ##

View File

@ -7,7 +7,9 @@
# See the COPYING file in the top-level directory. # See the COPYING file in the top-level directory.
## ##
# = Tracing # *******
# Tracing
# *******
## ##
## ##

View File

@ -3,7 +3,9 @@
# #
## ##
# = Transactions # ************
# Transactions
# ************
## ##
{ 'include': 'block-core.json' } { 'include': 'block-core.json' }

View File

@ -3,7 +3,9 @@
# #
## ##
# = UEFI Variable Store # *******************
# UEFI Variable Store
# *******************
# #
# The QEMU efi variable store implementation (hw/uefi/) uses this to # The QEMU efi variable store implementation (hw/uefi/) uses this to
# store non-volatile variables in json format on disk. # store non-volatile variables in json format on disk.

View File

@ -3,7 +3,9 @@
# #
## ##
# = Remote desktop # **************
# Remote desktop
# **************
## ##
{ 'include': 'common.json' } { 'include': 'common.json' }
@ -200,7 +202,8 @@
'if': 'CONFIG_PIXMAN' } 'if': 'CONFIG_PIXMAN' }
## ##
# == Spice # Spice
# =====
## ##
## ##
@ -461,7 +464,8 @@
'if': 'CONFIG_SPICE' } 'if': 'CONFIG_SPICE' }
## ##
# == VNC # VNC
# ===
## ##
## ##
@ -794,7 +798,9 @@
'if': 'CONFIG_VNC' } 'if': 'CONFIG_VNC' }
## ##
# = Input # *****
# Input
# *****
## ##
## ##

View File

@ -3,7 +3,9 @@
# #
## ##
# = VFIO devices # ************
# VFIO devices
# ************
## ##
## ##

View File

@ -3,7 +3,9 @@
# #
## ##
# = Virtio devices # **************
# Virtio devices
# **************
## ##
## ##

View File

@ -3,7 +3,9 @@
# #
## ##
# = Yank feature # ************
# Yank feature
# ************
## ##
## ##

View File

@ -597,22 +597,15 @@ def get_doc(self) -> 'QAPIDoc':
# Free-form documentation # Free-form documentation
doc = QAPIDoc(info) doc = QAPIDoc(info)
doc.ensure_untagged_section(self.info) doc.ensure_untagged_section(self.info)
first = True
while line is not None: while line is not None:
if match := self._match_at_name_colon(line): if match := self._match_at_name_colon(line):
raise QAPIParseError( raise QAPIParseError(
self, self,
"'@%s:' not allowed in free-form documentation" "'@%s:' not allowed in free-form documentation"
% match.group(1)) % match.group(1))
if line.startswith('='):
if not first:
raise QAPIParseError(
self,
"'=' heading must come first in a comment block")
doc.append_line(line) doc.append_line(line)
self.accept(False) self.accept(False)
line = self.get_doc_line() line = self.get_doc_line()
first = False
self.accept() self.accept()
doc.end() doc.end()

View File

@ -14,7 +14,9 @@
# storage daemon. # storage daemon.
## ##
# = Introduction # ************
# Introduction
# ************
# #
# This manual describes the commands and events supported by the QEMU # This manual describes the commands and events supported by the QEMU
# storage daemon QMP. # storage daemon QMP.
@ -51,7 +53,9 @@
{ 'include': '../../qapi/job.json' } { 'include': '../../qapi/job.json' }
## ##
# = Block devices # *************
# Block devices
# *************
## ##
{ 'include': '../../qapi/block-core.json' } { 'include': '../../qapi/block-core.json' }
{ 'include': '../../qapi/block-export.json' } { 'include': '../../qapi/block-export.json' }

View File

@ -8,7 +8,9 @@
'documentation-exceptions': [ 'Enum', 'Variant1', 'Alternate', 'cmd' ] } } 'documentation-exceptions': [ 'Enum', 'Variant1', 'Alternate', 'cmd' ] } }
## ##
# = Section # *******
# Section
# *******
## ##
## ##
@ -16,7 +18,8 @@
## ##
## ##
# == Subsection # Subsection
# ==========
# #
# *with emphasis* # *with emphasis*
# @var {in braces} # @var {in braces}
@ -144,7 +147,8 @@
'if': { 'not': { 'any': [ 'IFONE', 'IFTWO' ] } } } 'if': { 'not': { 'any': [ 'IFONE', 'IFTWO' ] } } }
## ##
# == Another subsection # Another subsection
# ==================
## ##
## ##

View File

@ -55,13 +55,16 @@ event EVT_BOXED Object
feature feat3 feature feat3
doc freeform doc freeform
body= body=
= Section *******
Section
*******
doc freeform doc freeform
body= body=
Just text, no heading. Just text, no heading.
doc freeform doc freeform
body= body=
== Subsection Subsection
==========
*with emphasis* *with emphasis*
@var {in braces} @var {in braces}
@ -155,7 +158,8 @@ description starts on the same line
a feature a feature
doc freeform doc freeform
body= body=
== Another subsection Another subsection
==================
doc symbol=cmd doc symbol=cmd
body= body=