From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:4830:134:3::10]:56825) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1c7P2i-000322-JB for qemu-devel@nongnu.org; Thu, 17 Nov 2016 10:56:10 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1c7P2d-0005Q2-Dn for qemu-devel@nongnu.org; Thu, 17 Nov 2016 10:56:04 -0500 Received: from mx1.redhat.com ([209.132.183.28]:12235) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1c7P2d-0005Pa-2t for qemu-devel@nongnu.org; Thu, 17 Nov 2016 10:55:59 -0500 Received: from int-mx13.intmail.prod.int.phx2.redhat.com (int-mx13.intmail.prod.int.phx2.redhat.com [10.5.11.26]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 4E1CD7EBA6 for ; Thu, 17 Nov 2016 15:55:58 +0000 (UTC) From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Thu, 17 Nov 2016 19:55:00 +0400 Message-Id: <20161117155504.21843-14-marcandre.lureau@redhat.com> In-Reply-To: <20161117155504.21843-1-marcandre.lureau@redhat.com> References: <20161117155504.21843-1-marcandre.lureau@redhat.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Subject: [Qemu-devel] [PATCH v5 13/17] qapi: add qapi2texi script List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: eblake@redhat.com, armbru@redhat.com, =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= 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: ## # =3D Section # =3D=3D 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 =3D "##\n" comment "##\n" comment =3D freeform_comment | symbol_comment freeform_comment =3D { "# " text "\n" | "#\n" } symbol_comment =3D "# @" name ":\n" { member | meta | freeform_comment } member =3D "# @" name ':' [ text ] freeform_comment meta =3D "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Exa= mples:" ) [ text ] freeform_comment text =3D 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=C3=A9 Lureau --- 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 +=3D base-cycle-direct.json qapi-schema +=3D base-cycle-indirect.json qapi-schema +=3D command-int.json qapi-schema +=3D comments.json +qapi-schema +=3D doc-bad-args.json +qapi-schema +=3D doc-bad-symbol.json +qapi-schema +=3D doc-duplicated-arg.json +qapi-schema +=3D doc-duplicated-return.json +qapi-schema +=3D doc-duplicated-since.json +qapi-schema +=3D doc-empty-arg.json +qapi-schema +=3D doc-empty-section.json +qapi-schema +=3D doc-empty-symbol.json +qapi-schema +=3D doc-invalid-end.json +qapi-schema +=3D doc-invalid-end2.json +qapi-schema +=3D doc-invalid-return.json +qapi-schema +=3D doc-invalid-section.json +qapi-schema +=3D doc-invalid-start.json +qapi-schema +=3D doc-missing-expr.json +qapi-schema +=3D doc-missing-space.json qapi-schema +=3D double-data.json qapi-schema +=3D double-type.json qapi-schema +=3D duplicate-key.json @@ -443,6 +458,8 @@ qapi-schema +=3D union-optional-branch.json qapi-schema +=3D union-unknown.json qapi-schema +=3D unknown-escape.json qapi-schema +=3D unknown-expr-key.json + + check-qapi-schema-y :=3D $(addprefix tests/qapi-schema/, $(qapi-schema)) =20 GENERATED_HEADERS +=3D 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.ms= g) =20 =20 +class QAPIDoc(object): + class Section(object): + def __init__(self, name=3D""): + # optional section name (argument/member or section name) + self.name =3D name + # the list of strings for this section + self.content =3D [] + + 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 =3D parser + self.symbol =3D None + self.body =3D QAPIDoc.Section() + # a dict {'arg': ArgSection, ...} + self.args =3D OrderedDict() + # a list of Section + self.meta =3D [] + # the current section + self.section =3D self.body + # associated expression and info (set by expression parser) + self.expr =3D None + self.info =3D None + + def has_meta(self, name): + """Returns True if the doc has a meta section 'name'""" + for i in self.meta: + if i.name =3D=3D name: + return True + return False + + def append(self, line): + """Adds a # comment line, to be parsed and added to current sect= ion""" + line =3D line[1:] + if not line: + self._append_freeform(line) + return + + if line[0] !=3D ' ': + raise QAPISchemaError(self.parser, "missing space after #") + line =3D line[1:] + + if self.symbol: + self._append_symbol_line(line) + elif (not self.body.content and + line.startswith("@") and line.endswith(":")): + self.symbol =3D 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 =3D line.split(' ', 1)[0] + + if name.startswith("@") and name.endswith(":"): + line =3D 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 =3D 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" % n= ame) + self.section =3D QAPIDoc.ArgSection(name) + self.args[name] =3D 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 =3D QAPIDoc.Section(name) + self.meta.append(self.section) + + def _append_freeform(self, line): + in_arg =3D 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 =3D self.body + self._append_freeform(line) + return + if in_arg or not self.section.name.startswith("Example"): + line =3D line.strip() + self.section.append(line) + + class QAPISchemaParser(object): =20 def __init__(self, fp, previously_included=3D[], incl_info=3DNone): @@ -137,11 +240,18 @@ class QAPISchemaParser(object): self.line =3D 1 self.line_pos =3D 0 self.exprs =3D [] + self.docs =3D [] self.accept() =20 while self.tok is not None: info =3D {'file': fname, 'line': self.line, 'parent': self.incl_info} + if self.tok =3D=3D '#' and self.val.startswith('##'): + doc =3D self.get_doc() + doc.info =3D info + self.docs.append(doc) + continue + expr =3D self.get_expr(False) if isinstance(expr, dict) and "include" in expr: if len(expr) !=3D 1: @@ -160,6 +270,7 @@ class QAPISchemaParser(object): raise QAPILineError(info, "Inclusion loop for %s= " % include) inf =3D 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 =3D QAPISchemaParser(fobj, previously_incl= uded, info) self.exprs.extend(exprs_include.exprs) + self.docs.extend(exprs_include.docs) else: expr_elem =3D {'expr': expr, 'info': info} + if self.docs and not self.docs[-1].expr: + self.docs[-1].expr =3D expr + expr_elem['doc'] =3D self.docs[-1] + self.exprs.append(expr_elem) =20 - def accept(self): + def get_doc(self): + if self.val !=3D '##': + raise QAPISchemaError(self, "Junk after '##' at start of " + "documentation comment") + + doc =3D QAPIDoc(self) + self.accept(False) + while self.tok =3D=3D '#': + if self.val.startswith('##'): + # End of doc comment + if self.val !=3D '##': + 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=3DTrue): while True: self.tok =3D self.src[self.cursor] self.pos =3D self.cursor @@ -184,7 +321,13 @@ class QAPISchemaParser(object): self.val =3D None =20 if self.tok =3D=3D '#': + if self.src[self.cursor] =3D=3D '#': + # Start of doc comment + skip_comment =3D False self.cursor =3D self.src.find('\n', self.cursor) + if not skip_comment: + self.val =3D self.src[self.pos:self.cursor] + return elif self.tok in "{}:,[]": return elif self.tok =3D=3D "'": @@ -713,7 +856,7 @@ def check_keys(expr_elem, meta, required, optional=3D= []): % (key, meta, name)) =20 =20 -def check_exprs(exprs): +def check_exprs(exprs, strict_doc): global all_names =20 # Learn the types and check for valid expression keys @@ -722,6 +865,11 @@ def check_exprs(exprs): for expr_elem in exprs: expr =3D expr_elem['expr'] info =3D expr_elem['info'] + + if strict_doc and 'doc' not in expr_elem: + raise QAPILineError(info, + "Expression missing documentation commen= t") + 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 =20 =20 +def check_simple_doc(doc): + if doc.symbol: + raise QAPILineError(doc.info, + "'%s' documention is not followed by the def= inition" + % doc.symbol) + + body =3D str(doc.body) + if re.search(r'@\S+:', body, re.MULTILINE): + raise QAPILineError(doc.info, + "Document body cannot contain @NAME: section= s") + + +def check_expr_doc(doc, expr, info): + for i in ('enum', 'union', 'alternate', 'struct', 'command', 'event'= ): + if i in expr: + meta =3D i + break + + name =3D expr[meta] + if doc.symbol !=3D name: + raise QAPILineError(info, "Definition of '%s' follows documentat= ion" + " for '%s'" % (name, doc.symbol)) + if doc.has_meta('Returns') and 'command' not in expr: + raise QAPILineError(info, "Invalid return documentation") + + doc_args =3D set(doc.args.keys()) + if meta =3D=3D 'union': + data =3D expr.get('base', []) + else: + data =3D expr.get('data', []) + if isinstance(data, dict): + data =3D data.keys() + args =3D set([name.strip('*') for name in data]) + if meta =3D=3D 'alternate' or \ + (meta =3D=3D '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 =3D str(section) + if not content or content.isspace(): + raise QAPILineError(doc.info, + "Empty doc section '%s'" % section.n= ame) + + 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): =20 =20 class QAPISchema(object): - def __init__(self, fname): + def __init__(self, fname, strict_doc=3DFalse): try: - self.exprs =3D check_exprs(QAPISchemaParser(open(fname, "r")= ).exprs) + parser =3D QAPISchemaParser(open(fname, "r")) + self.exprs =3D check_exprs(parser.exprs, strict_doc) + self.docs =3D check_docs(parser.docs) self._entity_dict =3D {} self._predefining =3D 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 fo= rmat""" +import re +import sys + +import qapi + +COMMAND_FMT =3D """ +@deftypefn {type} {{{ret}}} {name} @ +{{{args}}} + +{body} + +@end deftypefn + +""".format + +ENUM_FMT =3D """ +@deftp Enum {name} + +{body} + +@end deftp + +""".format + +STRUCT_FMT =3D """ +@deftp {{{type}}} {name} @ +{{{attrs}}} + +{body} + +@end deftp + +""".format + +EXAMPLE_FMT =3D """@example +{code} +@end 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 =3D subst_braces(doc).strip('\n') + return EXAMPLE_FMT(code=3Ddoc) + + +def texi_comment(doc): + """ + Format a comment + + Lines starting with: + - |: generates an @example + - =3D: generates @section + - =3D=3D: generates @subsection + - 1. or 1): generates an @enumerate @item + - o/*/-: generates an @itemize list + """ + lines =3D [] + doc =3D subst_braces(doc) + doc =3D subst_vars(doc) + doc =3D subst_emph(doc) + doc =3D subst_strong(doc) + inlist =3D "" + lastempty =3D False + for line in doc.split('\n'): + empty =3D line =3D=3D "" + + if line.startswith("| "): + line =3D EXAMPLE_FMT(code=3Dline[2:]) + elif line.startswith("=3D "): + line =3D "@section " + line[2:] + elif line.startswith("=3D=3D "): + line =3D "@subsection " + line[3:] + elif re.match("^([0-9]*[.)]) ", line): + if not inlist: + lines.append("@enumerate") + inlist =3D "enumerate" + line =3D 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 =3D "itemize" + lines.append("@item") + line =3D line[2:] + elif lastempty and inlist: + lines.append("@end %s\n" % inlist) + inlist =3D "" + + lastempty =3D empty + lines.append(line) + + if inlist: + lines.append("@end %s\n" % inlist) + return "\n".join(lines) + + +def texi_args(expr, key=3D"data"): + """ + Format the functions/structure/events.. arguments/members + """ + if key not in expr: + return "" + + args =3D expr[key] + arg_list =3D [] + if isinstance(args, str): + arg_list.append(args) + else: + for name, typ in args.iteritems(): + # optional arg + if name.startswith("*"): + name =3D 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 =3D "@table @asis\n" + for arg, section in doc.args.iteritems(): + desc =3D str(section) + opt =3D '' + if desc.startswith("#optional"): + desc =3D desc[10:] + opt =3D ' *' + elif desc.endswith("#optional"): + desc =3D desc[:-10] + opt =3D ' *' + body +=3D "@item @code{'%s'}%s\n%s\n" % (arg, opt, texi_comment(= desc)) + body +=3D "@end table\n" + body +=3D texi_comment(str(doc.body)) + + meta =3D sorted(doc.meta, key=3Dlambda i: _section_order(i.name)) + for section in meta: + name, doc =3D (section.name, str(section)) + func =3D texi_comment + if name.startswith("Example"): + func =3D texi_example + + body +=3D "\n@quotation %s\n%s\n@end quotation" % \ + (name, func(doc)) + return body + + +def texi_alternate(expr, doc): + """ + Format an alternate to texi + """ + args =3D texi_args(expr) + body =3D texi_body(doc) + return STRUCT_FMT(type=3D"Alternate", + name=3Ddoc.symbol, + attrs=3D"[ " + args + " ]", + body=3Dbody) + + +def texi_union(expr, doc): + """ + Format an union to texi + """ + attrs =3D "@{ " + texi_args(expr, "base") + " @}" + discriminator =3D expr.get("discriminator") + if not discriminator: + union =3D "Flat Union" + discriminator =3D "type" + else: + union =3D "Union" + attrs +=3D " + '%s' =3D [ " % discriminator + attrs +=3D texi_args(expr, "data") + " ]" + body =3D texi_body(doc) + + return STRUCT_FMT(type=3Dunion, + name=3Ddoc.symbol, + attrs=3Dattrs, + body=3Dbody) + + +def texi_enum(expr, doc): + """ + Format an enum to texi + """ + for i in expr['data']: + if i not in doc.args: + doc.args[i] =3D '' + body =3D texi_body(doc) + return ENUM_FMT(name=3Ddoc.symbol, + body=3Dbody) + + +def texi_struct(expr, doc): + """ + Format a struct to texi + """ + args =3D texi_args(expr) + body =3D texi_body(doc) + attrs =3D "@{ " + args + " @}" + base =3D expr.get("base") + if base: + attrs +=3D " + %s" % base + return STRUCT_FMT(type=3D"Struct", + name=3Ddoc.symbol, + attrs=3Dattrs, + body=3Dbody) + + +def texi_command(expr, doc): + """ + Format a command to texi + """ + args =3D texi_args(expr) + ret =3D expr["returns"] if "returns" in expr else "" + body =3D texi_body(doc) + return COMMAND_FMT(type=3D"Command", + name=3Ddoc.symbol, + ret=3Dret, + args=3D"(" + args + ")", + body=3Dbody) + + +def texi_event(expr, doc): + """ + Format an event to texi + """ + args =3D texi_args(expr) + body =3D texi_body(doc) + return COMMAND_FMT(type=3D"Event", + name=3Ddoc.symbol, + ret=3D"", + args=3D"(" + args + ")", + body=3Dbody) + + +def texi_expr(expr, doc): + """ + Format an expr to texi + """ + (kind, _) =3D expr.items()[0] + + fmt =3D {"command": texi_command, + "struct": texi_struct, + "enum": texi_enum, + "union": texi_union, + "alternate": texi_alternate, + "event": texi_event} + try: + fmt =3D 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 =3D [] + for doc in docs: + expr =3D doc.expr + if not expr: + res.append(texi_body(doc)) + continue + try: + doc =3D 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) !=3D 2: + print >>sys.stderr, "%s: need exactly 1 argument: SCHEMA" % argv= [0] + sys.exit(1) + + schema =3D qapi.QAPISchema(argv[1], strict_doc=3DTrue) + print texi(schema.docs) + + +if __name__ =3D=3D "__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. =20 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: =20 ## @@ -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'} } =20 +It's also possible to create documentation sections, such as: + + ## + # =3D Section + # =3D=3D 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 +- =3D are top section +- =3D=3D 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-b= ad-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 su= bset 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-b= ad-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 doc= umentation for 'food' diff --git a/tests/qapi-schema/doc-bad-symbol.exit b/tests/qapi-schema/do= c-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/do= c-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-schem= a/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-schem= a/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-sch= ema/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' s= ection diff --git a/tests/qapi-schema/doc-duplicated-return.exit b/tests/qapi-sc= hema/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-sc= hema/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-sch= ema/doc-duplicated-return.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/qapi-schema/doc-duplicated-since.err b/tests/qapi-sche= ma/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' sect= ion diff --git a/tests/qapi-schema/doc-duplicated-since.exit b/tests/qapi-sch= ema/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-sch= ema/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-sche= ma/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/d= oc-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/d= oc-empty-symbol.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/qapi-schema/doc-invalid-end.err b/tests/qapi-schema/do= c-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 e= nd with '##' diff --git a/tests/qapi-schema/doc-invalid-end.exit b/tests/qapi-schema/d= oc-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/d= oc-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/do= c-invalid-end.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/qapi-schema/doc-invalid-end2.err b/tests/qapi-schema/d= oc-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 d= ocumentation 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/d= oc-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 documentatio= n diff --git a/tests/qapi-schema/doc-invalid-return.exit b/tests/qapi-schem= a/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-schem= a/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-schem= a/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 conta= in @NAME: sections diff --git a/tests/qapi-schema/doc-invalid-section.exit b/tests/qapi-sche= ma/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-sche= ma/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-schem= a/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 o= f 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/d= oc-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 foll= owed 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/d= oc-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. =20 +## +# =3D Section +# =3D=3D 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' } } =20 @@ -123,6 +160,27 @@ 'data': {'ud1a': 'UserDefOne', '*ud1b': 'UserDefOne'}, 'returns': 'UserDefTwo' } =20 +## +# 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/q= api-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 -> Non= e gen=3DTrue success_response=3DTrue boxed=3DFalse command user_def_cmd2 q_obj_user_def_cmd2-arg -> UserDefTwo gen=3DTrue success_response=3DTrue boxed=3DFalse +doc freeform + body=3D +=3D Section +=3D=3D 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=3DTestStruct expr=3D('struct', 'TestStruct') + arg=3Dinteger +foo +blah + +bao + arg=3Dboolean +bar + arg=3Dstring +baz + meta=3DExample +-> { "execute": ... } +<- { "return": ... } + meta=3DSince +2.3 + meta=3DNote +a note + body=3D +body with @var +doc freeform + body=3D +Another comment +doc symbol=3Dguest-get-time expr=3D('command', 'guest-get-time') + arg=3Da +an integer + arg=3Db +#optional integer + meta=3DReturns +returns something + meta=3DExample +-> { "execute": "guest-get-time", ... } +<- { "return": "42" } + body=3D +@guest-get-time 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): =20 schema =3D QAPISchema(sys.argv[1]) schema.visit(QAPISchemaTestVisitor()) + +for doc in schema.docs: + if doc.symbol: + print 'doc symbol=3D%s expr=3D%s' % \ + (doc.symbol, doc.expr.items()[0]) + else: + print 'doc freeform' + for arg, section in doc.args.iteritems(): + print ' arg=3D%s\n%s' % (arg, section) + for section in doc.meta: + print ' meta=3D%s\n%s' % (section.name, section) + print ' body=3D\n%s' % doc.body --=20 2.10.0