[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH v5 13/17] qapi: add qapi2texi script
From: |
Marc-André Lureau |
Subject: |
[Qemu-devel] [PATCH v5 13/17] qapi: add qapi2texi script |
Date: |
Thu, 17 Nov 2016 19:55:00 +0400 |
As the name suggests, the qapi2texi script converts JSON QAPI
description into a texi file suitable for different target
formats (info/man/txt/pdf/html...).
It parses the following kind of blocks:
Free-form:
##
# = Section
# == Subsection
#
# Some text foo with *emphasis*
# 1. with a list
# 2. like that
#
# And some code:
# | $ echo foo
# | -> do this
# | <- get that
#
##
Symbol:
##
# @symbol:
#
# Symbol body ditto ergo sum. Foo bar
# baz ding.
#
# @arg: foo
# @arg: #optional foo
#
# Returns: returns bla bla
# Or bla blah
#
# Since: version
# Notes: notes, comments can have
# - itemized list
# - like this
#
# Example:
#
# -> { "execute": "quit" }
# <- { "return": {} }
#
##
That's roughly following the following EBNF grammar:
api_comment = "##\n" comment "##\n"
comment = freeform_comment | symbol_comment
freeform_comment = { "# " text "\n" | "#\n" }
symbol_comment = "# @" name ":\n" { member | meta | freeform_comment }
member = "# @" name ':' [ text ] freeform_comment
meta = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:"
) [ text ] freeform_comment
text = free-text markdown-like, "#optional" for members
Thanks to the following json expressions, the documentation is enhanced
with extra information about the type of arguments and return value
expected.
Signed-off-by: Marc-André Lureau <address@hidden>
---
tests/Makefile.include | 17 ++
scripts/qapi.py | 215 ++++++++++++++++-
scripts/qapi2texi.py | 331 +++++++++++++++++++++++++++
docs/qapi-code-gen.txt | 54 ++++-
tests/qapi-schema/doc-bad-args.err | 1 +
tests/qapi-schema/doc-bad-args.exit | 1 +
tests/qapi-schema/doc-bad-args.json | 6 +
tests/qapi-schema/doc-bad-args.out | 0
tests/qapi-schema/doc-bad-symbol.err | 1 +
tests/qapi-schema/doc-bad-symbol.exit | 1 +
tests/qapi-schema/doc-bad-symbol.json | 4 +
tests/qapi-schema/doc-bad-symbol.out | 0
tests/qapi-schema/doc-duplicated-arg.err | 1 +
tests/qapi-schema/doc-duplicated-arg.exit | 1 +
tests/qapi-schema/doc-duplicated-arg.json | 5 +
tests/qapi-schema/doc-duplicated-arg.out | 0
tests/qapi-schema/doc-duplicated-return.err | 1 +
tests/qapi-schema/doc-duplicated-return.exit | 1 +
tests/qapi-schema/doc-duplicated-return.json | 6 +
tests/qapi-schema/doc-duplicated-return.out | 0
tests/qapi-schema/doc-duplicated-since.err | 1 +
tests/qapi-schema/doc-duplicated-since.exit | 1 +
tests/qapi-schema/doc-duplicated-since.json | 6 +
tests/qapi-schema/doc-duplicated-since.out | 0
tests/qapi-schema/doc-empty-arg.err | 1 +
tests/qapi-schema/doc-empty-arg.exit | 1 +
tests/qapi-schema/doc-empty-arg.json | 4 +
tests/qapi-schema/doc-empty-arg.out | 0
tests/qapi-schema/doc-empty-section.err | 1 +
tests/qapi-schema/doc-empty-section.exit | 1 +
tests/qapi-schema/doc-empty-section.json | 6 +
tests/qapi-schema/doc-empty-section.out | 0
tests/qapi-schema/doc-empty-symbol.err | 1 +
tests/qapi-schema/doc-empty-symbol.exit | 1 +
tests/qapi-schema/doc-empty-symbol.json | 3 +
tests/qapi-schema/doc-empty-symbol.out | 0
tests/qapi-schema/doc-invalid-end.err | 1 +
tests/qapi-schema/doc-invalid-end.exit | 1 +
tests/qapi-schema/doc-invalid-end.json | 3 +
tests/qapi-schema/doc-invalid-end.out | 0
tests/qapi-schema/doc-invalid-end2.err | 1 +
tests/qapi-schema/doc-invalid-end2.exit | 1 +
tests/qapi-schema/doc-invalid-end2.json | 3 +
tests/qapi-schema/doc-invalid-end2.out | 0
tests/qapi-schema/doc-invalid-return.err | 1 +
tests/qapi-schema/doc-invalid-return.exit | 1 +
tests/qapi-schema/doc-invalid-return.json | 5 +
tests/qapi-schema/doc-invalid-return.out | 0
tests/qapi-schema/doc-invalid-section.err | 1 +
tests/qapi-schema/doc-invalid-section.exit | 1 +
tests/qapi-schema/doc-invalid-section.json | 4 +
tests/qapi-schema/doc-invalid-section.out | 0
tests/qapi-schema/doc-invalid-start.err | 1 +
tests/qapi-schema/doc-invalid-start.exit | 1 +
tests/qapi-schema/doc-invalid-start.json | 3 +
tests/qapi-schema/doc-invalid-start.out | 0
tests/qapi-schema/doc-missing-expr.err | 1 +
tests/qapi-schema/doc-missing-expr.exit | 1 +
tests/qapi-schema/doc-missing-expr.json | 3 +
tests/qapi-schema/doc-missing-expr.out | 0
tests/qapi-schema/doc-missing-space.err | 1 +
tests/qapi-schema/doc-missing-space.exit | 1 +
tests/qapi-schema/doc-missing-space.json | 4 +
tests/qapi-schema/doc-missing-space.out | 0
tests/qapi-schema/qapi-schema-test.json | 58 +++++
tests/qapi-schema/qapi-schema-test.out | 49 ++++
tests/qapi-schema/test-qapi.py | 12 +
67 files changed, 817 insertions(+), 14 deletions(-)
create mode 100755 scripts/qapi2texi.py
create mode 100644 tests/qapi-schema/doc-bad-args.err
create mode 100644 tests/qapi-schema/doc-bad-args.exit
create mode 100644 tests/qapi-schema/doc-bad-args.json
create mode 100644 tests/qapi-schema/doc-bad-args.out
create mode 100644 tests/qapi-schema/doc-bad-symbol.err
create mode 100644 tests/qapi-schema/doc-bad-symbol.exit
create mode 100644 tests/qapi-schema/doc-bad-symbol.json
create mode 100644 tests/qapi-schema/doc-bad-symbol.out
create mode 100644 tests/qapi-schema/doc-duplicated-arg.err
create mode 100644 tests/qapi-schema/doc-duplicated-arg.exit
create mode 100644 tests/qapi-schema/doc-duplicated-arg.json
create mode 100644 tests/qapi-schema/doc-duplicated-arg.out
create mode 100644 tests/qapi-schema/doc-duplicated-return.err
create mode 100644 tests/qapi-schema/doc-duplicated-return.exit
create mode 100644 tests/qapi-schema/doc-duplicated-return.json
create mode 100644 tests/qapi-schema/doc-duplicated-return.out
create mode 100644 tests/qapi-schema/doc-duplicated-since.err
create mode 100644 tests/qapi-schema/doc-duplicated-since.exit
create mode 100644 tests/qapi-schema/doc-duplicated-since.json
create mode 100644 tests/qapi-schema/doc-duplicated-since.out
create mode 100644 tests/qapi-schema/doc-empty-arg.err
create mode 100644 tests/qapi-schema/doc-empty-arg.exit
create mode 100644 tests/qapi-schema/doc-empty-arg.json
create mode 100644 tests/qapi-schema/doc-empty-arg.out
create mode 100644 tests/qapi-schema/doc-empty-section.err
create mode 100644 tests/qapi-schema/doc-empty-section.exit
create mode 100644 tests/qapi-schema/doc-empty-section.json
create mode 100644 tests/qapi-schema/doc-empty-section.out
create mode 100644 tests/qapi-schema/doc-empty-symbol.err
create mode 100644 tests/qapi-schema/doc-empty-symbol.exit
create mode 100644 tests/qapi-schema/doc-empty-symbol.json
create mode 100644 tests/qapi-schema/doc-empty-symbol.out
create mode 100644 tests/qapi-schema/doc-invalid-end.err
create mode 100644 tests/qapi-schema/doc-invalid-end.exit
create mode 100644 tests/qapi-schema/doc-invalid-end.json
create mode 100644 tests/qapi-schema/doc-invalid-end.out
create mode 100644 tests/qapi-schema/doc-invalid-end2.err
create mode 100644 tests/qapi-schema/doc-invalid-end2.exit
create mode 100644 tests/qapi-schema/doc-invalid-end2.json
create mode 100644 tests/qapi-schema/doc-invalid-end2.out
create mode 100644 tests/qapi-schema/doc-invalid-return.err
create mode 100644 tests/qapi-schema/doc-invalid-return.exit
create mode 100644 tests/qapi-schema/doc-invalid-return.json
create mode 100644 tests/qapi-schema/doc-invalid-return.out
create mode 100644 tests/qapi-schema/doc-invalid-section.err
create mode 100644 tests/qapi-schema/doc-invalid-section.exit
create mode 100644 tests/qapi-schema/doc-invalid-section.json
create mode 100644 tests/qapi-schema/doc-invalid-section.out
create mode 100644 tests/qapi-schema/doc-invalid-start.err
create mode 100644 tests/qapi-schema/doc-invalid-start.exit
create mode 100644 tests/qapi-schema/doc-invalid-start.json
create mode 100644 tests/qapi-schema/doc-invalid-start.out
create mode 100644 tests/qapi-schema/doc-missing-expr.err
create mode 100644 tests/qapi-schema/doc-missing-expr.exit
create mode 100644 tests/qapi-schema/doc-missing-expr.json
create mode 100644 tests/qapi-schema/doc-missing-expr.out
create mode 100644 tests/qapi-schema/doc-missing-space.err
create mode 100644 tests/qapi-schema/doc-missing-space.exit
create mode 100644 tests/qapi-schema/doc-missing-space.json
create mode 100644 tests/qapi-schema/doc-missing-space.out
diff --git a/tests/Makefile.include b/tests/Makefile.include
index e98d3b6..f16764c 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -350,6 +350,21 @@ qapi-schema += base-cycle-direct.json
qapi-schema += base-cycle-indirect.json
qapi-schema += command-int.json
qapi-schema += comments.json
+qapi-schema += doc-bad-args.json
+qapi-schema += doc-bad-symbol.json
+qapi-schema += doc-duplicated-arg.json
+qapi-schema += doc-duplicated-return.json
+qapi-schema += doc-duplicated-since.json
+qapi-schema += doc-empty-arg.json
+qapi-schema += doc-empty-section.json
+qapi-schema += doc-empty-symbol.json
+qapi-schema += doc-invalid-end.json
+qapi-schema += doc-invalid-end2.json
+qapi-schema += doc-invalid-return.json
+qapi-schema += doc-invalid-section.json
+qapi-schema += doc-invalid-start.json
+qapi-schema += doc-missing-expr.json
+qapi-schema += doc-missing-space.json
qapi-schema += double-data.json
qapi-schema += double-type.json
qapi-schema += duplicate-key.json
@@ -443,6 +458,8 @@ qapi-schema += union-optional-branch.json
qapi-schema += union-unknown.json
qapi-schema += unknown-escape.json
qapi-schema += unknown-expr-key.json
+
+
check-qapi-schema-y := $(addprefix tests/qapi-schema/, $(qapi-schema))
GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h \
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 4d1b0e4..1b456b4 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -122,6 +122,109 @@ class QAPILineError(Exception):
"%s:%d: %s" % (self.info['file'], self.info['line'], self.msg)
+class QAPIDoc(object):
+ class Section(object):
+ def __init__(self, name=""):
+ # optional section name (argument/member or section name)
+ self.name = name
+ # the list of strings for this section
+ self.content = []
+
+ def append(self, line):
+ self.content.append(line)
+
+ def __repr__(self):
+ return "\n".join(self.content).strip()
+
+ class ArgSection(Section):
+ pass
+
+ def __init__(self, parser):
+ self.parser = parser
+ self.symbol = None
+ self.body = QAPIDoc.Section()
+ # a dict {'arg': ArgSection, ...}
+ self.args = OrderedDict()
+ # a list of Section
+ self.meta = []
+ # the current section
+ self.section = self.body
+ # associated expression and info (set by expression parser)
+ self.expr = None
+ self.info = None
+
+ def has_meta(self, name):
+ """Returns True if the doc has a meta section 'name'"""
+ for i in self.meta:
+ if i.name == name:
+ return True
+ return False
+
+ def append(self, line):
+ """Adds a # comment line, to be parsed and added to current section"""
+ line = line[1:]
+ if not line:
+ self._append_freeform(line)
+ return
+
+ if line[0] != ' ':
+ raise QAPISchemaError(self.parser, "missing space after #")
+ line = line[1:]
+
+ if self.symbol:
+ self._append_symbol_line(line)
+ elif (not self.body.content and
+ line.startswith("@") and line.endswith(":")):
+ self.symbol = line[1:-1]
+ if not self.symbol:
+ raise QAPISchemaError(self.parser, "Invalid symbol")
+ else:
+ self._append_freeform(line)
+
+ def _append_symbol_line(self, line):
+ name = line.split(' ', 1)[0]
+
+ if name.startswith("@") and name.endswith(":"):
+ line = line[len(name)+1:]
+ self._start_args_section(name[1:-1])
+ elif name in ("Returns:", "Since:",
+ # those are often singular or plural
+ "Note:", "Notes:",
+ "Example:", "Examples:"):
+ line = line[len(name)+1:]
+ self._start_meta_section(name[:-1])
+
+ self._append_freeform(line)
+
+ def _start_args_section(self, name):
+ if not name:
+ raise QAPISchemaError(self.parser, "Invalid argument name")
+ if name in self.args:
+ raise QAPISchemaError(self.parser, "'%s' arg duplicated" % name)
+ self.section = QAPIDoc.ArgSection(name)
+ self.args[name] = self.section
+
+ def _start_meta_section(self, name):
+ if name in ("Returns", "Since") and self.has_meta(name):
+ raise QAPISchemaError(self.parser,
+ "Duplicated '%s' section" % name)
+ self.section = QAPIDoc.Section(name)
+ self.meta.append(self.section)
+
+ def _append_freeform(self, line):
+ in_arg = isinstance(self.section, QAPIDoc.ArgSection)
+ if in_arg and self.section.content and not self.section.content[-1] \
+ and line and not line[0].isspace():
+ # an empty line followed by a non-indented
+ # comment ends the argument section
+ self.section = self.body
+ self._append_freeform(line)
+ return
+ if in_arg or not self.section.name.startswith("Example"):
+ line = line.strip()
+ self.section.append(line)
+
+
class QAPISchemaParser(object):
def __init__(self, fp, previously_included=[], incl_info=None):
@@ -137,11 +240,18 @@ class QAPISchemaParser(object):
self.line = 1
self.line_pos = 0
self.exprs = []
+ self.docs = []
self.accept()
while self.tok is not None:
info = {'file': fname, 'line': self.line,
'parent': self.incl_info}
+ if self.tok == '#' and self.val.startswith('##'):
+ doc = self.get_doc()
+ doc.info = info
+ self.docs.append(doc)
+ continue
+
expr = self.get_expr(False)
if isinstance(expr, dict) and "include" in expr:
if len(expr) != 1:
@@ -160,6 +270,7 @@ class QAPISchemaParser(object):
raise QAPILineError(info, "Inclusion loop for %s"
% include)
inf = inf['parent']
+
# skip multiple include of the same file
if incl_abs_fname in previously_included:
continue
@@ -171,12 +282,38 @@ class QAPISchemaParser(object):
exprs_include = QAPISchemaParser(fobj, previously_included,
info)
self.exprs.extend(exprs_include.exprs)
+ self.docs.extend(exprs_include.docs)
else:
expr_elem = {'expr': expr,
'info': info}
+ if self.docs and not self.docs[-1].expr:
+ self.docs[-1].expr = expr
+ expr_elem['doc'] = self.docs[-1]
+
self.exprs.append(expr_elem)
- def accept(self):
+ def get_doc(self):
+ if self.val != '##':
+ raise QAPISchemaError(self, "Junk after '##' at start of "
+ "documentation comment")
+
+ doc = QAPIDoc(self)
+ self.accept(False)
+ while self.tok == '#':
+ if self.val.startswith('##'):
+ # End of doc comment
+ if self.val != '##':
+ raise QAPISchemaError(self, "Junk after '##' at end of "
+ "documentation comment")
+ self.accept()
+ return doc
+ else:
+ doc.append(self.val)
+ self.accept(False)
+
+ raise QAPISchemaError(self, "Documentation comment must end with '##'")
+
+ def accept(self, skip_comment=True):
while True:
self.tok = self.src[self.cursor]
self.pos = self.cursor
@@ -184,7 +321,13 @@ class QAPISchemaParser(object):
self.val = None
if self.tok == '#':
+ if self.src[self.cursor] == '#':
+ # Start of doc comment
+ skip_comment = False
self.cursor = self.src.find('\n', self.cursor)
+ if not skip_comment:
+ self.val = self.src[self.pos:self.cursor]
+ return
elif self.tok in "{}:,[]":
return
elif self.tok == "'":
@@ -713,7 +856,7 @@ def check_keys(expr_elem, meta, required, optional=[]):
% (key, meta, name))
-def check_exprs(exprs):
+def check_exprs(exprs, strict_doc):
global all_names
# Learn the types and check for valid expression keys
@@ -722,6 +865,11 @@ def check_exprs(exprs):
for expr_elem in exprs:
expr = expr_elem['expr']
info = expr_elem['info']
+
+ if strict_doc and 'doc' not in expr_elem:
+ raise QAPILineError(info,
+ "Expression missing documentation comment")
+
if 'enum' in expr:
check_keys(expr_elem, 'enum', ['data'], ['prefix'])
add_enum(expr['enum'], info, expr['data'])
@@ -780,6 +928,63 @@ def check_exprs(exprs):
return exprs
+def check_simple_doc(doc):
+ if doc.symbol:
+ raise QAPILineError(doc.info,
+ "'%s' documention is not followed by the
definition"
+ % doc.symbol)
+
+ body = str(doc.body)
+ if re.search(r'@\S+:', body, re.MULTILINE):
+ raise QAPILineError(doc.info,
+ "Document body cannot contain @NAME: sections")
+
+
+def check_expr_doc(doc, expr, info):
+ for i in ('enum', 'union', 'alternate', 'struct', 'command', 'event'):
+ if i in expr:
+ meta = i
+ break
+
+ name = expr[meta]
+ if doc.symbol != name:
+ raise QAPILineError(info, "Definition of '%s' follows documentation"
+ " for '%s'" % (name, doc.symbol))
+ if doc.has_meta('Returns') and 'command' not in expr:
+ raise QAPILineError(info, "Invalid return documentation")
+
+ doc_args = set(doc.args.keys())
+ if meta == 'union':
+ data = expr.get('base', [])
+ else:
+ data = expr.get('data', [])
+ if isinstance(data, dict):
+ data = data.keys()
+ args = set([name.strip('*') for name in data])
+ if meta == 'alternate' or \
+ (meta == 'union' and not expr.get('discriminator')):
+ args.add('type')
+ if not doc_args.issubset(args):
+ raise QAPILineError(info, "Members documentation is not a subset of"
+ " API %r > %r" % (list(doc_args), list(args)))
+
+
+def check_docs(docs):
+ for doc in docs:
+ for section in doc.args.values() + doc.meta:
+ content = str(section)
+ if not content or content.isspace():
+ raise QAPILineError(doc.info,
+ "Empty doc section '%s'" % section.name)
+
+ if not doc.expr:
+ check_simple_doc(doc)
+ else:
+ check_expr_doc(doc, doc.expr, doc.info)
+
+ return docs
+
+
#
# Schema compiler frontend
#
@@ -1249,9 +1454,11 @@ class QAPISchemaEvent(QAPISchemaEntity):
class QAPISchema(object):
- def __init__(self, fname):
+ def __init__(self, fname, strict_doc=False):
try:
- self.exprs = check_exprs(QAPISchemaParser(open(fname, "r")).exprs)
+ parser = QAPISchemaParser(open(fname, "r"))
+ self.exprs = check_exprs(parser.exprs, strict_doc)
+ self.docs = check_docs(parser.docs)
self._entity_dict = {}
self._predefining = True
self._def_predefineds()
diff --git a/scripts/qapi2texi.py b/scripts/qapi2texi.py
new file mode 100755
index 0000000..0cec43a
--- /dev/null
+++ b/scripts/qapi2texi.py
@@ -0,0 +1,331 @@
+#!/usr/bin/env python
+# QAPI texi generator
+#
+# This work is licensed under the terms of the GNU LGPL, version 2+.
+# See the COPYING file in the top-level directory.
+"""This script produces the documentation of a qapi schema in texinfo format"""
+import re
+import sys
+
+import qapi
+
+COMMAND_FMT = """
address@hidden {type} {{{ret}}} {name} @
+{{{args}}}
+
+{body}
+
address@hidden deftypefn
+
+""".format
+
+ENUM_FMT = """
address@hidden Enum {name}
+
+{body}
+
address@hidden deftp
+
+""".format
+
+STRUCT_FMT = """
address@hidden {{{type}}} {name} @
+{{{attrs}}}
+
+{body}
+
address@hidden deftp
+
+""".format
+
+EXAMPLE_FMT = """@example
+{code}
address@hidden example
+""".format
+
+
+def subst_strong(doc):
+ """Replaces *foo* by @strong{foo}"""
+ return re.sub(r'\*([^_\n]+)\*', r'@emph{\1}', doc)
+
+
+def subst_emph(doc):
+ """Replaces _foo_ by @emph{foo}"""
+ return re.sub(r'\s_([^_\n]+)_\s', r' @emph{\1} ', doc)
+
+
+def subst_vars(doc):
+ """Replaces @var by @code{var}"""
+ return re.sub(r'@([\w-]+)', r'@code{\1}', doc)
+
+
+def subst_braces(doc):
+ """Replaces {} with @{ @}"""
+ return doc.replace("{", "@{").replace("}", "@}")
+
+
+def texi_example(doc):
+ """Format @example"""
+ doc = subst_braces(doc).strip('\n')
+ return EXAMPLE_FMT(code=doc)
+
+
+def texi_comment(doc):
+ """
+ Format a comment
+
+ Lines starting with:
+ - |: generates an @example
+ - =: generates @section
+ - ==: generates @subsection
+ - 1. or 1): generates an @enumerate @item
+ - o/*/-: generates an @itemize list
+ """
+ lines = []
+ doc = subst_braces(doc)
+ doc = subst_vars(doc)
+ doc = subst_emph(doc)
+ doc = subst_strong(doc)
+ inlist = ""
+ lastempty = False
+ for line in doc.split('\n'):
+ empty = line == ""
+
+ if line.startswith("| "):
+ line = EXAMPLE_FMT(code=line[2:])
+ elif line.startswith("= "):
+ line = "@section " + line[2:]
+ elif line.startswith("== "):
+ line = "@subsection " + line[3:]
+ elif re.match("^([0-9]*[.)]) ", line):
+ if not inlist:
+ lines.append("@enumerate")
+ inlist = "enumerate"
+ line = line[line.find(" ")+1:]
+ lines.append("@item")
+ elif re.match("^[o*-] ", line):
+ if not inlist:
+ lines.append("@itemize %s" % {'o': "@bullet",
+ '*': "@minus",
+ '-': ""}[line[0]])
+ inlist = "itemize"
+ lines.append("@item")
+ line = line[2:]
+ elif lastempty and inlist:
+ lines.append("@end %s\n" % inlist)
+ inlist = ""
+
+ lastempty = empty
+ lines.append(line)
+
+ if inlist:
+ lines.append("@end %s\n" % inlist)
+ return "\n".join(lines)
+
+
+def texi_args(expr, key="data"):
+ """
+ Format the functions/structure/events.. arguments/members
+ """
+ if key not in expr:
+ return ""
+
+ args = expr[key]
+ arg_list = []
+ if isinstance(args, str):
+ arg_list.append(args)
+ else:
+ for name, typ in args.iteritems():
+ # optional arg
+ if name.startswith("*"):
+ name = name[1:]
+ arg_list.append("['%s': @var{%s}]" % (name, typ))
+ # regular arg
+ else:
+ arg_list.append("'%s': @var{%s}" % (name, typ))
+
+ return ", ".join(arg_list)
+
+
+def texi_body(doc):
+ """
+ Format the body of a symbol documentation:
+ - a table of arguments
+ - followed by "Returns/Notes/Since/Example" sections
+ """
+ def _section_order(section):
+ return {"Returns": 0,
+ "Note": 1,
+ "Notes": 1,
+ "Since": 2,
+ "Example": 3,
+ "Examples": 3}[section]
+
+ body = "@table @asis\n"
+ for arg, section in doc.args.iteritems():
+ desc = str(section)
+ opt = ''
+ if desc.startswith("#optional"):
+ desc = desc[10:]
+ opt = ' *'
+ elif desc.endswith("#optional"):
+ desc = desc[:-10]
+ opt = ' *'
+ body += "@item @code{'%s'}%s\n%s\n" % (arg, opt, texi_comment(desc))
+ body += "@end table\n"
+ body += texi_comment(str(doc.body))
+
+ meta = sorted(doc.meta, key=lambda i: _section_order(i.name))
+ for section in meta:
+ name, doc = (section.name, str(section))
+ func = texi_comment
+ if name.startswith("Example"):
+ func = texi_example
+
+ body += "address@hidden address@hidden quotation" % \
+ (name, func(doc))
+ return body
+
+
+def texi_alternate(expr, doc):
+ """
+ Format an alternate to texi
+ """
+ args = texi_args(expr)
+ body = texi_body(doc)
+ return STRUCT_FMT(type="Alternate",
+ name=doc.symbol,
+ attrs="[ " + args + " ]",
+ body=body)
+
+
+def texi_union(expr, doc):
+ """
+ Format an union to texi
+ """
+ attrs = "@{ " + texi_args(expr, "base") + " @}"
+ discriminator = expr.get("discriminator")
+ if not discriminator:
+ union = "Flat Union"
+ discriminator = "type"
+ else:
+ union = "Union"
+ attrs += " + '%s' = [ " % discriminator
+ attrs += texi_args(expr, "data") + " ]"
+ body = texi_body(doc)
+
+ return STRUCT_FMT(type=union,
+ name=doc.symbol,
+ attrs=attrs,
+ body=body)
+
+
+def texi_enum(expr, doc):
+ """
+ Format an enum to texi
+ """
+ for i in expr['data']:
+ if i not in doc.args:
+ doc.args[i] = ''
+ body = texi_body(doc)
+ return ENUM_FMT(name=doc.symbol,
+ body=body)
+
+
+def texi_struct(expr, doc):
+ """
+ Format a struct to texi
+ """
+ args = texi_args(expr)
+ body = texi_body(doc)
+ attrs = "@{ " + args + " @}"
+ base = expr.get("base")
+ if base:
+ attrs += " + %s" % base
+ return STRUCT_FMT(type="Struct",
+ name=doc.symbol,
+ attrs=attrs,
+ body=body)
+
+
+def texi_command(expr, doc):
+ """
+ Format a command to texi
+ """
+ args = texi_args(expr)
+ ret = expr["returns"] if "returns" in expr else ""
+ body = texi_body(doc)
+ return COMMAND_FMT(type="Command",
+ name=doc.symbol,
+ ret=ret,
+ args="(" + args + ")",
+ body=body)
+
+
+def texi_event(expr, doc):
+ """
+ Format an event to texi
+ """
+ args = texi_args(expr)
+ body = texi_body(doc)
+ return COMMAND_FMT(type="Event",
+ name=doc.symbol,
+ ret="",
+ args="(" + args + ")",
+ body=body)
+
+
+def texi_expr(expr, doc):
+ """
+ Format an expr to texi
+ """
+ (kind, _) = expr.items()[0]
+
+ fmt = {"command": texi_command,
+ "struct": texi_struct,
+ "enum": texi_enum,
+ "union": texi_union,
+ "alternate": texi_alternate,
+ "event": texi_event}
+ try:
+ fmt = fmt[kind]
+ except KeyError:
+ raise ValueError("Unknown expression kind '%s'" % kind)
+
+ return fmt(expr, doc)
+
+
+def texi(docs):
+ """
+ Convert QAPI schema expressions to texi documentation
+ """
+ res = []
+ for doc in docs:
+ expr = doc.expr
+ if not expr:
+ res.append(texi_body(doc))
+ continue
+ try:
+ doc = texi_expr(expr, doc)
+ res.append(doc)
+ except:
+ print >>sys.stderr, "error at @%s" % doc.info
+ raise
+
+ return '\n'.join(res)
+
+
+def main(argv):
+ """
+ Takes schema argument, prints result to stdout
+ """
+ if len(argv) != 2:
+ print >>sys.stderr, "%s: need exactly 1 argument: SCHEMA" % argv[0]
+ sys.exit(1)
+
+ schema = qapi.QAPISchema(argv[1], strict_doc=True)
+ print texi(schema.docs)
+
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index 2841c51..8bc963e 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -45,16 +45,13 @@ QAPI parser does not). At present, there is no place where
a QAPI
schema requires the use of JSON numbers or null.
Comments are allowed; anything between an unquoted # and the following
-newline is ignored. Although there is not yet a documentation
-generator, a form of stylized comments has developed for consistently
-documenting details about an expression and when it was added to the
-schema. The documentation is delimited between two lines of ##, then
-the first line names the expression, an optional overview is provided,
-then individual documentation about each member of 'data' is provided,
-and finally, a 'Since: x.y.z' tag lists the release that introduced
-the expression. Optional members are tagged with the phrase
-'#optional', often with their default value; and extensions added
-after the expression was first released are also given a '(since
+newline is ignored. The documentation is delimited between two lines
+of ##, then the first line names the expression, an optional overview
+is provided, then individual documentation about each member of 'data'
+is provided, and finally, a 'Since: x.y.z' tag lists the release that
+introduced the expression. Optional members are tagged with the
+phrase '#optional', often with their default value; and extensions
+added after the expression was first released are also given a '(since
x.y.z)' comment. For example:
##
@@ -73,12 +70,49 @@ x.y.z)' comment. For example:
# (Since 2.0)
#
# Since: 0.14.0
+ #
+ # Notes: You can also make a list:
+ # - with items
+ # - like this
+ #
+ # Example:
+ #
+ # -> { "execute": ... }
+ # <- { "return": ... }
+ #
##
{ 'struct': 'BlockStats',
'data': {'*device': 'str', 'stats': 'BlockDeviceStats',
'*parent': 'BlockStats',
'*backing': 'BlockStats'} }
+It's also possible to create documentation sections, such as:
+
+ ##
+ # = Section
+ # == Subsection
+ #
+ # Some text foo with *strong* and _emphasis_
+ # 1. with a list
+ # 2. like that
+ #
+ # And some code:
+ # | $ echo foo
+ # | -> do this
+ # | <- get that
+ #
+ ##
+
+Text *foo* and _foo_ are for "strong" and "emphasis" styles (they do
+not work over multiple lines). @foo is used to reference a symbol.
+
+Lines starting with the following characters and a space:
+- | are examples
+- = are top section
+- == are subsection
+- X. or X) are enumerations (X is any number)
+- o/*/- are itemized list
+
The schema sets up a series of types, as well as commands and events
that will use those types. Forward references are allowed: the parser
scans in two passes, where the first pass learns all type names, and
diff --git a/tests/qapi-schema/doc-bad-args.err
b/tests/qapi-schema/doc-bad-args.err
new file mode 100644
index 0000000..a55e003
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-args.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-bad-args.json:1: Members documentation is not a subset
of API ['a', 'b'] > ['a']
diff --git a/tests/qapi-schema/doc-bad-args.exit
b/tests/qapi-schema/doc-bad-args.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-args.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-bad-args.json
b/tests/qapi-schema/doc-bad-args.json
new file mode 100644
index 0000000..26992ea
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-args.json
@@ -0,0 +1,6 @@
+##
+# @foo:
+# @a: a
+# @b: b
+##
+{ 'command': 'foo', 'data': {'a': 'int'} }
diff --git a/tests/qapi-schema/doc-bad-args.out
b/tests/qapi-schema/doc-bad-args.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-bad-symbol.err
b/tests/qapi-schema/doc-bad-symbol.err
new file mode 100644
index 0000000..9c969d1
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-symbol.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-bad-symbol.json:1: Definition of 'foo' follows
documentation for 'food'
diff --git a/tests/qapi-schema/doc-bad-symbol.exit
b/tests/qapi-schema/doc-bad-symbol.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-symbol.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-bad-symbol.json
b/tests/qapi-schema/doc-bad-symbol.json
new file mode 100644
index 0000000..7255152
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-symbol.json
@@ -0,0 +1,4 @@
+##
+# @food:
+##
+{ 'command': 'foo', 'data': {'a': 'int'} }
diff --git a/tests/qapi-schema/doc-bad-symbol.out
b/tests/qapi-schema/doc-bad-symbol.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-duplicated-arg.err
b/tests/qapi-schema/doc-duplicated-arg.err
new file mode 100644
index 0000000..88a272b
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-arg.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-duplicated-arg.json:4:1: 'a' arg duplicated
diff --git a/tests/qapi-schema/doc-duplicated-arg.exit
b/tests/qapi-schema/doc-duplicated-arg.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-arg.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-duplicated-arg.json
b/tests/qapi-schema/doc-duplicated-arg.json
new file mode 100644
index 0000000..f7804c2
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-arg.json
@@ -0,0 +1,5 @@
+##
+# @foo:
+# @a:
+# @a:
+##
diff --git a/tests/qapi-schema/doc-duplicated-arg.out
b/tests/qapi-schema/doc-duplicated-arg.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-duplicated-return.err
b/tests/qapi-schema/doc-duplicated-return.err
new file mode 100644
index 0000000..1b02880
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-return.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-duplicated-return.json:5:1: Duplicated 'Returns' section
diff --git a/tests/qapi-schema/doc-duplicated-return.exit
b/tests/qapi-schema/doc-duplicated-return.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-return.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-duplicated-return.json
b/tests/qapi-schema/doc-duplicated-return.json
new file mode 100644
index 0000000..de7234b
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-return.json
@@ -0,0 +1,6 @@
+##
+# @foo:
+#
+# Returns: 0
+# Returns: 1
+##
diff --git a/tests/qapi-schema/doc-duplicated-return.out
b/tests/qapi-schema/doc-duplicated-return.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-duplicated-since.err
b/tests/qapi-schema/doc-duplicated-since.err
new file mode 100644
index 0000000..72efb2a
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-since.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-duplicated-since.json:5:1: Duplicated 'Since' section
diff --git a/tests/qapi-schema/doc-duplicated-since.exit
b/tests/qapi-schema/doc-duplicated-since.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-since.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-duplicated-since.json
b/tests/qapi-schema/doc-duplicated-since.json
new file mode 100644
index 0000000..da261a1
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-since.json
@@ -0,0 +1,6 @@
+##
+# @foo:
+#
+# Since: 0
+# Since: 1
+##
diff --git a/tests/qapi-schema/doc-duplicated-since.out
b/tests/qapi-schema/doc-duplicated-since.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-empty-arg.err
b/tests/qapi-schema/doc-empty-arg.err
new file mode 100644
index 0000000..0647eed
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-arg.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-empty-arg.json:3:1: Invalid argument name
diff --git a/tests/qapi-schema/doc-empty-arg.exit
b/tests/qapi-schema/doc-empty-arg.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-arg.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-empty-arg.json
b/tests/qapi-schema/doc-empty-arg.json
new file mode 100644
index 0000000..74f526c
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-arg.json
@@ -0,0 +1,4 @@
+##
+# @foo:
+# @:
+##
diff --git a/tests/qapi-schema/doc-empty-arg.out
b/tests/qapi-schema/doc-empty-arg.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-empty-section.err
b/tests/qapi-schema/doc-empty-section.err
new file mode 100644
index 0000000..c940332
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-section.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-empty-section.json:1: Empty doc section 'Note'
diff --git a/tests/qapi-schema/doc-empty-section.exit
b/tests/qapi-schema/doc-empty-section.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-section.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-empty-section.json
b/tests/qapi-schema/doc-empty-section.json
new file mode 100644
index 0000000..ed3867d
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-section.json
@@ -0,0 +1,6 @@
+##
+# @foo:
+#
+# Note:
+##
+{ 'command': 'foo', 'data': {'a': 'int'} }
diff --git a/tests/qapi-schema/doc-empty-section.out
b/tests/qapi-schema/doc-empty-section.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-empty-symbol.err
b/tests/qapi-schema/doc-empty-symbol.err
new file mode 100644
index 0000000..955dc3c
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-symbol.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-empty-symbol.json:2:1: Invalid symbol
diff --git a/tests/qapi-schema/doc-empty-symbol.exit
b/tests/qapi-schema/doc-empty-symbol.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-symbol.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-empty-symbol.json
b/tests/qapi-schema/doc-empty-symbol.json
new file mode 100644
index 0000000..8a2d662
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-symbol.json
@@ -0,0 +1,3 @@
+##
+# @:
+##
diff --git a/tests/qapi-schema/doc-empty-symbol.out
b/tests/qapi-schema/doc-empty-symbol.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-invalid-end.err
b/tests/qapi-schema/doc-invalid-end.err
new file mode 100644
index 0000000..5ed8911
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-end.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-invalid-end.json:3:2: Documentation comment must end
with '##'
diff --git a/tests/qapi-schema/doc-invalid-end.exit
b/tests/qapi-schema/doc-invalid-end.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-end.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-invalid-end.json
b/tests/qapi-schema/doc-invalid-end.json
new file mode 100644
index 0000000..7452718
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-end.json
@@ -0,0 +1,3 @@
+##
+# An invalid comment
+#
diff --git a/tests/qapi-schema/doc-invalid-end.out
b/tests/qapi-schema/doc-invalid-end.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-invalid-end2.err
b/tests/qapi-schema/doc-invalid-end2.err
new file mode 100644
index 0000000..acd23bb
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-end2.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-invalid-end2.json:3:1: Junk after '##' at end of
documentation comment
diff --git a/tests/qapi-schema/doc-invalid-end2.exit
b/tests/qapi-schema/doc-invalid-end2.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-end2.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-invalid-end2.json
b/tests/qapi-schema/doc-invalid-end2.json
new file mode 100644
index 0000000..12e669e
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-end2.json
@@ -0,0 +1,3 @@
+##
+#
+## invalid
diff --git a/tests/qapi-schema/doc-invalid-end2.out
b/tests/qapi-schema/doc-invalid-end2.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-invalid-return.err
b/tests/qapi-schema/doc-invalid-return.err
new file mode 100644
index 0000000..c3f2691
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-return.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-invalid-return.json:1: Invalid return documentation
diff --git a/tests/qapi-schema/doc-invalid-return.exit
b/tests/qapi-schema/doc-invalid-return.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-return.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-invalid-return.json
b/tests/qapi-schema/doc-invalid-return.json
new file mode 100644
index 0000000..6568b6d
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-return.json
@@ -0,0 +1,5 @@
+##
+# @foo:
+# Returns: blah
+##
+{ 'event': 'foo' }
diff --git a/tests/qapi-schema/doc-invalid-return.out
b/tests/qapi-schema/doc-invalid-return.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-invalid-section.err
b/tests/qapi-schema/doc-invalid-section.err
new file mode 100644
index 0000000..f4c12aa
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-section.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-invalid-section.json:1: Document body cannot contain
@NAME: sections
diff --git a/tests/qapi-schema/doc-invalid-section.exit
b/tests/qapi-schema/doc-invalid-section.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-section.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-invalid-section.json
b/tests/qapi-schema/doc-invalid-section.json
new file mode 100644
index 0000000..9afa2f1
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-section.json
@@ -0,0 +1,4 @@
+##
+# freeform
+# @note: foo
+##
diff --git a/tests/qapi-schema/doc-invalid-section.out
b/tests/qapi-schema/doc-invalid-section.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-invalid-start.err
b/tests/qapi-schema/doc-invalid-start.err
new file mode 100644
index 0000000..194c8d7
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-start.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-invalid-start.json:1:1: Junk after '##' at start of
documentation comment
diff --git a/tests/qapi-schema/doc-invalid-start.exit
b/tests/qapi-schema/doc-invalid-start.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-start.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-invalid-start.json
b/tests/qapi-schema/doc-invalid-start.json
new file mode 100644
index 0000000..f85adfd
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-start.json
@@ -0,0 +1,3 @@
+## invalid
+#
+##
diff --git a/tests/qapi-schema/doc-invalid-start.out
b/tests/qapi-schema/doc-invalid-start.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-missing-expr.err
b/tests/qapi-schema/doc-missing-expr.err
new file mode 100644
index 0000000..e4ed135
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-expr.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-missing-expr.json:1: 'bar' documention is not followed
by the definition
diff --git a/tests/qapi-schema/doc-missing-expr.exit
b/tests/qapi-schema/doc-missing-expr.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-expr.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-missing-expr.json
b/tests/qapi-schema/doc-missing-expr.json
new file mode 100644
index 0000000..a0be2e1
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-expr.json
@@ -0,0 +1,3 @@
+##
+# @bar:
+##
diff --git a/tests/qapi-schema/doc-missing-expr.out
b/tests/qapi-schema/doc-missing-expr.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/doc-missing-space.err
b/tests/qapi-schema/doc-missing-space.err
new file mode 100644
index 0000000..37056ce
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-space.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-missing-space.json:3:1: missing space after #
diff --git a/tests/qapi-schema/doc-missing-space.exit
b/tests/qapi-schema/doc-missing-space.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-space.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-missing-space.json
b/tests/qapi-schema/doc-missing-space.json
new file mode 100644
index 0000000..39303de
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-space.json
@@ -0,0 +1,4 @@
+##
+# missing space:
+#wef
+##
diff --git a/tests/qapi-schema/doc-missing-space.out
b/tests/qapi-schema/doc-missing-space.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/qapi-schema-test.json
b/tests/qapi-schema/qapi-schema-test.json
index 1719463..d921ec4 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -3,6 +3,43 @@
# This file is a stress test of supported qapi constructs that must
# parse and compile correctly.
+##
+# = Section
+# == subsection
+#
+# Some text foo with *strong* and _emphasis_
+# 1. with a list
+# 2. like that @foo
+#
+# And some code:
+# | $ echo foo
+# | -> do this
+# | <- get that
+#
+# Note: is not a meta
+##
+
+##
+# @TestStruct:
+# @integer: foo
+# blah
+#
+# bao
+#
+# @boolean: bar
+# @string: baz
+#
+# body with @var
+#
+# Example:
+#
+# -> { "execute": ... }
+# <- { "return": ... }
+#
+# Since: 2.3
+# Note: a note
+#
+##
{ 'struct': 'TestStruct',
'data': { 'integer': 'int', 'boolean': 'bool', 'string': 'str' } }
@@ -123,6 +160,27 @@
'data': {'ud1a': 'UserDefOne', '*ud1b': 'UserDefOne'},
'returns': 'UserDefTwo' }
+##
+# Another comment
+##
+
+##
+# @guest-get-time:
+#
+# @a: an integer
+# @b: #optional integer
+#
+# @guest-get-time body
+#
+# Returns: returns something
+#
+# Example:
+#
+# -> { "execute": "guest-get-time", ... }
+# <- { "return": "42" }
+#
+##
+
# Returning a non-dictionary requires a name from the whitelist
{ 'command': 'guest-get-time', 'data': {'a': 'int', '*b': 'int' },
'returns': 'int' }
diff --git a/tests/qapi-schema/qapi-schema-test.out
b/tests/qapi-schema/qapi-schema-test.out
index 9d99c4e..fde9e06 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -232,3 +232,52 @@ command user_def_cmd1 q_obj_user_def_cmd1-arg -> None
gen=True success_response=True boxed=False
command user_def_cmd2 q_obj_user_def_cmd2-arg -> UserDefTwo
gen=True success_response=True boxed=False
+doc freeform
+ body=
+= Section
+== subsection
+
+Some text foo with *strong* and _emphasis_
+1. with a list
+2. like that @foo
+
+And some code:
+| $ echo foo
+| -> do this
+| <- get that
+
+Note: is not a meta
+doc symbol=TestStruct expr=('struct', 'TestStruct')
+ arg=integer
+foo
+blah
+
+bao
+ arg=boolean
+bar
+ arg=string
+baz
+ meta=Example
+-> { "execute": ... }
+<- { "return": ... }
+ meta=Since
+2.3
+ meta=Note
+a note
+ body=
+body with @var
+doc freeform
+ body=
+Another comment
+doc symbol=guest-get-time expr=('command', 'guest-get-time')
+ arg=a
+an integer
+ arg=b
+#optional integer
+ meta=Returns
+returns something
+ meta=Example
+-> { "execute": "guest-get-time", ... }
+<- { "return": "42" }
+ body=
address@hidden body
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index ef74e2c..22da014 100644
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -55,3 +55,15 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor):
schema = QAPISchema(sys.argv[1])
schema.visit(QAPISchemaTestVisitor())
+
+for doc in schema.docs:
+ if doc.symbol:
+ print 'doc symbol=%s expr=%s' % \
+ (doc.symbol, doc.expr.items()[0])
+ else:
+ print 'doc freeform'
+ for arg, section in doc.args.iteritems():
+ print ' arg=%s\n%s' % (arg, section)
+ for section in doc.meta:
+ print ' meta=%s\n%s' % (section.name, section)
+ print ' body=\n%s' % doc.body
--
2.10.0
[Qemu-devel] [PATCH v5 17/17] build-sys: add qapi doc generation targets, Marc-André Lureau, 2016/11/17
[Qemu-devel] [PATCH v5 15/17] (SQUASHED) move doc to schema, Marc-André Lureau, 2016/11/17