All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed)
@ 2017-01-09 14:34 Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 01/21] qapi: replace 'o' for list items Marc-André Lureau
                   ` (21 more replies)
  0 siblings, 22 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Add a qapi2texi script to generate the documentation from the qapi
schemas.

The SQUASHED patch in this series is a squashed version of the
documentation move from qmp-commands.txt to the schemas. The whole
version (not sent on the ML to avoid spamming) is in the following git
branch: https://github.com/elmarco/qemu/commits/qapi-doc

PDF preview:
https://fedorapeople.org/~elmarco/qemu-qmp-ref.pdf (v7 without type info)

v7: after Markus review of v6
- removed type information, doc and syntax (post-poned)
- keep original documentation order (consequently,
  tests/qapi-schema/doc-bad-section.json is now accepted)
- removed "qapi: use a QAPIParseError in parser" patch
- removed "reorder documentation body" patch
- removed 'o' support for list items
- added several TODOs
- added a few preliminary patches for the new changes
- improved some error messages
- updated tests corresponding to the changes above
- added r-b tags

v6:
- rebased on top of armbru/qapi-next branch
- add a few patches to improve Exception subclasses and usage in
  qapi.py as suggested during review
- parser and generator fixes and improvements after v5 review:
  - various union improvements, hopefully with a better syntax
  - improve error messages
  - improve docs/qapi-code-gen.txt documentation section
  - do not allow interleaved body documentation between sections
  - more tests for new cases
  - make expression documentation mandatory, fix the tests
  - replace bad usage of @var{} with @t{} in texi, fix texi2pod to
    handle it
  - renaming, reordering etc..
- add docs/qapi-syntax.texi to describe the API syntax used in the
  texi documentation
- fix interleaved body and section documentation
- improve documentation sections name
- many build-sys improvements after review
- fix and improve commit messages, update R-b tags

v5:
- many parser and generator fixes and improvements after v4 review:
 - simplified current section handling by using a Section object
 - adding a line is more stateful: either freeform or symbol comment
 - always check_docs() when parsing with QAPISchema
 - simplified some code and comments
 - do not break current section on empty line, but break after a non
   indented paragraph in an argument section. This seems to reflects
   the way documentation is written:

   ##
   # @foo:
   # @arg: fluctuat nec mergitur
   #       - continues here
   #
   #       Since: 1853
   #
   # Body
   #
   ##

   Other sections (Note/Examples etc) are not indented (it seems), but
   could use a similar rule. I prefer to keep this only for args, for
   styling reasons (bikeshedding?).

- better handling of flat-union in generator
- list all enum values (even when not documented)
- added qapi-doc parsing tests and more error checking
- pep8/pylint fixes
- some more schema doc fixes
- do not move logo to docs/

v4:
- more device_add schema fixes
- do not merge docs/qmp-intro.txt in qemu-qmp-ref.texi
- remove needless @ifinfo, add GPL copying text
- added qemu logo to pdf
- added some r-b tags

v3:
- many improvements to the doc parser:
  - throws an error in various malformated conditions
  - allows multiple meta-sections, except for "Since:" and "Return:"
  - build a list of docs, instead of attaching docs to expressions
  - accept() breaks on new doc block, and get_doc() returns a QAPIDoc
- fix more documentation to fit the new parser
- use a master texi file that includes the generated file, instead of
  templated texi file
- texi fixes after Markus review
- only build and install html and man pages by default
- fix .gitignore

v2:
- change licence to be lgpl2+
- fix some comments & commit message
- add more code comments
- improve the doc parsing to treat only "Since" as a special case not
  requiring ":" (common notation in the doc)
- include some early schema doc fixes (to fix generated doc)
- include the squashed version of the doc move
- include the man page and installation build changes

Marc-André Lureau (21):
  qapi: replace 'o' for list items
  qapi: move QKeyCode doc body at the top
  qapi: make TODOs named-sections
  qapi: improve device_add schema
  qapi: improve TransactionAction doc
  qga/schema: improve guest-set-vcpus Returns: section
  qapi: avoid interleaving sections and parameters
  qapi: move experimental note down
  qapi: add some sections in docs
  docs: add master qapi texi files
  qapi: rework qapi Exception
  qapi.py: fix line break before binary operator pep8
  texi2pod: learn quotation, deftp and deftypefn
  (SQUASHED) move doc to schema
  qapi: add qapi2texi script
  docs: add qemu logo to pdf
  build-sys: use --no-split for info
  build-sys: remove dvi doc generation
  build-sys: use a generic TEXI2MAN rule
  build-sys: add txt documentation rules
  build-sys: add qapi doc generation targets

 qapi-schema.json                                   | 1338 ++++++-
 qapi/block-core.json                               |  700 +++-
 qapi/block.json                                    |   66 +-
 qapi/common.json                                   |   38 +-
 qapi/crypto.json                                   |    5 +-
 qapi/event.json                                    |  248 +-
 qapi/introspect.json                               |    5 +-
 qapi/rocker.json                                   |   61 +-
 qapi/trace.json                                    |   17 +
 scripts/qapi.py                                    |  583 ++-
 scripts/qapi2texi.py                               |  266 ++
 .gitignore                                         |   11 +-
 Makefile                                           |   94 +-
 configure                                          |    2 +-
 docs/qapi-code-gen.txt                             |  174 +-
 docs/qemu-ga-ref.texi                              |   78 +
 docs/qemu-qmp-ref.texi                             |   78 +
 docs/qemu_logo.pdf                                 |  Bin 0 -> 9117 bytes
 docs/qmp-commands.txt                              | 3824 --------------------
 docs/qmp-events.txt                                |  731 ----
 docs/qmp-intro.txt                                 |    3 +-
 qga/qapi-schema.json                               |   11 +-
 rules.mak                                          |   12 +
 scripts/texi2pod.pl                                |   54 +-
 tests/Makefile.include                             |   20 +
 tests/qapi-schema/alternate-any.err                |    2 +-
 tests/qapi-schema/alternate-any.json               |    4 +
 tests/qapi-schema/alternate-array.err              |    2 +-
 tests/qapi-schema/alternate-array.json             |    7 +
 tests/qapi-schema/alternate-base.err               |    2 +-
 tests/qapi-schema/alternate-base.json              |    7 +
 tests/qapi-schema/alternate-clash.err              |    2 +-
 tests/qapi-schema/alternate-clash.json             |    4 +
 tests/qapi-schema/alternate-conflict-dict.err      |    2 +-
 tests/qapi-schema/alternate-conflict-dict.json     |   10 +
 tests/qapi-schema/alternate-conflict-string.err    |    2 +-
 tests/qapi-schema/alternate-conflict-string.json   |    7 +
 tests/qapi-schema/alternate-empty.err              |    2 +-
 tests/qapi-schema/alternate-empty.json             |    4 +
 tests/qapi-schema/alternate-nested.err             |    2 +-
 tests/qapi-schema/alternate-nested.json            |    7 +
 tests/qapi-schema/alternate-unknown.err            |    2 +-
 tests/qapi-schema/alternate-unknown.json           |    4 +
 tests/qapi-schema/args-alternate.err               |    2 +-
 tests/qapi-schema/args-alternate.json              |    8 +
 tests/qapi-schema/args-any.err                     |    2 +-
 tests/qapi-schema/args-any.json                    |    4 +
 tests/qapi-schema/args-array-empty.err             |    2 +-
 tests/qapi-schema/args-array-empty.json            |    4 +
 tests/qapi-schema/args-array-unknown.err           |    2 +-
 tests/qapi-schema/args-array-unknown.json          |    4 +
 tests/qapi-schema/args-bad-boxed.err               |    2 +-
 tests/qapi-schema/args-bad-boxed.json              |    4 +
 tests/qapi-schema/args-boxed-anon.err              |    2 +-
 tests/qapi-schema/args-boxed-anon.json             |    4 +
 tests/qapi-schema/args-boxed-empty.err             |    2 +-
 tests/qapi-schema/args-boxed-empty.json            |    8 +
 tests/qapi-schema/args-boxed-string.err            |    2 +-
 tests/qapi-schema/args-boxed-string.json           |    4 +
 tests/qapi-schema/args-int.err                     |    2 +-
 tests/qapi-schema/args-int.json                    |    4 +
 tests/qapi-schema/args-invalid.err                 |    2 +-
 tests/qapi-schema/args-invalid.json                |    3 +
 tests/qapi-schema/args-member-array-bad.err        |    2 +-
 tests/qapi-schema/args-member-array-bad.json       |    4 +
 tests/qapi-schema/args-member-case.err             |    2 +-
 tests/qapi-schema/args-member-case.json            |    4 +
 tests/qapi-schema/args-member-unknown.err          |    2 +-
 tests/qapi-schema/args-member-unknown.json         |    4 +
 tests/qapi-schema/args-name-clash.err              |    2 +-
 tests/qapi-schema/args-name-clash.json             |    4 +
 tests/qapi-schema/args-union.err                   |    2 +-
 tests/qapi-schema/args-union.json                  |    7 +
 tests/qapi-schema/args-unknown.err                 |    2 +-
 tests/qapi-schema/args-unknown.json                |    4 +
 tests/qapi-schema/bad-base.err                     |    2 +-
 tests/qapi-schema/bad-base.json                    |    7 +
 tests/qapi-schema/bad-data.err                     |    2 +-
 tests/qapi-schema/bad-data.json                    |    4 +
 tests/qapi-schema/bad-ident.err                    |    2 +-
 tests/qapi-schema/bad-ident.json                   |    4 +
 tests/qapi-schema/bad-type-bool.err                |    2 +-
 tests/qapi-schema/bad-type-bool.json               |    4 +
 tests/qapi-schema/bad-type-dict.err                |    2 +-
 tests/qapi-schema/bad-type-dict.json               |    4 +
 tests/qapi-schema/base-cycle-direct.err            |    2 +-
 tests/qapi-schema/base-cycle-direct.json           |    4 +
 tests/qapi-schema/base-cycle-indirect.err          |    2 +-
 tests/qapi-schema/base-cycle-indirect.json         |    7 +
 tests/qapi-schema/command-int.err                  |    2 +-
 tests/qapi-schema/command-int.json                 |    4 +
 tests/qapi-schema/comments.json                    |    4 +
 tests/qapi-schema/comments.out                     |    3 +
 tests/qapi-schema/doc-bad-args.err                 |    1 +
 tests/qapi-schema/doc-bad-args.exit                |    1 +
 tests/qapi-schema/doc-bad-args.json                |    8 +
 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              |    6 +
 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          |    7 +
 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       |    8 +
 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        |    8 +
 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               |    6 +
 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           |    8 +
 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            |    5 +
 tests/qapi-schema/doc-empty-symbol.out             |    0
 tests/qapi-schema/doc-interleaved-section.err      |    1 +
 tests/qapi-schema/doc-interleaved-section.exit     |    1 +
 tests/qapi-schema/doc-interleaved-section.json     |   21 +
 tests/qapi-schema/doc-interleaved-section.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             |    5 +
 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            |    5 +
 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          |    7 +
 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         |    6 +
 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           |    5 +
 tests/qapi-schema/doc-invalid-start.out            |    0
 tests/qapi-schema/doc-missing-colon.err            |    1 +
 tests/qapi-schema/doc-missing-colon.exit           |    1 +
 tests/qapi-schema/doc-missing-colon.json           |    5 +
 tests/qapi-schema/doc-missing-colon.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            |    5 +
 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           |    6 +
 tests/qapi-schema/doc-missing-space.out            |    0
 tests/qapi-schema/doc-optional.err                 |    1 +
 tests/qapi-schema/doc-optional.exit                |    1 +
 tests/qapi-schema/doc-optional.json                |    7 +
 tests/qapi-schema/doc-optional.out                 |    0
 tests/qapi-schema/double-type.err                  |    2 +-
 tests/qapi-schema/double-type.json                 |    4 +
 tests/qapi-schema/enum-bad-name.err                |    2 +-
 tests/qapi-schema/enum-bad-name.json               |    4 +
 tests/qapi-schema/enum-bad-prefix.err              |    2 +-
 tests/qapi-schema/enum-bad-prefix.json             |    4 +
 tests/qapi-schema/enum-clash-member.err            |    2 +-
 tests/qapi-schema/enum-clash-member.json           |    4 +
 tests/qapi-schema/enum-dict-member.err             |    2 +-
 tests/qapi-schema/enum-dict-member.json            |    4 +
 tests/qapi-schema/enum-member-case.err             |    2 +-
 tests/qapi-schema/enum-member-case.json            |    7 +
 tests/qapi-schema/enum-missing-data.err            |    2 +-
 tests/qapi-schema/enum-missing-data.json           |    4 +
 tests/qapi-schema/enum-wrong-data.err              |    2 +-
 tests/qapi-schema/enum-wrong-data.json             |    4 +
 tests/qapi-schema/event-boxed-empty.err            |    2 +-
 tests/qapi-schema/event-boxed-empty.json           |    4 +
 tests/qapi-schema/event-case.json                  |    4 +
 tests/qapi-schema/event-case.out                   |    3 +
 tests/qapi-schema/event-nest-struct.err            |    2 +-
 tests/qapi-schema/event-nest-struct.json           |    4 +
 tests/qapi-schema/flat-union-array-branch.err      |    2 +-
 tests/qapi-schema/flat-union-array-branch.json     |   12 +
 tests/qapi-schema/flat-union-bad-base.err          |    2 +-
 tests/qapi-schema/flat-union-bad-base.json         |   13 +
 tests/qapi-schema/flat-union-bad-discriminator.err |    2 +-
 .../qapi-schema/flat-union-bad-discriminator.json  |   16 +
 tests/qapi-schema/flat-union-base-any.err          |    2 +-
 tests/qapi-schema/flat-union-base-any.json         |   13 +
 tests/qapi-schema/flat-union-base-union.err        |    2 +-
 tests/qapi-schema/flat-union-base-union.json       |   16 +
 tests/qapi-schema/flat-union-clash-member.err      |    2 +-
 tests/qapi-schema/flat-union-clash-member.json     |   16 +
 tests/qapi-schema/flat-union-empty.err             |    2 +-
 tests/qapi-schema/flat-union-empty.json            |   10 +
 tests/qapi-schema/flat-union-incomplete-branch.err |    2 +-
 .../qapi-schema/flat-union-incomplete-branch.json  |   10 +
 tests/qapi-schema/flat-union-inline.err            |    2 +-
 tests/qapi-schema/flat-union-inline.json           |   10 +
 tests/qapi-schema/flat-union-int-branch.err        |    2 +-
 tests/qapi-schema/flat-union-int-branch.json       |   13 +
 .../qapi-schema/flat-union-invalid-branch-key.err  |    2 +-
 .../qapi-schema/flat-union-invalid-branch-key.json |   15 +
 .../flat-union-invalid-discriminator.err           |    2 +-
 .../flat-union-invalid-discriminator.json          |   15 +
 tests/qapi-schema/flat-union-no-base.err           |    2 +-
 tests/qapi-schema/flat-union-no-base.json          |   13 +
 .../flat-union-optional-discriminator.err          |    2 +-
 .../flat-union-optional-discriminator.json         |   13 +
 .../flat-union-string-discriminator.err            |    2 +-
 .../flat-union-string-discriminator.json           |   15 +
 tests/qapi-schema/ident-with-escape.json           |    4 +
 tests/qapi-schema/ident-with-escape.out            |    3 +
 tests/qapi-schema/include-relpath-sub.json         |    3 +
 tests/qapi-schema/include-relpath.out              |    3 +
 tests/qapi-schema/include-repetition.out           |    3 +
 tests/qapi-schema/include-simple-sub.json          |    3 +
 tests/qapi-schema/include-simple.out               |    3 +
 tests/qapi-schema/indented-expr.json               |    6 +
 tests/qapi-schema/indented-expr.out                |    6 +
 tests/qapi-schema/missing-type.err                 |    2 +-
 tests/qapi-schema/missing-type.json                |    4 +
 tests/qapi-schema/nested-struct-data.err           |    2 +-
 tests/qapi-schema/nested-struct-data.json          |    4 +
 tests/qapi-schema/qapi-schema-test.json            |  213 ++
 tests/qapi-schema/qapi-schema-test.out             |  212 ++
 tests/qapi-schema/redefined-builtin.err            |    2 +-
 tests/qapi-schema/redefined-builtin.json           |    4 +
 tests/qapi-schema/redefined-command.err            |    2 +-
 tests/qapi-schema/redefined-command.json           |    7 +
 tests/qapi-schema/redefined-event.err              |    2 +-
 tests/qapi-schema/redefined-event.json             |    7 +
 tests/qapi-schema/redefined-type.err               |    2 +-
 tests/qapi-schema/redefined-type.json              |    7 +
 tests/qapi-schema/reserved-command-q.err           |    2 +-
 tests/qapi-schema/reserved-command-q.json          |    7 +
 tests/qapi-schema/reserved-enum-q.err              |    2 +-
 tests/qapi-schema/reserved-enum-q.json             |    4 +
 tests/qapi-schema/reserved-member-has.err          |    2 +-
 tests/qapi-schema/reserved-member-has.json         |    4 +
 tests/qapi-schema/reserved-member-q.err            |    2 +-
 tests/qapi-schema/reserved-member-q.json           |    4 +
 tests/qapi-schema/reserved-member-u.err            |    2 +-
 tests/qapi-schema/reserved-member-u.json           |    4 +
 tests/qapi-schema/reserved-member-underscore.err   |    2 +-
 tests/qapi-schema/reserved-member-underscore.json  |    4 +
 tests/qapi-schema/reserved-type-kind.err           |    2 +-
 tests/qapi-schema/reserved-type-kind.json          |    4 +
 tests/qapi-schema/reserved-type-list.err           |    2 +-
 tests/qapi-schema/reserved-type-list.json          |    4 +
 tests/qapi-schema/returns-alternate.err            |    2 +-
 tests/qapi-schema/returns-alternate.json           |    7 +
 tests/qapi-schema/returns-array-bad.err            |    2 +-
 tests/qapi-schema/returns-array-bad.json           |    4 +
 tests/qapi-schema/returns-dict.err                 |    2 +-
 tests/qapi-schema/returns-dict.json                |    4 +
 tests/qapi-schema/returns-unknown.err              |    2 +-
 tests/qapi-schema/returns-unknown.json             |    4 +
 tests/qapi-schema/returns-whitelist.err            |    2 +-
 tests/qapi-schema/returns-whitelist.json           |   16 +
 tests/qapi-schema/struct-base-clash-deep.err       |    2 +-
 tests/qapi-schema/struct-base-clash-deep.json      |   10 +
 tests/qapi-schema/struct-base-clash.err            |    2 +-
 tests/qapi-schema/struct-base-clash.json           |    7 +
 tests/qapi-schema/struct-data-invalid.err          |    2 +-
 tests/qapi-schema/struct-data-invalid.json         |    3 +
 tests/qapi-schema/struct-member-invalid.err        |    2 +-
 tests/qapi-schema/struct-member-invalid.json       |    3 +
 tests/qapi-schema/test-qapi.py                     |   12 +
 tests/qapi-schema/type-bypass-bad-gen.err          |    2 +-
 tests/qapi-schema/type-bypass-bad-gen.json         |    4 +
 tests/qapi-schema/unicode-str.err                  |    2 +-
 tests/qapi-schema/unicode-str.json                 |    4 +
 tests/qapi-schema/union-base-no-discriminator.err  |    2 +-
 tests/qapi-schema/union-base-no-discriminator.json |   12 +
 tests/qapi-schema/union-branch-case.err            |    2 +-
 tests/qapi-schema/union-branch-case.json           |    4 +
 tests/qapi-schema/union-clash-branches.err         |    2 +-
 tests/qapi-schema/union-clash-branches.json        |    4 +
 tests/qapi-schema/union-empty.err                  |    2 +-
 tests/qapi-schema/union-empty.json                 |    4 +
 tests/qapi-schema/union-invalid-base.err           |    2 +-
 tests/qapi-schema/union-invalid-base.json          |   10 +
 tests/qapi-schema/union-optional-branch.err        |    2 +-
 tests/qapi-schema/union-optional-branch.json       |    4 +
 tests/qapi-schema/union-unknown.err                |    2 +-
 tests/qapi-schema/union-unknown.json               |    4 +
 tests/qapi-schema/unknown-escape.err               |    2 +-
 tests/qapi-schema/unknown-escape.json              |    4 +
 tests/qapi-schema/unknown-expr-key.err             |    2 +-
 tests/qapi-schema/unknown-expr-key.json            |    4 +
 297 files changed, 4832 insertions(+), 5013 deletions(-)
 create mode 100755 scripts/qapi2texi.py
 create mode 100644 docs/qemu-ga-ref.texi
 create mode 100644 docs/qemu-qmp-ref.texi
 create mode 100644 docs/qemu_logo.pdf
 delete mode 100644 docs/qmp-commands.txt
 delete mode 100644 docs/qmp-events.txt
 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-interleaved-section.err
 create mode 100644 tests/qapi-schema/doc-interleaved-section.exit
 create mode 100644 tests/qapi-schema/doc-interleaved-section.json
 create mode 100644 tests/qapi-schema/doc-interleaved-section.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-colon.err
 create mode 100644 tests/qapi-schema/doc-missing-colon.exit
 create mode 100644 tests/qapi-schema/doc-missing-colon.json
 create mode 100644 tests/qapi-schema/doc-missing-colon.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
 create mode 100644 tests/qapi-schema/doc-optional.err
 create mode 100644 tests/qapi-schema/doc-optional.exit
 create mode 100644 tests/qapi-schema/doc-optional.json
 create mode 100644 tests/qapi-schema/doc-optional.out

-- 
2.11.0

^ permalink raw reply	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 01/21] qapi: replace 'o' for list items
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-10  8:49   ` Markus Armbruster
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 02/21] qapi: move QKeyCode doc body at the top Marc-André Lureau
                   ` (20 subsequent siblings)
  21 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Replace with '*', the common form for list items.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi-schema.json | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/qapi-schema.json b/qapi-schema.json
index a0d3b5d7c5..3bdc6d8869 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1883,11 +1883,11 @@
 #
 #        Known limitations:
 #
-#        o This command is stateless, this means that commands that depend
+#        * This command is stateless, this means that commands that depend
 #          on state information (such as getfd) might not work
 #
-#       o Commands that prompt the user for data (eg. 'cont' when the block
-#         device is encrypted) don't currently work
+#        * Commands that prompt the user for data (eg. 'cont' when the block
+#          device is encrypted) don't currently work
 ##
 { 'command': 'human-monitor-command',
   'data': {'command-line': 'str', '*cpu-index': 'int'},
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 02/21] qapi: move QKeyCode doc body at the top
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 01/21] qapi: replace 'o' for list items Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-10  8:51   ` Markus Armbruster
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 03/21] qapi: make TODOs named-sections Marc-André Lureau
                   ` (19 subsequent siblings)
  21 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Following documentation guidelines.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi-schema.json | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/qapi-schema.json b/qapi-schema.json
index 3bdc6d8869..2990efa95d 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3612,6 +3612,10 @@
 ##
 # @QKeyCode:
 #
+# An enumeration of key name.
+#
+# This is used by the @send-key command.
+#
 # @unmapped: since 2.0
 # @pause: since 2.0
 # @ro: since 2.4
@@ -3619,10 +3623,6 @@
 # @kp_equals: since 2.6
 # @power: since 2.6
 #
-# An enumeration of key name.
-#
-# This is used by the send-key command.
-#
 # Since: 1.3.0
 #
 ##
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 03/21] qapi: make TODOs named-sections
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 01/21] qapi: replace 'o' for list items Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 02/21] qapi: move QKeyCode doc body at the top Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-10  9:11   ` Markus Armbruster
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 04/21] qapi: improve device_add schema Marc-André Lureau
                   ` (18 subsequent siblings)
  21 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Have the TODO in the TAG: format, so they will stand out in the
generated documentation.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi-schema.json     | 4 ++--
 qapi/introspect.json | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/qapi-schema.json b/qapi-schema.json
index 2990efa95d..5733487fc4 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2314,7 +2314,7 @@
 #                     "mac": "52:54:00:12:34:56" } }
 # <- { "return": {} }
 #
-# TODO This command effectively bypasses QAPI completely due to its
+# TODO: This command effectively bypasses QAPI completely due to its
 # "additional arguments" business.  It shouldn't have been added to
 # the schema in this form.  It should be qapified properly, or
 # replaced by a properly qapified command.
@@ -2515,7 +2515,7 @@
 #
 # Additional arguments depend on the type.
 #
-# TODO This command effectively bypasses QAPI completely due to its
+# TODO: This command effectively bypasses QAPI completely due to its
 # "additional arguments" business.  It shouldn't have been added to
 # the schema in this form.  It should be qapified properly, or
 # replaced by a properly qapified command.
diff --git a/qapi/introspect.json b/qapi/introspect.json
index fd4dc84196..464097a235 100644
--- a/qapi/introspect.json
+++ b/qapi/introspect.json
@@ -258,7 +258,7 @@
 #
 # @ret-type: the name of the command's result type.
 #
-# TODO @success-response (currently irrelevant, because it's QGA, not QMP)
+# TODO: @success-response (currently irrelevant, because it's QGA, not QMP)
 #
 # Since: 2.5
 ##
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 04/21] qapi: improve device_add schema
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (2 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 03/21] qapi: make TODOs named-sections Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 05/21] qapi: improve TransactionAction doc Marc-André Lureau
                   ` (17 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

'device_add' is still incomplete for now, but we can fix a few
arguments:
- 'bus' is a common argument, regardless of the device
- 'id' is an optional argument

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
---
 qapi-schema.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qapi-schema.json b/qapi-schema.json
index 5733487fc4..85efdfccd8 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2292,7 +2292,7 @@
 #
 # @bus: #optional the device's parent bus (device tree path)
 #
-# @id: the device's ID, must be unique
+# @id: #optional the device's ID, must be unique
 #
 # Additional arguments depend on the type.
 #
@@ -2322,7 +2322,7 @@
 # Since: 0.13
 ##
 { 'command': 'device_add',
-  'data': {'driver': 'str', 'id': 'str'},
+  'data': {'driver': 'str', '*bus': 'str', '*id': 'str'},
   'gen': false } # so we can get the additional arguments
 
 ##
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 05/21] qapi: improve TransactionAction doc
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (3 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 04/21] qapi: improve device_add schema Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 06/21] qga/schema: improve guest-set-vcpus Returns: section Marc-André Lureau
                   ` (16 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

TransactionAction is a flat union, document 'type' versions
exhaustively, and sort the members.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 qapi-schema.json | 31 ++++++++++++++++---------------
 1 file changed, 16 insertions(+), 15 deletions(-)

diff --git a/qapi-schema.json b/qapi-schema.json
index 85efdfccd8..ac6352fcf9 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1795,28 +1795,29 @@
 # @TransactionAction:
 #
 # A discriminated record of operations that can be performed with
-# @transaction.
+# @transaction. Action @type can be:
 #
-# Since: 1.1
+# - @abort: since 1.6
+# - @block-dirty-bitmap-add: since 2.5
+# - @block-dirty-bitmap-clear: since 2.5
+# - @blockdev-backup: since 2.3
+# - @blockdev-snapshot: since 2.5
+# - @blockdev-snapshot-internal-sync: since 1.7
+# - @blockdev-snapshot-sync: since 1.1
+# - @drive-backup: since 1.6
 #
-# drive-backup since 1.6
-# abort since 1.6
-# blockdev-snapshot-internal-sync since 1.7
-# blockdev-backup since 2.3
-# blockdev-snapshot since 2.5
-# block-dirty-bitmap-add since 2.5
-# block-dirty-bitmap-clear since 2.5
+# Since: 1.1
 ##
 { 'union': 'TransactionAction',
   'data': {
-       'blockdev-snapshot': 'BlockdevSnapshot',
-       'blockdev-snapshot-sync': 'BlockdevSnapshotSync',
-       'drive-backup': 'DriveBackup',
-       'blockdev-backup': 'BlockdevBackup',
        'abort': 'Abort',
-       'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal',
        'block-dirty-bitmap-add': 'BlockDirtyBitmapAdd',
-       'block-dirty-bitmap-clear': 'BlockDirtyBitmap'
+       'block-dirty-bitmap-clear': 'BlockDirtyBitmap',
+       'blockdev-backup': 'BlockdevBackup',
+       'blockdev-snapshot': 'BlockdevSnapshot',
+       'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal',
+       'blockdev-snapshot-sync': 'BlockdevSnapshotSync',
+       'drive-backup': 'DriveBackup'
    } }
 
 ##
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 06/21] qga/schema: improve guest-set-vcpus Returns: section
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (4 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 05/21] qapi: improve TransactionAction doc Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 07/21] qapi: avoid interleaving sections and parameters Marc-André Lureau
                   ` (15 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Itemize the possible return values of guest-set-vcpus.

Drop the blank lines for consistency with itemized
lists elsewhere.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 qga/qapi-schema.json | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index 94c03128fd..d421609dcb 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -697,21 +697,18 @@
 # Returns: The length of the initial sublist that has been successfully
 #          processed. The guest agent maximizes this value. Possible cases:
 #
-#          0:                if the @vcpus list was empty on input. Guest state
+#          - 0:              if the @vcpus list was empty on input. Guest state
 #                            has not been changed. Otherwise,
-#
-#          Error:            processing the first node of @vcpus failed for the
+#          - Error:          processing the first node of @vcpus failed for the
 #                            reason returned. Guest state has not been changed.
 #                            Otherwise,
-#
-#          < length(@vcpus): more than zero initial nodes have been processed,
+#          - < length(@vcpus): more than zero initial nodes have been processed,
 #                            but not the entire @vcpus list. Guest state has
 #                            changed accordingly. To retrieve the error
 #                            (assuming it persists), repeat the call with the
 #                            successfully processed initial sublist removed.
 #                            Otherwise,
-#
-#          length(@vcpus):   call successful.
+#          - length(@vcpus): call successful.
 #
 # Since: 1.5
 ##
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 07/21] qapi: avoid interleaving sections and parameters
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (5 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 06/21] qga/schema: improve guest-set-vcpus Returns: section Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-10 10:32   ` Markus Armbruster
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 08/21] qapi: move experimental note down Marc-André Lureau
                   ` (14 subsequent siblings)
  21 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Follow documentation guideline, body, parameters then additional
sections.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi-schema.json     | 10 +++++-----
 qapi/event.json      |  4 ++--
 qapi/introspect.json |  3 +--
 3 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/qapi-schema.json b/qapi-schema.json
index ac6352fcf9..10a3506528 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -4729,17 +4729,17 @@
 # it should be passed by management with device_add command when
 # a CPU is being hotplugged.
 #
+# @node-id: #optional NUMA node ID the CPU belongs to
+# @socket-id: #optional socket number within node/board the CPU belongs to
+# @core-id: #optional core number within socket the CPU belongs to
+# @thread-id: #optional thread number within core the CPU belongs to
+#
 # Note: currently there are 4 properties that could be present
 # but management should be prepared to pass through other
 # properties with device_add command to allow for future
 # interface extension. This also requires the filed names to be kept in
 # sync with the properties passed to -device/device_add.
 #
-# @node-id: #optional NUMA node ID the CPU belongs to
-# @socket-id: #optional socket number within node/board the CPU belongs to
-# @core-id: #optional core number within socket the CPU belongs to
-# @thread-id: #optional thread number within core the CPU belongs to
-#
 # Since: 2.7
 ##
 { 'struct': 'CpuInstanceProperties',
diff --git a/qapi/event.json b/qapi/event.json
index 37bf34ed6d..c7689b211d 100644
--- a/qapi/event.json
+++ b/qapi/event.json
@@ -272,9 +272,9 @@
 #
 # Emitted when guest executes ACPI _OST method.
 #
-# Since: 2.1
-#
 # @info: ACPIOSTInfo type as described in qapi-schema.json
+#
+# Since: 2.1
 ##
 { 'event': 'ACPI_DEVICE_OST',
      'data': { 'info': 'ACPIOSTInfo' } }
diff --git a/qapi/introspect.json b/qapi/introspect.json
index 464097a235..f6adc439bb 100644
--- a/qapi/introspect.json
+++ b/qapi/introspect.json
@@ -78,14 +78,13 @@
 # @SchemaInfo:
 #
 # @name: the entity's name, inherited from @base.
+#        The SchemaInfo is always referenced by this name.
 #        Commands and events have the name defined in the QAPI schema.
 #        Unlike command and event names, type names are not part of
 #        the wire ABI.  Consequently, type names are meaningless
 #        strings here, although they are still guaranteed unique
 #        regardless of @meta-type.
 #
-# All references to other SchemaInfo are by name.
-#
 # @meta-type: the entity's meta type, inherited from @base.
 #
 # Additional members depend on the value of @meta-type.
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 08/21] qapi: move experimental note down
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (6 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 07/21] qapi: avoid interleaving sections and parameters Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-10 12:08   ` Markus Armbruster
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 09/21] qapi: add some sections in docs Marc-André Lureau
                   ` (13 subsequent siblings)
  21 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Use a better 'Note:' section, move it below parameters following
guidelines.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/block-core.json | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/qapi/block-core.json b/qapi/block-core.json
index ec1da2a29a..b611bd6f44 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -2440,12 +2440,12 @@
 # BlockBackend will be created; otherwise, @node-name is mandatory at the top
 # level and no BlockBackend will be created.
 #
-# This command is still a work in progress.  It doesn't support all
+# For the arguments, see the documentation of BlockdevOptions.
+#
+# Note: This command is still a work in progress.  It doesn't support all
 # block drivers among other things.  Stay away from it unless you want
 # to help with its development.
 #
-# For the arguments, see the documentation of BlockdevOptions.
-#
 # Since: 1.7
 ##
 { 'command': 'blockdev-add', 'data': 'BlockdevOptions', 'boxed': true }
@@ -2457,12 +2457,12 @@
 # The command will fail if the node is attached to a device or is
 # otherwise being used.
 #
-# This command is still a work in progress and is considered
+# @node-name: Name of the graph node to delete.
+#
+# Note: This command is still a work in progress and is considered
 # experimental. Stay away from it unless you want to help with its
 # development.
 #
-# @node-name: Name of the graph node to delete.
-#
 # Since: 2.5
 ##
 { 'command': 'x-blockdev-del', 'data': { 'node-name': 'str' } }
@@ -2529,13 +2529,13 @@
 #
 # If the tray is open and there is no medium inserted, this will be a no-op.
 #
-# This command is still a work in progress and is considered experimental.
-# Stay away from it unless you want to help with its development.
-#
 # @device: #optional Block device name (deprecated, use @id instead)
 #
 # @id:     #optional The name or QOM path of the guest device (since: 2.8)
 #
+# Note: This command is still a work in progress and is considered experimental.
+# Stay away from it unless you want to help with its development.
+#
 # Since: 2.5
 ##
 { 'command': 'x-blockdev-remove-medium',
@@ -2549,15 +2549,15 @@
 # device's tray must currently be open (unless there is no attached guest
 # device) and there must be no medium inserted already.
 #
-# This command is still a work in progress and is considered experimental.
-# Stay away from it unless you want to help with its development.
-#
 # @device:    #optional Block device name (deprecated, use @id instead)
 #
 # @id:        #optional The name or QOM path of the guest device (since: 2.8)
 #
 # @node-name: name of a node in the block driver state graph
 #
+# Note: This command is still a work in progress and is considered experimental.
+# Stay away from it unless you want to help with its development.
+#
 # Since: 2.5
 ##
 { 'command': 'x-blockdev-insert-medium',
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 09/21] qapi: add some sections in docs
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (7 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 08/21] qapi: move experimental note down Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 10/21] docs: add master qapi texi files Marc-André Lureau
                   ` (12 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Add some more section titles to organize the documentation we're going
to generate.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 qapi-schema.json     |  4 ++++
 qapi/block-core.json |  6 ++++--
 qapi/block.json      | 10 ++++++++--
 qapi/common.json     |  6 ++++--
 qapi/crypto.json     |  5 ++++-
 qapi/event.json      |  6 ++++++
 qapi/rocker.json     |  4 ++++
 qapi/trace.json      |  3 +++
 8 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/qapi-schema.json b/qapi-schema.json
index 10a3506528..4452aef44a 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -20,6 +20,10 @@
 # QAPI introspection
 { 'include': 'qapi/introspect.json' }
 
+##
+# = QMP commands
+##
+
 ##
 # @qmp_capabilities:
 #
diff --git a/qapi/block-core.json b/qapi/block-core.json
index b611bd6f44..79474548fc 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1,6 +1,8 @@
 # -*- Mode: Python -*-
-#
-# QAPI block core definitions (vm unrelated)
+
+##
+# == QAPI block core definitions (vm unrelated)
+##
 
 # QAPI common definitions
 { 'include': 'common.json' }
diff --git a/qapi/block.json b/qapi/block.json
index 937df05830..e4ad74bcb2 100644
--- a/qapi/block.json
+++ b/qapi/block.json
@@ -1,10 +1,16 @@
 # -*- Mode: Python -*-
-#
-# QAPI block definitions (vm related)
+
+##
+# = QAPI block definitions
+##
 
 # QAPI block core definitions
 { 'include': 'block-core.json' }
 
+##
+# == QAPI block definitions (vm unrelated)
+##
+
 ##
 # @BiosAtaTranslation:
 #
diff --git a/qapi/common.json b/qapi/common.json
index 624a8619c8..d93f159946 100644
--- a/qapi/common.json
+++ b/qapi/common.json
@@ -1,6 +1,8 @@
 # -*- Mode: Python -*-
-#
-# QAPI common definitions
+
+##
+# = QAPI common definitions
+##
 
 ##
 # @QapiErrorClass:
diff --git a/qapi/crypto.json b/qapi/crypto.json
index 15d296e3c1..1e517b0841 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -1,6 +1,9 @@
 # -*- Mode: Python -*-
 #
-# QAPI crypto definitions
+
+##
+# = QAPI crypto definitions
+##
 
 ##
 # @QCryptoTLSCredsEndpoint:
diff --git a/qapi/event.json b/qapi/event.json
index c7689b211d..0cd4b79ad8 100644
--- a/qapi/event.json
+++ b/qapi/event.json
@@ -1,3 +1,9 @@
+# -*- Mode: Python -*-
+
+##
+# = Other events
+##
+
 ##
 # @SHUTDOWN:
 #
diff --git a/qapi/rocker.json b/qapi/rocker.json
index ace27760f1..1e511cd37a 100644
--- a/qapi/rocker.json
+++ b/qapi/rocker.json
@@ -1,3 +1,7 @@
+##
+# = Rocker switch device
+##
+
 ##
 # @RockerSwitch:
 #
diff --git a/qapi/trace.json b/qapi/trace.json
index 4fd39b7792..3ad7df7fdb 100644
--- a/qapi/trace.json
+++ b/qapi/trace.json
@@ -5,6 +5,9 @@
 # This work is licensed under the terms of the GNU GPL, version 2 or later.
 # See the COPYING file in the top-level directory.
 
+##
+# = Tracing commands
+##
 
 ##
 # @TraceEventState:
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 10/21] docs: add master qapi texi files
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (8 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 09/21] qapi: add some sections in docs Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 11/21] qapi: rework qapi Exception Marc-André Lureau
                   ` (11 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

The qapi2texi script generates a file to be included in a texi file. Add
"QEMU QMP Reference Manual" and "QEMU Guest Agent Protocol Reference"
master texi files.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 docs/qemu-ga-ref.texi  | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++
 docs/qemu-qmp-ref.texi | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 148 insertions(+)
 create mode 100644 docs/qemu-ga-ref.texi
 create mode 100644 docs/qemu-qmp-ref.texi

diff --git a/docs/qemu-ga-ref.texi b/docs/qemu-ga-ref.texi
new file mode 100644
index 0000000000..b8898027dc
--- /dev/null
+++ b/docs/qemu-ga-ref.texi
@@ -0,0 +1,74 @@
+\input texinfo
+@setfilename qemu-ga-ref.info
+
+@exampleindent 0
+@paragraphindent 0
+
+@settitle QEMU Guest Agent Protocol Reference
+
+@copying
+This is the QEMU Guest Agent Protocol reference manual.
+
+Copyright @copyright{} 2016 The QEMU Project developers
+
+@quotation
+This manual is free documentation: you can redistribute it and/or
+modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation, either version 2 of the
+License, or (at your option) any later version.
+
+This manual is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this manual.  If not, see http://www.gnu.org/licenses/.
+@end quotation
+@end copying
+
+@dircategory QEMU
+@direntry
+* QEMU-GA-Ref: (qemu-ga-ref).   QEMU Guest Agent Protocol Reference
+@end direntry
+
+@titlepage
+@title Guest Agent Protocol Reference Manual
+@subtitle QEMU version @value{VERSION}
+@page
+@vskip 0pt plus 1filll
+@insertcopying
+@end titlepage
+
+@contents
+
+@ifnottex
+@node Top
+@top QEMU Guest Agent protocol reference
+@end ifnottex
+
+@menu
+* API Reference::
+* Commands and Events Index::
+* Data Types Index::
+@end menu
+
+@node API Reference
+@chapter API Reference
+
+@c for texi2pod:
+@c man begin DESCRIPTION
+
+@include qemu-ga-qapi.texi
+
+@c man end
+
+@node Commands and Events Index
+@unnumbered Commands and Events Index
+@printindex fn
+
+@node Data Types Index
+@unnumbered Data Types Index
+@printindex tp
+
+@bye
diff --git a/docs/qemu-qmp-ref.texi b/docs/qemu-qmp-ref.texi
new file mode 100644
index 0000000000..efb3370a24
--- /dev/null
+++ b/docs/qemu-qmp-ref.texi
@@ -0,0 +1,74 @@
+\input texinfo
+@setfilename qemu-qmp-ref.info
+
+@exampleindent 0
+@paragraphindent 0
+
+@settitle QEMU QMP Reference Manual
+
+@copying
+This is the QEMU QMP reference manual.
+
+Copyright @copyright{} 2016 The QEMU Project developers
+
+@quotation
+This manual is free documentation: you can redistribute it and/or
+modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation, either version 2 of the
+License, or (at your option) any later version.
+
+This manual is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this manual.  If not, see http://www.gnu.org/licenses/.
+@end quotation
+@end copying
+
+@dircategory QEMU
+@direntry
+* QEMU-QMP-Ref: (qemu-qmp-ref). QEMU QMP Reference Manual
+@end direntry
+
+@titlepage
+@title QMP Reference Manual
+@subtitle QEMU version @value{VERSION}
+@page
+@vskip 0pt plus 1filll
+@insertcopying
+@end titlepage
+
+@contents
+
+@ifnottex
+@node Top
+@top QEMU QMP reference
+@end ifnottex
+
+@menu
+* API Reference::
+* Commands and Events Index::
+* Data Types Index::
+@end menu
+
+@node API Reference
+@chapter API Reference
+
+@c for texi2pod:
+@c man begin DESCRIPTION
+
+@include qemu-qapi.texi
+
+@c man end
+
+@node Commands and Events Index
+@unnumbered Commands and Events Index
+@printindex fn
+
+@node Data Types Index
+@unnumbered Data Types Index
+@printindex tp
+
+@bye
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 11/21] qapi: rework qapi Exception
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (9 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 10/21] docs: add master qapi texi files Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 12/21] qapi.py: fix line break before binary operator pep8 Marc-André Lureau
                   ` (10 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Use a base class QAPIError, and QAPIParseError for parser errors and
QAPISemError for semantic errors, suggested by Markus Armbruster.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 scripts/qapi.py | 334 ++++++++++++++++++++++++++------------------------------
 1 file changed, 156 insertions(+), 178 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 21bc32fda3..1483ec09f5 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -91,35 +91,38 @@ def error_path(parent):
     return res
 
 
-class QAPISchemaError(Exception):
-    def __init__(self, schema, msg):
+class QAPIError(Exception):
+    def __init__(self, fname, line, col, incl_info, msg):
         Exception.__init__(self)
-        self.fname = schema.fname
+        self.fname = fname
+        self.line = line
+        self.col = col
+        self.info = incl_info
         self.msg = msg
-        self.col = 1
-        self.line = schema.line
-        for ch in schema.src[schema.line_pos:schema.pos]:
-            if ch == '\t':
-                self.col = (self.col + 7) % 8 + 1
-            else:
-                self.col += 1
-        self.info = schema.incl_info
 
     def __str__(self):
-        return error_path(self.info) + \
-            "%s:%d:%d: %s" % (self.fname, self.line, self.col, self.msg)
+        loc = "%s:%d" % (self.fname, self.line)
+        if self.col is not None:
+            loc += ":%s" % self.col
+        return error_path(self.info) + "%s: %s" % (loc, self.msg)
 
 
-class QAPIExprError(Exception):
-    def __init__(self, expr_info, msg):
-        Exception.__init__(self)
-        assert expr_info
-        self.info = expr_info
-        self.msg = msg
+class QAPIParseError(QAPIError):
+    def __init__(self, parser, msg):
+        col = 1
+        for ch in parser.src[parser.line_pos:parser.pos]:
+            if ch == '\t':
+                col = (col + 7) % 8 + 1
+            else:
+                col += 1
+        QAPIError.__init__(self, parser.fname, parser.line, col,
+                           parser.incl_info, msg)
 
-    def __str__(self):
-        return error_path(self.info['parent']) + \
-            "%s:%d: %s" % (self.info['file'], self.info['line'], self.msg)
+
+class QAPISemError(QAPIError):
+    def __init__(self, info, msg):
+        QAPIError.__init__(self, info['file'], info['line'], None,
+                           info['parent'], msg)
 
 
 class QAPISchemaParser(object):
@@ -140,25 +143,24 @@ class QAPISchemaParser(object):
         self.accept()
 
         while self.tok is not None:
-            expr_info = {'file': fname, 'line': self.line,
-                         'parent': self.incl_info}
+            info = {'file': fname, 'line': self.line,
+                    'parent': self.incl_info}
             expr = self.get_expr(False)
             if isinstance(expr, dict) and "include" in expr:
                 if len(expr) != 1:
-                    raise QAPIExprError(expr_info,
-                                        "Invalid 'include' directive")
+                    raise QAPISemError(info, "Invalid 'include' directive")
                 include = expr["include"]
                 if not isinstance(include, str):
-                    raise QAPIExprError(expr_info,
-                                        "Value of 'include' must be a string")
+                    raise QAPISemError(info,
+                                       "Value of 'include' must be a string")
                 incl_abs_fname = os.path.join(os.path.dirname(abs_fname),
                                               include)
                 # catch inclusion cycle
-                inf = expr_info
+                inf = info
                 while inf:
                     if incl_abs_fname == os.path.abspath(inf['file']):
-                        raise QAPIExprError(expr_info, "Inclusion loop for %s"
-                                            % include)
+                        raise QAPISemError(info, "Inclusion loop for %s"
+                                           % include)
                     inf = inf['parent']
                 # skip multiple include of the same file
                 if incl_abs_fname in previously_included:
@@ -166,14 +168,13 @@ class QAPISchemaParser(object):
                 try:
                     fobj = open(incl_abs_fname, 'r')
                 except IOError as e:
-                    raise QAPIExprError(expr_info,
-                                        '%s: %s' % (e.strerror, include))
+                    raise QAPISemError(info, '%s: %s' % (e.strerror, include))
                 exprs_include = QAPISchemaParser(fobj, previously_included,
-                                                 expr_info)
+                                                 info)
                 self.exprs.extend(exprs_include.exprs)
             else:
                 expr_elem = {'expr': expr,
-                             'info': expr_info}
+                             'info': info}
                 self.exprs.append(expr_elem)
 
     def accept(self):
@@ -194,8 +195,7 @@ class QAPISchemaParser(object):
                     ch = self.src[self.cursor]
                     self.cursor += 1
                     if ch == '\n':
-                        raise QAPISchemaError(self,
-                                              'Missing terminating "\'"')
+                        raise QAPIParseError(self, 'Missing terminating "\'"')
                     if esc:
                         if ch == 'b':
                             string += '\b'
@@ -213,25 +213,25 @@ class QAPISchemaParser(object):
                                 ch = self.src[self.cursor]
                                 self.cursor += 1
                                 if ch not in "0123456789abcdefABCDEF":
-                                    raise QAPISchemaError(self,
-                                                          '\\u escape needs 4 '
-                                                          'hex digits')
+                                    raise QAPIParseError(self,
+                                                         '\\u escape needs 4 '
+                                                         'hex digits')
                                 value = (value << 4) + int(ch, 16)
                             # If Python 2 and 3 didn't disagree so much on
                             # how to handle Unicode, then we could allow
                             # Unicode string defaults.  But most of QAPI is
                             # ASCII-only, so we aren't losing much for now.
                             if not value or value > 0x7f:
-                                raise QAPISchemaError(self,
-                                                      'For now, \\u escape '
-                                                      'only supports non-zero '
-                                                      'values up to \\u007f')
+                                raise QAPIParseError(self,
+                                                     'For now, \\u escape '
+                                                     'only supports non-zero '
+                                                     'values up to \\u007f')
                             string += chr(value)
                         elif ch in "\\/'\"":
                             string += ch
                         else:
-                            raise QAPISchemaError(self,
-                                                  "Unknown escape \\%s" % ch)
+                            raise QAPIParseError(self,
+                                                 "Unknown escape \\%s" % ch)
                         esc = False
                     elif ch == "\\":
                         esc = True
@@ -259,7 +259,7 @@ class QAPISchemaParser(object):
                 self.line += 1
                 self.line_pos = self.cursor
             elif not self.tok.isspace():
-                raise QAPISchemaError(self, 'Stray "%s"' % self.tok)
+                raise QAPIParseError(self, 'Stray "%s"' % self.tok)
 
     def get_members(self):
         expr = OrderedDict()
@@ -267,24 +267,24 @@ class QAPISchemaParser(object):
             self.accept()
             return expr
         if self.tok != "'":
-            raise QAPISchemaError(self, 'Expected string or "}"')
+            raise QAPIParseError(self, 'Expected string or "}"')
         while True:
             key = self.val
             self.accept()
             if self.tok != ':':
-                raise QAPISchemaError(self, 'Expected ":"')
+                raise QAPIParseError(self, 'Expected ":"')
             self.accept()
             if key in expr:
-                raise QAPISchemaError(self, 'Duplicate key "%s"' % key)
+                raise QAPIParseError(self, 'Duplicate key "%s"' % key)
             expr[key] = self.get_expr(True)
             if self.tok == '}':
                 self.accept()
                 return expr
             if self.tok != ',':
-                raise QAPISchemaError(self, 'Expected "," or "}"')
+                raise QAPIParseError(self, 'Expected "," or "}"')
             self.accept()
             if self.tok != "'":
-                raise QAPISchemaError(self, 'Expected string')
+                raise QAPIParseError(self, 'Expected string')
 
     def get_values(self):
         expr = []
@@ -292,20 +292,20 @@ class QAPISchemaParser(object):
             self.accept()
             return expr
         if self.tok not in "{['tfn":
-            raise QAPISchemaError(self, 'Expected "{", "[", "]", string, '
-                                  'boolean or "null"')
+            raise QAPIParseError(self, 'Expected "{", "[", "]", string, '
+                                 'boolean or "null"')
         while True:
             expr.append(self.get_expr(True))
             if self.tok == ']':
                 self.accept()
                 return expr
             if self.tok != ',':
-                raise QAPISchemaError(self, 'Expected "," or "]"')
+                raise QAPIParseError(self, 'Expected "," or "]"')
             self.accept()
 
     def get_expr(self, nested):
         if self.tok != '{' and not nested:
-            raise QAPISchemaError(self, 'Expected "{"')
+            raise QAPIParseError(self, 'Expected "{"')
         if self.tok == '{':
             self.accept()
             expr = self.get_members()
@@ -316,7 +316,7 @@ class QAPISchemaParser(object):
             expr = self.val
             self.accept()
         else:
-            raise QAPISchemaError(self, 'Expected "{", "[" or string')
+            raise QAPIParseError(self, 'Expected "{", "[" or string')
         return expr
 
 #
@@ -375,20 +375,18 @@ valid_name = re.compile('^(__[a-zA-Z0-9.-]+_)?'
                         '[a-zA-Z][a-zA-Z0-9_-]*$')
 
 
-def check_name(expr_info, source, name, allow_optional=False,
+def check_name(info, source, name, allow_optional=False,
                enum_member=False):
     global valid_name
     membername = name
 
     if not isinstance(name, str):
-        raise QAPIExprError(expr_info,
-                            "%s requires a string name" % source)
+        raise QAPISemError(info, "%s requires a string name" % source)
     if name.startswith('*'):
         membername = name[1:]
         if not allow_optional:
-            raise QAPIExprError(expr_info,
-                                "%s does not allow optional name '%s'"
-                                % (source, name))
+            raise QAPISemError(info, "%s does not allow optional name '%s'"
+                               % (source, name))
     # Enum members can start with a digit, because the generated C
     # code always prefixes it with the enum name
     if enum_member and membername[0].isdigit():
@@ -397,8 +395,7 @@ def check_name(expr_info, source, name, allow_optional=False,
     # and 'q_obj_*' implicit type names.
     if not valid_name.match(membername) or \
        c_name(membername, False).startswith('q_'):
-        raise QAPIExprError(expr_info,
-                            "%s uses invalid name '%s'" % (source, name))
+        raise QAPISemError(info, "%s uses invalid name '%s'" % (source, name))
 
 
 def add_name(name, info, meta, implicit=False):
@@ -407,13 +404,11 @@ def add_name(name, info, meta, implicit=False):
     # FIXME should reject names that differ only in '_' vs. '.'
     # vs. '-', because they're liable to clash in generated C.
     if name in all_names:
-        raise QAPIExprError(info,
-                            "%s '%s' is already defined"
-                            % (all_names[name], name))
+        raise QAPISemError(info, "%s '%s' is already defined"
+                           % (all_names[name], name))
     if not implicit and (name.endswith('Kind') or name.endswith('List')):
-        raise QAPIExprError(info,
-                            "%s '%s' should not end in '%s'"
-                            % (meta, name, name[-4:]))
+        raise QAPISemError(info, "%s '%s' should not end in '%s'"
+                           % (meta, name, name[-4:]))
     all_names[name] = meta
 
 
@@ -465,7 +460,7 @@ def is_enum(name):
     return find_enum(name) is not None
 
 
-def check_type(expr_info, source, value, allow_array=False,
+def check_type(info, source, value, allow_array=False,
                allow_dict=False, allow_optional=False,
                allow_metas=[]):
     global all_names
@@ -476,69 +471,64 @@ def check_type(expr_info, source, value, allow_array=False,
     # Check if array type for value is okay
     if isinstance(value, list):
         if not allow_array:
-            raise QAPIExprError(expr_info,
-                                "%s cannot be an array" % source)
+            raise QAPISemError(info, "%s cannot be an array" % source)
         if len(value) != 1 or not isinstance(value[0], str):
-            raise QAPIExprError(expr_info,
-                                "%s: array type must contain single type name"
-                                % source)
+            raise QAPISemError(info,
+                               "%s: array type must contain single type name" %
+                               source)
         value = value[0]
 
     # Check if type name for value is okay
     if isinstance(value, str):
         if value not in all_names:
-            raise QAPIExprError(expr_info,
-                                "%s uses unknown type '%s'"
-                                % (source, value))
+            raise QAPISemError(info, "%s uses unknown type '%s'"
+                               % (source, value))
         if not all_names[value] in allow_metas:
-            raise QAPIExprError(expr_info,
-                                "%s cannot use %s type '%s'"
-                                % (source, all_names[value], value))
+            raise QAPISemError(info, "%s cannot use %s type '%s'" %
+                               (source, all_names[value], value))
         return
 
     if not allow_dict:
-        raise QAPIExprError(expr_info,
-                            "%s should be a type name" % source)
+        raise QAPISemError(info, "%s should be a type name" % source)
 
     if not isinstance(value, OrderedDict):
-        raise QAPIExprError(expr_info,
-                            "%s should be a dictionary or type name" % source)
+        raise QAPISemError(info,
+                           "%s should be a dictionary or type name" % source)
 
     # value is a dictionary, check that each member is okay
     for (key, arg) in value.items():
-        check_name(expr_info, "Member of %s" % source, key,
+        check_name(info, "Member of %s" % source, key,
                    allow_optional=allow_optional)
         if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
-            raise QAPIExprError(expr_info,
-                                "Member of %s uses reserved name '%s'"
-                                % (source, key))
+            raise QAPISemError(info, "Member of %s uses reserved name '%s'"
+                               % (source, key))
         # Todo: allow dictionaries to represent default values of
         # an optional argument.
-        check_type(expr_info, "Member '%s' of %s" % (key, source), arg,
+        check_type(info, "Member '%s' of %s" % (key, source), arg,
                    allow_array=True,
                    allow_metas=['built-in', 'union', 'alternate', 'struct',
                                 'enum'])
 
 
-def check_command(expr, expr_info):
+def check_command(expr, info):
     name = expr['command']
     boxed = expr.get('boxed', False)
 
     args_meta = ['struct']
     if boxed:
         args_meta += ['union', 'alternate']
-    check_type(expr_info, "'data' for command '%s'" % name,
+    check_type(info, "'data' for command '%s'" % name,
                expr.get('data'), allow_dict=not boxed, allow_optional=True,
                allow_metas=args_meta)
     returns_meta = ['union', 'struct']
     if name in returns_whitelist:
         returns_meta += ['built-in', 'alternate', 'enum']
-    check_type(expr_info, "'returns' for command '%s'" % name,
+    check_type(info, "'returns' for command '%s'" % name,
                expr.get('returns'), allow_array=True,
                allow_optional=True, allow_metas=returns_meta)
 
 
-def check_event(expr, expr_info):
+def check_event(expr, info):
     global events
     name = expr['event']
     boxed = expr.get('boxed', False)
@@ -547,12 +537,12 @@ def check_event(expr, expr_info):
     if boxed:
         meta += ['union', 'alternate']
     events.append(name)
-    check_type(expr_info, "'data' for event '%s'" % name,
+    check_type(info, "'data' for event '%s'" % name,
                expr.get('data'), allow_dict=not boxed, allow_optional=True,
                allow_metas=meta)
 
 
-def check_union(expr, expr_info):
+def check_union(expr, info):
     name = expr['union']
     base = expr.get('base')
     discriminator = expr.get('discriminator')
@@ -565,123 +555,117 @@ def check_union(expr, expr_info):
         enum_define = None
         allow_metas = ['built-in', 'union', 'alternate', 'struct', 'enum']
         if base is not None:
-            raise QAPIExprError(expr_info,
-                                "Simple union '%s' must not have a base"
-                                % name)
+            raise QAPISemError(info, "Simple union '%s' must not have a base" %
+                               name)
 
     # Else, it's a flat union.
     else:
         # The object must have a string or dictionary 'base'.
-        check_type(expr_info, "'base' for union '%s'" % name,
+        check_type(info, "'base' for union '%s'" % name,
                    base, allow_dict=True, allow_optional=True,
                    allow_metas=['struct'])
         if not base:
-            raise QAPIExprError(expr_info,
-                                "Flat union '%s' must have a base"
-                                % name)
+            raise QAPISemError(info, "Flat union '%s' must have a base"
+                               % name)
         base_members = find_base_members(base)
         assert base_members
 
         # The value of member 'discriminator' must name a non-optional
         # member of the base struct.
-        check_name(expr_info, "Discriminator of flat union '%s'" % name,
+        check_name(info, "Discriminator of flat union '%s'" % name,
                    discriminator)
         discriminator_type = base_members.get(discriminator)
         if not discriminator_type:
-            raise QAPIExprError(expr_info,
-                                "Discriminator '%s' is not a member of base "
-                                "struct '%s'"
-                                % (discriminator, base))
+            raise QAPISemError(info,
+                               "Discriminator '%s' is not a member of base "
+                               "struct '%s'"
+                               % (discriminator, base))
         enum_define = find_enum(discriminator_type)
         allow_metas = ['struct']
         # Do not allow string discriminator
         if not enum_define:
-            raise QAPIExprError(expr_info,
-                                "Discriminator '%s' must be of enumeration "
-                                "type" % discriminator)
+            raise QAPISemError(info,
+                               "Discriminator '%s' must be of enumeration "
+                               "type" % discriminator)
 
     # Check every branch; don't allow an empty union
     if len(members) == 0:
-        raise QAPIExprError(expr_info,
-                            "Union '%s' cannot have empty 'data'" % name)
+        raise QAPISemError(info, "Union '%s' cannot have empty 'data'" % name)
     for (key, value) in members.items():
-        check_name(expr_info, "Member of union '%s'" % name, key)
+        check_name(info, "Member of union '%s'" % name, key)
 
         # Each value must name a known type
-        check_type(expr_info, "Member '%s' of union '%s'" % (key, name),
+        check_type(info, "Member '%s' of union '%s'" % (key, name),
                    value, allow_array=not base, allow_metas=allow_metas)
 
         # If the discriminator names an enum type, then all members
         # of 'data' must also be members of the enum type.
         if enum_define:
             if key not in enum_define['enum_values']:
-                raise QAPIExprError(expr_info,
-                                    "Discriminator value '%s' is not found in "
-                                    "enum '%s'" %
-                                    (key, enum_define["enum_name"]))
+                raise QAPISemError(info,
+                                   "Discriminator value '%s' is not found in "
+                                   "enum '%s'"
+                                   % (key, enum_define["enum_name"]))
 
     # If discriminator is user-defined, ensure all values are covered
     if enum_define:
         for value in enum_define['enum_values']:
             if value not in members.keys():
-                raise QAPIExprError(expr_info,
-                                    "Union '%s' data missing '%s' branch"
-                                    % (name, value))
+                raise QAPISemError(info, "Union '%s' data missing '%s' branch"
+                                   % (name, value))
 
 
-def check_alternate(expr, expr_info):
+def check_alternate(expr, info):
     name = expr['alternate']
     members = expr['data']
     types_seen = {}
 
     # Check every branch; require at least two branches
     if len(members) < 2:
-        raise QAPIExprError(expr_info,
-                            "Alternate '%s' should have at least two branches "
-                            "in 'data'" % name)
+        raise QAPISemError(info,
+                           "Alternate '%s' should have at least two branches "
+                           "in 'data'" % name)
     for (key, value) in members.items():
-        check_name(expr_info, "Member of alternate '%s'" % name, key)
+        check_name(info, "Member of alternate '%s'" % name, key)
 
         # Ensure alternates have no type conflicts.
-        check_type(expr_info, "Member '%s' of alternate '%s'" % (key, name),
+        check_type(info, "Member '%s' of alternate '%s'" % (key, name),
                    value,
                    allow_metas=['built-in', 'union', 'struct', 'enum'])
         qtype = find_alternate_member_qtype(value)
         if not qtype:
-            raise QAPIExprError(expr_info,
-                                "Alternate '%s' member '%s' cannot use "
-                                "type '%s'" % (name, key, value))
+            raise QAPISemError(info, "Alternate '%s' member '%s' cannot use "
+                               "type '%s'" % (name, key, value))
         if qtype in types_seen:
-            raise QAPIExprError(expr_info,
-                                "Alternate '%s' member '%s' can't "
-                                "be distinguished from member '%s'"
-                                % (name, key, types_seen[qtype]))
+            raise QAPISemError(info, "Alternate '%s' member '%s' can't "
+                               "be distinguished from member '%s'"
+                               % (name, key, types_seen[qtype]))
         types_seen[qtype] = key
 
 
-def check_enum(expr, expr_info):
+def check_enum(expr, info):
     name = expr['enum']
     members = expr.get('data')
     prefix = expr.get('prefix')
 
     if not isinstance(members, list):
-        raise QAPIExprError(expr_info,
-                            "Enum '%s' requires an array for 'data'" % name)
+        raise QAPISemError(info,
+                           "Enum '%s' requires an array for 'data'" % name)
     if prefix is not None and not isinstance(prefix, str):
-        raise QAPIExprError(expr_info,
-                            "Enum '%s' requires a string for 'prefix'" % name)
+        raise QAPISemError(info,
+                           "Enum '%s' requires a string for 'prefix'" % name)
     for member in members:
-        check_name(expr_info, "Member of enum '%s'" % name, member,
+        check_name(info, "Member of enum '%s'" % name, member,
                    enum_member=True)
 
 
-def check_struct(expr, expr_info):
+def check_struct(expr, info):
     name = expr['struct']
     members = expr['data']
 
-    check_type(expr_info, "'data' for struct '%s'" % name, members,
+    check_type(info, "'data' for struct '%s'" % name, members,
                allow_dict=True, allow_optional=True)
-    check_type(expr_info, "'base' for struct '%s'" % name, expr.get('base'),
+    check_type(info, "'base' for struct '%s'" % name, expr.get('base'),
                allow_metas=['struct'])
 
 
@@ -690,27 +674,24 @@ def check_keys(expr_elem, meta, required, optional=[]):
     info = expr_elem['info']
     name = expr[meta]
     if not isinstance(name, str):
-        raise QAPIExprError(info,
-                            "'%s' key must have a string value" % meta)
+        raise QAPISemError(info, "'%s' key must have a string value" % meta)
     required = required + [meta]
     for (key, value) in expr.items():
         if key not in required and key not in optional:
-            raise QAPIExprError(info,
-                                "Unknown key '%s' in %s '%s'"
-                                % (key, meta, name))
+            raise QAPISemError(info, "Unknown key '%s' in %s '%s'"
+                               % (key, meta, name))
         if (key == 'gen' or key == 'success-response') and value is not False:
-            raise QAPIExprError(info,
-                                "'%s' of %s '%s' should only use false value"
-                                % (key, meta, name))
+            raise QAPISemError(info,
+                               "'%s' of %s '%s' should only use false value"
+                               % (key, meta, name))
         if key == 'boxed' and value is not True:
-            raise QAPIExprError(info,
-                                "'%s' of %s '%s' should only use true value"
-                                % (key, meta, name))
+            raise QAPISemError(info,
+                               "'%s' of %s '%s' should only use true value"
+                               % (key, meta, name))
     for key in required:
         if key not in expr:
-            raise QAPIExprError(info,
-                                "Key '%s' is missing from %s '%s'"
-                                % (key, meta, name))
+            raise QAPISemError(info, "Key '%s' is missing from %s '%s'"
+                               % (key, meta, name))
 
 
 def check_exprs(exprs):
@@ -743,8 +724,8 @@ def check_exprs(exprs):
             check_keys(expr_elem, 'event', [], ['data', 'boxed'])
             add_name(expr['event'], info, 'event')
         else:
-            raise QAPIExprError(expr_elem['info'],
-                                "Expression is missing metatype")
+            raise QAPISemError(expr_elem['info'],
+                               "Expression is missing metatype")
 
     # Try again for hidden UnionKind enum
     for expr_elem in exprs:
@@ -978,8 +959,8 @@ class QAPISchemaObjectType(QAPISchemaType):
 
     def check(self, schema):
         if self.members is False:               # check for cycles
-            raise QAPIExprError(self.info,
-                                "Object %s contains itself" % self.name)
+            raise QAPISemError(self.info,
+                               "Object %s contains itself" % self.name)
         if self.members:
             return
         self.members = False                    # mark as being checked
@@ -1051,12 +1032,11 @@ class QAPISchemaMember(object):
     def check_clash(self, info, seen):
         cname = c_name(self.name)
         if cname.lower() != cname and self.owner not in case_whitelist:
-            raise QAPIExprError(info,
-                                "%s should not use uppercase" % self.describe())
+            raise QAPISemError(info,
+                               "%s should not use uppercase" % self.describe())
         if cname in seen:
-            raise QAPIExprError(info,
-                                "%s collides with %s"
-                                % (self.describe(), seen[cname].describe()))
+            raise QAPISemError(info, "%s collides with %s" %
+                               (self.describe(), seen[cname].describe()))
         seen[cname] = self
 
     def _pretty_owner(self):
@@ -1201,14 +1181,13 @@ class QAPISchemaCommand(QAPISchemaEntity):
             self.arg_type.check(schema)
             if self.boxed:
                 if self.arg_type.is_empty():
-                    raise QAPIExprError(self.info,
-                                        "Cannot use 'boxed' with empty type")
+                    raise QAPISemError(self.info,
+                                       "Cannot use 'boxed' with empty type")
             else:
                 assert not isinstance(self.arg_type, QAPISchemaAlternateType)
                 assert not self.arg_type.variants
         elif self.boxed:
-            raise QAPIExprError(self.info,
-                                "Use of 'boxed' requires 'data'")
+            raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
         if self._ret_type_name:
             self.ret_type = schema.lookup_type(self._ret_type_name)
             assert isinstance(self.ret_type, QAPISchemaType)
@@ -1235,14 +1214,13 @@ class QAPISchemaEvent(QAPISchemaEntity):
             self.arg_type.check(schema)
             if self.boxed:
                 if self.arg_type.is_empty():
-                    raise QAPIExprError(self.info,
-                                        "Cannot use 'boxed' with empty type")
+                    raise QAPISemError(self.info,
+                                       "Cannot use 'boxed' with empty type")
             else:
                 assert not isinstance(self.arg_type, QAPISchemaAlternateType)
                 assert not self.arg_type.variants
         elif self.boxed:
-            raise QAPIExprError(self.info,
-                                "Use of 'boxed' requires 'data'")
+            raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
 
     def visit(self, visitor):
         visitor.visit_event(self.name, self.info, self.arg_type, self.boxed)
@@ -1258,7 +1236,7 @@ class QAPISchema(object):
             self._predefining = False
             self._def_exprs()
             self.check()
-        except (QAPISchemaError, QAPIExprError) as err:
+        except QAPIError as err:
             print >>sys.stderr, err
             exit(1)
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 12/21] qapi.py: fix line break before binary operator pep8
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (10 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 11/21] qapi: rework qapi Exception Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-10 12:23   ` Markus Armbruster
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 13/21] texi2pod: learn quotation, deftp and deftypefn Marc-André Lureau
                   ` (9 subsequent siblings)
  21 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Python code style accepts both form, but pep8 complains. Better to clean
up the single warning for now, so new errors stand out more easily.

Fix scripts/qapi.py:1539:21: W503 line break before binary operator

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 1483ec09f5..3d5f9e1eaf 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -1535,8 +1535,8 @@ def c_name(name, protect=True):
     # namespace pollution:
     polluted_words = set(['unix', 'errno', 'mips', 'sparc'])
     name = name.translate(c_name_trans)
-    if protect and (name in c89_words | c99_words | c11_words | gcc_words
-                    | cpp_words | polluted_words):
+    if protect and (name in c89_words | c99_words | c11_words | gcc_words |
+                    cpp_words | polluted_words):
         return "q_" + name
     return name
 
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 13/21] texi2pod: learn quotation, deftp and deftypefn
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (11 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 12/21] qapi.py: fix line break before binary operator pep8 Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 14/21] (SQUASHED) move doc to schema Marc-André Lureau
                   ` (8 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Learn a few more markups used for API documentation.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/texi2pod.pl | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 51 insertions(+), 3 deletions(-)

diff --git a/scripts/texi2pod.pl b/scripts/texi2pod.pl
index 8767662d30..6e8fec41a1 100755
--- a/scripts/texi2pod.pl
+++ b/scripts/texi2pod.pl
@@ -37,6 +37,7 @@ $inf = "";
 $ibase = "";
 @ipath = ();
 $encoding = undef;
+@args = ();
 
 while ($_ = shift) {
     if (/^-D(.*)$/) {
@@ -162,7 +163,8 @@ while(<$inf>) {
 	if ($ended =~ /^(?:ifset|ifclear|ignore|menu|iftex)$/) {
 	    $skipping = pop @skstack;
 	    next;
-	} elsif ($ended =~ /^(?:example|smallexample|display)$/) {
+	} elsif ($ended =~ /^(?:example|smallexample|display
+                            |quotation|deftp|deftypefn)$/x) {
 	    $shift = "";
 	    $_ = "";	# need a paragraph break
 	} elsif ($ended =~ /^(?:itemize|enumerate|[fv]?table)$/) {
@@ -303,6 +305,7 @@ while(<$inf>) {
 	$ic =~ s/\@(?:code|kbd)/C/;
 	$ic =~ s/\@(?:dfn|var|emph|cite|i)/I/;
 	$ic =~ s/\@(?:file)/F/;
+	$ic =~ s/\@(?:asis)//;
 	$_ = "\n=over 4\n";
     };
 
@@ -323,10 +326,54 @@ while(<$inf>) {
 	$_ = "\n=item ".join (" : ", @columns)."\n";
     };
 
+    /^\@(quotation)\s*(.+)?$/ and do {
+        push @endwstack, $endw;
+        $endw = $1;
+        $_ = "\n$2:"
+    };
+
+    /^{(.*)}$|^(.*)$/ and $#args > 0 and do {
+        $kind = $args[0];
+        $arguments = $1 // "";
+        if ($endw eq "deftypefn") {
+            $ret = $args[1];
+            $fname = "B<$args[2]>";
+            $_ = $ret ? "$ret " : "";
+            $_ .= "$fname $arguments ($kind)";
+        } else {
+            $_ = "B<$args[1]> ($kind)\n\n$arguments";
+        }
+        @args = ();
+    };
+
+    /^\@(deftp)\s*(.+)?$/ and do {
+        push @endwstack, $endw;
+        $endw = $1;
+        $arg = $2;
+        $arg =~ s/{([^}]*)}/$1/g;
+        $arg =~ s/\@$//;
+        @args = split (/ /, $arg);
+        $_ = "";
+    };
+
+    /^\@(deftypefn)\s*(.+)?$/ and do {
+        push @endwstack, $endw;
+        $endw = $1;
+        $arg = $2;
+        $arg =~ s/{([^}]*)}/$1/g;
+        $arg =~ s/\@$//;
+        @args = split (/ /, $arg);
+        $_ = "";
+    };
+
     /^\@itemx?\s*(.+)?$/ and do {
 	if (defined $1) {
-	    # Entity escapes prevent munging by the <> processing below.
-	    $_ = "\n=item $ic\&LT;$1\&GT;\n";
+            if ($ic eq "") {
+                $_ = "\n=item $1\n";
+            } else {
+                # Entity escapes prevent munging by the <> processing below.
+                $_ = "\n=item $ic\&LT;$1\&GT;\n";
+            }
 	} else {
 	    $_ = "\n=item $ic\n";
 	    $ic =~ y/A-Ya-y/B-Zb-z/;
@@ -388,6 +435,7 @@ sub postprocess
     s/\@sc\{([^\}]*)\}/\U$1/g;
     s/\@file\{([^\}]*)\}/F<$1>/g;
     s/\@w\{([^\}]*)\}/S<$1>/g;
+    s/\@t\{([^\}]*)\}/$1/g;
     s/\@(?:dmn|math)\{([^\}]*)\}/$1/g;
 
     # keep references of the form @ref{...}, print them bold
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 14/21] (SQUASHED) move doc to schema
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (12 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 13/21] texi2pod: learn quotation, deftp and deftypefn Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 15/21] qapi: add qapi2texi script Marc-André Lureau
                   ` (7 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

qmp-commands: move 'add_client' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-name' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-kvm' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-status' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-uuid' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-chardev' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-chardev-backends' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'ringbuf-write' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'ringbuf-read' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-events' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-migrate' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'migrate-set-capabilities' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-migrate-capabilities' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'migrate-set-parameters' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-migrate-parameters' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'client_migrate_info' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'migrate-start-postcopy' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-mice' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-cpus' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-iothreads' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-vnc' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-spice' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-balloon' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-pci' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'quit' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'stop' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'system_reset' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'system_powerdown' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'cpu-add' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'memsave' doc to schema

Notice that "cpu" argument is actually "cpu-index" in the json.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'pmemsave' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'cont' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'system_wakeup' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'inject-nmi' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'set_link' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'balloon' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'transaction' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'human-monitor-command' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'migrate_cancel' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'migrate_set_downtime' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'migrate_set_speed' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-migrate-cache-size' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'set_password' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'expire_password' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'change' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'migrate' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'migrate-incoming' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'xen-save-devices-state' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'xen-set-global-dirty-log' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'device_del' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'dump-guest-memory' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-dump-guest-memory-capability' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'dump-skeys' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'netdev_add' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'netdev_del' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'object-add' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'object-del' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'getfd' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'closefd' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'add-fd' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'remove-fd' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-fdsets' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'send-key' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'screendump' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'chardev-add' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'chardev-remove' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-tpm-models' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-tpm-types' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-tpm' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-command-line-options' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-rx-filter' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'input-send-event' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-memdev' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-memory-devices' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-acpi-ospm-status' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'rtc-reset-reinjection' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-block' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-blockstats' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'block_passwd' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'block_resize' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'blockdev-snapshot-sync' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'blockdev-snapshot' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'change-backing-file' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'block-commit' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'drive-backup' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'blockdev-backup' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-named-block-nodes' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'drive-mirror' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'block-dirty-bitmap-add' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'block-dirty-bitmap-remove' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'block-dirty-bitmap-clear' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'block_set_io_throttle' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'block-stream' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'blockdev-add' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'x-blockdev-del' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'blockdev-open-tray' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'blockdev-close-tray' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'x-blockdev-remove-medium' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'x-blockdev-insert-medium' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'blockdev-change-medium' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'block-set-write-threshold' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'blockdev-snapshot-internal-sync' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'blockdev-snapshot-delete-internal-sync' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'eject' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-version' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-commands' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-rocker' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-rocker-ports' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-rocker-of-dpa-flows' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-rocker-of-dpa-groups' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'trace-event-get-state' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'trace-event-set-state' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-hotpluggable-cpus' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-gic-capabilities' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'x-blockdev-change' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'blockdev-mirror' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'query-dump' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'migrate-set-cache-size' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'xen-load-devices-state' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move 'x-colo-lost-heartbeat' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: remove query-qmp-schema from txt

It's better described in the schema already.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: remove qmp_capabilities from txt

It's better described in the schema already.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: remove cpu from txt

The cpu command is deprecated since commit 755f196898.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: remove device_add from txt

It's already fully described in the schema. Fix the schema to document
the command and not the argument (place doc before first arg).

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-commands: move documentation bits to schema

Moving the remaining bits of documentation to the json
file (text improvements is not the objective of this patch)

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'BLOCK_IMAGE_CORRUPTED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'BLOCK_IO_ERROR' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'BLOCK_JOB_COMPLETED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'BLOCK_JOB_CANCELLED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'BLOCK_JOB_ERROR' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'BLOCK_JOB_READY' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'DEVICE_TRAY_MOVED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'SHUTDOWN' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'POWERDOWN' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'RESET' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'STOP' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'RESUME' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'SUSPEND' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'SUSPEND_DISK' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'WAKEUP' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'RTC_CHANGE' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'WATCHDOG' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'DEVICE_DELETED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'NIC_RX_FILTER_CHANGED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'VNC_CONNECTED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'VNC_INITIALIZED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'VNC_DISCONNECTED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'SPICE_CONNECTED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'SPICE_INITIALIZED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'SPICE_DISCONNECTED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'SPICE_MIGRATE_COMPLETED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'MIGRATION' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'ACPI_DEVICE_OST' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'BALLOON_CHANGE' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'GUEST_PANICKED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'QUORUM_FAILURE' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'QUORUM_REPORT_BAD' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'VSERPORT_CHANGE' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'MEM_UNPLUG_ERROR' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'DUMP_COMPLETED' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

qmp-events: move 'MIGRATION_PASS' doc to schema

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi-schema.json      | 1271 +++++++++++++++-
 qapi/block-core.json  |  670 ++++++++-
 qapi/block.json       |   56 +-
 qapi/common.json      |   32 +
 qapi/event.json       |  240 ++++
 qapi/rocker.json      |   57 +-
 qapi/trace.json       |   14 +
 Makefile              |    1 -
 docs/qmp-commands.txt | 3824 -------------------------------------------------
 docs/qmp-events.txt   |  731 ----------
 10 files changed, 2300 insertions(+), 4596 deletions(-)
 delete mode 100644 docs/qmp-commands.txt
 delete mode 100644 docs/qmp-events.txt

diff --git a/qapi-schema.json b/qapi-schema.json
index 4452aef44a..824b07c556 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1,6 +1,53 @@
 # -*- Mode: Python -*-
+##
+# = Introduction
+#
+# This document describes all commands currently supported by QMP.
+#
+# Most of the time their usage is exactly the same as in the user Monitor, this
+# means that any other document which also describe commands (the manpage,
+# QEMU's manual, etc) can and should be consulted.
+#
+# QMP has two types of commands: regular and query commands. Regular commands
+# usually change the Virtual Machine's state someway, while query commands just
+# return information. The sections below are divided accordingly.
+#
+# It's important to observe that all communication examples are formatted in
+# a reader-friendly way, so that they're easier to understand. However, in real
+# protocol usage, they're emitted as a single line.
+#
+# Also, the following notation is used to denote data flow:
+#
+# Example:
+#
+# | -> data issued by the Client
+# | <- Server data response
+#
+# Please, refer to the QMP specification (docs/qmp-spec.txt) for
+# detailed information on the Server command and response formats.
+#
+# = Stability Considerations
+#
+# The current QMP command set (described in this file) may be useful for a
+# number of use cases, however it's limited and several commands have bad
+# defined semantics, specially with regard to command completion.
+#
+# These problems are going to be solved incrementally in the next QEMU releases
+# and we're going to establish a deprecation policy for badly defined commands.
+#
+# If you're planning to adopt QMP, please observe the following:
 #
-# QAPI Schema
+#     1. The deprecation policy will take effect and be documented soon, please
+#        check the documentation of each used command as soon as a new release of
+#        QEMU is available
+#
+#     2. DO NOT rely on anything which is not explicit documented
+#
+#     3. Errors, in special, are not documented. Applications should NOT check
+#        for specific errors classes or data (it's strongly recommended to only
+#        check for the "error" key)
+#
+##
 
 # QAPI common definitions
 { 'include': 'qapi/common.json' }
@@ -89,6 +136,13 @@
 # Returns: nothing on success.
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "add_client", "arguments": { "protocol": "vnc",
+#                                              "fdname": "myclient" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'add_client',
   'data': { 'protocol': 'str', 'fdname': 'str', '*skipauth': 'bool',
@@ -113,6 +167,12 @@
 # Returns: @NameInfo of the guest
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-name" }
+# <- { "return": { "name": "qemu-name" } }
+#
 ##
 { 'command': 'query-name', 'returns': 'NameInfo' }
 
@@ -137,6 +197,12 @@
 # Returns: @KvmInfo
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-kvm" }
+# <- { "return": { "enabled": true, "present": true } }
+#
 ##
 { 'command': 'query-kvm', 'returns': 'KvmInfo' }
 
@@ -217,13 +283,21 @@
 # Returns: @StatusInfo reflecting all VCPUs
 #
 # Since:  0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-status" }
+# <- { "return": { "running": true,
+#                  "singlestep": false,
+#                  "status": "running" } }
+#
 ##
 { 'command': 'query-status', 'returns': 'StatusInfo' }
 
 ##
 # @UuidInfo:
 #
-# Guest UUID information.
+# Guest UUID information (Universally Unique Identifier).
 #
 # @UUID: the UUID of the guest
 #
@@ -241,6 +315,12 @@
 # Returns: The @UuidInfo for the guest
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-uuid" }
+# <- { "return": { "UUID": "550e8400-e29b-41d4-a716-446655440000" } }
+#
 ##
 { 'command': 'query-uuid', 'returns': 'UuidInfo' }
 
@@ -274,6 +354,30 @@
 # Returns: a list of @ChardevInfo
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-chardev" }
+# <- {
+#       "return": [
+#          {
+#             "label": "charchannel0",
+#             "filename": "unix:/var/lib/libvirt/qemu/seabios.rhel6.agent,server",
+#             "frontend-open": false
+#          },
+#          {
+#             "label": "charmonitor",
+#             "filename": "unix:/var/lib/libvirt/qemu/seabios.rhel6.monitor,server",
+#             "frontend-open": true
+#          },
+#          {
+#             "label": "charserial0",
+#             "filename": "pty:/dev/pts/2",
+#             "frontend-open": true
+#          }
+#       ]
+#    }
+#
 ##
 { 'command': 'query-chardev', 'returns': ['ChardevInfo'] }
 
@@ -296,6 +400,27 @@
 # Returns: a list of @ChardevBackendInfo
 #
 # Since: 2.0
+#
+# Example:
+#
+# -> { "execute": "query-chardev-backends" }
+# <- {
+#       "return":[
+#          {
+#             "name":"udp"
+#          },
+#          {
+#             "name":"tcp"
+#          },
+#          {
+#             "name":"unix"
+#          },
+#          {
+#             "name":"spiceport"
+#          }
+#       ]
+#    }
+#
 ##
 { 'command': 'query-chardev-backends', 'returns': ['ChardevBackendInfo'] }
 
@@ -332,6 +457,15 @@
 # Returns: Nothing on success
 #
 # Since: 1.4
+#
+# Example:
+#
+# -> { "execute": "ringbuf-write",
+#      "arguments": { "device": "foo",
+#                     "data": "abcdefgh",
+#                     "format": "utf8" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'ringbuf-write',
   'data': {'device': 'str', 'data': 'str',
@@ -359,6 +493,15 @@
 # Returns: data read from the device
 #
 # Since: 1.4
+#
+# Example:
+#
+# -> { "execute": "ringbuf-read",
+#      "arguments": { "device": "foo",
+#                     "size": 1000,
+#                     "format": "utf8" } }
+# <- { "return": "abcdefgh" }
+#
 ##
 { 'command': 'ringbuf-read',
   'data': {'device': 'str', 'size': 'int', '*format': 'DataFormat'},
@@ -383,6 +526,23 @@
 # Returns: A list of @EventInfo for all supported events
 #
 # Since: 1.2.0
+#
+# Example:
+#
+# -> { "execute": "query-events" }
+# <- {
+#      "return": [
+#          {
+#             "name":"SHUTDOWN"
+#          },
+#          {
+#             "name":"RESET"
+#          }
+#       ]
+#    }
+#
+# Note: This example has been shortened as the real response is too long.
+#
 ##
 { 'command': 'query-events', 'returns': ['EventInfo'] }
 
@@ -542,11 +702,124 @@
 ##
 # @query-migrate:
 #
-# Returns information about current migration process.
+# Returns information about current migration process. If migration
+# is active there will be another json-object with RAM migration
+# status and if block migration is active another one with block
+# migration status.
 #
 # Returns: @MigrationInfo
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# 1. Before the first migration
+#
+# -> { "execute": "query-migrate" }
+# <- { "return": {} }
+#
+# 2. Migration is done and has succeeded
+#
+# -> { "execute": "query-migrate" }
+# <- { "return": {
+#         "status": "completed",
+#         "ram":{
+#           "transferred":123,
+#           "remaining":123,
+#           "total":246,
+#           "total-time":12345,
+#           "setup-time":12345,
+#           "downtime":12345,
+#           "duplicate":123,
+#           "normal":123,
+#           "normal-bytes":123456,
+#           "dirty-sync-count":15
+#         }
+#      }
+#    }
+#
+# 3. Migration is done and has failed
+#
+# -> { "execute": "query-migrate" }
+# <- { "return": { "status": "failed" } }
+#
+# 4. Migration is being performed and is not a block migration:
+#
+# -> { "execute": "query-migrate" }
+# <- {
+#       "return":{
+#          "status":"active",
+#          "ram":{
+#             "transferred":123,
+#             "remaining":123,
+#             "total":246,
+#             "total-time":12345,
+#             "setup-time":12345,
+#             "expected-downtime":12345,
+#             "duplicate":123,
+#             "normal":123,
+#             "normal-bytes":123456,
+#             "dirty-sync-count":15
+#          }
+#       }
+#    }
+#
+# 5. Migration is being performed and is a block migration:
+#
+# -> { "execute": "query-migrate" }
+# <- {
+#       "return":{
+#          "status":"active",
+#          "ram":{
+#             "total":1057024,
+#             "remaining":1053304,
+#             "transferred":3720,
+#             "total-time":12345,
+#             "setup-time":12345,
+#             "expected-downtime":12345,
+#             "duplicate":123,
+#             "normal":123,
+#             "normal-bytes":123456,
+#             "dirty-sync-count":15
+#          },
+#          "disk":{
+#             "total":20971520,
+#             "remaining":20880384,
+#             "transferred":91136
+#          }
+#       }
+#    }
+#
+# 6. Migration is being performed and XBZRLE is active:
+#
+# -> { "execute": "query-migrate" }
+# <- {
+#       "return":{
+#          "status":"active",
+#          "capabilities" : [ { "capability": "xbzrle", "state" : true } ],
+#          "ram":{
+#             "total":1057024,
+#             "remaining":1053304,
+#             "transferred":3720,
+#             "total-time":12345,
+#             "setup-time":12345,
+#             "expected-downtime":12345,
+#             "duplicate":10,
+#             "normal":3333,
+#             "normal-bytes":3412992,
+#             "dirty-sync-count":15
+#          },
+#          "xbzrle-cache":{
+#             "cache-size":67108864,
+#             "bytes":20971520,
+#             "pages":2444343,
+#             "cache-miss":2244,
+#             "cache-miss-rate":0.123,
+#             "overflow":34434
+#          }
+#       }
+#    }
+#
 ##
 { 'command': 'query-migrate', 'returns': 'MigrationInfo' }
 
@@ -620,6 +893,12 @@
 # @capabilities: json array of capability modifications to make
 #
 # Since: 1.2
+#
+# Example:
+#
+# -> { "execute": "migrate-set-capabilities" , "arguments":
+#      { "capabilities": [ { "capability": "xbzrle", "state": true } ] } }
+#
 ##
 { 'command': 'migrate-set-capabilities',
   'data': { 'capabilities': ['MigrationCapabilityStatus'] } }
@@ -632,6 +911,21 @@
 # Returns: @MigrationCapabilitiesStatus
 #
 # Since: 1.2
+#
+# Example:
+#
+# -> { "execute": "query-migrate-capabilities" }
+# <- { "return": [
+#       {"state": false, "capability": "xbzrle"},
+#       {"state": false, "capability": "rdma-pin-all"},
+#       {"state": false, "capability": "auto-converge"},
+#       {"state": false, "capability": "zero-blocks"},
+#       {"state": false, "capability": "compress"},
+#       {"state": true, "capability": "events"},
+#       {"state": false, "capability": "postcopy-ram"},
+#       {"state": false, "capability": "x-colo"}
+#    ]}
+#
 ##
 { 'command': 'query-migrate-capabilities', 'returns':   ['MigrationCapabilityStatus']}
 
@@ -700,6 +994,12 @@
 # Set various migration parameters.  See MigrationParameters for details.
 #
 # Since: 2.4
+#
+# Example:
+#
+# -> { "execute": "migrate-set-parameters" ,
+#      "arguments": { "compress-level": 1 } }
+#
 ##
 { 'command': 'migrate-set-parameters', 'boxed': true,
   'data': 'MigrationParameters' }
@@ -771,6 +1071,21 @@
 # Returns: @MigrationParameters
 #
 # Since: 2.4
+#
+# Example:
+#
+# -> { "execute": "query-migrate-parameters" }
+# <- { "return": {
+#          "decompress-threads": 2,
+#          "cpu-throttle-increment": 10,
+#          "compress-threads": 8,
+#          "compress-level": 1,
+#          "cpu-throttle-initial": 20,
+#          "max-bandwidth": 33554432,
+#          "downtime-limit": 300
+#       }
+#    }
+#
 ##
 { 'command': 'query-migrate-parameters',
   'returns': 'MigrationParameters' }
@@ -789,6 +1104,15 @@
 # @cert-subject: #optional server certificate subject
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "client_migrate_info",
+#      "arguments": { "protocol": "spice",
+#                     "hostname": "virt42.lab.kraxel.org",
+#                     "port": 1234 } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'client_migrate_info',
   'data': { 'protocol': 'str', 'hostname': 'str', '*port': 'int',
@@ -802,6 +1126,12 @@
 # command.
 #
 # Since: 2.5
+#
+# Example:
+#
+# -> { "execute": "migrate-start-postcopy" }
+# <- { "return": {} }
+#
 ##
 { 'command': 'migrate-start-postcopy' }
 
@@ -874,6 +1204,12 @@
 # then takes over server operation to become the service VM.
 #
 # Since: 2.8
+#
+# Example:
+#
+# -> { "execute": "x-colo-lost-heartbeat" }
+# <- { "return": {} }
+#
 ##
 { 'command': 'x-colo-lost-heartbeat' }
 
@@ -904,6 +1240,26 @@
 # Returns: a list of @MouseInfo for each device
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-mice" }
+# <- { "return": [
+#          {
+#             "name":"QEMU Microsoft Mouse",
+#             "index":0,
+#             "current":false,
+#             "absolute":false
+#          },
+#          {
+#             "name":"QEMU PS/2 Mouse",
+#             "index":1,
+#             "current":true,
+#             "absolute":true
+#          }
+#       ]
+#    }
+#
 ##
 { 'command': 'query-mice', 'returns': ['MouseInfo'] }
 
@@ -1028,6 +1384,32 @@
 # Returns: a list of @CpuInfo for each virtual CPU
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-cpus" }
+# <- { "return": [
+#          {
+#             "CPU":0,
+#             "current":true,
+#             "halted":false,
+#             "qom_path":"/machine/unattached/device[0]",
+#             "arch":"x86",
+#             "pc":3227107138,
+#             "thread_id":3134
+#          },
+#          {
+#             "CPU":1,
+#             "current":false,
+#             "halted":true,
+#             "qom_path":"/machine/unattached/device[2]",
+#             "arch":"x86",
+#             "pc":7108165,
+#             "thread_id":3135
+#          }
+#       ]
+#    }
+#
 ##
 { 'command': 'query-cpus', 'returns': ['CpuInfo'] }
 
@@ -1057,6 +1439,22 @@
 # Returns: a list of @IOThreadInfo for each iothread
 #
 # Since: 2.0
+#
+# Example:
+#
+# -> { "execute": "query-iothreads" }
+# <- { "return": [
+#          {
+#             "id":"iothread0",
+#             "thread-id":3134
+#          },
+#          {
+#             "id":"iothread1",
+#             "thread-id":3135
+#          }
+#       ]
+#    }
+#
 ##
 { 'command': 'query-iothreads', 'returns': ['IOThreadInfo'] }
 
@@ -1239,6 +1637,26 @@
 # Returns: @VncInfo
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-vnc" }
+# <- { "return": {
+#          "enabled":true,
+#          "host":"0.0.0.0",
+#          "service":"50402",
+#          "auth":"vnc",
+#          "family":"ipv4",
+#          "clients":[
+#             {
+#                "host":"127.0.0.1",
+#                "service":"50401",
+#                "family":"ipv4"
+#             }
+#          ]
+#       }
+#    }
+#
 ##
 { 'command': 'query-vnc', 'returns': 'VncInfo' }
 
@@ -1336,7 +1754,7 @@
 # @enabled: true if the SPICE server is enabled, false otherwise
 #
 # @migrated: true if the last guest migration completed and spice
-#            migration had completed as well. false otherwise.
+#            migration had completed as well. false otherwise. (since 1.4)
 #
 # @host: #optional The hostname the SPICE server is bound to.  This depends on
 #        the name resolution on the host and may be an IP address.
@@ -1354,9 +1772,7 @@
 #
 # @mouse-mode: The mode in which the mouse cursor is displayed currently. Can
 #              be determined by the client or the server, or unknown if spice
-#              server doesn't provide this information.
-#
-#              Since: 1.1
+#              server doesn't provide this information. (since: 1.1)
 #
 # @channels: a list of @SpiceChannel for each active spice channel
 #
@@ -1375,6 +1791,40 @@
 # Returns: @SpiceInfo
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-spice" }
+# <- { "return": {
+#          "enabled": true,
+#          "auth": "spice",
+#          "port": 5920,
+#          "tls-port": 5921,
+#          "host": "0.0.0.0",
+#          "channels": [
+#             {
+#                "port": "54924",
+#                "family": "ipv4",
+#                "channel-type": 1,
+#                "connection-id": 1804289383,
+#                "host": "127.0.0.1",
+#                "channel-id": 0,
+#                "tls": true
+#             },
+#             {
+#                "port": "36710",
+#                "family": "ipv4",
+#                "channel-type": 4,
+#                "connection-id": 1804289383,
+#                "host": "127.0.0.1",
+#                "channel-id": 0,
+#                "tls": false
+#             },
+#             [ ... more channels follow ... ]
+#          ]
+#       }
+#    }
+#
 ##
 { 'command': 'query-spice', 'returns': 'SpiceInfo' }
 
@@ -1396,11 +1846,22 @@
 # Return information about the balloon device.
 #
 # Returns: @BalloonInfo on success
+#
 #          If the balloon driver is enabled but not functional because the KVM
 #          kernel module cannot support it, KvmMissingCap
+#
 #          If no balloon device is present, DeviceNotActive
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-balloon" }
+# <- { "return": {
+#          "actual": 1073741824,
+#       }
+#    }
+#
 ##
 { 'command': 'query-balloon', 'returns': 'BalloonInfo' }
 
@@ -1427,6 +1888,8 @@
 # @type: 'io' if the region is a PIO region
 #        'memory' if the region is a MMIO region
 #
+# @size: memory size
+#
 # @prefetch: #optional if @type is 'memory', true if the memory is prefetchable
 #
 # @mem_type_64: #optional if @type is 'memory', true if the BAR is 64-bit
@@ -1560,9 +2023,144 @@
 #
 # Return information about the PCI bus topology of the guest.
 #
-# Returns: a list of @PciInfo for each PCI bus
+# Returns: a list of @PciInfo for each PCI bus. Each bus is
+# represented by a json-object, which has a key with a json-array of
+# all PCI devices attached to it. Each device is represented by a
+# json-object.
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-pci" }
+# <- { "return": [
+#          {
+#             "bus": 0,
+#             "devices": [
+#                {
+#                   "bus": 0,
+#                   "qdev_id": "",
+#                   "slot": 0,
+#                   "class_info": {
+#                      "class": 1536,
+#                      "desc": "Host bridge"
+#                   },
+#                   "id": {
+#                      "device": 32902,
+#                      "vendor": 4663
+#                   },
+#                   "function": 0,
+#                   "regions": [
+#                   ]
+#                },
+#                {
+#                   "bus": 0,
+#                   "qdev_id": "",
+#                   "slot": 1,
+#                   "class_info": {
+#                      "class": 1537,
+#                      "desc": "ISA bridge"
+#                   },
+#                   "id": {
+#                      "device": 32902,
+#                      "vendor": 28672
+#                   },
+#                   "function": 0,
+#                   "regions": [
+#                   ]
+#                },
+#                {
+#                   "bus": 0,
+#                   "qdev_id": "",
+#                   "slot": 1,
+#                   "class_info": {
+#                      "class": 257,
+#                      "desc": "IDE controller"
+#                   },
+#                   "id": {
+#                      "device": 32902,
+#                      "vendor": 28688
+#                   },
+#                   "function": 1,
+#                   "regions": [
+#                      {
+#                         "bar": 4,
+#                         "size": 16,
+#                         "address": 49152,
+#                         "type": "io"
+#                      }
+#                   ]
+#                },
+#                {
+#                   "bus": 0,
+#                   "qdev_id": "",
+#                   "slot": 2,
+#                   "class_info": {
+#                      "class": 768,
+#                      "desc": "VGA controller"
+#                   },
+#                   "id": {
+#                      "device": 4115,
+#                      "vendor": 184
+#                   },
+#                   "function": 0,
+#                   "regions": [
+#                      {
+#                         "prefetch": true,
+#                         "mem_type_64": false,
+#                         "bar": 0,
+#                         "size": 33554432,
+#                         "address": 4026531840,
+#                         "type": "memory"
+#                      },
+#                      {
+#                         "prefetch": false,
+#                         "mem_type_64": false,
+#                         "bar": 1,
+#                         "size": 4096,
+#                         "address": 4060086272,
+#                         "type": "memory"
+#                      },
+#                      {
+#                         "prefetch": false,
+#                         "mem_type_64": false,
+#                         "bar": 6,
+#                         "size": 65536,
+#                         "address": -1,
+#                         "type": "memory"
+#                      }
+#                   ]
+#                },
+#                {
+#                   "bus": 0,
+#                   "qdev_id": "",
+#                   "irq": 11,
+#                   "slot": 4,
+#                   "class_info": {
+#                      "class": 1280,
+#                      "desc": "RAM controller"
+#                   },
+#                   "id": {
+#                      "device": 6900,
+#                      "vendor": 4098
+#                   },
+#                   "function": 0,
+#                   "regions": [
+#                      {
+#                         "bar": 0,
+#                         "size": 32,
+#                         "address": 49280,
+#                         "type": "io"
+#                      }
+#                   ]
+#                }
+#             ]
+#          }
+#       ]
+#    }
+#
+# Note: This example has been shortened as the real response is too long.
+#
 ##
 { 'command': 'query-pci', 'returns': ['PciInfo'] }
 
@@ -1575,6 +2173,11 @@
 # unexpected.
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "quit" }
+# <- { "return": {} }
 ##
 { 'command': 'quit' }
 
@@ -1589,6 +2192,12 @@
 #         state.  In "inmigrate" state, it will ensure that the guest
 #         remains paused once migration finishes, as if the -S option was
 #         passed on the command line.
+#
+# Example:
+#
+# -> { "execute": "stop" }
+# <- { "return": {} }
+#
 ##
 { 'command': 'stop' }
 
@@ -1598,6 +2207,12 @@
 # Performs a hard reset of a guest.
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "system_reset" }
+# <- { "return": {} }
+#
 ##
 { 'command': 'system_reset' }
 
@@ -1612,6 +2227,11 @@
 #        returning does not indicate that a guest has accepted the request or
 #        that it has shut down.  Many guests will respond to this command by
 #        prompting the user in some way.
+# Example:
+#
+# -> { "execute": "system_powerdown" }
+# <- { "return": {} }
+#
 ##
 { 'command': 'system_powerdown' }
 
@@ -1636,6 +2256,12 @@
 # Returns: Nothing on success
 #
 # Since: 1.5
+#
+# Example:
+#
+# -> { "execute": "cpu-add", "arguments": { "id": 2 } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'cpu-add', 'data': {'id': 'int'} }
 
@@ -1658,6 +2284,15 @@
 # Since: 0.14.0
 #
 # Notes: Errors were not reliably returned until 1.1
+#
+# Example:
+#
+# -> { "execute": "memsave",
+#      "arguments": { "val": 10,
+#                     "size": 100,
+#                     "filename": "/tmp/virtual-mem-dump" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'memsave',
   'data': {'val': 'int', 'size': 'int', 'filename': 'str', '*cpu-index': 'int'} }
@@ -1678,6 +2313,15 @@
 # Since: 0.14.0
 #
 # Notes: Errors were not reliably returned until 1.1
+#
+# Example:
+#
+# -> { "execute": "pmemsave",
+#      "arguments": { "val": 10,
+#                     "size": 100,
+#                     "filename": "/tmp/physical-mem-dump" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'pmemsave',
   'data': {'val': 'int', 'size': 'int', 'filename': 'str'} }
@@ -1698,6 +2342,12 @@
 #         this case, the effect of the command is to make sure the guest
 #         starts once migration finishes, removing the effect of the -S
 #         command line option if it was passed.
+#
+# Example:
+#
+# -> { "execute": "cont" }
+# <- { "return": {} }
+#
 ##
 { 'command': 'cont' }
 
@@ -1709,6 +2359,12 @@
 # Since:  1.1
 #
 # Returns:  nothing.
+#
+# Example:
+#
+# -> { "execute": "system_wakeup" }
+# <- { "return": {} }
+#
 ##
 { 'command': 'system_wakeup' }
 
@@ -1716,12 +2372,19 @@
 # @inject-nmi:
 #
 # Injects a Non-Maskable Interrupt into the default CPU (x86/s390) or all CPUs (ppc64).
+# The command fails when the guest doesn't support injecting.
 #
 # Returns:  If successful, nothing
 #
 # Since:  0.14.0
 #
 # Note: prior to 2.1, this command was only supported for x86 and s390 VMs
+#
+# Example:
+#
+# -> { "execute": "inject-nmi" }
+# <- { "return": {} }
+#
 ##
 { 'command': 'inject-nmi' }
 
@@ -1742,6 +2405,13 @@
 # Notes: Not all network adapters support setting link status.  This command
 #        will succeed even if the network adapter does not support link status
 #        notification.
+#
+# Example:
+#
+# -> { "execute": "set_link",
+#      "arguments": { "name": "e1000.0", "up": false } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'set_link', 'data': {'name': 'str', 'up': 'bool'} }
 
@@ -1762,6 +2432,12 @@
 #        size independent of this command.
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "balloon", "arguments": { "value": 536870912 } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'balloon', 'data': {'value': 'int'} }
 
@@ -1848,6 +2524,28 @@
 # operation fails, then the entire set of actions will be abandoned and the
 # appropriate error returned.
 #
+# For external snapshots, the dictionary contains the device, the file to use for
+# the new snapshot, and the format.  The default format, if not specified, is
+# qcow2.
+#
+# Each new snapshot defaults to being created by QEMU (wiping any
+# contents if the file already exists), but it is also possible to reuse
+# an externally-created file.  In the latter case, you should ensure that
+# the new image file has the same contents as the current one; QEMU cannot
+# perform any meaningful check.  Typically this is achieved by using the
+# current image file as the backing file for the new image.
+#
+# On failure, the original disks pre-snapshot attempt will be used.
+#
+# For internal snapshots, the dictionary contains the device and the snapshot's
+# name.  If an internal snapshot matching name already exists, the request will
+# be rejected.  Only some image formats support it, for example, qcow2, rbd,
+# and sheepdog.
+#
+# On failure, qemu will try delete the newly created internal snapshot in the
+# transaction.  When an I/O error occurs during deletion, the user needs to fix
+# it later with qemu-img or other command.
+#
 # @actions: List of @TransactionAction;
 #           information needed for the respective operations.
 #
@@ -1856,6 +2554,7 @@
 #              for additional detail.
 #
 # Returns: nothing on success
+#
 #          Errors depend on the operations of the transaction
 #
 # Note: The transaction aborts on the first failure.  Therefore, there will be
@@ -1863,6 +2562,28 @@
 # subsequent actions will not have been attempted.
 #
 # Since: 1.1
+#
+# Example:
+#
+# -> { "execute": "transaction",
+#      "arguments": { "actions": [
+#          { "type": "blockdev-snapshot-sync", "data" : { "device": "ide-hd0",
+#                                      "snapshot-file": "/some/place/my-image",
+#                                      "format": "qcow2" } },
+#          { "type": "blockdev-snapshot-sync", "data" : { "node-name": "myfile",
+#                                      "snapshot-file": "/some/place/my-image2",
+#                                      "snapshot-node-name": "node3432",
+#                                      "mode": "existing",
+#                                      "format": "qcow2" } },
+#          { "type": "blockdev-snapshot-sync", "data" : { "device": "ide-hd1",
+#                                      "snapshot-file": "/some/place/my-image2",
+#                                      "mode": "existing",
+#                                      "format": "qcow2" } },
+#          { "type": "blockdev-snapshot-internal-sync", "data" : {
+#                                      "device": "ide-hd2",
+#                                      "name": "snapshot0" } } ] } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'transaction',
   'data': { 'actions': [ 'TransactionAction' ],
@@ -1884,7 +2605,11 @@
 # Since: 0.14.0
 #
 # Notes: This command only exists as a stop-gap.  Its use is highly
-#        discouraged.  The semantics of this command are not guaranteed.
+#        discouraged.  The semantics of this command are not
+#        guaranteed: this means that command names, arguments and
+#        responses can change or be removed at ANY time.  Applications
+#        that rely on long term stability guarantees should NOT
+#        use this command.
 #
 #        Known limitations:
 #
@@ -1893,6 +2618,13 @@
 #
 #        * Commands that prompt the user for data (eg. 'cont' when the block
 #          device is encrypted) don't currently work
+#
+# Example:
+#
+# -> { "execute": "human-monitor-command",
+#      "arguments": { "command-line": "info kvm" } }
+# <- { "return": "kvm support: enabled\r\n" }
+#
 ##
 { 'command': 'human-monitor-command',
   'data': {'command-line': 'str', '*cpu-index': 'int'},
@@ -1908,6 +2640,12 @@
 # Notes: This command succeeds even if there is no migration process running.
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "migrate_cancel" }
+# <- { "return": {} }
+#
 ##
 { 'command': 'migrate_cancel' }
 
@@ -1923,6 +2661,12 @@
 # Notes: This command is deprecated in favor of 'migrate-set-parameters'
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "migrate_set_downtime", "arguments": { "value": 0.1 } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'migrate_set_downtime', 'data': {'value': 'number'} }
 
@@ -1931,20 +2675,26 @@
 #
 # Set maximum speed for migration.
 #
-# @value: maximum speed in bytes.
+# @value: maximum speed in bytes per second.
 #
 # Returns: nothing on success
 #
 # Notes: This command is deprecated in favor of 'migrate-set-parameters'
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "migrate_set_speed", "arguments": { "value": 1024 } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'migrate_set_speed', 'data': {'value': 'int'} }
 
 ##
 # @migrate-set-cache-size:
 #
-# Set XBZRLE cache size
+# Set cache size to be used by XBZRLE migration
 #
 # @value: cache size in bytes
 #
@@ -1954,17 +2704,30 @@
 # Returns: nothing on success
 #
 # Since: 1.2
+#
+# Example:
+#
+# -> { "execute": "migrate-set-cache-size",
+#      "arguments": { "value": 536870912 } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'migrate-set-cache-size', 'data': {'value': 'int'} }
 
 ##
 # @query-migrate-cache-size:
 #
-# query XBZRLE cache size
+# Query migration XBZRLE cache size
 #
 # Returns: XBZRLE cache size in bytes
 #
 # Since: 1.2
+#
+# Example:
+#
+# -> { "execute": "query-migrate-cache-size" }
+# <- { "return": 67108864 }
+#
 ##
 { 'command': 'query-migrate-cache-size', 'returns': 'int' }
 
@@ -2081,6 +2844,13 @@
 #          If Spice is not enabled, DeviceNotFound
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "set_password", "arguments": { "protocol": "vnc",
+#                                                "password": "secret" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'set_password',
   'data': {'protocol': 'str', 'password': 'str', '*connected': 'str'} }
@@ -2107,6 +2877,13 @@
 #        coordinate server time with client time.  It is not recommended to
 #        use the absolute time version of the @time parameter unless you're
 #        sure you are on the same machine as the QEMU instance.
+#
+# Example:
+#
+# -> { "execute": "expire_password", "arguments": { "protocol": "vnc",
+#                                                   "time": "+60" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'expire_password', 'data': {'protocol': 'str', 'time': 'str'} }
 
@@ -2157,6 +2934,23 @@
 #         change-vnc-password.
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# 1. Change a removable medium
+#
+# -> { "execute": "change",
+#      "arguments": { "device": "ide1-cd0",
+#                     "target": "/srv/images/Fedora-12-x86_64-DVD.iso" } }
+# <- { "return": {} }
+#
+# 2. Change VNC password
+#
+# -> { "execute": "change",
+#      "arguments": { "device": "vnc", "target": "password",
+#                     "arg": "foobar1" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'change',
   'data': {'device': 'str', 'target': 'str', '*arg': 'str'} }
@@ -2239,6 +3033,22 @@
 # Returns: nothing on success
 #
 # Since: 0.14.0
+#
+# Notes:
+#
+# 1. The 'query-migrate' command should be used to check migration's progress
+#    and final result (this information is provided by the 'status' member)
+#
+# 2. All boolean arguments default to false
+#
+# 3. The user Monitor's "detach" argument is invalid in QMP and should not
+#    be used
+#
+# Example:
+#
+# -> { "execute": "migrate", "arguments": { "uri": "tcp:0:4446" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'migrate',
   'data': {'uri': 'str', '*blk': 'bool', '*inc': 'bool', '*detach': 'bool' } }
@@ -2255,9 +3065,24 @@
 # Returns: nothing on success
 #
 # Since: 2.3
-# Note: It's a bad idea to use a string for the uri, but it needs to stay
-# compatible with -incoming and the format of the uri is already exposed
-# above libvirt
+#
+# Notes:
+#
+# 1. It's a bad idea to use a string for the uri, but it needs to stay
+#    compatible with -incoming and the format of the uri is already exposed
+#    above libvirt.
+#
+# 2. QEMU must be started with -incoming defer to allow migrate-incoming to
+#    be used.
+#
+# 3. The uri format is the same as for -incoming
+#
+# Example:
+#
+# -> { "execute": "migrate-incoming",
+#      "arguments": { "uri": "tcp::4446" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'migrate-incoming', 'data': {'uri': 'str' } }
 
@@ -2274,6 +3099,13 @@
 # Returns: Nothing on success
 #
 # Since: 1.1
+#
+# Example:
+#
+# -> { "execute": "xen-save-devices-state",
+#      "arguments": { "filename": "/tmp/save" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'xen-save-devices-state', 'data': {'filename': 'str'} }
 
@@ -2287,6 +3119,13 @@
 # Returns: nothing
 #
 # Since: 1.3
+#
+# Example:
+#
+# -> { "execute": "xen-set-global-dirty-log",
+#      "arguments": { "enable": true } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'xen-set-global-dirty-log', 'data': { 'enable': 'bool' } }
 
@@ -2335,7 +3174,7 @@
 #
 # Remove a device from a guest
 #
-# @id: the name or QOM path of the device
+# @id: the device's ID or QOM path
 #
 # Returns: Nothing on success
 #          If @id is not a valid device, DeviceNotFound
@@ -2348,6 +3187,17 @@
 #        for all devices.
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "device_del",
+#      "arguments": { "id": "net1" } }
+# <- { "return": {} }
+#
+# -> { "execute": "device_del",
+#      "arguments": { "id": "/machine/peripheral-anon/device[0]" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'device_del', 'data': {'id': 'str'} }
 
@@ -2414,9 +3264,18 @@
 #          @length is not allowed to be specified with non-elf @format at the
 #          same time (since 2.0)
 #
+# Note: All boolean arguments default to false
+#
 # Returns: nothing on success
 #
 # Since: 1.2
+#
+# Example:
+#
+# -> { "execute": "dump-guest-memory",
+#      "arguments": { "protocol": "fd:dump" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'dump-guest-memory',
   'data': { 'paging': 'bool', 'protocol': 'str', '*detach': 'bool',
@@ -2467,6 +3326,13 @@
 # Returns: A @DumpStatus object showing the dump status.
 #
 # Since: 2.6
+#
+# Example:
+#
+# -> { "execute": "query-dump" }
+# <- { "return": { "status": "active", "completed": 1024000,
+#                  "total": 2048000 } }
+#
 ##
 { 'command': 'query-dump', 'returns': 'DumpQueryResult' }
 
@@ -2490,6 +3356,13 @@
 #           dump-guest-memory
 #
 # Since: 2.0
+#
+# Example:
+#
+# -> { "execute": "query-dump-guest-memory-capability" }
+# <- { "return": { "formats":
+#                  ["elf", "kdump-zlib", "kdump-lzo", "kdump-snappy"] }
+#
 ##
 { 'command': 'query-dump-guest-memory-capability',
   'returns': 'DumpGuestMemoryCapability' }
@@ -2504,6 +3377,13 @@
 # This command is only supported on s390 architecture.
 #
 # Since: 2.5
+#
+# Example:
+#
+# -> { "execute": "dump-skeys",
+#      "arguments": { "filename": "/tmp/skeys" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'dump-skeys',
   'data': { 'filename': 'str' } }
@@ -2529,6 +3409,14 @@
 #
 # Returns: Nothing on success
 #          If @type is not a valid network backend, DeviceNotFound
+#
+# Example:
+#
+# -> { "execute": "netdev_add",
+#      "arguments": { "type": "user", "id": "netdev1",
+#                     "dnssearch": "example.org" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'netdev_add',
   'data': {'type': 'str', 'id': 'str'},
@@ -2545,6 +3433,12 @@
 #          If @id is not a valid network backend, DeviceNotFound
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "netdev_del", "arguments": { "id": "netdev1" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'netdev_del', 'data': {'id': 'str'} }
 
@@ -2563,6 +3457,14 @@
 #          Error if @qom-type is not a valid class name
 #
 # Since: 2.0
+#
+# Example:
+#
+# -> { "execute": "object-add",
+#      "arguments": { "qom-type": "rng-random", "id": "rng1",
+#                     "props": { "filename": "/dev/hwrng" } } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'object-add',
   'data': {'qom-type': 'str', 'id': 'str', '*props': 'any'} }
@@ -2578,6 +3480,12 @@
 #          Error if @id is not a valid id for a QOM object
 #
 # Since: 2.0
+#
+# Example:
+#
+# -> { "execute": "object-del", "arguments": { "id": "rng1" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'object-del', 'data': {'id': 'str'} }
 
@@ -3152,8 +4060,15 @@
 # Notes: If @fdname already exists, the file descriptor assigned to
 #        it will be closed and replaced by the received file
 #        descriptor.
+#
 #        The 'closefd' command can be used to explicitly close the
 #        file descriptor when it is no longer needed.
+#
+# Example:
+#
+# -> { "execute": "getfd", "arguments": { "fdname": "fd1" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'getfd', 'data': {'fdname': 'str'} }
 
@@ -3167,6 +4082,12 @@
 # Returns: Nothing on success
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "closefd", "arguments": { "fdname": "fd1" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'closefd', 'data': {'fdname': 'str'} }
 
@@ -3516,7 +4437,9 @@
 # @opaque: #optional A free-form string that can be used to describe the fd.
 #
 # Returns: @AddfdInfo on success
+#
 #          If file descriptor was not received, FdNotSupplied
+#
 #          If @fdset-id is a negative value, InvalidParameterValue
 #
 # Notes: The list of fd sets is shared by all monitor connections.
@@ -3524,6 +4447,12 @@
 #        If @fdset-id is not specified, a new fd set will be created.
 #
 # Since: 1.2.0
+#
+# Example:
+#
+# -> { "execute": "add-fd", "arguments": { "fdset-id": 1 } }
+# <- { "return": { "fdset-id": 1, "fd": 3 } }
+#
 ##
 { 'command': 'add-fd', 'data': {'*fdset-id': 'int', '*opaque': 'str'},
   'returns': 'AddfdInfo' }
@@ -3546,6 +4475,12 @@
 #
 #        If @fd is not specified, all file descriptors in @fdset-id
 #        will be removed.
+#
+# Example:
+#
+# -> { "execute": "remove-fd", "arguments": { "fdset-id": 1, "fd": 3 } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'remove-fd', 'data': {'fdset-id': 'int', '*fd': 'int'} }
 
@@ -3588,6 +4523,37 @@
 #
 # Note: The list of fd sets is shared by all monitor connections.
 #
+# Example:
+#
+# -> { "execute": "query-fdsets" }
+# <- { "return": [
+#        {
+#          "fds": [
+#            {
+#              "fd": 30,
+#              "opaque": "rdonly:/path/to/file"
+#            },
+#            {
+#              "fd": 24,
+#              "opaque": "rdwr:/path/to/file"
+#            }
+#          ],
+#          "fdset-id": 1
+#        },
+#        {
+#          "fds": [
+#            {
+#              "fd": 28
+#            },
+#            {
+#              "fd": 29
+#            }
+#          ],
+#          "fdset-id": 0
+#        }
+#      ]
+#    }
+#
 ##
 { 'command': 'query-fdsets', 'returns': ['FdsetInfo'] }
 
@@ -3680,6 +4646,14 @@
 #
 # Since: 1.3.0
 #
+# Example:
+#
+# -> { "execute": "send-key",
+#      "arguments": { "keys": [ { "type": "qcode", "data": "ctrl" },
+#                               { "type": "qcode", "data": "alt" },
+#                               { "type": "qcode", "data": "delete" } ] } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'send-key',
   'data': { 'keys': ['KeyValue'], '*hold-time': 'int' } }
@@ -3694,6 +4668,13 @@
 # Returns: Nothing on success
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "screendump",
+#      "arguments": { "filename": "/tmp/image" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'screendump', 'data': {'filename': 'str'} }
 
@@ -3918,6 +4899,25 @@
 # Returns: ChardevReturn.
 #
 # Since: 1.4
+#
+# Example:
+#
+# -> { "execute" : "chardev-add",
+#      "arguments" : { "id" : "foo",
+#                      "backend" : { "type" : "null", "data" : {} } } }
+# <- { "return": {} }
+#
+# -> { "execute" : "chardev-add",
+#      "arguments" : { "id" : "bar",
+#                      "backend" : { "type" : "file",
+#                                    "data" : { "out" : "/tmp/bar.log" } } } }
+# <- { "return": {} }
+#
+# -> { "execute" : "chardev-add",
+#      "arguments" : { "id" : "baz",
+#                      "backend" : { "type" : "pty", "data" : {} } } }
+# <- { "return": { "pty" : "/dev/pty/42" } }
+#
 ##
 { 'command': 'chardev-add', 'data': {'id'      : 'str',
                                      'backend' : 'ChardevBackend' },
@@ -3933,6 +4933,12 @@
 # Returns: Nothing on success
 #
 # Since: 1.4
+#
+# Example:
+#
+# -> { "execute": "chardev-remove", "arguments": { "id" : "foo" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'chardev-remove', 'data': {'id': 'str'} }
 
@@ -3955,6 +4961,12 @@
 # Returns: a list of TpmModel
 #
 # Since: 1.5
+#
+# Example:
+#
+# -> { "execute": "query-tpm-models" }
+# <- { "return": [ "tpm-tis" ] }
+#
 ##
 { 'command': 'query-tpm-models', 'returns': ['TpmModel'] }
 
@@ -3977,6 +4989,12 @@
 # Returns: a list of TpmType
 #
 # Since: 1.5
+#
+# Example:
+#
+# -> { "execute": "query-tpm-types" }
+# <- { "return": [ "passthrough" ] }
+#
 ##
 { 'command': 'query-tpm-types', 'returns': ['TpmType'] }
 
@@ -4033,6 +5051,25 @@
 # Returns: @TPMInfo on success
 #
 # Since: 1.5
+#
+# Example:
+#
+# -> { "execute": "query-tpm" }
+# <- { "return":
+#      [
+#        { "model": "tpm-tis",
+#          "options":
+#            { "type": "passthrough",
+#              "data":
+#                { "cancel-path": "/sys/class/misc/tpm0/device/cancel",
+#                  "path": "/dev/tpm0"
+#                }
+#            },
+#          "id": "tpm0"
+#        }
+#      ]
+#    }
+#
 ##
 { 'command': 'query-tpm', 'returns': ['TPMInfo'] }
 
@@ -4159,6 +5196,28 @@
 #          @option).  Returns an error if the given @option doesn't exist.
 #
 # Since: 1.5
+#
+# Example:
+#
+# -> { "execute": "query-command-line-options",
+#      "arguments": { "option": "option-rom" } }
+# <- { "return": [
+#         {
+#             "parameters": [
+#                 {
+#                     "name": "romfile",
+#                     "type": "string"
+#                 },
+#                 {
+#                     "name": "bootindex",
+#                     "type": "number"
+#                 }
+#             ],
+#             "option": "option-rom"
+#         }
+#      ]
+#    }
+#
 ##
 {'command': 'query-command-line-options', 'data': { '*option': 'str' },
  'returns': ['CommandLineOptionInfo'] }
@@ -4280,6 +5339,36 @@
 #          isn't a NIC.
 #
 # Since: 1.6
+#
+# Example:
+#
+# -> { "execute": "query-rx-filter", "arguments": { "name": "vnet0" } }
+# <- { "return": [
+#         {
+#             "promiscuous": true,
+#             "name": "vnet0",
+#             "main-mac": "52:54:00:12:34:56",
+#             "unicast": "normal",
+#             "vlan": "normal",
+#             "vlan-table": [
+#                 4,
+#                 0
+#             ],
+#             "unicast-table": [
+#             ],
+#             "multicast": "normal",
+#             "multicast-overflow": false,
+#             "unicast-overflow": false,
+#             "multicast-table": [
+#                 "01:00:5e:00:00:01",
+#                 "33:33:00:00:00:01",
+#                 "33:33:ff:12:34:56"
+#             ],
+#             "broadcast-allowed": false
+#         }
+#       ]
+#    }
+#
 ##
 { 'command': 'query-rx-filter', 'data': { '*name': 'str' },
   'returns': ['RxFilterInfo'] }
@@ -4378,9 +5467,9 @@
 #
 # Returns: Nothing on success.
 #
-# The @display and @head parameters can be used to send the input
-# event to specific input devices in case (a) multiple input devices
-# of the same kind are added to the virtual machine and (b) you have
+# The @device and @head parameters can be used to send the input event
+# to specific input devices in case (a) multiple input devices of the
+# same kind are added to the virtual machine and (b) you have
 # configured input routing (see docs/multiseat.txt) for those input
 # devices.  The parameters work exactly like the device and head
 # properties of input devices.  If @device is missing, only devices
@@ -4390,6 +5479,48 @@
 # precedence.
 #
 # Since: 2.6
+#
+# Note: The consoles are visible in the qom tree, under
+# /backend/console[$index]. They have a device link and head property,
+# so it is possible to map which console belongs to which device and
+# display.
+#
+# Example:
+#
+# 1. Press left mouse button.
+#
+# -> { "execute": "input-send-event",
+#     "arguments": { "device": "video0",
+#                    "events": [ { "type": "btn",
+#                    "data" : { "down": true, "button": "left" } } ] } }
+# <- { "return": {} }
+#
+# -> { "execute": "input-send-event",
+#     "arguments": { "device": "video0",
+#                    "events": [ { "type": "btn",
+#                    "data" : { "down": false, "button": "left" } } ] } }
+# <- { "return": {} }
+#
+# 2. Press ctrl-alt-del.
+#
+# -> { "execute": "input-send-event",
+#      "arguments": { "events": [
+#         { "type": "key", "data" : { "down": true,
+#           "key": {"type": "qcode", "data": "ctrl" } } },
+#         { "type": "key", "data" : { "down": true,
+#           "key": {"type": "qcode", "data": "alt" } } },
+#         { "type": "key", "data" : { "down": true,
+#           "key": {"type": "qcode", "data": "delete" } } } ] } }
+# <- { "return": {} }
+#
+# 3. Move mouse pointer to absolute coordinates (20000, 400).
+#
+# -> { "execute": "input-send-event" ,
+#   "arguments": { "events": [
+#                { "type": "abs", "data" : { "axis": "x", "value" : 20000 } },
+#                { "type": "abs", "data" : { "axis": "y", "value" : 400 } } ] } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'input-send-event',
   'data': { '*device': 'str',
@@ -4489,6 +5620,30 @@
 # Returns: a list of @Memdev.
 #
 # Since: 2.1
+#
+# Example:
+#
+# -> { "execute": "query-memdev" }
+# <- { "return": [
+#        {
+#          "size": 536870912,
+#          "merge": false,
+#          "dump": true,
+#          "prealloc": false,
+#          "host-nodes": [0, 1],
+#          "policy": "bind"
+#        },
+#        {
+#          "size": 536870912,
+#          "merge": false,
+#          "dump": true,
+#          "prealloc": true,
+#          "host-nodes": [2, 3],
+#          "policy": "preferred"
+#        }
+#      ]
+#    }
+#
 ##
 { 'command': 'query-memdev', 'returns': ['Memdev'] }
 
@@ -4542,6 +5697,22 @@
 # Lists available memory devices and their state
 #
 # Since: 2.1
+#
+# Example:
+#
+# -> { "execute": "query-memory-devices" }
+# <- { "return": [ { "data":
+#                       { "addr": 5368709120,
+#                         "hotpluggable": true,
+#                         "hotplugged": true,
+#                         "id": "d1",
+#                         "memdev": "/objects/memX",
+#                         "node": 0,
+#                         "size": 1073741824,
+#                         "slot": 0},
+#                    "type": "dimm"
+#                  } ] }
+#
 ##
 { 'command': 'query-memory-devices', 'returns': ['MemoryDeviceInfo'] }
 
@@ -4582,10 +5753,20 @@
 ##
 # @query-acpi-ospm-status:
 #
-# Lists ACPI OSPM status of ACPI device objects,
-# which might be reported via _OST method
+# Return a list of ACPIOSTInfo for devices that support status
+# reporting via ACPI _OST method.
 #
 # Since: 2.1
+#
+# Example:
+#
+# -> { "execute": "query-acpi-ospm-status" }
+# <- { "return": [ { "device": "d1", "slot": "0", "slot-type": "DIMM", "source": 1, "status": 0},
+#                  { "slot": "1", "slot-type": "DIMM", "source": 0, "status": 0},
+#                  { "slot": "2", "slot-type": "DIMM", "source": 0, "status": 0},
+#                  { "slot": "3", "slot-type": "DIMM", "source": 0, "status": 0}
+#    ]}
+#
 ##
 { 'command': 'query-acpi-ospm-status', 'returns': ['ACPIOSTInfo'] }
 
@@ -4652,6 +5833,12 @@
 # command.
 #
 # Since: 2.1
+#
+# Example:
+#
+# -> { "execute": "rtc-reset-reinjection" }
+# <- { "return": {} }
+#
 ##
 { 'command': 'rtc-reset-reinjection' }
 
@@ -4687,6 +5874,13 @@
 # format.
 #
 # Since: 2.7
+#
+# Example:
+#
+# -> { "execute": "xen-load-devices-state",
+#      "arguments": { "filename": "/tmp/resume" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'xen-load-devices-state', 'data': {'filename': 'str'} }
 
@@ -4723,6 +5917,13 @@
 # Returns: a list of GICCapability objects.
 #
 # Since: 2.6
+#
+# Example:
+#
+# -> { "execute": "query-gic-capabilities" }
+# <- { "return": [{ "version": 2, "emulated": true, "kernel": false },
+#                 { "version": 3, "emulated": false, "kernel": true } ] }
+#
 ##
 { 'command': 'query-gic-capabilities', 'returns': ['GICCapability'] }
 
@@ -4779,5 +5980,33 @@
 # Returns: a list of HotpluggableCPU objects.
 #
 # Since: 2.7
+#
+# Example:
+#
+# For pseries machine type started with -smp 2,cores=2,maxcpus=4 -cpu POWER8:
+#
+# -> { "execute": "query-hotpluggable-cpus" }
+# <- {"return": [
+#      { "props": { "core": 8 }, "type": "POWER8-spapr-cpu-core",
+#        "vcpus-count": 1 },
+#      { "props": { "core": 0 }, "type": "POWER8-spapr-cpu-core",
+#        "vcpus-count": 1, "qom-path": "/machine/unattached/device[0]"}
+#    ]}'
+#
+# For pc machine type started with -smp 1,maxcpus=2:
+#
+# -> { "execute": "query-hotpluggable-cpus" }
+# <- {"return": [
+#      {
+#         "type": "qemu64-x86_64-cpu", "vcpus-count": 1,
+#         "props": {"core-id": 0, "socket-id": 1, "thread-id": 0}
+#      },
+#      {
+#         "qom-path": "/machine/unattached/device[0]",
+#         "type": "qemu64-x86_64-cpu", "vcpus-count": 1,
+#         "props": {"core-id": 0, "socket-id": 0, "thread-id": 0}
+#      }
+#    ]}
+#
 ##
 { 'command': 'query-hotpluggable-cpus', 'returns': ['HotpluggableCPU'] }
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 79474548fc..3043c0ad1e 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -469,6 +469,87 @@
 # Returns: a list of @BlockInfo describing each virtual block device
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-block" }
+# <- {
+#       "return":[
+#          {
+#             "io-status": "ok",
+#             "device":"ide0-hd0",
+#             "locked":false,
+#             "removable":false,
+#             "inserted":{
+#                "ro":false,
+#                "drv":"qcow2",
+#                "encrypted":false,
+#                "file":"disks/test.qcow2",
+#                "backing_file_depth":1,
+#                "bps":1000000,
+#                "bps_rd":0,
+#                "bps_wr":0,
+#                "iops":1000000,
+#                "iops_rd":0,
+#                "iops_wr":0,
+#                "bps_max": 8000000,
+#                "bps_rd_max": 0,
+#                "bps_wr_max": 0,
+#                "iops_max": 0,
+#                "iops_rd_max": 0,
+#                "iops_wr_max": 0,
+#                "iops_size": 0,
+#                "detect_zeroes": "on",
+#                "write_threshold": 0,
+#                "image":{
+#                   "filename":"disks/test.qcow2",
+#                   "format":"qcow2",
+#                   "virtual-size":2048000,
+#                   "backing_file":"base.qcow2",
+#                   "full-backing-filename":"disks/base.qcow2",
+#                   "backing-filename-format":"qcow2",
+#                   "snapshots":[
+#                      {
+#                         "id": "1",
+#                         "name": "snapshot1",
+#                         "vm-state-size": 0,
+#                         "date-sec": 10000200,
+#                         "date-nsec": 12,
+#                         "vm-clock-sec": 206,
+#                         "vm-clock-nsec": 30
+#                      }
+#                   ],
+#                   "backing-image":{
+#                       "filename":"disks/base.qcow2",
+#                       "format":"qcow2",
+#                       "virtual-size":2048000
+#                   }
+#                }
+#             },
+#             "type":"unknown"
+#          },
+#          {
+#             "io-status": "ok",
+#             "device":"ide1-cd0",
+#             "locked":false,
+#             "removable":true,
+#             "type":"unknown"
+#          },
+#          {
+#             "device":"floppy0",
+#             "locked":false,
+#             "removable":true,
+#             "type":"unknown"
+#          },
+#          {
+#             "device":"sd0",
+#             "locked":false,
+#             "removable":true,
+#             "type":"unknown"
+#          }
+#       ]
+#    }
+#
 ##
 { 'command': 'query-block', 'returns': ['BlockInfo'] }
 
@@ -616,6 +697,9 @@
 # @stats:  A @BlockDeviceStats for the device.
 #
 # @parent: #optional This describes the file block device if it has one.
+#          Contains recursively the statistics of the underlying
+#          protocol (e.g. the host file for a qcow2 image). If there is
+#          no underlying protocol, this field is omitted
 #
 # @backing: #optional This describes the backing block device if it has one.
 #           (Since 2.0)
@@ -643,6 +727,106 @@
 # Returns: A list of @BlockStats for each virtual block devices.
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-blockstats" }
+# <- {
+#       "return":[
+#          {
+#             "device":"ide0-hd0",
+#             "parent":{
+#                "stats":{
+#                   "wr_highest_offset":3686448128,
+#                   "wr_bytes":9786368,
+#                   "wr_operations":751,
+#                   "rd_bytes":122567168,
+#                   "rd_operations":36772
+#                   "wr_total_times_ns":313253456
+#                   "rd_total_times_ns":3465673657
+#                   "flush_total_times_ns":49653
+#                   "flush_operations":61,
+#                   "rd_merged":0,
+#                   "wr_merged":0,
+#                   "idle_time_ns":2953431879,
+#                   "account_invalid":true,
+#                   "account_failed":false
+#                }
+#             },
+#             "stats":{
+#                "wr_highest_offset":2821110784,
+#                "wr_bytes":9786368,
+#                "wr_operations":692,
+#                "rd_bytes":122739200,
+#                "rd_operations":36604
+#                "flush_operations":51,
+#                "wr_total_times_ns":313253456
+#                "rd_total_times_ns":3465673657
+#                "flush_total_times_ns":49653,
+#                "rd_merged":0,
+#                "wr_merged":0,
+#                "idle_time_ns":2953431879,
+#                "account_invalid":true,
+#                "account_failed":false
+#             }
+#          },
+#          {
+#             "device":"ide1-cd0",
+#             "stats":{
+#                "wr_highest_offset":0,
+#                "wr_bytes":0,
+#                "wr_operations":0,
+#                "rd_bytes":0,
+#                "rd_operations":0
+#                "flush_operations":0,
+#                "wr_total_times_ns":0
+#                "rd_total_times_ns":0
+#                "flush_total_times_ns":0,
+#                "rd_merged":0,
+#                "wr_merged":0,
+#                "account_invalid":false,
+#                "account_failed":false
+#             }
+#          },
+#          {
+#             "device":"floppy0",
+#             "stats":{
+#                "wr_highest_offset":0,
+#                "wr_bytes":0,
+#                "wr_operations":0,
+#                "rd_bytes":0,
+#                "rd_operations":0
+#                "flush_operations":0,
+#                "wr_total_times_ns":0
+#                "rd_total_times_ns":0
+#                "flush_total_times_ns":0,
+#                "rd_merged":0,
+#                "wr_merged":0,
+#                "account_invalid":false,
+#                "account_failed":false
+#             }
+#          },
+#          {
+#             "device":"sd0",
+#             "stats":{
+#                "wr_highest_offset":0,
+#                "wr_bytes":0,
+#                "wr_operations":0,
+#                "rd_bytes":0,
+#                "rd_operations":0
+#                "flush_operations":0,
+#                "wr_total_times_ns":0
+#                "rd_total_times_ns":0
+#                "flush_total_times_ns":0,
+#                "rd_merged":0,
+#                "wr_merged":0,
+#                "account_invalid":false,
+#                "account_failed":false
+#             }
+#          }
+#       ]
+#    }
+#
 ##
 { 'command': 'query-blockstats',
   'data': { '*query-nodes': 'bool' },
@@ -787,6 +971,13 @@
 #         occur if an invalid password is specified.
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "block_passwd", "arguments": { "device": "ide0-hd0",
+#                                                "password": "12345" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'block_passwd', 'data': {'*device': 'str',
                                       '*node-name': 'str', 'password': 'str'} }
@@ -808,6 +999,13 @@
 #          If @device is not a valid block device, DeviceNotFound
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "block_resize",
+#      "arguments": { "device": "scratch", "size": 1073741824 } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'block_resize', 'data': { '*device': 'str',
                                        '*node-name': 'str',
@@ -839,7 +1037,9 @@
 #
 # @node-name: #optional graph node name to generate the snapshot from (Since 2.0)
 #
-# @snapshot-file: the target of the new image. A new file will be created.
+# @snapshot-file: the target of the new image. If the file exists, or
+# if it is a device, the snapshot will be created in the existing
+# file/device. Otherwise, a new file will be created.
 #
 # @snapshot-node-name: #optional the graph node name of the new image (Since 2.0)
 #
@@ -973,6 +1173,16 @@
 #          If @device is not a valid block device, DeviceNotFound
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "blockdev-snapshot-sync",
+#      "arguments": { "device": "ide-hd0",
+#                     "snapshot-file":
+#                     "/some/place/my-image",
+#                     "format": "qcow2" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'blockdev-snapshot-sync',
   'data': 'BlockdevSnapshotSync' }
@@ -983,9 +1193,31 @@
 #
 # Generates a snapshot of a block device.
 #
+# Create a snapshot, by installing 'node' as the backing image of
+# 'overlay'. Additionally, if 'node' is associated with a block
+# device, the block device changes to using 'overlay' as its new active
+# image.
+#
 # For the arguments, see the documentation of BlockdevSnapshot.
 #
 # Since: 2.5
+#
+# Example:
+#
+# -> { "execute": "blockdev-add",
+#      "arguments": { "options": { "driver": "qcow2",
+#                                  "node-name": "node1534",
+#                                  "file": { "driver": "file",
+#                                            "filename": "hd1.qcow2" },
+#                                  "backing": "" } } }
+#
+# <- { "return": {} }
+#
+# -> { "execute": "blockdev-snapshot",
+#      "arguments": { "node": "ide-hd0",
+#                     "overlay": "node1534" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'blockdev-snapshot',
   'data': 'BlockdevSnapshot' }
@@ -1001,7 +1233,9 @@
 # updated.
 #
 # @image-node-name: The name of the block driver state node of the
-#                   image to modify.
+#                   image to modify. The "device" argument is used
+#                   to verify "image-node-name" is in the chain
+#                   described by "device".
 #
 # @device:          The device name or node-name of the root node that owns
 #                   image-node-name.
@@ -1011,6 +1245,10 @@
 #                   when specifying the string or the image chain may
 #                   not be able to be reopened again.
 #
+# Returns: Nothing on success
+#
+#          If "device" does not exist or cannot be determined, DeviceNotFound
+#
 # Since: 2.1
 ##
 { 'command': 'change-backing-file',
@@ -1029,7 +1267,7 @@
 # @device:  the device name or node-name of a root node
 #
 # @base:   #optional The file name of the backing image to write data into.
-#                    If not specified, this is the deepest backing image
+#                    If not specified, this is the deepest backing image.
 #
 # @top:    #optional The file name of the backing image within the image chain,
 #                    which contains the topmost data to be committed down. If
@@ -1075,6 +1313,13 @@
 #
 # Since: 1.3
 #
+# Example:
+#
+# -> { "execute": "block-commit",
+#      "arguments": { "device": "virtio0",
+#                     "top": "/tmp/snap1.qcow2" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'block-commit',
   'data': { '*job-id': 'str', 'device': 'str', '*base': 'str', '*top': 'str',
@@ -1095,6 +1340,15 @@
 #          If @device is not a valid block device, GenericError
 #
 # Since: 1.6
+#
+# Example:
+#
+# -> { "execute": "drive-backup",
+#      "arguments": { "device": "drive0",
+#                     "sync": "full",
+#                     "target": "backup.img" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'drive-backup', 'boxed': true,
   'data': 'DriveBackup' }
@@ -1114,6 +1368,14 @@
 #          If @device is not a valid block device, DeviceNotFound
 #
 # Since: 2.3
+#
+# Example:
+# -> { "execute": "blockdev-backup",
+#      "arguments": { "device": "src-id",
+#                     "sync": "full",
+#                     "target": "tgt-id" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'blockdev-backup', 'boxed': true,
   'data': 'BlockdevBackup' }
@@ -1127,13 +1389,67 @@
 # Returns: the list of BlockDeviceInfo
 #
 # Since: 2.0
+#
+# Example:
+#
+# -> { "execute": "query-named-block-nodes" }
+# <- { "return": [ { "ro":false,
+#                    "drv":"qcow2",
+#                    "encrypted":false,
+#                    "file":"disks/test.qcow2",
+#                    "node-name": "my-node",
+#                    "backing_file_depth":1,
+#                    "bps":1000000,
+#                    "bps_rd":0,
+#                    "bps_wr":0,
+#                    "iops":1000000,
+#                    "iops_rd":0,
+#                    "iops_wr":0,
+#                    "bps_max": 8000000,
+#                    "bps_rd_max": 0,
+#                    "bps_wr_max": 0,
+#                    "iops_max": 0,
+#                    "iops_rd_max": 0,
+#                    "iops_wr_max": 0,
+#                    "iops_size": 0,
+#                    "write_threshold": 0,
+#                    "image":{
+#                       "filename":"disks/test.qcow2",
+#                       "format":"qcow2",
+#                       "virtual-size":2048000,
+#                       "backing_file":"base.qcow2",
+#                       "full-backing-filename":"disks/base.qcow2",
+#                       "backing-filename-format":"qcow2",
+#                       "snapshots":[
+#                          {
+#                             "id": "1",
+#                             "name": "snapshot1",
+#                             "vm-state-size": 0,
+#                             "date-sec": 10000200,
+#                             "date-nsec": 12,
+#                             "vm-clock-sec": 206,
+#                             "vm-clock-nsec": 30
+#                          }
+#                       ],
+#                       "backing-image":{
+#                           "filename":"disks/base.qcow2",
+#                           "format":"qcow2",
+#                           "virtual-size":2048000
+#                       }
+#                    } } ] }
+#
 ##
 { 'command': 'query-named-block-nodes', 'returns': [ 'BlockDeviceInfo' ] }
 
 ##
 # @drive-mirror:
 #
-# Start mirroring a block device's writes to a new destination.
+# Start mirroring a block device's writes to a new destination. target
+# specifies the target of the new image. If the file exists, or if it
+# is a device, it will be used as the new destination for writes. If
+# it does not exist, a new file will be created. format specifies the
+# format of the mirror image, default is to probe if mode='existing',
+# else the format of the source.
 #
 # See DriveMirror for parameter descriptions
 #
@@ -1141,6 +1457,16 @@
 #          If @device is not a valid block device, GenericError
 #
 # Since: 1.3
+#
+# Example:
+#
+# -> { "execute": "drive-mirror",
+#      "arguments": { "device": "ide-hd0",
+#                     "target": "/some/place/my-image",
+#                     "sync": "full",
+#                     "format": "qcow2" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'drive-mirror', 'boxed': true,
   'data': 'DriveMirror' }
@@ -1241,13 +1567,20 @@
 ##
 # @block-dirty-bitmap-add:
 #
-# Create a dirty bitmap with a name on the node
+# Create a dirty bitmap with a name on the node, and start tracking the writes.
 #
 # Returns: nothing on success
 #          If @node is not a valid block device or node, DeviceNotFound
 #          If @name is already taken, GenericError with an explanation
 #
 # Since: 2.4
+#
+# Example:
+#
+# -> { "execute": "block-dirty-bitmap-add",
+#      "arguments": { "node": "drive0", "name": "bitmap0" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'block-dirty-bitmap-add',
   'data': 'BlockDirtyBitmapAdd' }
@@ -1255,7 +1588,8 @@
 ##
 # @block-dirty-bitmap-remove:
 #
-# Remove a dirty bitmap on the node
+# Stop write tracking and remove the dirty bitmap that was created
+# with block-dirty-bitmap-add.
 #
 # Returns: nothing on success
 #          If @node is not a valid block device or node, DeviceNotFound
@@ -1263,6 +1597,13 @@
 #          if @name is frozen by an operation, GenericError
 #
 # Since: 2.4
+#
+# Example:
+#
+# -> { "execute": "block-dirty-bitmap-remove",
+#      "arguments": { "node": "drive0", "name": "bitmap0" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'block-dirty-bitmap-remove',
   'data': 'BlockDirtyBitmap' }
@@ -1270,13 +1611,22 @@
 ##
 # @block-dirty-bitmap-clear:
 #
-# Clear (reset) a dirty bitmap on the device
+# Clear (reset) a dirty bitmap on the device, so that an incremental
+# backup from this point in time forward will only backup clusters
+# modified after this clear operation.
 #
 # Returns: nothing on success
 #          If @node is not a valid block device, DeviceNotFound
 #          If @name is not found, GenericError with an explanation
 #
 # Since: 2.4
+#
+# Example:
+#
+# -> { "execute": "block-dirty-bitmap-clear",
+#      "arguments": { "node": "drive0", "name": "bitmap0" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'block-dirty-bitmap-clear',
   'data': 'BlockDirtyBitmap' }
@@ -1324,6 +1674,15 @@
 # Returns: nothing on success.
 #
 # Since: 2.6
+#
+# Example:
+#
+# -> { "execute": "blockdev-mirror",
+#      "arguments": { "device": "ide-hd0",
+#                     "target": "target0",
+#                     "sync": "full" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'blockdev-mirror',
   'data': { '*job-id': 'str', 'device': 'str', 'target': 'str',
@@ -1365,6 +1724,26 @@
 #          If @device is not a valid block device, DeviceNotFound
 #
 # Since: 1.1
+#
+# Example:
+#
+# -> { "execute": "block_set_io_throttle",
+#      "arguments": { "id": "ide0-1-0",
+#                     "bps": 1000000,
+#                     "bps_rd": 0,
+#                     "bps_wr": 0,
+#                     "iops": 0,
+#                     "iops_rd": 0,
+#                     "iops_wr": 0,
+#                     "bps_max": 8000000,
+#                     "bps_rd_max": 0,
+#                     "bps_wr_max": 0,
+#                     "iops_max": 0,
+#                     "iops_rd_max": 0,
+#                     "iops_wr_max": 0,
+#                     "bps_max_length": 60,
+#                     "iops_size": 0 } }
+# <- { "return": {} }
 ##
 { 'command': 'block_set_io_throttle', 'boxed': true,
   'data': 'BlockIOThrottle' }
@@ -1513,7 +1892,17 @@
 #            'stop' and 'enospc' can only be used if the block device
 #            supports io-status (see BlockInfo).  Since 1.3.
 #
+# Returns: Nothing on success. If @device does not exist, DeviceNotFound.
+#
 # Since: 1.1
+#
+# Example:
+#
+# -> { "execute": "block-stream",
+#      "arguments": { "device": "virtio0",
+#                     "base": "/tmp/master.qcow2" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'block-stream',
   'data': { '*job-id': 'str', 'device': 'str', '*base': 'str',
@@ -2449,6 +2838,45 @@
 # to help with its development.
 #
 # Since: 1.7
+#
+# Example:
+#
+# 1.
+# -> { "execute": "blockdev-add",
+#      "arguments": {
+#          "options" : { "driver": "qcow2",
+#                        "file": { "driver": "file",
+#                                  "filename": "test.qcow2" } } } }
+# <- { "return": {} }
+#
+# 2.
+# -> { "execute": "blockdev-add",
+#      "arguments": {
+#          "options": {
+#            "driver": "qcow2",
+#            "node-name": "node0",
+#            "discard": "unmap",
+#            "cache": {
+#                "direct": true,
+#                "writeback": true
+#            },
+#            "file": {
+#                "driver": "file",
+#                "filename": "/tmp/test.qcow2"
+#            },
+#            "backing": {
+#                "driver": "raw",
+#                "file": {
+#                    "driver": "file",
+#                    "filename": "/dev/fdset/4"
+#                }
+#            }
+#          }
+#        }
+#      }
+#
+# <- { "return": {} }
+#
 ##
 { 'command': 'blockdev-add', 'data': 'BlockdevOptions', 'boxed': true }
 
@@ -2466,6 +2894,28 @@
 # development.
 #
 # Since: 2.5
+#
+# Example:
+#
+# -> { "execute": "blockdev-add",
+#      "arguments": {
+#          "options": {
+#              "driver": "qcow2",
+#              "node-name": "node0",
+#              "file": {
+#                  "driver": "file",
+#                  "filename": "test.qcow2"
+#              }
+#          }
+#      }
+#    }
+# <- { "return": {} }
+#
+# -> { "execute": "x-blockdev-del",
+#      "arguments": { "node-name": "node0" }
+#    }
+# <- { "return": {} }
+#
 ##
 { 'command': 'x-blockdev-del', 'data': { 'node-name': 'str' } }
 
@@ -2497,6 +2947,21 @@
 #          it is locked
 #
 # Since: 2.5
+#
+# Example:
+#
+# -> { "execute": "blockdev-open-tray",
+#      "arguments": { "id": "ide0-1-0" } }
+#
+# <- { "timestamp": { "seconds": 1418751016,
+#                     "microseconds": 716996 },
+#      "event": "DEVICE_TRAY_MOVED",
+#      "data": { "device": "ide1-cd0",
+#                "id": "ide0-1-0",
+#                "tray-open": true } }
+#
+# <- { "return": {} }
+#
 ##
 { 'command': 'blockdev-open-tray',
   'data': { '*device': 'str',
@@ -2517,6 +2982,21 @@
 # @id:      #optional The name or QOM path of the guest device (since: 2.8)
 #
 # Since: 2.5
+#
+# Example:
+#
+# -> { "execute": "blockdev-close-tray",
+#      "arguments": { "id": "ide0-1-0" } }
+#
+# <- { "timestamp": { "seconds": 1418751345,
+#                     "microseconds": 272147 },
+#      "event": "DEVICE_TRAY_MOVED",
+#      "data": { "device": "ide1-cd0",
+#                "id": "ide0-1-0",
+#                "tray-open": false } }
+#
+# <- { "return": {} }
+#
 ##
 { 'command': 'blockdev-close-tray',
   'data': { '*device': 'str',
@@ -2539,6 +3019,32 @@
 # Stay away from it unless you want to help with its development.
 #
 # Since: 2.5
+#
+# Example:
+#
+# -> { "execute": "x-blockdev-remove-medium",
+#      "arguments": { "id": "ide0-1-0" } }
+#
+# <- { "error": { "class": "GenericError",
+#                 "desc": "Tray of device 'ide0-1-0' is not open" } }
+#
+# -> { "execute": "blockdev-open-tray",
+#      "arguments": { "id": "ide0-1-0" } }
+#
+# <- { "timestamp": { "seconds": 1418751627,
+#                     "microseconds": 549958 },
+#      "event": "DEVICE_TRAY_MOVED",
+#      "data": { "device": "ide1-cd0",
+#                "id": "ide0-1-0",
+#                "tray-open": true } }
+#
+# <- { "return": {} }
+#
+# -> { "execute": "x-blockdev-remove-medium",
+#      "arguments": { "device": "ide0-1-0" } }
+#
+# <- { "return": {} }
+#
 ##
 { 'command': 'x-blockdev-remove-medium',
   'data': { '*device': 'str',
@@ -2561,6 +3067,23 @@
 # Stay away from it unless you want to help with its development.
 #
 # Since: 2.5
+#
+# Example:
+#
+# -> { "execute": "blockdev-add",
+#      "arguments": {
+#          "options": { "node-name": "node0",
+#                       "driver": "raw",
+#                       "file": { "driver": "file",
+#                                 "filename": "fedora.iso" } } } }
+# <- { "return": {} }
+#
+# -> { "execute": "x-blockdev-insert-medium",
+#      "arguments": { "id": "ide0-1-0",
+#                     "node-name": "node0" } }
+#
+# <- { "return": {} }
+#
 ##
 { 'command': 'x-blockdev-insert-medium',
   'data': { '*device': 'str',
@@ -2581,6 +3104,7 @@
 # @read-write:  Makes the device writable
 #
 # Since: 2.3
+#
 ##
 { 'enum': 'BlockdevChangeReadOnlyMode',
   'data': ['retain', 'read-only', 'read-write'] }
@@ -2608,6 +3132,37 @@
 #                   to 'retain'
 #
 # Since: 2.5
+#
+# Examples:
+#
+# 1. Change a removable medium
+#
+# -> { "execute": "blockdev-change-medium",
+#      "arguments": { "id": "ide0-1-0",
+#                     "filename": "/srv/images/Fedora-12-x86_64-DVD.iso",
+#                     "format": "raw" } }
+# <- { "return": {} }
+#
+# 2. Load a read-only medium into a writable drive
+#
+# -> { "execute": "blockdev-change-medium",
+#      "arguments": { "id": "floppyA",
+#                     "filename": "/srv/images/ro.img",
+#                     "format": "raw",
+#                     "read-only-mode": "retain" } }
+#
+# <- { "error":
+#      { "class": "GenericError",
+#        "desc": "Could not open '/srv/images/ro.img': Permission denied" } }
+#
+# -> { "execute": "blockdev-change-medium",
+#      "arguments": { "id": "floppyA",
+#                     "filename": "/srv/images/ro.img",
+#                     "format": "raw",
+#                     "read-only-mode": "read-only" } }
+#
+# <- { "return": {} }
+#
 ##
 { 'command': 'blockdev-change-medium',
   'data': { '*device': 'str',
@@ -2637,7 +3192,10 @@
 ##
 # @BLOCK_IMAGE_CORRUPTED:
 #
-# Emitted when a corruption has been detected in a disk image
+# Emitted when a disk image is being marked corrupt. The image can be
+# identified by its device or node name. The 'device' field is always
+# present for compatibility reasons, but it can be empty ("") if the
+# image does not have a device name associated.
 #
 # @device: device name. This is always present for compatibility
 #          reasons, but it can be empty ("") if the image does not
@@ -2655,10 +3213,21 @@
 # @size: #optional, if the corruption resulted from an image access, this is
 #        the access size
 #
-# fatal: if set, the image is marked corrupt and therefore unusable after this
+# @fatal: if set, the image is marked corrupt and therefore unusable after this
 #        event and must be repaired (Since 2.2; before, every
 #        BLOCK_IMAGE_CORRUPTED event was fatal)
 #
+# Note: If action is "stop", a STOP event will eventually follow the
+#       BLOCK_IO_ERROR event.
+#
+# Example:
+#
+# <- { "event": "BLOCK_IMAGE_CORRUPTED",
+#      "data": { "device": "ide0-hd0", "node-name": "node0",
+#                "msg": "Prevented active L1 table overwrite", "offset": 196608,
+#                "size": 65536 },
+#      "timestamp": { "seconds": 1378126126, "microseconds": 966463 } }
+#
 # Since: 1.7
 ##
 { 'event': 'BLOCK_IMAGE_CORRUPTED',
@@ -2699,6 +3268,16 @@
 # BLOCK_IO_ERROR event
 #
 # Since: 0.13.0
+#
+# Example:
+#
+# <- { "event": "BLOCK_IO_ERROR",
+#      "data": { "device": "ide0-hd1",
+#                "node-name": "#block212",
+#                "operation": "write",
+#                "action": "stop" },
+#      "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
+#
 ##
 { 'event': 'BLOCK_IO_ERROR',
   'data': { 'device': 'str', 'node-name': 'str', 'operation': 'IoOperationType',
@@ -2728,6 +3307,15 @@
 #         interpret the error string
 #
 # Since: 1.1
+#
+# Example:
+#
+# <- { "event": "BLOCK_JOB_COMPLETED",
+#      "data": { "type": "stream", "device": "virtio-disk0",
+#                "len": 10737418240, "offset": 10737418240,
+#                "speed": 0 },
+#      "timestamp": { "seconds": 1267061043, "microseconds": 959568 } }
+#
 ##
 { 'event': 'BLOCK_JOB_COMPLETED',
   'data': { 'type'  : 'BlockJobType',
@@ -2755,6 +3343,15 @@
 # @speed: rate limit, bytes per second
 #
 # Since: 1.1
+#
+# Example:
+#
+# <- { "event": "BLOCK_JOB_CANCELLED",
+#      "data": { "type": "stream", "device": "virtio-disk0",
+#                "len": 10737418240, "offset": 134217728,
+#                "speed": 0 },
+#      "timestamp": { "seconds": 1267061043, "microseconds": 959568 } }
+#
 ##
 { 'event': 'BLOCK_JOB_CANCELLED',
   'data': { 'type'  : 'BlockJobType',
@@ -2776,6 +3373,15 @@
 # @action: action that has been taken
 #
 # Since: 1.3
+#
+# Example:
+#
+# <- { "event": "BLOCK_JOB_ERROR",
+#      "data": { "device": "ide0-hd1",
+#                "operation": "write",
+#                "action": "stop" },
+#      "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
+#
 ##
 { 'event': 'BLOCK_JOB_ERROR',
   'data': { 'device'   : 'str',
@@ -2803,6 +3409,14 @@
 # event
 #
 # Since: 1.3
+#
+# Example:
+#
+# <- { "event": "BLOCK_JOB_READY",
+#      "data": { "device": "drive0", "type": "mirror", "speed": 0,
+#                "len": 2097152, "offset": 2097152 }
+#      "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
+#
 ##
 { 'event': 'BLOCK_JOB_READY',
   'data': { 'type'  : 'BlockJobType',
@@ -2855,8 +3469,12 @@
 ##
 # @block-set-write-threshold:
 #
-# Change the write threshold for a block drive. An event will be delivered
-# if a write to this block drive crosses the configured threshold.
+# Change the write threshold for a block drive. An event will be
+# delivered if a write to this block drive crosses the configured
+# threshold.  The threshold is an offset, thus must be
+# non-negative. Default is no write threshold. Setting the threshold
+# to zero disables it.
+#
 # This is useful to transparently resize thin-provisioned drives without
 # the guest OS noticing.
 #
@@ -2866,6 +3484,14 @@
 #                   Use 0 to disable the threshold.
 #
 # Since: 2.3
+#
+# Example:
+#
+# -> { "execute": "block-set-write-threshold",
+#      "arguments": { "node-name": "mydev",
+#                     "write-threshold": 17179869184 } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'block-set-write-threshold',
   'data': { 'node-name': 'str', 'write-threshold': 'uint64' } }
@@ -2896,6 +3522,28 @@
 # the rest of the array.
 #
 # Since: 2.7
+#
+# Example:
+#
+# 1. Add a new node to a quorum
+# -> { "execute": "blockdev-add",
+#      "arguments": {
+#          "options": { "driver": "raw",
+#                       "node-name": "new_node",
+#                        "file": { "driver": "file",
+#                                  "filename": "test.raw" } } } }
+# <- { "return": {} }
+# -> { "execute": "x-blockdev-change",
+#      "arguments": { "parent": "disk1",
+#                     "node": "new_node" } }
+# <- { "return": {} }
+#
+# 2. Delete a quorum's node
+# -> { "execute": "x-blockdev-change",
+#      "arguments": { "parent": "disk1",
+#                     "child": "children.1" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'x-blockdev-change',
   'data' : { 'parent': 'str',
diff --git a/qapi/block.json b/qapi/block.json
index e4ad74bcb2..2a2d95a4e8 100644
--- a/qapi/block.json
+++ b/qapi/block.json
@@ -81,19 +81,33 @@
 ##
 # @blockdev-snapshot-internal-sync:
 #
-# Synchronously take an internal snapshot of a block device, when the format
-# of the image used supports it.
+# Synchronously take an internal snapshot of a block device, when the
+# format of the image used supports it. If the name is an empty
+# string, or a snapshot with name already exists, the operation will
+# fail.
 #
 # For the arguments, see the documentation of BlockdevSnapshotInternal.
 #
 # Returns: nothing on success
+#
 #          If @device is not a valid block device, GenericError
+#
 #          If any snapshot matching @name exists, or @name is empty,
 #          GenericError
+#
 #          If the format of the image used does not support it,
 #          BlockFormatFeatureNotSupported
 #
 # Since: 1.7
+#
+# Example:
+#
+# -> { "execute": "blockdev-snapshot-internal-sync",
+#      "arguments": { "device": "ide-hd0",
+#                     "name": "snapshot0" }
+#    }
+# <- { "return": {} }
+#
 ##
 { 'command': 'blockdev-snapshot-internal-sync',
   'data': 'BlockdevSnapshotInternal' }
@@ -121,6 +135,24 @@
 #          If @id and @name are both not specified, GenericError
 #
 # Since: 1.7
+#
+# Example:
+#
+# -> { "execute": "blockdev-snapshot-delete-internal-sync",
+#      "arguments": { "device": "ide-hd0",
+#                     "name": "snapshot0" }
+#    }
+# <- { "return": {
+#                    "id": "1",
+#                    "name": "snapshot0",
+#                    "vm-state-size": 0,
+#                    "date-sec": 1000012,
+#                    "date-nsec": 10,
+#                    "vm-clock-sec": 100,
+#                    "vm-clock-nsec": 20
+#      }
+#    }
+#
 ##
 { 'command': 'blockdev-snapshot-delete-internal-sync',
   'data': { 'device': 'str', '*id': 'str', '*name': 'str'},
@@ -135,15 +167,21 @@
 #
 # @id:      #optional The name or QOM path of the guest device (since: 2.8)
 #
-# @force:   @optional If true, eject regardless of whether the drive is locked.
+# @force:   #optional If true, eject regardless of whether the drive is locked.
 #           If not specified, the default value is false.
 #
 # Returns:  Nothing on success
+#
 #           If @device is not a valid block device, DeviceNotFound
 #
-# Notes:    Ejecting a device will no media results in success
+# Notes:    Ejecting a device with no media results in success
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "eject", "arguments": { "device": "ide1-0-1" } }
+# <- { "return": {} }
 ##
 { 'command': 'eject',
   'data': { '*device': 'str',
@@ -210,6 +248,16 @@
 # @tray-open: true if the tray has been opened or false if it has been closed
 #
 # Since: 1.1
+#
+# Example:
+#
+# <- { "event": "DEVICE_TRAY_MOVED",
+#      "data": { "device": "ide1-cd0",
+#                "id": "/machine/unattached/device[22]",
+#                "tray-open": true
+#      },
+#      "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
+#
 ##
 { 'event': 'DEVICE_TRAY_MOVED',
   'data': { 'device': 'str', 'id': 'str', 'tray-open': 'bool' } }
diff --git a/qapi/common.json b/qapi/common.json
index d93f159946..b626647b2f 100644
--- a/qapi/common.json
+++ b/qapi/common.json
@@ -77,6 +77,21 @@
 # Returns:  A @VersionInfo object describing the current version of QEMU.
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-version" }
+# <- {
+#       "return":{
+#          "qemu":{
+#             "major":0,
+#             "minor":11,
+#             "micro":5
+#          },
+#          "package":""
+#       }
+#    }
+#
 ##
 { 'command': 'query-version', 'returns': 'VersionInfo' }
 
@@ -99,6 +114,23 @@
 # Returns: A list of @CommandInfo for all supported commands
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-commands" }
+# <- {
+#      "return":[
+#         {
+#            "name":"query-balloon"
+#         },
+#         {
+#            "name":"system_powerdown"
+#         }
+#      ]
+#    }
+#
+# Note: This example has been shortened as the real response is too long.
+#
 ##
 { 'command': 'query-commands', 'returns': ['CommandInfo'] }
 
diff --git a/qapi/event.json b/qapi/event.json
index 0cd4b79ad8..f3737b771f 100644
--- a/qapi/event.json
+++ b/qapi/event.json
@@ -14,6 +14,12 @@
 # not exit, and a STOP event will eventually follow the SHUTDOWN event
 #
 # Since: 0.12.0
+#
+# Example:
+#
+# <- { "event": "SHUTDOWN",
+#      "timestamp": { "seconds": 1267040730, "microseconds": 682951 } }
+#
 ##
 { 'event': 'SHUTDOWN' }
 
@@ -24,6 +30,12 @@
 # system, such as via ACPI.
 #
 # Since: 0.12.0
+#
+# Example:
+#
+# <- { "event": "POWERDOWN",
+#      "timestamp": { "seconds": 1267040730, "microseconds": 682951 } }
+#
 ##
 { 'event': 'POWERDOWN' }
 
@@ -33,6 +45,12 @@
 # Emitted when the virtual machine is reset
 #
 # Since: 0.12.0
+#
+# Example:
+#
+# <- { "event": "RESET",
+#      "timestamp": { "seconds": 1267041653, "microseconds": 9518 } }
+#
 ##
 { 'event': 'RESET' }
 
@@ -42,6 +60,12 @@
 # Emitted when the virtual machine is stopped
 #
 # Since: 0.12.0
+#
+# Example:
+#
+# <- { "event": "STOP",
+#      "timestamp": { "seconds": 1267041730, "microseconds": 281295 } }
+#
 ##
 { 'event': 'STOP' }
 
@@ -51,6 +75,12 @@
 # Emitted when the virtual machine resumes execution
 #
 # Since: 0.12.0
+#
+# Example:
+#
+# <- { "event": "RESUME",
+#      "timestamp": { "seconds": 1271770767, "microseconds": 582542 } }
+#
 ##
 { 'event': 'RESUME' }
 
@@ -61,6 +91,12 @@
 # which is sometimes called standby state
 #
 # Since: 1.1
+#
+# Example:
+#
+# <- { "event": "SUSPEND",
+#      "timestamp": { "seconds": 1344456160, "microseconds": 309119 } }
+#
 ##
 { 'event': 'SUSPEND' }
 
@@ -73,6 +109,12 @@
 # Note: QEMU shuts down (similar to event @SHUTDOWN) when entering this state
 #
 # Since: 1.2
+#
+# Example:
+#
+# <-   { "event": "SUSPEND_DISK",
+#        "timestamp": { "seconds": 1344456160, "microseconds": 309119 } }
+#
 ##
 { 'event': 'SUSPEND_DISK' }
 
@@ -82,6 +124,12 @@
 # Emitted when the guest has woken up from suspend state and is running
 #
 # Since: 1.1
+#
+# Example:
+#
+# <- { "event": "WAKEUP",
+#      "timestamp": { "seconds": 1344522075, "microseconds": 745528 } }
+#
 ##
 { 'event': 'WAKEUP' }
 
@@ -93,7 +141,16 @@
 # @offset: offset between base RTC clock (as specified by -rtc base), and
 #          new RTC clock value
 #
+# Note: This event is rate-limited.
+#
 # Since: 0.13.0
+#
+# Example:
+#
+# <-   { "event": "RTC_CHANGE",
+#        "data": { "offset": 78 },
+#        "timestamp": { "seconds": 1267020223, "microseconds": 435656 } }
+#
 ##
 { 'event': 'RTC_CHANGE',
   'data': { 'offset': 'int' } }
@@ -108,7 +165,16 @@
 # Note: If action is "reset", "shutdown", or "pause" the WATCHDOG event is
 # followed respectively by the RESET, SHUTDOWN, or STOP events
 #
+# Note: This event is rate-limited.
+#
 # Since: 0.13.0
+#
+# Example:
+#
+# <- { "event": "WATCHDOG",
+#      "data": { "action": "reset" },
+#      "timestamp": { "seconds": 1267061043, "microseconds": 959568 } }
+#
 ##
 { 'event': 'WATCHDOG',
   'data': { 'action': 'WatchdogExpirationAction' } }
@@ -125,6 +191,14 @@
 # @path: device path
 #
 # Since: 1.5
+#
+# Example:
+#
+# <- { "event": "DEVICE_DELETED",
+#      "data": { "device": "virtio-net-pci-0",
+#                "path": "/machine/peripheral/virtio-net-pci-0" },
+#      "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
+#
 ##
 { 'event': 'DEVICE_DELETED',
   'data': { '*device': 'str', 'path': 'str' } }
@@ -140,6 +214,15 @@
 # @path: device path
 #
 # Since: 1.6
+#
+# Example:
+#
+# <- { "event": "NIC_RX_FILTER_CHANGED",
+#      "data": { "name": "vnet0",
+#                "path": "/machine/peripheral/vnet0/virtio-backend" },
+#      "timestamp": { "seconds": 1368697518, "microseconds": 326866 } }
+#    }
+#
 ##
 { 'event': 'NIC_RX_FILTER_CHANGED',
   'data': { '*name': 'str', 'path': 'str' } }
@@ -157,6 +240,17 @@
 # the authentication ID is not provided
 #
 # Since: 0.13.0
+#
+# Example:
+#
+# <- { "event": "VNC_CONNECTED",
+#      "data": {
+#            "server": { "auth": "sasl", "family": "ipv4",
+#                        "service": "5901", "host": "0.0.0.0" },
+#            "client": { "family": "ipv4", "service": "58425",
+#                        "host": "127.0.0.1" } },
+#      "timestamp": { "seconds": 1262976601, "microseconds": 975795 } }
+#
 ##
 { 'event': 'VNC_CONNECTED',
   'data': { 'server': 'VncServerInfo',
@@ -173,6 +267,17 @@
 # @client: client information
 #
 # Since: 0.13.0
+#
+# Example:
+#
+# <-  { "event": "VNC_INITIALIZED",
+#       "data": {
+#            "server": { "auth": "sasl", "family": "ipv4",
+#                        "service": "5901", "host": "0.0.0.0"},
+#            "client": { "family": "ipv4", "service": "46089",
+#                        "host": "127.0.0.1", "sasl_username": "luiz" } },
+#       "timestamp": { "seconds": 1263475302, "microseconds": 150772 } }
+#
 ##
 { 'event': 'VNC_INITIALIZED',
   'data': { 'server': 'VncServerInfo',
@@ -188,6 +293,17 @@
 # @client: client information
 #
 # Since: 0.13.0
+#
+# Example:
+#
+# <- { "event": "VNC_DISCONNECTED",
+#      "data": {
+#            "server": { "auth": "sasl", "family": "ipv4",
+#                        "service": "5901", "host": "0.0.0.0" },
+#            "client": { "family": "ipv4", "service": "58425",
+#                        "host": "127.0.0.1", "sasl_username": "luiz" } },
+#      "timestamp": { "seconds": 1262976601, "microseconds": 975795 } }
+#
 ##
 { 'event': 'VNC_DISCONNECTED',
   'data': { 'server': 'VncServerInfo',
@@ -203,6 +319,16 @@
 # @client: client information
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# <- { "timestamp": {"seconds": 1290688046, "microseconds": 388707},
+#      "event": "SPICE_CONNECTED",
+#      "data": {
+#        "server": { "port": "5920", "family": "ipv4", "host": "127.0.0.1"},
+#        "client": {"port": "52873", "family": "ipv4", "host": "127.0.0.1"}
+#    }}
+#
 ##
 { 'event': 'SPICE_CONNECTED',
   'data': { 'server': 'SpiceBasicInfo',
@@ -219,6 +345,18 @@
 # @client: client information
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# <- { "timestamp": {"seconds": 1290688046, "microseconds": 417172},
+#      "event": "SPICE_INITIALIZED",
+#      "data": {"server": {"auth": "spice", "port": "5921",
+#                          "family": "ipv4", "host": "127.0.0.1"},
+#               "client": {"port": "49004", "family": "ipv4", "channel-type": 3,
+#                          "connection-id": 1804289383, "host": "127.0.0.1",
+#                          "channel-id": 0, "tls": true}
+#    }}
+#
 ##
 { 'event': 'SPICE_INITIALIZED',
   'data': { 'server': 'SpiceServerInfo',
@@ -234,6 +372,16 @@
 # @client: client information
 #
 # Since: 0.14.0
+#
+# Example:
+#
+# <- { "timestamp": {"seconds": 1290688046, "microseconds": 388707},
+#      "event": "SPICE_DISCONNECTED",
+#      "data": {
+#        "server": { "port": "5920", "family": "ipv4", "host": "127.0.0.1"},
+#        "client": {"port": "52873", "family": "ipv4", "host": "127.0.0.1"}
+#    }}
+#
 ##
 { 'event': 'SPICE_DISCONNECTED',
   'data': { 'server': 'SpiceBasicInfo',
@@ -245,6 +393,12 @@
 # Emitted when SPICE migration has completed
 #
 # Since: 1.3
+#
+# Example:
+#
+# <- { "timestamp": {"seconds": 1290688046, "microseconds": 417172},
+#      "event": "SPICE_MIGRATE_COMPLETED" }
+#
 ##
 { 'event': 'SPICE_MIGRATE_COMPLETED' }
 
@@ -256,6 +410,13 @@
 # @status: @MigrationStatus describing the current migration status.
 #
 # Since: 2.4
+#
+# Example:
+#
+# <- {"timestamp": {"seconds": 1432121972, "microseconds": 744001},
+#     "event": "MIGRATION",
+#     "data": {"status": "completed"} }
+#
 ##
 { 'event': 'MIGRATION',
   'data': {'status': 'MigrationStatus'}}
@@ -269,6 +430,12 @@
 # @pass: An incrementing count (starting at 1 on the first pass)
 #
 # Since: 2.6
+#
+# Example:
+#
+# { "timestamp": {"seconds": 1449669631, "microseconds": 239225},
+#   "event": "MIGRATION_PASS", "data": {"pass": 2} }
+#
 ##
 { 'event': 'MIGRATION_PASS',
   'data': { 'pass': 'int' } }
@@ -281,6 +448,13 @@
 # @info: ACPIOSTInfo type as described in qapi-schema.json
 #
 # Since: 2.1
+#
+# Example:
+#
+# <- { "event": "ACPI_DEVICE_OST",
+#      "data": { "device": "d1", "slot": "0",
+#                "slot-type": "DIMM", "source": 1, "status": 0 } }
+#
 ##
 { 'event': 'ACPI_DEVICE_OST',
      'data': { 'info': 'ACPIOSTInfo' } }
@@ -293,7 +467,16 @@
 #
 # @actual: actual level of the guest memory balloon in bytes
 #
+# Note: this event is rate-limited.
+#
 # Since: 1.2
+#
+# Example:
+#
+# <- { "event": "BALLOON_CHANGE",
+#      "data": { "actual": 944766976 },
+#      "timestamp": { "seconds": 1267020223, "microseconds": 435656 } }
+#
 ##
 { 'event': 'BALLOON_CHANGE',
   'data': { 'actual': 'int' } }
@@ -306,6 +489,12 @@
 # @action: action that has been taken, currently always "pause"
 #
 # Since: 1.5
+#
+# Example:
+#
+# <- { "event": "GUEST_PANICKED",
+#      "data": { "action": "pause" } }
+#
 ##
 { 'event': 'GUEST_PANICKED',
   'data': { 'action': 'GuestPanicAction' } }
@@ -321,7 +510,16 @@
 #
 # @sectors-count: failed read operation sector count
 #
+# Note: This event is rate-limited.
+#
 # Since: 2.0
+#
+# Example:
+#
+# <- { "event": "QUORUM_FAILURE",
+#      "data": { "reference": "usr1", "sector-num": 345435, "sectors-count": 5 },
+#      "timestamp": { "seconds": 1344522075, "microseconds": 745528 } }
+#
 ##
 { 'event': 'QUORUM_FAILURE',
   'data': { 'reference': 'str', 'sector-num': 'int', 'sectors-count': 'int' } }
@@ -344,7 +542,26 @@
 #
 # @sectors-count: failed read operation sector count
 #
+# Note: This event is rate-limited.
+#
 # Since: 2.0
+#
+# Example:
+#
+# 1. Read operation
+#
+# { "event": "QUORUM_REPORT_BAD",
+#      "data": { "node-name": "node0", "sector-num": 345435, "sectors-count": 5,
+#                "type": "read" },
+#      "timestamp": { "seconds": 1344522075, "microseconds": 745528 } }
+#
+# 2. Flush operation
+#
+# { "event": "QUORUM_REPORT_BAD",
+#      "data": { "node-name": "node0", "sector-num": 0, "sectors-count": 2097120,
+#                "type": "flush", "error": "Broken pipe" },
+#      "timestamp": { "seconds": 1456406829, "microseconds": 291763 } }
+#
 ##
 { 'event': 'QUORUM_REPORT_BAD',
   'data': { 'type': 'QuorumOpType', '*error': 'str', 'node-name': 'str',
@@ -360,6 +577,13 @@
 # @open: true if the guest has opened the virtio-serial port
 #
 # Since: 2.1
+#
+# Example:
+#
+# <- { "event": "VSERPORT_CHANGE",
+#      "data": { "id": "channel0", "open": true },
+#      "timestamp": { "seconds": 1401385907, "microseconds": 422329 } }
+#
 ##
 { 'event': 'VSERPORT_CHANGE',
   'data': { 'id': 'str', 'open': 'bool' } }
@@ -374,6 +598,15 @@
 # @msg: Informative message
 #
 # Since: 2.4
+#
+# Example:
+#
+# <- { "event": "MEM_UNPLUG_ERROR"
+#      "data": { "device": "dimm1",
+#                "msg": "acpi: device unplug for unsupported device"
+#      },
+#      "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
+#
 ##
 { 'event': 'MEM_UNPLUG_ERROR',
   'data': { 'device': 'str', 'msg': 'str' } }
@@ -390,6 +623,13 @@
 #         user should not try to interpret the error string.
 #
 # Since: 2.6
+#
+# Example:
+#
+# { "event": "DUMP_COMPLETED",
+#   "data": {"result": {"total": 1090650112, "status": "completed",
+#                       "completed": 1090650112} } }
+#
 ##
 { 'event': 'DUMP_COMPLETED' ,
   'data': { 'result': 'DumpQueryResult', '*error': 'str' } }
diff --git a/qapi/rocker.json b/qapi/rocker.json
index 1e511cd37a..97e2b8376f 100644
--- a/qapi/rocker.json
+++ b/qapi/rocker.json
@@ -26,6 +26,12 @@
 # Returns: @Rocker information
 #
 # Since: 2.4
+#
+# Example:
+#
+# -> { "execute": "query-rocker", "arguments": { "name": "sw1" } }
+# <- { "return": {"name": "sw1", "ports": 2, "id": 1327446905938}}
+#
 ##
 { 'command': 'query-rocker',
   'data': { 'name': 'str' },
@@ -84,11 +90,21 @@
 ##
 # @query-rocker-ports:
 #
-# Return rocker switch information.
+# Return rocker switch port information.
 #
-# Returns: @Rocker information
+# Returns: a list of @RockerPort information
 #
 # Since: 2.4
+#
+# Example:
+#
+# -> { "execute": "query-rocker-ports", "arguments": { "name": "sw1" } }
+# <- { "return": [ {"duplex": "full", "enabled": true, "name": "sw1.1",
+#                   "autoneg": "off", "link-up": true, "speed": 10000},
+#                  {"duplex": "full", "enabled": true, "name": "sw1.2",
+#                   "autoneg": "off", "link-up": true, "speed": 10000}
+#    ]}
+#
 ##
 { 'command': 'query-rocker-ports',
   'data': { 'name': 'str' },
@@ -219,9 +235,23 @@
 # @tbl-id: #optional flow table ID.  If tbl-id is not specified, returns
 # flow information for all tables.
 #
-# Returns: @Rocker OF-DPA flow information
+# Returns: rocker OF-DPA flow information
 #
 # Since: 2.4
+#
+# Example:
+#
+# -> { "execute": "query-rocker-of-dpa-flows",
+#      "arguments": { "name": "sw1" } }
+# <- { "return": [ {"key": {"in-pport": 0, "priority": 1, "tbl-id": 0},
+#                   "hits": 138,
+#                   "cookie": 0,
+#                   "action": {"goto-tbl": 10},
+#                   "mask": {"in-pport": 4294901760}
+#                  },
+#                  {...more...},
+#    ]}
+#
 ##
 { 'command': 'query-rocker-of-dpa-flows',
   'data': { 'name': 'str', '*tbl-id': 'uint32' },
@@ -281,9 +311,28 @@
 # @type: #optional group type.  If type is not specified, returns
 # group information for all group types.
 #
-# Returns: @Rocker OF-DPA group information
+# Returns: rocker OF-DPA group information
 #
 # Since: 2.4
+#
+# Example:
+#
+# -> { "execute": "query-rocker-of-dpa-groups",
+#      "arguments": { "name": "sw1" } }
+# <- { "return": [ {"type": 0, "out-pport": 2,
+#                   "pport": 2, "vlan-id": 3841,
+#                   "pop-vlan": 1, "id": 251723778},
+#                  {"type": 0, "out-pport": 0,
+#                   "pport": 0, "vlan-id": 3841,
+#                   "pop-vlan": 1, "id": 251723776},
+#                  {"type": 0, "out-pport": 1,
+#                   "pport": 1, "vlan-id": 3840,
+#                   "pop-vlan": 1, "id": 251658241},
+#                  {"type": 0, "out-pport": 0,
+#                   "pport": 0, "vlan-id": 3840,
+#                   "pop-vlan": 1, "id": 251658240}
+#    ]}
+#
 ##
 { 'command': 'query-rocker-of-dpa-groups',
   'data': { 'name': 'str', '*type': 'uint8' },
diff --git a/qapi/trace.json b/qapi/trace.json
index 3ad7df7fdb..2bfda7ac7c 100644
--- a/qapi/trace.json
+++ b/qapi/trace.json
@@ -62,6 +62,13 @@
 # an error is returned.
 #
 # Since: 2.2
+#
+# Example:
+#
+# -> { "execute": "trace-event-get-state",
+#      "arguments": { "name": "qemu_memalign" } }
+# <- { "return": [ { "name": "qemu_memalign", "state": "disabled" } ] }
+#
 ##
 { 'command': 'trace-event-get-state',
   'data': {'name': 'str', '*vcpu': 'int'},
@@ -87,6 +94,13 @@
 # error is returned.
 #
 # Since: 2.2
+#
+# Example:
+#
+# -> { "execute": "trace-event-set-state",
+#      "arguments": { "name": "qemu_memalign", "enable": "true" } }
+# <- { "return": {} }
+#
 ##
 { 'command': 'trace-event-set-state',
   'data': {'name': 'str', 'enable': 'bool', '*ignore-unavailable': 'bool',
diff --git a/Makefile b/Makefile
index 474cc5e66a..361773634d 100644
--- a/Makefile
+++ b/Makefile
@@ -431,7 +431,6 @@ endif
 install-doc: $(DOCS)
 	$(INSTALL_DIR) "$(DESTDIR)$(qemu_docdir)"
 	$(INSTALL_DATA) qemu-doc.html "$(DESTDIR)$(qemu_docdir)"
-	$(INSTALL_DATA) $(SRC_PATH)/docs/qmp-commands.txt "$(DESTDIR)$(qemu_docdir)"
 ifdef CONFIG_POSIX
 	$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
 	$(INSTALL_DATA) qemu.1 "$(DESTDIR)$(mandir)/man1"
diff --git a/docs/qmp-commands.txt b/docs/qmp-commands.txt
deleted file mode 100644
index abf210a596..0000000000
--- a/docs/qmp-commands.txt
+++ /dev/null
@@ -1,3824 +0,0 @@
-                        QMP Supported Commands
-                        ----------------------
-
-This document describes all commands currently supported by QMP.
-
-Most of the time their usage is exactly the same as in the user Monitor, this
-means that any other document which also describe commands (the manpage,
-QEMU's manual, etc) can and should be consulted.
-
-QMP has two types of commands: regular and query commands. Regular commands
-usually change the Virtual Machine's state someway, while query commands just
-return information. The sections below are divided accordingly.
-
-It's important to observe that all communication examples are formatted in
-a reader-friendly way, so that they're easier to understand. However, in real
-protocol usage, they're emitted as a single line.
-
-Also, the following notation is used to denote data flow:
-
--> data issued by the Client
-<- Server data response
-
-Please, refer to the QMP specification (docs/qmp-spec.txt) for detailed
-information on the Server command and response formats.
-
-NOTE: This document is temporary and will be replaced soon.
-
-1. Stability Considerations
-===========================
-
-The current QMP command set (described in this file) may be useful for a
-number of use cases, however it's limited and several commands have bad
-defined semantics, specially with regard to command completion.
-
-These problems are going to be solved incrementally in the next QEMU releases
-and we're going to establish a deprecation policy for badly defined commands.
-
-If you're planning to adopt QMP, please observe the following:
-
-    1. The deprecation policy will take effect and be documented soon, please
-       check the documentation of each used command as soon as a new release of
-       QEMU is available
-
-    2. DO NOT rely on anything which is not explicit documented
-
-    3. Errors, in special, are not documented. Applications should NOT check
-       for specific errors classes or data (it's strongly recommended to only
-       check for the "error" key)
-
-2. Regular Commands
-===================
-
-Server's responses in the examples below are always a success response, please
-refer to the QMP specification for more details on error responses.
-
-quit
-----
-
-Quit the emulator.
-
-Arguments: None.
-
-Example:
-
--> { "execute": "quit" }
-<- { "return": {} }
-
-eject
------
-
-Eject a removable medium.
-
-Arguments:
-
-- "force": force ejection (json-bool, optional)
-- "device": block device name (deprecated, use @id instead)
-            (json-string, optional)
-- "id": the name or QOM path of the guest device (json-string, optional)
-
-Example:
-
--> { "execute": "eject", "arguments": { "id": "ide0-1-0" } }
-<- { "return": {} }
-
-Note: The "force" argument defaults to false.
-
-change
-------
-
-Change a removable medium or VNC configuration.
-
-Arguments:
-
-- "device": device name (json-string)
-- "target": filename or item (json-string)
-- "arg": additional argument (json-string, optional)
-
-Examples:
-
-1. Change a removable medium
-
--> { "execute": "change",
-             "arguments": { "device": "ide1-cd0",
-                            "target": "/srv/images/Fedora-12-x86_64-DVD.iso" } }
-<- { "return": {} }
-
-2. Change VNC password
-
--> { "execute": "change",
-             "arguments": { "device": "vnc", "target": "password",
-                            "arg": "foobar1" } }
-<- { "return": {} }
-
-screendump
-----------
-
-Save screen into PPM image.
-
-Arguments:
-
-- "filename": file path (json-string)
-
-Example:
-
--> { "execute": "screendump", "arguments": { "filename": "/tmp/image" } }
-<- { "return": {} }
-
-stop
-----
-
-Stop the emulator.
-
-Arguments: None.
-
-Example:
-
--> { "execute": "stop" }
-<- { "return": {} }
-
-cont
-----
-
-Resume emulation.
-
-Arguments: None.
-
-Example:
-
--> { "execute": "cont" }
-<- { "return": {} }
-
-system_wakeup
--------------
-
-Wakeup guest from suspend.
-
-Arguments: None.
-
-Example:
-
--> { "execute": "system_wakeup" }
-<- { "return": {} }
-
-system_reset
-------------
-
-Reset the system.
-
-Arguments: None.
-
-Example:
-
--> { "execute": "system_reset" }
-<- { "return": {} }
-
-system_powerdown
-----------------
-
-Send system power down event.
-
-Arguments: None.
-
-Example:
-
--> { "execute": "system_powerdown" }
-<- { "return": {} }
-
-device_add
-----------
-
-Add a device.
-
-Arguments:
-
-- "driver": the name of the new device's driver (json-string)
-- "bus": the device's parent bus (device tree path, json-string, optional)
-- "id": the device's ID, must be unique (json-string)
-- device properties
-
-Example:
-
--> { "execute": "device_add", "arguments": { "driver": "e1000", "id": "net1" } }
-<- { "return": {} }
-
-Notes:
-
-(1) For detailed information about this command, please refer to the
-    'docs/qdev-device-use.txt' file.
-
-(2) It's possible to list device properties by running QEMU with the
-    "-device DEVICE,\?" command-line argument, where DEVICE is the device's name
-
-device_del
-----------
-
-Remove a device.
-
-Arguments:
-
-- "id": the device's ID or QOM path (json-string)
-
-Example:
-
--> { "execute": "device_del", "arguments": { "id": "net1" } }
-<- { "return": {} }
-
-Example:
-
--> { "execute": "device_del", "arguments": { "id": "/machine/peripheral-anon/device[0]" } }
-<- { "return": {} }
-
-send-key
-----------
-
-Send keys to VM.
-
-Arguments:
-
-keys array:
-    - "key": key sequence (a json-array of key union values,
-             union can be number or qcode enum)
-
-- hold-time: time to delay key up events, milliseconds. Defaults to 100
-             (json-int, optional)
-
-Example:
-
--> { "execute": "send-key",
-     "arguments": { "keys": [ { "type": "qcode", "data": "ctrl" },
-                              { "type": "qcode", "data": "alt" },
-                              { "type": "qcode", "data": "delete" } ] } }
-<- { "return": {} }
-
-cpu
----
-
-Set the default CPU.
-
-Arguments:
-
-- "index": the CPU's index (json-int)
-
-Example:
-
--> { "execute": "cpu", "arguments": { "index": 0 } }
-<- { "return": {} }
-
-Note: CPUs' indexes are obtained with the 'query-cpus' command.
-
-cpu-add
--------
-
-Adds virtual cpu
-
-Arguments:
-
-- "id": cpu id (json-int)
-
-Example:
-
--> { "execute": "cpu-add", "arguments": { "id": 2 } }
-<- { "return": {} }
-
-memsave
--------
-
-Save to disk virtual memory dump starting at 'val' of size 'size'.
-
-Arguments:
-
-- "val": the starting address (json-int)
-- "size": the memory size, in bytes (json-int)
-- "filename": file path (json-string)
-- "cpu": virtual CPU index (json-int, optional)
-
-Example:
-
--> { "execute": "memsave",
-             "arguments": { "val": 10,
-                            "size": 100,
-                            "filename": "/tmp/virtual-mem-dump" } }
-<- { "return": {} }
-
-pmemsave
---------
-
-Save to disk physical memory dump starting at 'val' of size 'size'.
-
-Arguments:
-
-- "val": the starting address (json-int)
-- "size": the memory size, in bytes (json-int)
-- "filename": file path (json-string)
-
-Example:
-
--> { "execute": "pmemsave",
-             "arguments": { "val": 10,
-                            "size": 100,
-                            "filename": "/tmp/physical-mem-dump" } }
-<- { "return": {} }
-
-inject-nmi
-----------
-
-Inject an NMI on the default CPU (x86/s390) or all CPUs (ppc64).
-
-Arguments: None.
-
-Example:
-
--> { "execute": "inject-nmi" }
-<- { "return": {} }
-
-Note: inject-nmi fails when the guest doesn't support injecting.
-
-ringbuf-write
--------------
-
-Write to a ring buffer character device.
-
-Arguments:
-
-- "device": ring buffer character device name (json-string)
-- "data": data to write (json-string)
-- "format": data format (json-string, optional)
-          - Possible values: "utf8" (default), "base64"
-
-Example:
-
--> { "execute": "ringbuf-write",
-                "arguments": { "device": "foo",
-                               "data": "abcdefgh",
-                               "format": "utf8" } }
-<- { "return": {} }
-
-ringbuf-read
--------------
-
-Read from a ring buffer character device.
-
-Arguments:
-
-- "device": ring buffer character device name (json-string)
-- "size": how many bytes to read at most (json-int)
-          - Number of data bytes, not number of characters in encoded data
-- "format": data format (json-string, optional)
-          - Possible values: "utf8" (default), "base64"
-          - Naturally, format "utf8" works only when the ring buffer
-            contains valid UTF-8 text.  Invalid UTF-8 sequences get
-            replaced.  Bug: replacement doesn't work.  Bug: can screw
-            up on encountering NUL characters, after the ring buffer
-            lost data, and when reading stops because the size limit
-            is reached.
-
-Example:
-
--> { "execute": "ringbuf-read",
-                "arguments": { "device": "foo",
-                               "size": 1000,
-                               "format": "utf8" } }
-<- {"return": "abcdefgh"}
-
-xen-save-devices-state
--------
-
-Save the state of all devices to file. The RAM and the block devices
-of the VM are not saved by this command.
-
-Arguments:
-
-- "filename": the file to save the state of the devices to as binary
-data. See xen-save-devices-state.txt for a description of the binary
-format.
-
-Example:
-
--> { "execute": "xen-save-devices-state",
-     "arguments": { "filename": "/tmp/save" } }
-<- { "return": {} }
-
-xen-load-devices-state
-----------------------
-
-Load the state of all devices from file. The RAM and the block devices
-of the VM are not loaded by this command.
-
-Arguments:
-
-- "filename": the file to load the state of the devices from as binary
-data. See xen-save-devices-state.txt for a description of the binary
-format.
-
-Example:
-
--> { "execute": "xen-load-devices-state",
-     "arguments": { "filename": "/tmp/resume" } }
-<- { "return": {} }
-
-xen-set-global-dirty-log
--------
-
-Enable or disable the global dirty log mode.
-
-Arguments:
-
-- "enable": Enable it or disable it.
-
-Example:
-
--> { "execute": "xen-set-global-dirty-log",
-     "arguments": { "enable": true } }
-<- { "return": {} }
-
-migrate
--------
-
-Migrate to URI.
-
-Arguments:
-
-- "blk": block migration, full disk copy (json-bool, optional)
-- "inc": incremental disk copy (json-bool, optional)
-- "uri": Destination URI (json-string)
-
-Example:
-
--> { "execute": "migrate", "arguments": { "uri": "tcp:0:4446" } }
-<- { "return": {} }
-
-Notes:
-
-(1) The 'query-migrate' command should be used to check migration's progress
-    and final result (this information is provided by the 'status' member)
-(2) All boolean arguments default to false
-(3) The user Monitor's "detach" argument is invalid in QMP and should not
-    be used
-
-migrate_cancel
---------------
-
-Cancel the current migration.
-
-Arguments: None.
-
-Example:
-
--> { "execute": "migrate_cancel" }
-<- { "return": {} }
-
-migrate-incoming
-----------------
-
-Continue an incoming migration
-
-Arguments:
-
-- "uri": Source/listening URI (json-string)
-
-Example:
-
--> { "execute": "migrate-incoming", "arguments": { "uri": "tcp::4446" } }
-<- { "return": {} }
-
-Notes:
-
-(1) QEMU must be started with -incoming defer to allow migrate-incoming to
-    be used
-(2) The uri format is the same as for -incoming
-
-migrate-set-cache-size
-----------------------
-
-Set cache size to be used by XBZRLE migration, the cache size will be rounded
-down to the nearest power of 2
-
-Arguments:
-
-- "value": cache size in bytes (json-int)
-
-Example:
-
--> { "execute": "migrate-set-cache-size", "arguments": { "value": 536870912 } }
-<- { "return": {} }
-
-migrate-start-postcopy
-----------------------
-
-Switch an in-progress migration to postcopy mode. Ignored after the end of
-migration (or once already in postcopy).
-
-Example:
--> { "execute": "migrate-start-postcopy" }
-<- { "return": {} }
-
-query-migrate-cache-size
-------------------------
-
-Show cache size to be used by XBZRLE migration
-
-returns a json-object with the following information:
-- "size" : json-int
-
-Example:
-
--> { "execute": "query-migrate-cache-size" }
-<- { "return": 67108864 }
-
-migrate_set_speed
------------------
-
-Set maximum speed for migrations.
-
-Arguments:
-
-- "value": maximum speed, in bytes per second (json-int)
-
-Example:
-
--> { "execute": "migrate_set_speed", "arguments": { "value": 1024 } }
-<- { "return": {} }
-
-migrate_set_downtime
---------------------
-
-Set maximum tolerated downtime (in seconds) for migrations.
-
-Arguments:
-
-- "value": maximum downtime (json-number)
-
-Example:
-
--> { "execute": "migrate_set_downtime", "arguments": { "value": 0.1 } }
-<- { "return": {} }
-
-x-colo-lost-heartbeat
---------------------
-
-Tell COLO that heartbeat is lost, a failover or takeover is needed.
-
-Example:
-
--> { "execute": "x-colo-lost-heartbeat" }
-<- { "return": {} }
-
-client_migrate_info
--------------------
-
-Set migration information for remote display.  This makes the server
-ask the client to automatically reconnect using the new parameters
-once migration finished successfully.  Only implemented for SPICE.
-
-Arguments:
-
-- "protocol":     must be "spice" (json-string)
-- "hostname":     migration target hostname (json-string)
-- "port":         spice tcp port for plaintext channels (json-int, optional)
-- "tls-port":     spice tcp port for tls-secured channels (json-int, optional)
-- "cert-subject": server certificate subject (json-string, optional)
-
-Example:
-
--> { "execute": "client_migrate_info",
-     "arguments": { "protocol": "spice",
-                    "hostname": "virt42.lab.kraxel.org",
-                    "port": 1234 } }
-<- { "return": {} }
-
-dump
-
-
-Dump guest memory to file. The file can be processed with crash or gdb.
-
-Arguments:
-
-- "paging": do paging to get guest's memory mapping (json-bool)
-- "protocol": destination file(started with "file:") or destination file
-              descriptor (started with "fd:") (json-string)
-- "detach": if specified, command will return immediately, without waiting
-            for the dump to finish. The user can track progress using
-            "query-dump". (json-bool)
-- "begin": the starting physical address. It's optional, and should be specified
-           with length together (json-int)
-- "length": the memory size, in bytes. It's optional, and should be specified
-            with begin together (json-int)
-- "format": the format of guest memory dump. It's optional, and can be
-            elf|kdump-zlib|kdump-lzo|kdump-snappy, but non-elf formats will
-            conflict with paging and filter, ie. begin and length (json-string)
-
-Example:
-
--> { "execute": "dump-guest-memory", "arguments": { "protocol": "fd:dump" } }
-<- { "return": {} }
-
-Notes:
-
-(1) All boolean arguments default to false
-
-query-dump-guest-memory-capability
-----------
-
-Show available formats for 'dump-guest-memory'
-
-Example:
-
--> { "execute": "query-dump-guest-memory-capability" }
-<- { "return": { "formats":
-                    ["elf", "kdump-zlib", "kdump-lzo", "kdump-snappy"] }
-
-query-dump
-----------
-
-Query background dump status.
-
-Arguments: None.
-
-Example:
-
--> { "execute": "query-dump" }
-<- { "return": { "status": "active", "completed": 1024000,
-                 "total": 2048000 } }
-
-dump-skeys
-----------
-
-Save guest storage keys to file.
-
-Arguments:
-
-- "filename": file path (json-string)
-
-Example:
-
--> { "execute": "dump-skeys", "arguments": { "filename": "/tmp/skeys" } }
-<- { "return": {} }
-
-netdev_add
-----------
-
-Add host network device.
-
-Arguments:
-
-- "type": the device type, "tap", "user", ... (json-string)
-- "id": the device's ID, must be unique (json-string)
-- device options
-
-Example:
-
--> { "execute": "netdev_add",
-     "arguments": { "type": "user", "id": "netdev1",
-                    "dnssearch": "example.org" } }
-<- { "return": {} }
-
-Note: The supported device options are the same ones supported by the '-netdev'
-      command-line argument, which are listed in the '-help' output or QEMU's
-      manual
-
-netdev_del
-----------
-
-Remove host network device.
-
-Arguments:
-
-- "id": the device's ID, must be unique (json-string)
-
-Example:
-
--> { "execute": "netdev_del", "arguments": { "id": "netdev1" } }
-<- { "return": {} }
-
-
-object-add
-----------
-
-Create QOM object.
-
-Arguments:
-
-- "qom-type": the object's QOM type, i.e. the class name (json-string)
-- "id": the object's ID, must be unique (json-string)
-- "props": a dictionary of object property values (optional, json-dict)
-
-Example:
-
--> { "execute": "object-add", "arguments": { "qom-type": "rng-random", "id": "rng1",
-     "props": { "filename": "/dev/hwrng" } } }
-<- { "return": {} }
-
-object-del
-----------
-
-Remove QOM object.
-
-Arguments:
-
-- "id": the object's ID (json-string)
-
-Example:
-
--> { "execute": "object-del", "arguments": { "id": "rng1" } }
-<- { "return": {} }
-
-
-block_resize
-------------
-
-Resize a block image while a guest is running.
-
-Arguments:
-
-- "device": the device's ID, must be unique (json-string)
-- "node-name": the node name in the block driver state graph (json-string)
-- "size": new size
-
-Example:
-
--> { "execute": "block_resize", "arguments": { "device": "scratch", "size": 1073741824 } }
-<- { "return": {} }
-
-block-stream
-------------
-
-Copy data from a backing file into a block device.
-
-Arguments:
-
-- "job-id": Identifier for the newly-created block job. If omitted,
-            the device name will be used. (json-string, optional)
-- "device": The device name or node-name of a root node (json-string)
-- "base": The file name of the backing image above which copying starts.
-          It cannot be set if 'base-node' is also set (json-string, optional)
-- "base-node": the node name of the backing image above which copying starts.
-               It cannot be set if 'base' is also set.
-               (json-string, optional) (Since 2.8)
-- "backing-file": The backing file string to write into the active layer. This
-                  filename is not validated.
-
-                  If a pathname string is such that it cannot be resolved by
-                  QEMU, that means that subsequent QMP or HMP commands must use
-                  node-names for the image in question, as filename lookup
-                  methods will fail.
-
-                  If not specified, QEMU will automatically determine the
-                  backing file string to use, or error out if there is no
-                  obvious choice.  Care should be taken when specifying the
-                  string, to specify a valid filename or protocol.
-                  (json-string, optional) (Since 2.1)
-- "speed":  the maximum speed, in bytes per second (json-int, optional)
-- "on-error": the action to take on an error (default 'report').  'stop' and
-              'enospc' can only be used if the block device supports io-status.
-              (json-string, optional) (Since 2.1)
-
-Example:
-
--> { "execute": "block-stream", "arguments": { "device": "virtio0",
-                                               "base": "/tmp/master.qcow2" } }
-<- { "return": {} }
-
-block-commit
-------------
-
-Live commit of data from overlay image nodes into backing nodes - i.e., writes
-data between 'top' and 'base' into 'base'.
-
-Arguments:
-
-- "job-id": Identifier for the newly-created block job. If omitted,
-            the device name will be used. (json-string, optional)
-- "device": The device name or node-name of a root node (json-string)
-- "base": The file name of the backing image to write data into.
-          If not specified, this is the deepest backing image
-          (json-string, optional)
-- "top":  The file name of the backing image within the image chain,
-          which contains the topmost data to be committed down. If
-          not specified, this is the active layer. (json-string, optional)
-
-- backing-file:     The backing file string to write into the overlay
-                    image of 'top'.  If 'top' is the active layer,
-                    specifying a backing file string is an error. This
-                    filename is not validated.
-
-                    If a pathname string is such that it cannot be
-                    resolved by QEMU, that means that subsequent QMP or
-                    HMP commands must use node-names for the image in
-                    question, as filename lookup methods will fail.
-
-                    If not specified, QEMU will automatically determine
-                    the backing file string to use, or error out if
-                    there is no obvious choice. Care should be taken
-                    when specifying the string, to specify a valid
-                    filename or protocol.
-                    (json-string, optional) (Since 2.1)
-
-          If top == base, that is an error.
-          If top == active, the job will not be completed by itself,
-          user needs to complete the job with the block-job-complete
-          command after getting the ready event. (Since 2.0)
-
-          If the base image is smaller than top, then the base image
-          will be resized to be the same size as top.  If top is
-          smaller than the base image, the base will not be
-          truncated.  If you want the base image size to match the
-          size of the smaller top, you can safely truncate it
-          yourself once the commit operation successfully completes.
-          (json-string)
-- "speed":  the maximum speed, in bytes per second (json-int, optional)
-
-
-Example:
-
--> { "execute": "block-commit", "arguments": { "device": "virtio0",
-                                              "top": "/tmp/snap1.qcow2" } }
-<- { "return": {} }
-
-drive-backup
-------------
-
-Start a point-in-time copy of a block device to a new destination.  The
-status of ongoing drive-backup operations can be checked with
-query-block-jobs where the BlockJobInfo.type field has the value 'backup'.
-The operation can be stopped before it has completed using the
-block-job-cancel command.
-
-Arguments:
-
-- "job-id": Identifier for the newly-created block job. If omitted,
-            the device name will be used. (json-string, optional)
-- "device": the device name or node-name of a root node which should be copied.
-            (json-string)
-- "target": the target of the new image. If the file exists, or if it is a
-            device, the existing file/device will be used as the new
-            destination.  If it does not exist, a new file will be created.
-            (json-string)
-- "format": the format of the new destination, default is to probe if 'mode' is
-            'existing', else the format of the source
-            (json-string, optional)
-- "sync": what parts of the disk image should be copied to the destination;
-  possibilities include "full" for all the disk, "top" for only the sectors
-  allocated in the topmost image, "incremental" for only the dirty sectors in
-  the bitmap, or "none" to only replicate new I/O (MirrorSyncMode).
-- "bitmap": dirty bitmap name for sync==incremental. Must be present if sync
-            is "incremental", must NOT be present otherwise.
-- "mode": whether and how QEMU should create a new image
-          (NewImageMode, optional, default 'absolute-paths')
-- "speed": the maximum speed, in bytes per second (json-int, optional)
-- "compress": true to compress data, if the target format supports it.
-              (json-bool, optional, default false)
-- "on-source-error": the action to take on an error on the source, default
-                     'report'.  'stop' and 'enospc' can only be used
-                     if the block device supports io-status.
-                     (BlockdevOnError, optional)
-- "on-target-error": the action to take on an error on the target, default
-                     'report' (no limitations, since this applies to
-                     a different block device than device).
-                     (BlockdevOnError, optional)
-
-Example:
--> { "execute": "drive-backup", "arguments": { "device": "drive0",
-                                               "sync": "full",
-                                               "target": "backup.img" } }
-<- { "return": {} }
-
-blockdev-backup
----------------
-
-The device version of drive-backup: this command takes an existing named device
-as backup target.
-
-Arguments:
-
-- "job-id": Identifier for the newly-created block job. If omitted,
-            the device name will be used. (json-string, optional)
-- "device": the device name or node-name of a root node which should be copied.
-            (json-string)
-- "target": the name of the backup target device. (json-string)
-- "sync": what parts of the disk image should be copied to the destination;
-          possibilities include "full" for all the disk, "top" for only the
-          sectors allocated in the topmost image, or "none" to only replicate
-          new I/O (MirrorSyncMode).
-- "speed": the maximum speed, in bytes per second (json-int, optional)
-- "compress": true to compress data, if the target format supports it.
-              (json-bool, optional, default false)
-- "on-source-error": the action to take on an error on the source, default
-                     'report'.  'stop' and 'enospc' can only be used
-                     if the block device supports io-status.
-                     (BlockdevOnError, optional)
-- "on-target-error": the action to take on an error on the target, default
-                     'report' (no limitations, since this applies to
-                     a different block device than device).
-                     (BlockdevOnError, optional)
-
-Example:
--> { "execute": "blockdev-backup", "arguments": { "device": "src-id",
-                                                  "sync": "full",
-                                                  "target": "tgt-id" } }
-<- { "return": {} }
-
-transaction
------------
-
-Atomically operate on one or more block devices.  Operations that are
-currently supported:
-
-    - drive-backup
-    - blockdev-backup
-    - blockdev-snapshot-sync
-    - blockdev-snapshot-internal-sync
-    - abort
-    - block-dirty-bitmap-add
-    - block-dirty-bitmap-clear
-
-Refer to the qemu/qapi-schema.json file for minimum required QEMU
-versions for these operations.  A list of dictionaries is accepted,
-that contains the actions to be performed.  If there is any failure
-performing any of the operations, all operations for the group are
-abandoned.
-
-For external snapshots, the dictionary contains the device, the file to use for
-the new snapshot, and the format.  The default format, if not specified, is
-qcow2.
-
-Each new snapshot defaults to being created by QEMU (wiping any
-contents if the file already exists), but it is also possible to reuse
-an externally-created file.  In the latter case, you should ensure that
-the new image file has the same contents as the current one; QEMU cannot
-perform any meaningful check.  Typically this is achieved by using the
-current image file as the backing file for the new image.
-
-On failure, the original disks pre-snapshot attempt will be used.
-
-For internal snapshots, the dictionary contains the device and the snapshot's
-name.  If an internal snapshot matching name already exists, the request will
-be rejected.  Only some image formats support it, for example, qcow2, rbd,
-and sheepdog.
-
-On failure, qemu will try delete the newly created internal snapshot in the
-transaction.  When an I/O error occurs during deletion, the user needs to fix
-it later with qemu-img or other command.
-
-Arguments:
-
-actions array:
-    - "type": the operation to perform (json-string).  Possible
-              values: "drive-backup", "blockdev-backup",
-                      "blockdev-snapshot-sync",
-                      "blockdev-snapshot-internal-sync",
-                      "abort", "block-dirty-bitmap-add",
-                      "block-dirty-bitmap-clear"
-    - "data": a dictionary.  The contents depend on the value
-      of "type".  When "type" is "blockdev-snapshot-sync":
-      - "device": device name to snapshot (json-string)
-      - "node-name": graph node name to snapshot (json-string)
-      - "snapshot-file": name of new image file (json-string)
-      - "snapshot-node-name": graph node name of the new snapshot (json-string)
-      - "format": format of new image (json-string, optional)
-      - "mode": whether and how QEMU should create the snapshot file
-        (NewImageMode, optional, default "absolute-paths")
-      When "type" is "blockdev-snapshot-internal-sync":
-      - "device": the device name or node-name of a root node to snapshot
-                  (json-string)
-      - "name": name of the new snapshot (json-string)
-
-Example:
-
--> { "execute": "transaction",
-     "arguments": { "actions": [
-         { "type": "blockdev-snapshot-sync", "data" : { "device": "ide-hd0",
-                                         "snapshot-file": "/some/place/my-image",
-                                         "format": "qcow2" } },
-         { "type": "blockdev-snapshot-sync", "data" : { "node-name": "myfile",
-                                         "snapshot-file": "/some/place/my-image2",
-                                         "snapshot-node-name": "node3432",
-                                         "mode": "existing",
-                                         "format": "qcow2" } },
-         { "type": "blockdev-snapshot-sync", "data" : { "device": "ide-hd1",
-                                         "snapshot-file": "/some/place/my-image2",
-                                         "mode": "existing",
-                                         "format": "qcow2" } },
-         { "type": "blockdev-snapshot-internal-sync", "data" : {
-                                         "device": "ide-hd2",
-                                         "name": "snapshot0" } } ] } }
-<- { "return": {} }
-
-block-dirty-bitmap-add
-----------------------
-Since 2.4
-
-Create a dirty bitmap with a name on the device, and start tracking the writes.
-
-Arguments:
-
-- "node": device/node on which to create dirty bitmap (json-string)
-- "name": name of the new dirty bitmap (json-string)
-- "granularity": granularity to track writes with (int, optional)
-
-Example:
-
--> { "execute": "block-dirty-bitmap-add", "arguments": { "node": "drive0",
-                                                   "name": "bitmap0" } }
-<- { "return": {} }
-
-block-dirty-bitmap-remove
--------------------------
-Since 2.4
-
-Stop write tracking and remove the dirty bitmap that was created with
-block-dirty-bitmap-add.
-
-Arguments:
-
-- "node": device/node on which to remove dirty bitmap (json-string)
-- "name": name of the dirty bitmap to remove (json-string)
-
-Example:
-
--> { "execute": "block-dirty-bitmap-remove", "arguments": { "node": "drive0",
-                                                      "name": "bitmap0" } }
-<- { "return": {} }
-
-block-dirty-bitmap-clear
-------------------------
-Since 2.4
-
-Reset the dirty bitmap associated with a node so that an incremental backup
-from this point in time forward will only backup clusters modified after this
-clear operation.
-
-Arguments:
-
-- "node": device/node on which to remove dirty bitmap (json-string)
-- "name": name of the dirty bitmap to remove (json-string)
-
-Example:
-
--> { "execute": "block-dirty-bitmap-clear", "arguments": { "node": "drive0",
-                                                           "name": "bitmap0" } }
-<- { "return": {} }
-
-blockdev-snapshot-sync
-----------------------
-
-Synchronous snapshot of a block device. snapshot-file specifies the
-target of the new image. If the file exists, or if it is a device, the
-snapshot will be created in the existing file/device. If does not
-exist, a new file will be created. format specifies the format of the
-snapshot image, default is qcow2.
-
-Arguments:
-
-- "device": device name to snapshot (json-string)
-- "node-name": graph node name to snapshot (json-string)
-- "snapshot-file": name of new image file (json-string)
-- "snapshot-node-name": graph node name of the new snapshot (json-string)
-- "mode": whether and how QEMU should create the snapshot file
-  (NewImageMode, optional, default "absolute-paths")
-- "format": format of new image (json-string, optional)
-
-Example:
-
--> { "execute": "blockdev-snapshot-sync", "arguments": { "device": "ide-hd0",
-                                                         "snapshot-file":
-                                                        "/some/place/my-image",
-                                                        "format": "qcow2" } }
-<- { "return": {} }
-
-blockdev-snapshot
------------------
-Since 2.5
-
-Create a snapshot, by installing 'node' as the backing image of
-'overlay'. Additionally, if 'node' is associated with a block
-device, the block device changes to using 'overlay' as its new active
-image.
-
-Arguments:
-
-- "node": device that will have a snapshot created (json-string)
-- "overlay": device that will have 'node' as its backing image (json-string)
-
-Example:
-
--> { "execute": "blockdev-add",
-                "arguments": { "driver": "qcow2",
-                               "node-name": "node1534",
-                               "file": { "driver": "file",
-                                         "filename": "hd1.qcow2" },
-                               "backing": "" } }
-
-<- { "return": {} }
-
--> { "execute": "blockdev-snapshot", "arguments": { "node": "ide-hd0",
-                                                    "overlay": "node1534" } }
-<- { "return": {} }
-
-blockdev-snapshot-internal-sync
--------------------------------
-
-Synchronously take an internal snapshot of a block device when the format of
-image used supports it.  If the name is an empty string, or a snapshot with
-name already exists, the operation will fail.
-
-Arguments:
-
-- "device": the device name or node-name of a root node to snapshot
-            (json-string)
-- "name": name of the new snapshot (json-string)
-
-Example:
-
--> { "execute": "blockdev-snapshot-internal-sync",
-                "arguments": { "device": "ide-hd0",
-                               "name": "snapshot0" }
-   }
-<- { "return": {} }
-
-blockdev-snapshot-delete-internal-sync
---------------------------------------
-
-Synchronously delete an internal snapshot of a block device when the format of
-image used supports it.  The snapshot is identified by name or id or both.  One
-of name or id is required.  If the snapshot is not found, the operation will
-fail.
-
-Arguments:
-
-- "device": the device name or node-name of a root node (json-string)
-- "id": ID of the snapshot (json-string, optional)
-- "name": name of the snapshot (json-string, optional)
-
-Example:
-
--> { "execute": "blockdev-snapshot-delete-internal-sync",
-                "arguments": { "device": "ide-hd0",
-                               "name": "snapshot0" }
-   }
-<- { "return": {
-                   "id": "1",
-                   "name": "snapshot0",
-                   "vm-state-size": 0,
-                   "date-sec": 1000012,
-                   "date-nsec": 10,
-                   "vm-clock-sec": 100,
-                   "vm-clock-nsec": 20
-     }
-   }
-
-drive-mirror
-------------
-
-Start mirroring a block device's writes to a new destination. target
-specifies the target of the new image. If the file exists, or if it is
-a device, it will be used as the new destination for writes. If it does not
-exist, a new file will be created. format specifies the format of the
-mirror image, default is to probe if mode='existing', else the format
-of the source.
-
-Arguments:
-
-- "job-id": Identifier for the newly-created block job. If omitted,
-            the device name will be used. (json-string, optional)
-- "device": the device name or node-name of a root node whose writes should be
-            mirrored. (json-string)
-- "target": name of new image file (json-string)
-- "format": format of new image (json-string, optional)
-- "node-name": the name of the new block driver state in the node graph
-               (json-string, optional)
-- "replaces": the block driver node name to replace when finished
-              (json-string, optional)
-- "mode": how an image file should be created into the target
-  file/device (NewImageMode, optional, default 'absolute-paths')
-- "speed": maximum speed of the streaming job, in bytes per second
-  (json-int)
-- "granularity": granularity of the dirty bitmap, in bytes (json-int, optional)
-- "buf-size": maximum amount of data in flight from source to target, in bytes
-  (json-int, default 10M)
-- "sync": what parts of the disk image should be copied to the destination;
-  possibilities include "full" for all the disk, "top" for only the sectors
-  allocated in the topmost image, or "none" to only replicate new I/O
-  (MirrorSyncMode).
-- "on-source-error": the action to take on an error on the source
-  (BlockdevOnError, default 'report')
-- "on-target-error": the action to take on an error on the target
-  (BlockdevOnError, default 'report')
-- "unmap": whether the target sectors should be discarded where source has only
-  zeroes. (json-bool, optional, default true)
-
-The default value of the granularity is the image cluster size clamped
-between 4096 and 65536, if the image format defines one.  If the format
-does not define a cluster size, the default value of the granularity
-is 65536.
-
-
-Example:
-
--> { "execute": "drive-mirror", "arguments": { "device": "ide-hd0",
-                                               "target": "/some/place/my-image",
-                                               "sync": "full",
-                                               "format": "qcow2" } }
-<- { "return": {} }
-
-blockdev-mirror
-------------
-
-Start mirroring a block device's writes to another block device. target
-specifies the target of mirror operation.
-
-Arguments:
-
-- "job-id": Identifier for the newly-created block job. If omitted,
-            the device name will be used. (json-string, optional)
-- "device": The device name or node-name of a root node whose writes should be
-            mirrored (json-string)
-- "target": device name to mirror to (json-string)
-- "replaces": the block driver node name to replace when finished
-              (json-string, optional)
-- "speed": maximum speed of the streaming job, in bytes per second
-  (json-int)
-- "granularity": granularity of the dirty bitmap, in bytes (json-int, optional)
-- "buf_size": maximum amount of data in flight from source to target, in bytes
-  (json-int, default 10M)
-- "sync": what parts of the disk image should be copied to the destination;
-  possibilities include "full" for all the disk, "top" for only the sectors
-  allocated in the topmost image, or "none" to only replicate new I/O
-  (MirrorSyncMode).
-- "on-source-error": the action to take on an error on the source
-  (BlockdevOnError, default 'report')
-- "on-target-error": the action to take on an error on the target
-  (BlockdevOnError, default 'report')
-
-The default value of the granularity is the image cluster size clamped
-between 4096 and 65536, if the image format defines one.  If the format
-does not define a cluster size, the default value of the granularity
-is 65536.
-
-Example:
-
--> { "execute": "blockdev-mirror", "arguments": { "device": "ide-hd0",
-                                                  "target": "target0",
-                                                  "sync": "full" } }
-<- { "return": {} }
-
-change-backing-file
--------------------
-Since: 2.1
-
-Change the backing file in the image file metadata.  This does not cause
-QEMU to reopen the image file to reparse the backing filename (it may,
-however, perform a reopen to change permissions from r/o -> r/w -> r/o,
-if needed). The new backing file string is written into the image file
-metadata, and the QEMU internal strings are updated.
-
-Arguments:
-
-- "image-node-name":    The name of the block driver state node of the
-                        image to modify.  The "device" is argument is used to
-                        verify "image-node-name" is in the chain described by
-                        "device".
-                        (json-string, optional)
-
-- "device":             The device name or node-name of the root node that owns
-                        image-node-name.
-                        (json-string)
-
-- "backing-file":       The string to write as the backing file.  This string is
-                        not validated, so care should be taken when specifying
-                        the string or the image chain may not be able to be
-                        reopened again.
-                        (json-string)
-
-Returns: Nothing on success
-         If "device" does not exist or cannot be determined, DeviceNotFound
-
-balloon
--------
-
-Request VM to change its memory allocation (in bytes).
-
-Arguments:
-
-- "value": New memory allocation (json-int)
-
-Example:
-
--> { "execute": "balloon", "arguments": { "value": 536870912 } }
-<- { "return": {} }
-
-set_link
---------
-
-Change the link status of a network adapter.
-
-Arguments:
-
-- "name": network device name (json-string)
-- "up": status is up (json-bool)
-
-Example:
-
--> { "execute": "set_link", "arguments": { "name": "e1000.0", "up": false } }
-<- { "return": {} }
-
-getfd
------
-
-Receive a file descriptor via SCM rights and assign it a name.
-
-Arguments:
-
-- "fdname": file descriptor name (json-string)
-
-Example:
-
--> { "execute": "getfd", "arguments": { "fdname": "fd1" } }
-<- { "return": {} }
-
-Notes:
-
-(1) If the name specified by the "fdname" argument already exists,
-    the file descriptor assigned to it will be closed and replaced
-    by the received file descriptor.
-(2) The 'closefd' command can be used to explicitly close the file
-    descriptor when it is no longer needed.
-
-closefd
--------
-
-Close a file descriptor previously passed via SCM rights.
-
-Arguments:
-
-- "fdname": file descriptor name (json-string)
-
-Example:
-
--> { "execute": "closefd", "arguments": { "fdname": "fd1" } }
-<- { "return": {} }
-
-add-fd
--------
-
-Add a file descriptor, that was passed via SCM rights, to an fd set.
-
-Arguments:
-
-- "fdset-id": The ID of the fd set to add the file descriptor to.
-              (json-int, optional)
-- "opaque": A free-form string that can be used to describe the fd.
-            (json-string, optional)
-
-Return a json-object with the following information:
-
-- "fdset-id": The ID of the fd set that the fd was added to. (json-int)
-- "fd": The file descriptor that was received via SCM rights and added to the
-        fd set. (json-int)
-
-Example:
-
--> { "execute": "add-fd", "arguments": { "fdset-id": 1 } }
-<- { "return": { "fdset-id": 1, "fd": 3 } }
-
-Notes:
-
-(1) The list of fd sets is shared by all monitor connections.
-(2) If "fdset-id" is not specified, a new fd set will be created.
-
-remove-fd
----------
-
-Remove a file descriptor from an fd set.
-
-Arguments:
-
-- "fdset-id": The ID of the fd set that the file descriptor belongs to.
-              (json-int)
-- "fd": The file descriptor that is to be removed. (json-int, optional)
-
-Example:
-
--> { "execute": "remove-fd", "arguments": { "fdset-id": 1, "fd": 3 } }
-<- { "return": {} }
-
-Notes:
-
-(1) The list of fd sets is shared by all monitor connections.
-(2) If "fd" is not specified, all file descriptors in "fdset-id" will be
-    removed.
-
-query-fdsets
--------------
-
-Return information describing all fd sets.
-
-Arguments: None
-
-Example:
-
--> { "execute": "query-fdsets" }
-<- { "return": [
-       {
-         "fds": [
-           {
-             "fd": 30,
-             "opaque": "rdonly:/path/to/file"
-           },
-           {
-             "fd": 24,
-             "opaque": "rdwr:/path/to/file"
-           }
-         ],
-         "fdset-id": 1
-       },
-       {
-         "fds": [
-           {
-             "fd": 28
-           },
-           {
-             "fd": 29
-           }
-         ],
-         "fdset-id": 0
-       }
-     ]
-   }
-
-Note: The list of fd sets is shared by all monitor connections.
-
-block_passwd
-------------
-
-Set the password of encrypted block devices.
-
-Arguments:
-
-- "device": device name (json-string)
-- "node-name": name in the block driver state graph (json-string)
-- "password": password (json-string)
-
-Example:
-
--> { "execute": "block_passwd", "arguments": { "device": "ide0-hd0",
-                                               "password": "12345" } }
-<- { "return": {} }
-
-block_set_io_throttle
-------------
-
-Change I/O throttle limits for a block drive.
-
-Arguments:
-
-- "device": block device name (deprecated, use @id instead)
-            (json-string, optional)
-- "id": the name or QOM path of the guest device (json-string, optional)
-- "bps": total throughput limit in bytes per second (json-int)
-- "bps_rd": read throughput limit in bytes per second (json-int)
-- "bps_wr": write throughput limit in bytes per second (json-int)
-- "iops": total I/O operations per second (json-int)
-- "iops_rd": read I/O operations per second (json-int)
-- "iops_wr": write I/O operations per second (json-int)
-- "bps_max": total throughput limit during bursts, in bytes (json-int, optional)
-- "bps_rd_max": read throughput limit during bursts, in bytes (json-int, optional)
-- "bps_wr_max": write throughput limit during bursts, in bytes (json-int, optional)
-- "iops_max": total I/O operations per second during bursts (json-int, optional)
-- "iops_rd_max": read I/O operations per second during bursts (json-int, optional)
-- "iops_wr_max": write I/O operations per second during bursts (json-int, optional)
-- "bps_max_length": maximum length of the @bps_max burst period, in seconds (json-int, optional)
-- "bps_rd_max_length": maximum length of the @bps_rd_max burst period, in seconds (json-int, optional)
-- "bps_wr_max_length": maximum length of the @bps_wr_max burst period, in seconds (json-int, optional)
-- "iops_max_length": maximum length of the @iops_max burst period, in seconds (json-int, optional)
-- "iops_rd_max_length": maximum length of the @iops_rd_max burst period, in seconds (json-int, optional)
-- "iops_wr_max_length": maximum length of the @iops_wr_max burst period, in seconds (json-int, optional)
-- "iops_size":  I/O size in bytes when limiting (json-int, optional)
-- "group": throttle group name (json-string, optional)
-
-Example:
-
--> { "execute": "block_set_io_throttle", "arguments": { "id": "ide0-1-0",
-                                               "bps": 1000000,
-                                               "bps_rd": 0,
-                                               "bps_wr": 0,
-                                               "iops": 0,
-                                               "iops_rd": 0,
-                                               "iops_wr": 0,
-                                               "bps_max": 8000000,
-                                               "bps_rd_max": 0,
-                                               "bps_wr_max": 0,
-                                               "iops_max": 0,
-                                               "iops_rd_max": 0,
-                                               "iops_wr_max": 0,
-                                               "bps_max_length": 60,
-                                               "iops_size": 0 } }
-<- { "return": {} }
-
-set_password
-------------
-
-Set the password for vnc/spice protocols.
-
-Arguments:
-
-- "protocol": protocol name (json-string)
-- "password": password (json-string)
-- "connected": [ keep | disconnect | fail ] (json-string, optional)
-
-Example:
-
--> { "execute": "set_password", "arguments": { "protocol": "vnc",
-                                               "password": "secret" } }
-<- { "return": {} }
-
-expire_password
----------------
-
-Set the password expire time for vnc/spice protocols.
-
-Arguments:
-
-- "protocol": protocol name (json-string)
-- "time": [ now | never | +secs | secs ] (json-string)
-
-Example:
-
--> { "execute": "expire_password", "arguments": { "protocol": "vnc",
-                                                  "time": "+60" } }
-<- { "return": {} }
-
-add_client
-----------
-
-Add a graphics client
-
-Arguments:
-
-- "protocol": protocol name (json-string)
-- "fdname": file descriptor name (json-string)
-- "skipauth": whether to skip authentication (json-bool, optional)
-- "tls": whether to perform TLS (json-bool, optional)
-
-Example:
-
--> { "execute": "add_client", "arguments": { "protocol": "vnc",
-                                             "fdname": "myclient" } }
-<- { "return": {} }
-
-qmp_capabilities
-----------------
-
-Enable QMP capabilities.
-
-Arguments: None.
-
-Example:
-
--> { "execute": "qmp_capabilities" }
-<- { "return": {} }
-
-Note: This command must be issued before issuing any other command.
-
-human-monitor-command
----------------------
-
-Execute a Human Monitor command.
-
-Arguments:
-
-- command-line: the command name and its arguments, just like the
-                Human Monitor's shell (json-string)
-- cpu-index: select the CPU number to be used by commands which access CPU
-             data, like 'info registers'. The Monitor selects CPU 0 if this
-             argument is not provided (json-int, optional)
-
-Example:
-
--> { "execute": "human-monitor-command", "arguments": { "command-line": "info kvm" } }
-<- { "return": "kvm support: enabled\r\n" }
-
-Notes:
-
-(1) The Human Monitor is NOT an stable interface, this means that command
-    names, arguments and responses can change or be removed at ANY time.
-    Applications that rely on long term stability guarantees should NOT
-    use this command
-
-(2) Limitations:
-
-    o This command is stateless, this means that commands that depend
-      on state information (such as getfd) might not work
-
-    o Commands that prompt the user for data (eg. 'cont' when the block
-      device is encrypted) don't currently work
-
-3. Query Commands
-=================
-
-
-query-version
--------------
-
-Show QEMU version.
-
-Return a json-object with the following information:
-
-- "qemu": A json-object containing three integer values:
-    - "major": QEMU's major version (json-int)
-    - "minor": QEMU's minor version (json-int)
-    - "micro": QEMU's micro version (json-int)
-- "package": package's version (json-string)
-
-Example:
-
--> { "execute": "query-version" }
-<- {
-      "return":{
-         "qemu":{
-            "major":0,
-            "minor":11,
-            "micro":5
-         },
-         "package":""
-      }
-   }
-
-query-commands
---------------
-
-List QMP available commands.
-
-Each command is represented by a json-object, the returned value is a json-array
-of all commands.
-
-Each json-object contain:
-
-- "name": command's name (json-string)
-
-Example:
-
--> { "execute": "query-commands" }
-<- {
-      "return":[
-         {
-            "name":"query-balloon"
-         },
-         {
-            "name":"system_powerdown"
-         }
-      ]
-   }
-
-Note: This example has been shortened as the real response is too long.
-
-query-events
---------------
-
-List QMP available events.
-
-Each event is represented by a json-object, the returned value is a json-array
-of all events.
-
-Each json-object contains:
-
-- "name": event's name (json-string)
-
-Example:
-
--> { "execute": "query-events" }
-<- {
-      "return":[
-         {
-            "name":"SHUTDOWN"
-         },
-         {
-            "name":"RESET"
-         }
-      ]
-   }
-
-Note: This example has been shortened as the real response is too long.
-
-query-qmp-schema
-----------------
-
-Return the QMP wire schema.  The returned value is a json-array of
-named schema entities.  Entities are commands, events and various
-types.  See docs/qapi-code-gen.txt for information on their structure
-and intended use.
-
-query-chardev
--------------
-
-Each device is represented by a json-object. The returned value is a json-array
-of all devices.
-
-Each json-object contain the following:
-
-- "label": device's label (json-string)
-- "filename": device's file (json-string)
-- "frontend-open": open/closed state of the frontend device attached to this
-                   backend (json-bool)
-
-Example:
-
--> { "execute": "query-chardev" }
-<- {
-      "return": [
-         {
-            "label": "charchannel0",
-            "filename": "unix:/var/lib/libvirt/qemu/seabios.rhel6.agent,server",
-            "frontend-open": false
-         },
-         {
-            "label": "charmonitor",
-            "filename": "unix:/var/lib/libvirt/qemu/seabios.rhel6.monitor,server",
-            "frontend-open": true
-         },
-         {
-            "label": "charserial0",
-            "filename": "pty:/dev/pts/2",
-            "frontend-open": true
-         }
-      ]
-   }
-
-query-chardev-backends
--------------
-
-List available character device backends.
-
-Each backend is represented by a json-object, the returned value is a json-array
-of all backends.
-
-Each json-object contains:
-
-- "name": backend name (json-string)
-
-Example:
-
--> { "execute": "query-chardev-backends" }
-<- {
-      "return":[
-         {
-            "name":"udp"
-         },
-         {
-            "name":"tcp"
-         },
-         {
-            "name":"unix"
-         },
-         {
-            "name":"spiceport"
-         }
-      ]
-   }
-
-query-block
------------
-
-Show the block devices.
-
-Each block device information is stored in a json-object and the returned value
-is a json-array of all devices.
-
-Each json-object contain the following:
-
-- "device": device name (json-string)
-- "type": device type (json-string)
-         - deprecated, retained for backward compatibility
-         - Possible values: "unknown"
-- "removable": true if the device is removable, false otherwise (json-bool)
-- "locked": true if the device is locked, false otherwise (json-bool)
-- "tray_open": only present if removable, true if the device has a tray,
-               and it is open (json-bool)
-- "inserted": only present if the device is inserted, it is a json-object
-   containing the following:
-         - "file": device file name (json-string)
-         - "ro": true if read-only, false otherwise (json-bool)
-         - "drv": driver format name (json-string)
-             - Possible values: "blkdebug", "bochs", "cloop", "dmg",
-                                "file", "file", "ftp", "ftps", "host_cdrom",
-                                "host_device", "http", "https",
-                                "nbd", "parallels", "qcow", "qcow2", "raw",
-                                "vdi", "vmdk", "vpc", "vvfat"
-         - "backing_file": backing file name (json-string, optional)
-         - "backing_file_depth": number of files in the backing file chain (json-int)
-         - "encrypted": true if encrypted, false otherwise (json-bool)
-         - "bps": limit total bytes per second (json-int)
-         - "bps_rd": limit read bytes per second (json-int)
-         - "bps_wr": limit write bytes per second (json-int)
-         - "iops": limit total I/O operations per second (json-int)
-         - "iops_rd": limit read operations per second (json-int)
-         - "iops_wr": limit write operations per second (json-int)
-         - "bps_max":  total max in bytes (json-int)
-         - "bps_rd_max":  read max in bytes (json-int)
-         - "bps_wr_max":  write max in bytes (json-int)
-         - "iops_max":  total I/O operations max (json-int)
-         - "iops_rd_max":  read I/O operations max (json-int)
-         - "iops_wr_max":  write I/O operations max (json-int)
-         - "iops_size": I/O size when limiting by iops (json-int)
-         - "detect_zeroes": detect and optimize zero writing (json-string)
-             - Possible values: "off", "on", "unmap"
-         - "write_threshold": write offset threshold in bytes, a event will be
-                              emitted if crossed. Zero if disabled (json-int)
-         - "image": the detail of the image, it is a json-object containing
-            the following:
-             - "filename": image file name (json-string)
-             - "format": image format (json-string)
-             - "virtual-size": image capacity in bytes (json-int)
-             - "dirty-flag": true if image is not cleanly closed, not present
-                             means clean (json-bool, optional)
-             - "actual-size": actual size on disk in bytes of the image, not
-                              present when image does not support thin
-                              provision (json-int, optional)
-             - "cluster-size": size of a cluster in bytes, not present if image
-                               format does not support it (json-int, optional)
-             - "encrypted": true if the image is encrypted, not present means
-                            false or the image format does not support
-                            encryption (json-bool, optional)
-             - "backing_file": backing file name, not present means no backing
-                               file is used or the image format does not
-                               support backing file chain
-                               (json-string, optional)
-             - "full-backing-filename": full path of the backing file, not
-                                        present if it equals backing_file or no
-                                        backing file is used
-                                        (json-string, optional)
-             - "backing-filename-format": the format of the backing file, not
-                                          present means unknown or no backing
-                                          file (json-string, optional)
-             - "snapshots": the internal snapshot info, it is an optional list
-                of json-object containing the following:
-                 - "id": unique snapshot id (json-string)
-                 - "name": snapshot name (json-string)
-                 - "vm-state-size": size of the VM state in bytes (json-int)
-                 - "date-sec": UTC date of the snapshot in seconds (json-int)
-                 - "date-nsec": fractional part in nanoseconds to be used with
-                                date-sec (json-int)
-                 - "vm-clock-sec": VM clock relative to boot in seconds
-                                   (json-int)
-                 - "vm-clock-nsec": fractional part in nanoseconds to be used
-                                    with vm-clock-sec (json-int)
-             - "backing-image": the detail of the backing image, it is an
-                                optional json-object only present when a
-                                backing image present for this image
-
-- "io-status": I/O operation status, only present if the device supports it
-               and the VM is configured to stop on errors. It's always reset
-               to "ok" when the "cont" command is issued (json_string, optional)
-             - Possible values: "ok", "failed", "nospace"
-
-Example:
-
--> { "execute": "query-block" }
-<- {
-      "return":[
-         {
-            "io-status": "ok",
-            "device":"ide0-hd0",
-            "locked":false,
-            "removable":false,
-            "inserted":{
-               "ro":false,
-               "drv":"qcow2",
-               "encrypted":false,
-               "file":"disks/test.qcow2",
-               "backing_file_depth":1,
-               "bps":1000000,
-               "bps_rd":0,
-               "bps_wr":0,
-               "iops":1000000,
-               "iops_rd":0,
-               "iops_wr":0,
-               "bps_max": 8000000,
-               "bps_rd_max": 0,
-               "bps_wr_max": 0,
-               "iops_max": 0,
-               "iops_rd_max": 0,
-               "iops_wr_max": 0,
-               "iops_size": 0,
-               "detect_zeroes": "on",
-               "write_threshold": 0,
-               "image":{
-                  "filename":"disks/test.qcow2",
-                  "format":"qcow2",
-                  "virtual-size":2048000,
-                  "backing_file":"base.qcow2",
-                  "full-backing-filename":"disks/base.qcow2",
-                  "backing-filename-format":"qcow2",
-                  "snapshots":[
-                     {
-                        "id": "1",
-                        "name": "snapshot1",
-                        "vm-state-size": 0,
-                        "date-sec": 10000200,
-                        "date-nsec": 12,
-                        "vm-clock-sec": 206,
-                        "vm-clock-nsec": 30
-                     }
-                  ],
-                  "backing-image":{
-                      "filename":"disks/base.qcow2",
-                      "format":"qcow2",
-                      "virtual-size":2048000
-                  }
-               }
-            },
-            "type":"unknown"
-         },
-         {
-            "io-status": "ok",
-            "device":"ide1-cd0",
-            "locked":false,
-            "removable":true,
-            "type":"unknown"
-         },
-         {
-            "device":"floppy0",
-            "locked":false,
-            "removable":true,
-            "type":"unknown"
-         },
-         {
-            "device":"sd0",
-            "locked":false,
-            "removable":true,
-            "type":"unknown"
-         }
-      ]
-   }
-
-query-blockstats
-----------------
-
-Show block device statistics.
-
-Each device statistic information is stored in a json-object and the returned
-value is a json-array of all devices.
-
-Each json-object contain the following:
-
-- "device": device name (json-string)
-- "stats": A json-object with the statistics information, it contains:
-    - "rd_bytes": bytes read (json-int)
-    - "wr_bytes": bytes written (json-int)
-    - "rd_operations": read operations (json-int)
-    - "wr_operations": write operations (json-int)
-    - "flush_operations": cache flush operations (json-int)
-    - "wr_total_time_ns": total time spend on writes in nano-seconds (json-int)
-    - "rd_total_time_ns": total time spend on reads in nano-seconds (json-int)
-    - "flush_total_time_ns": total time spend on cache flushes in nano-seconds (json-int)
-    - "wr_highest_offset": The offset after the greatest byte written to the
-                           BlockDriverState since it has been opened (json-int)
-    - "rd_merged": number of read requests that have been merged into
-                   another request (json-int)
-    - "wr_merged": number of write requests that have been merged into
-                   another request (json-int)
-    - "idle_time_ns": time since the last I/O operation, in
-                      nanoseconds. If the field is absent it means
-                      that there haven't been any operations yet
-                      (json-int, optional)
-    - "failed_rd_operations": number of failed read operations
-                              (json-int)
-    - "failed_wr_operations": number of failed write operations
-                              (json-int)
-    - "failed_flush_operations": number of failed flush operations
-                               (json-int)
-    - "invalid_rd_operations": number of invalid read operations
-                               (json-int)
-    - "invalid_wr_operations": number of invalid write operations
-                               (json-int)
-    - "invalid_flush_operations": number of invalid flush operations
-                                  (json-int)
-    - "account_invalid": whether invalid operations are included in
-                         the last access statistics (json-bool)
-    - "account_failed": whether failed operations are included in the
-                         latency and last access statistics
-                         (json-bool)
-    - "timed_stats": A json-array containing statistics collected in
-                     specific intervals, with the following members:
-        - "interval_length": interval used for calculating the
-                             statistics, in seconds (json-int)
-        - "min_rd_latency_ns": minimum latency of read operations in
-                               the defined interval, in nanoseconds
-                               (json-int)
-        - "min_wr_latency_ns": minimum latency of write operations in
-                               the defined interval, in nanoseconds
-                               (json-int)
-        - "min_flush_latency_ns": minimum latency of flush operations
-                                  in the defined interval, in
-                                  nanoseconds (json-int)
-        - "max_rd_latency_ns": maximum latency of read operations in
-                               the defined interval, in nanoseconds
-                               (json-int)
-        - "max_wr_latency_ns": maximum latency of write operations in
-                               the defined interval, in nanoseconds
-                               (json-int)
-        - "max_flush_latency_ns": maximum latency of flush operations
-                                  in the defined interval, in
-                                  nanoseconds (json-int)
-        - "avg_rd_latency_ns": average latency of read operations in
-                               the defined interval, in nanoseconds
-                               (json-int)
-        - "avg_wr_latency_ns": average latency of write operations in
-                               the defined interval, in nanoseconds
-                               (json-int)
-        - "avg_flush_latency_ns": average latency of flush operations
-                                  in the defined interval, in
-                                  nanoseconds (json-int)
-        - "avg_rd_queue_depth": average number of pending read
-                                operations in the defined interval
-                                (json-number)
-        - "avg_wr_queue_depth": average number of pending write
-                                operations in the defined interval
-                                (json-number).
-- "parent": Contains recursively the statistics of the underlying
-            protocol (e.g. the host file for a qcow2 image). If there is
-            no underlying protocol, this field is omitted
-            (json-object, optional)
-
-Example:
-
--> { "execute": "query-blockstats" }
-<- {
-      "return":[
-         {
-            "device":"ide0-hd0",
-            "parent":{
-               "stats":{
-                  "wr_highest_offset":3686448128,
-                  "wr_bytes":9786368,
-                  "wr_operations":751,
-                  "rd_bytes":122567168,
-                  "rd_operations":36772
-                  "wr_total_times_ns":313253456
-                  "rd_total_times_ns":3465673657
-                  "flush_total_times_ns":49653
-                  "flush_operations":61,
-                  "rd_merged":0,
-                  "wr_merged":0,
-                  "idle_time_ns":2953431879,
-                  "account_invalid":true,
-                  "account_failed":false
-               }
-            },
-            "stats":{
-               "wr_highest_offset":2821110784,
-               "wr_bytes":9786368,
-               "wr_operations":692,
-               "rd_bytes":122739200,
-               "rd_operations":36604
-               "flush_operations":51,
-               "wr_total_times_ns":313253456
-               "rd_total_times_ns":3465673657
-               "flush_total_times_ns":49653,
-               "rd_merged":0,
-               "wr_merged":0,
-               "idle_time_ns":2953431879,
-               "account_invalid":true,
-               "account_failed":false
-            }
-         },
-         {
-            "device":"ide1-cd0",
-            "stats":{
-               "wr_highest_offset":0,
-               "wr_bytes":0,
-               "wr_operations":0,
-               "rd_bytes":0,
-               "rd_operations":0
-               "flush_operations":0,
-               "wr_total_times_ns":0
-               "rd_total_times_ns":0
-               "flush_total_times_ns":0,
-               "rd_merged":0,
-               "wr_merged":0,
-               "account_invalid":false,
-               "account_failed":false
-            }
-         },
-         {
-            "device":"floppy0",
-            "stats":{
-               "wr_highest_offset":0,
-               "wr_bytes":0,
-               "wr_operations":0,
-               "rd_bytes":0,
-               "rd_operations":0
-               "flush_operations":0,
-               "wr_total_times_ns":0
-               "rd_total_times_ns":0
-               "flush_total_times_ns":0,
-               "rd_merged":0,
-               "wr_merged":0,
-               "account_invalid":false,
-               "account_failed":false
-            }
-         },
-         {
-            "device":"sd0",
-            "stats":{
-               "wr_highest_offset":0,
-               "wr_bytes":0,
-               "wr_operations":0,
-               "rd_bytes":0,
-               "rd_operations":0
-               "flush_operations":0,
-               "wr_total_times_ns":0
-               "rd_total_times_ns":0
-               "flush_total_times_ns":0,
-               "rd_merged":0,
-               "wr_merged":0,
-               "account_invalid":false,
-               "account_failed":false
-            }
-         }
-      ]
-   }
-
-query-cpus
-----------
-
-Show CPU information.
-
-Return a json-array. Each CPU is represented by a json-object, which contains:
-
-- "CPU": CPU index (json-int)
-- "current": true if this is the current CPU, false otherwise (json-bool)
-- "halted": true if the cpu is halted, false otherwise (json-bool)
-- "qom_path": path to the CPU object in the QOM tree (json-str)
-- "arch": architecture of the cpu, which determines what additional
-          keys will be present (json-str)
-- Current program counter. The key's name depends on the architecture:
-     "pc": i386/x86_64 (json-int)
-     "nip": PPC (json-int)
-     "pc" and "npc": sparc (json-int)
-     "PC": mips (json-int)
-- "thread_id": ID of the underlying host thread (json-int)
-
-Example:
-
--> { "execute": "query-cpus" }
-<- {
-      "return":[
-         {
-            "CPU":0,
-            "current":true,
-            "halted":false,
-            "qom_path":"/machine/unattached/device[0]",
-            "arch":"x86",
-            "pc":3227107138,
-            "thread_id":3134
-         },
-         {
-            "CPU":1,
-            "current":false,
-            "halted":true,
-            "qom_path":"/machine/unattached/device[2]",
-            "arch":"x86",
-            "pc":7108165,
-            "thread_id":3135
-         }
-      ]
-   }
-
-query-iothreads
----------------
-
-Returns a list of information about each iothread.
-
-Note this list excludes the QEMU main loop thread, which is not declared
-using the -object iothread command-line option.  It is always the main thread
-of the process.
-
-Return a json-array. Each iothread is represented by a json-object, which contains:
-
-- "id": name of iothread (json-str)
-- "thread-id": ID of the underlying host thread (json-int)
-
-Example:
-
--> { "execute": "query-iothreads" }
-<- {
-      "return":[
-         {
-            "id":"iothread0",
-            "thread-id":3134
-         },
-         {
-            "id":"iothread1",
-            "thread-id":3135
-         }
-      ]
-   }
-
-query-pci
----------
-
-PCI buses and devices information.
-
-The returned value is a json-array of all buses. Each bus is represented by
-a json-object, which has a key with a json-array of all PCI devices attached
-to it. Each device is represented by a json-object.
-
-The bus json-object contains the following:
-
-- "bus": bus number (json-int)
-- "devices": a json-array of json-objects, each json-object represents a
-             PCI device
-
-The PCI device json-object contains the following:
-
-- "bus": identical to the parent's bus number (json-int)
-- "slot": slot number (json-int)
-- "function": function number (json-int)
-- "class_info": a json-object containing:
-     - "desc": device class description (json-string, optional)
-     - "class": device class number (json-int)
-- "id": a json-object containing:
-     - "device": device ID (json-int)
-     - "vendor": vendor ID (json-int)
-- "irq": device's IRQ if assigned (json-int, optional)
-- "qdev_id": qdev id string (json-string)
-- "pci_bridge": It's a json-object, only present if this device is a
-                PCI bridge, contains:
-     - "bus": bus number (json-int)
-     - "secondary": secondary bus number (json-int)
-     - "subordinate": subordinate bus number (json-int)
-     - "io_range": I/O memory range information, a json-object with the
-                   following members:
-                 - "base": base address, in bytes (json-int)
-                 - "limit": limit address, in bytes (json-int)
-     - "memory_range": memory range information, a json-object with the
-                       following members:
-                 - "base": base address, in bytes (json-int)
-                 - "limit": limit address, in bytes (json-int)
-     - "prefetchable_range": Prefetchable memory range information, a
-                             json-object with the following members:
-                 - "base": base address, in bytes (json-int)
-                 - "limit": limit address, in bytes (json-int)
-     - "devices": a json-array of PCI devices if there's any attached, each
-                  each element is represented by a json-object, which contains
-                  the same members of the 'PCI device json-object' described
-                  above (optional)
-- "regions": a json-array of json-objects, each json-object represents a
-             memory region of this device
-
-The memory range json-object contains the following:
-
-- "base": base memory address (json-int)
-- "limit": limit value (json-int)
-
-The region json-object can be an I/O region or a memory region, an I/O region
-json-object contains the following:
-
-- "type": "io" (json-string, fixed)
-- "bar": BAR number (json-int)
-- "address": memory address (json-int)
-- "size": memory size (json-int)
-
-A memory region json-object contains the following:
-
-- "type": "memory" (json-string, fixed)
-- "bar": BAR number (json-int)
-- "address": memory address (json-int)
-- "size": memory size (json-int)
-- "mem_type_64": true or false (json-bool)
-- "prefetch": true or false (json-bool)
-
-Example:
-
--> { "execute": "query-pci" }
-<- {
-      "return":[
-         {
-            "bus":0,
-            "devices":[
-               {
-                  "bus":0,
-                  "qdev_id":"",
-                  "slot":0,
-                  "class_info":{
-                     "class":1536,
-                     "desc":"Host bridge"
-                  },
-                  "id":{
-                     "device":32902,
-                     "vendor":4663
-                  },
-                  "function":0,
-                  "regions":[
-
-                  ]
-               },
-               {
-                  "bus":0,
-                  "qdev_id":"",
-                  "slot":1,
-                  "class_info":{
-                     "class":1537,
-                     "desc":"ISA bridge"
-                  },
-                  "id":{
-                     "device":32902,
-                     "vendor":28672
-                  },
-                  "function":0,
-                  "regions":[
-
-                  ]
-               },
-               {
-                  "bus":0,
-                  "qdev_id":"",
-                  "slot":1,
-                  "class_info":{
-                     "class":257,
-                     "desc":"IDE controller"
-                  },
-                  "id":{
-                     "device":32902,
-                     "vendor":28688
-                  },
-                  "function":1,
-                  "regions":[
-                     {
-                        "bar":4,
-                        "size":16,
-                        "address":49152,
-                        "type":"io"
-                     }
-                  ]
-               },
-               {
-                  "bus":0,
-                  "qdev_id":"",
-                  "slot":2,
-                  "class_info":{
-                     "class":768,
-                     "desc":"VGA controller"
-                  },
-                  "id":{
-                     "device":4115,
-                     "vendor":184
-                  },
-                  "function":0,
-                  "regions":[
-                     {
-                        "prefetch":true,
-                        "mem_type_64":false,
-                        "bar":0,
-                        "size":33554432,
-                        "address":4026531840,
-                        "type":"memory"
-                     },
-                     {
-                        "prefetch":false,
-                        "mem_type_64":false,
-                        "bar":1,
-                        "size":4096,
-                        "address":4060086272,
-                        "type":"memory"
-                     },
-                     {
-                        "prefetch":false,
-                        "mem_type_64":false,
-                        "bar":6,
-                        "size":65536,
-                        "address":-1,
-                        "type":"memory"
-                     }
-                  ]
-               },
-               {
-                  "bus":0,
-                  "qdev_id":"",
-                  "irq":11,
-                  "slot":4,
-                  "class_info":{
-                     "class":1280,
-                     "desc":"RAM controller"
-                  },
-                  "id":{
-                     "device":6900,
-                     "vendor":4098
-                  },
-                  "function":0,
-                  "regions":[
-                     {
-                        "bar":0,
-                        "size":32,
-                        "address":49280,
-                        "type":"io"
-                     }
-                  ]
-               }
-            ]
-         }
-      ]
-   }
-
-Note: This example has been shortened as the real response is too long.
-
-query-kvm
----------
-
-Show KVM information.
-
-Return a json-object with the following information:
-
-- "enabled": true if KVM support is enabled, false otherwise (json-bool)
-- "present": true if QEMU has KVM support, false otherwise (json-bool)
-
-Example:
-
--> { "execute": "query-kvm" }
-<- { "return": { "enabled": true, "present": true } }
-
-query-status
-------------
-
-Return a json-object with the following information:
-
-- "running": true if the VM is running, or false if it is paused (json-bool)
-- "singlestep": true if the VM is in single step mode,
-                false otherwise (json-bool)
-- "status": one of the following values (json-string)
-    "debug" - QEMU is running on a debugger
-    "inmigrate" - guest is paused waiting for an incoming migration
-    "internal-error" - An internal error that prevents further guest
-    execution has occurred
-    "io-error" - the last IOP has failed and the device is configured
-    to pause on I/O errors
-    "paused" - guest has been paused via the 'stop' command
-    "postmigrate" - guest is paused following a successful 'migrate'
-    "prelaunch" - QEMU was started with -S and guest has not started
-    "finish-migrate" - guest is paused to finish the migration process
-    "restore-vm" - guest is paused to restore VM state
-    "running" - guest is actively running
-    "save-vm" - guest is paused to save the VM state
-    "shutdown" - guest is shut down (and -no-shutdown is in use)
-    "watchdog" - the watchdog action is configured to pause and
-     has been triggered
-
-Example:
-
--> { "execute": "query-status" }
-<- { "return": { "running": true, "singlestep": false, "status": "running" } }
-
-query-mice
-----------
-
-Show VM mice information.
-
-Each mouse is represented by a json-object, the returned value is a json-array
-of all mice.
-
-The mouse json-object contains the following:
-
-- "name": mouse's name (json-string)
-- "index": mouse's index (json-int)
-- "current": true if this mouse is receiving events, false otherwise (json-bool)
-- "absolute": true if the mouse generates absolute input events (json-bool)
-
-Example:
-
--> { "execute": "query-mice" }
-<- {
-      "return":[
-         {
-            "name":"QEMU Microsoft Mouse",
-            "index":0,
-            "current":false,
-            "absolute":false
-         },
-         {
-            "name":"QEMU PS/2 Mouse",
-            "index":1,
-            "current":true,
-            "absolute":true
-         }
-      ]
-   }
-
-query-vnc
----------
-
-Show VNC server information.
-
-Return a json-object with server information. Connected clients are returned
-as a json-array of json-objects.
-
-The main json-object contains the following:
-
-- "enabled": true or false (json-bool)
-- "host": server's IP address (json-string)
-- "family": address family (json-string)
-         - Possible values: "ipv4", "ipv6", "unix", "unknown"
-- "service": server's port number (json-string)
-- "auth": authentication method (json-string)
-         - Possible values: "invalid", "none", "ra2", "ra2ne", "sasl", "tight",
-                            "tls", "ultra", "unknown", "vencrypt", "vencrypt",
-                            "vencrypt+plain", "vencrypt+tls+none",
-                            "vencrypt+tls+plain", "vencrypt+tls+sasl",
-                            "vencrypt+tls+vnc", "vencrypt+x509+none",
-                            "vencrypt+x509+plain", "vencrypt+x509+sasl",
-                            "vencrypt+x509+vnc", "vnc"
-- "clients": a json-array of all connected clients
-
-Clients are described by a json-object, each one contain the following:
-
-- "host": client's IP address (json-string)
-- "family": address family (json-string)
-         - Possible values: "ipv4", "ipv6", "unix", "unknown"
-- "service": client's port number (json-string)
-- "x509_dname": TLS dname (json-string, optional)
-- "sasl_username": SASL username (json-string, optional)
-
-Example:
-
--> { "execute": "query-vnc" }
-<- {
-      "return":{
-         "enabled":true,
-         "host":"0.0.0.0",
-         "service":"50402",
-         "auth":"vnc",
-         "family":"ipv4",
-         "clients":[
-            {
-               "host":"127.0.0.1",
-               "service":"50401",
-               "family":"ipv4"
-            }
-         ]
-      }
-   }
-
-query-spice
------------
-
-Show SPICE server information.
-
-Return a json-object with server information. Connected clients are returned
-as a json-array of json-objects.
-
-The main json-object contains the following:
-
-- "enabled": true or false (json-bool)
-- "host": server's IP address (json-string)
-- "port": server's port number (json-int, optional)
-- "tls-port": server's port number (json-int, optional)
-- "auth": authentication method (json-string)
-         - Possible values: "none", "spice"
-- "channels": a json-array of all active channels clients
-
-Channels are described by a json-object, each one contain the following:
-
-- "host": client's IP address (json-string)
-- "family": address family (json-string)
-         - Possible values: "ipv4", "ipv6", "unix", "unknown"
-- "port": client's port number (json-string)
-- "connection-id": spice connection id.  All channels with the same id
-                   belong to the same spice session (json-int)
-- "channel-type": channel type.  "1" is the main control channel, filter for
-                  this one if you want track spice sessions only (json-int)
-- "channel-id": channel id.  Usually "0", might be different needed when
-                multiple channels of the same type exist, such as multiple
-                display channels in a multihead setup (json-int)
-- "tls": whether the channel is encrypted (json-bool)
-
-Example:
-
--> { "execute": "query-spice" }
-<- {
-      "return": {
-         "enabled": true,
-         "auth": "spice",
-         "port": 5920,
-         "tls-port": 5921,
-         "host": "0.0.0.0",
-         "channels": [
-            {
-               "port": "54924",
-               "family": "ipv4",
-               "channel-type": 1,
-               "connection-id": 1804289383,
-               "host": "127.0.0.1",
-               "channel-id": 0,
-               "tls": true
-            },
-            {
-               "port": "36710",
-               "family": "ipv4",
-               "channel-type": 4,
-               "connection-id": 1804289383,
-               "host": "127.0.0.1",
-               "channel-id": 0,
-               "tls": false
-            },
-            [ ... more channels follow ... ]
-         ]
-      }
-   }
-
-query-name
-----------
-
-Show VM name.
-
-Return a json-object with the following information:
-
-- "name": VM's name (json-string, optional)
-
-Example:
-
--> { "execute": "query-name" }
-<- { "return": { "name": "qemu-name" } }
-
-query-uuid
-----------
-
-Show VM UUID.
-
-Return a json-object with the following information:
-
-- "UUID": Universally Unique Identifier (json-string)
-
-Example:
-
--> { "execute": "query-uuid" }
-<- { "return": { "UUID": "550e8400-e29b-41d4-a716-446655440000" } }
-
-query-command-line-options
---------------------------
-
-Show command line option schema.
-
-Return a json-array of command line option schema for all options (or for
-the given option), returning an error if the given option doesn't exist.
-
-Each array entry contains the following:
-
-- "option": option name (json-string)
-- "parameters": a json-array describes all parameters of the option:
-    - "name": parameter name (json-string)
-    - "type": parameter type (one of 'string', 'boolean', 'number',
-              or 'size')
-    - "help": human readable description of the parameter
-              (json-string, optional)
-    - "default": default value string for the parameter
-                 (json-string, optional)
-
-Example:
-
--> { "execute": "query-command-line-options", "arguments": { "option": "option-rom" } }
-<- { "return": [
-        {
-            "parameters": [
-                {
-                    "name": "romfile",
-                    "type": "string"
-                },
-                {
-                    "name": "bootindex",
-                    "type": "number"
-                }
-            ],
-            "option": "option-rom"
-        }
-     ]
-   }
-
-query-migrate
--------------
-
-Migration status.
-
-Return a json-object. If migration is active there will be another json-object
-with RAM migration status and if block migration is active another one with
-block migration status.
-
-The main json-object contains the following:
-
-- "status": migration status (json-string)
-     - Possible values: "setup", "active", "completed", "failed", "cancelled"
-- "total-time": total amount of ms since migration started.  If
-                migration has ended, it returns the total migration
-                time (json-int)
-- "setup-time" amount of setup time in milliseconds _before_ the
-               iterations begin but _after_ the QMP command is issued.
-               This is designed to provide an accounting of any activities
-               (such as RDMA pinning) which may be expensive, but do not
-               actually occur during the iterative migration rounds
-               themselves. (json-int)
-- "downtime": only present when migration has finished correctly
-              total amount in ms for downtime that happened (json-int)
-- "expected-downtime": only present while migration is active
-                total amount in ms for downtime that was calculated on
-                the last bitmap round (json-int)
-- "ram": only present if "status" is "active", it is a json-object with the
-  following RAM information:
-         - "transferred": amount transferred in bytes (json-int)
-         - "remaining": amount remaining to transfer in bytes (json-int)
-         - "total": total amount of memory in bytes (json-int)
-         - "duplicate": number of pages filled entirely with the same
-            byte (json-int)
-            These are sent over the wire much more efficiently.
-         - "skipped": number of skipped zero pages (json-int)
-         - "normal" : number of whole pages transferred.  I.e. they
-            were not sent as duplicate or xbzrle pages (json-int)
-         - "normal-bytes" : number of bytes transferred in whole
-            pages. This is just normal pages times size of one page,
-            but this way upper levels don't need to care about page
-            size (json-int)
-         - "dirty-sync-count": times that dirty ram was synchronized (json-int)
-- "disk": only present if "status" is "active" and it is a block migration,
-  it is a json-object with the following disk information:
-         - "transferred": amount transferred in bytes (json-int)
-         - "remaining": amount remaining to transfer in bytes json-int)
-         - "total": total disk size in bytes (json-int)
-- "xbzrle-cache": only present if XBZRLE is active.
-  It is a json-object with the following XBZRLE information:
-         - "cache-size": XBZRLE cache size in bytes
-         - "bytes": number of bytes transferred for XBZRLE compressed pages
-         - "pages": number of XBZRLE compressed pages
-         - "cache-miss": number of XBRZRLE page cache misses
-         - "cache-miss-rate": rate of XBRZRLE page cache misses
-         - "overflow": number of times XBZRLE overflows.  This means
-           that the XBZRLE encoding was bigger than just sent the
-           whole page, and then we sent the whole page instead (as as
-           normal page).
-
-Examples:
-
-1. Before the first migration
-
--> { "execute": "query-migrate" }
-<- { "return": {} }
-
-2. Migration is done and has succeeded
-
--> { "execute": "query-migrate" }
-<- { "return": {
-        "status": "completed",
-        "ram":{
-          "transferred":123,
-          "remaining":123,
-          "total":246,
-          "total-time":12345,
-          "setup-time":12345,
-          "downtime":12345,
-          "duplicate":123,
-          "normal":123,
-          "normal-bytes":123456,
-          "dirty-sync-count":15
-        }
-     }
-   }
-
-3. Migration is done and has failed
-
--> { "execute": "query-migrate" }
-<- { "return": { "status": "failed" } }
-
-4. Migration is being performed and is not a block migration:
-
--> { "execute": "query-migrate" }
-<- {
-      "return":{
-         "status":"active",
-         "ram":{
-            "transferred":123,
-            "remaining":123,
-            "total":246,
-            "total-time":12345,
-            "setup-time":12345,
-            "expected-downtime":12345,
-            "duplicate":123,
-            "normal":123,
-            "normal-bytes":123456,
-            "dirty-sync-count":15
-         }
-      }
-   }
-
-5. Migration is being performed and is a block migration:
-
--> { "execute": "query-migrate" }
-<- {
-      "return":{
-         "status":"active",
-         "ram":{
-            "total":1057024,
-            "remaining":1053304,
-            "transferred":3720,
-            "total-time":12345,
-            "setup-time":12345,
-            "expected-downtime":12345,
-            "duplicate":123,
-            "normal":123,
-            "normal-bytes":123456,
-            "dirty-sync-count":15
-         },
-         "disk":{
-            "total":20971520,
-            "remaining":20880384,
-            "transferred":91136
-         }
-      }
-   }
-
-6. Migration is being performed and XBZRLE is active:
-
--> { "execute": "query-migrate" }
-<- {
-      "return":{
-         "status":"active",
-         "capabilities" : [ { "capability": "xbzrle", "state" : true } ],
-         "ram":{
-            "total":1057024,
-            "remaining":1053304,
-            "transferred":3720,
-            "total-time":12345,
-            "setup-time":12345,
-            "expected-downtime":12345,
-            "duplicate":10,
-            "normal":3333,
-            "normal-bytes":3412992,
-            "dirty-sync-count":15
-         },
-         "xbzrle-cache":{
-            "cache-size":67108864,
-            "bytes":20971520,
-            "pages":2444343,
-            "cache-miss":2244,
-            "cache-miss-rate":0.123,
-            "overflow":34434
-         }
-      }
-   }
-
-migrate-set-capabilities
-------------------------
-
-Enable/Disable migration capabilities
-
-- "xbzrle": XBZRLE support
-- "rdma-pin-all": pin all pages when using RDMA during migration
-- "auto-converge": throttle down guest to help convergence of migration
-- "zero-blocks": compress zero blocks during block migration
-- "compress": use multiple compression threads to accelerate live migration
-- "events": generate events for each migration state change
-- "postcopy-ram": postcopy mode for live migration
-- "x-colo": COarse-Grain LOck Stepping (COLO) for Non-stop Service
-
-Arguments:
-
-Example:
-
--> { "execute": "migrate-set-capabilities" , "arguments":
-     { "capabilities": [ { "capability": "xbzrle", "state": true } ] } }
-
-query-migrate-capabilities
---------------------------
-
-Query current migration capabilities
-
-- "capabilities": migration capabilities state
-         - "xbzrle" : XBZRLE state (json-bool)
-         - "rdma-pin-all" : RDMA Pin Page state (json-bool)
-         - "auto-converge" : Auto Converge state (json-bool)
-         - "zero-blocks" : Zero Blocks state (json-bool)
-         - "compress": Multiple compression threads state (json-bool)
-         - "events": Migration state change event state (json-bool)
-         - "postcopy-ram": postcopy ram state (json-bool)
-         - "x-colo": COarse-Grain LOck Stepping for Non-stop Service (json-bool)
-
-Arguments:
-
-Example:
-
--> { "execute": "query-migrate-capabilities" }
-<- {"return": [
-     {"state": false, "capability": "xbzrle"},
-     {"state": false, "capability": "rdma-pin-all"},
-     {"state": false, "capability": "auto-converge"},
-     {"state": false, "capability": "zero-blocks"},
-     {"state": false, "capability": "compress"},
-     {"state": true, "capability": "events"},
-     {"state": false, "capability": "postcopy-ram"},
-     {"state": false, "capability": "x-colo"}
-   ]}
-
-migrate-set-parameters
-----------------------
-
-Set migration parameters
-
-- "compress-level": set compression level during migration (json-int)
-- "compress-threads": set compression thread count for migration (json-int)
-- "decompress-threads": set decompression thread count for migration (json-int)
-- "cpu-throttle-initial": set initial percentage of time guest cpus are
-                          throttled for auto-converge (json-int)
-- "cpu-throttle-increment": set throttle increasing percentage for
-                            auto-converge (json-int)
-- "max-bandwidth": set maximum speed for migrations (in bytes/sec) (json-int)
-- "downtime-limit": set maximum tolerated downtime (in milliseconds) for
-                    migrations (json-int)
-- "x-checkpoint-delay": set the delay time for periodic checkpoint (json-int)
-
-Arguments:
-
-Example:
-
--> { "execute": "migrate-set-parameters" , "arguments":
-      { "compress-level": 1 } }
-
-query-migrate-parameters
-------------------------
-
-Query current migration parameters
-
-- "parameters": migration parameters value
-         - "compress-level" : compression level value (json-int)
-         - "compress-threads" : compression thread count value (json-int)
-         - "decompress-threads" : decompression thread count value (json-int)
-         - "cpu-throttle-initial" : initial percentage of time guest cpus are
-                                    throttled (json-int)
-         - "cpu-throttle-increment" : throttle increasing percentage for
-                                      auto-converge (json-int)
-         - "max-bandwidth" : maximium migration speed in bytes per second
-                             (json-int)
-         - "downtime-limit" : maximum tolerated downtime of migration in
-                              milliseconds (json-int)
-Arguments:
-
-Example:
-
--> { "execute": "query-migrate-parameters" }
-<- {
-      "return": {
-         "decompress-threads": 2,
-         "cpu-throttle-increment": 10,
-         "compress-threads": 8,
-         "compress-level": 1,
-         "cpu-throttle-initial": 20,
-         "max-bandwidth": 33554432,
-         "downtime-limit": 300
-      }
-   }
-
-query-balloon
--------------
-
-Show balloon information.
-
-Make an asynchronous request for balloon info. When the request completes a
-json-object will be returned containing the following data:
-
-- "actual": current balloon value in bytes (json-int)
-
-Example:
-
--> { "execute": "query-balloon" }
-<- {
-      "return":{
-         "actual":1073741824,
-      }
-   }
-
-query-tpm
----------
-
-Return information about the TPM device.
-
-Arguments: None
-
-Example:
-
--> { "execute": "query-tpm" }
-<- { "return":
-     [
-       { "model": "tpm-tis",
-         "options":
-           { "type": "passthrough",
-             "data":
-               { "cancel-path": "/sys/class/misc/tpm0/device/cancel",
-                 "path": "/dev/tpm0"
-               }
-           },
-         "id": "tpm0"
-       }
-     ]
-   }
-
-query-tpm-models
-----------------
-
-Return a list of supported TPM models.
-
-Arguments: None
-
-Example:
-
--> { "execute": "query-tpm-models" }
-<- { "return": [ "tpm-tis" ] }
-
-query-tpm-types
----------------
-
-Return a list of supported TPM types.
-
-Arguments: None
-
-Example:
-
--> { "execute": "query-tpm-types" }
-<- { "return": [ "passthrough" ] }
-
-chardev-add
-----------------
-
-Add a chardev.
-
-Arguments:
-
-- "id": the chardev's ID, must be unique (json-string)
-- "backend": chardev backend type + parameters
-
-Examples:
-
--> { "execute" : "chardev-add",
-     "arguments" : { "id" : "foo",
-                     "backend" : { "type" : "null", "data" : {} } } }
-<- { "return": {} }
-
--> { "execute" : "chardev-add",
-     "arguments" : { "id" : "bar",
-                     "backend" : { "type" : "file",
-                                   "data" : { "out" : "/tmp/bar.log" } } } }
-<- { "return": {} }
-
--> { "execute" : "chardev-add",
-     "arguments" : { "id" : "baz",
-                     "backend" : { "type" : "pty", "data" : {} } } }
-<- { "return": { "pty" : "/dev/pty/42" } }
-
-chardev-remove
---------------
-
-Remove a chardev.
-
-Arguments:
-
-- "id": the chardev's ID, must exist and not be in use (json-string)
-
-Example:
-
--> { "execute": "chardev-remove", "arguments": { "id" : "foo" } }
-<- { "return": {} }
-
-query-rx-filter
----------------
-
-Show rx-filter information.
-
-Returns a json-array of rx-filter information for all NICs (or for the
-given NIC), returning an error if the given NIC doesn't exist, or
-given NIC doesn't support rx-filter querying, or given net client
-isn't a NIC.
-
-The query will clear the event notification flag of each NIC, then qemu
-will start to emit event to QMP monitor.
-
-Each array entry contains the following:
-
-- "name": net client name (json-string)
-- "promiscuous": promiscuous mode is enabled (json-bool)
-- "multicast": multicast receive state (one of 'normal', 'none', 'all')
-- "unicast": unicast receive state  (one of 'normal', 'none', 'all')
-- "vlan": vlan receive state (one of 'normal', 'none', 'all') (Since 2.0)
-- "broadcast-allowed": allow to receive broadcast (json-bool)
-- "multicast-overflow": multicast table is overflowed (json-bool)
-- "unicast-overflow": unicast table is overflowed (json-bool)
-- "main-mac": main macaddr string (json-string)
-- "vlan-table": a json-array of active vlan id
-- "unicast-table": a json-array of unicast macaddr string
-- "multicast-table": a json-array of multicast macaddr string
-
-Example:
-
--> { "execute": "query-rx-filter", "arguments": { "name": "vnet0" } }
-<- { "return": [
-        {
-            "promiscuous": true,
-            "name": "vnet0",
-            "main-mac": "52:54:00:12:34:56",
-            "unicast": "normal",
-            "vlan": "normal",
-            "vlan-table": [
-                4,
-                0
-            ],
-            "unicast-table": [
-            ],
-            "multicast": "normal",
-            "multicast-overflow": false,
-            "unicast-overflow": false,
-            "multicast-table": [
-                "01:00:5e:00:00:01",
-                "33:33:00:00:00:01",
-                "33:33:ff:12:34:56"
-            ],
-            "broadcast-allowed": false
-        }
-      ]
-   }
-
-blockdev-add
-------------
-
-Add a block device.
-
-This command is still a work in progress.  It doesn't support all
-block drivers among other things.  Stay away from it unless you want
-to help with its development.
-
-For the arguments, see the QAPI schema documentation of BlockdevOptions.
-
-Example (1):
-
--> { "execute": "blockdev-add",
-    "arguments": { "driver": "qcow2",
-                   "file": { "driver": "file",
-                             "filename": "test.qcow2" } } }
-<- { "return": {} }
-
-Example (2):
-
--> { "execute": "blockdev-add",
-     "arguments": {
-         "driver": "qcow2",
-         "node-name": "my_disk",
-         "discard": "unmap",
-         "cache": {
-             "direct": true,
-             "writeback": true
-         },
-         "file": {
-             "driver": "file",
-             "filename": "/tmp/test.qcow2"
-         },
-         "backing": {
-             "driver": "raw",
-             "file": {
-                 "driver": "file",
-                 "filename": "/dev/fdset/4"
-             }
-         }
-       }
-     }
-
-<- { "return": {} }
-
-x-blockdev-del
-------------
-Since 2.5
-
-Deletes a block device that has been added using blockdev-add.
-The command will fail if the node is attached to a device or is
-otherwise being used.
-
-This command is still a work in progress and is considered
-experimental. Stay away from it unless you want to help with its
-development.
-
-Arguments:
-
-- "node-name": Name of the graph node to delete (json-string)
-
-Example:
-
--> { "execute": "blockdev-add",
-     "arguments": {
-         "driver": "qcow2",
-         "node-name": "node0",
-         "file": {
-             "driver": "file",
-             "filename": "test.qcow2"
-         }
-     }
-   }
-
-<- { "return": {} }
-
--> { "execute": "x-blockdev-del",
-     "arguments": { "node-name": "node0" }
-   }
-<- { "return": {} }
-
-blockdev-open-tray
-------------------
-
-Opens a block device's tray. If there is a block driver state tree inserted as a
-medium, it will become inaccessible to the guest (but it will remain associated
-to the block device, so closing the tray will make it accessible again).
-
-If the tray was already open before, this will be a no-op.
-
-Once the tray opens, a DEVICE_TRAY_MOVED event is emitted. There are cases in
-which no such event will be generated, these include:
-- if the guest has locked the tray, @force is false and the guest does not
-  respond to the eject request
-- if the BlockBackend denoted by @device does not have a guest device attached
-  to it
-- if the guest device does not have an actual tray and is empty, for instance
-  for floppy disk drives
-
-Arguments:
-
-- "device": block device name (deprecated, use @id instead)
-            (json-string, optional)
-- "id": the name or QOM path of the guest device (json-string, optional)
-- "force": if false (the default), an eject request will be sent to the guest if
-           it has locked the tray (and the tray will not be opened immediately);
-           if true, the tray will be opened regardless of whether it is locked
-           (json-bool, optional)
-
-Example:
-
--> { "execute": "blockdev-open-tray",
-     "arguments": { "id": "ide0-1-0" } }
-
-<- { "timestamp": { "seconds": 1418751016,
-                    "microseconds": 716996 },
-     "event": "DEVICE_TRAY_MOVED",
-     "data": { "device": "ide1-cd0",
-               "id": "ide0-1-0",
-               "tray-open": true } }
-
-<- { "return": {} }
-
-blockdev-close-tray
--------------------
-
-Closes a block device's tray. If there is a block driver state tree associated
-with the block device (which is currently ejected), that tree will be loaded as
-the medium.
-
-If the tray was already closed before, this will be a no-op.
-
-Arguments:
-
-- "device": block device name (deprecated, use @id instead)
-            (json-string, optional)
-- "id": the name or QOM path of the guest device (json-string, optional)
-
-Example:
-
--> { "execute": "blockdev-close-tray",
-     "arguments": { "id": "ide0-1-0" } }
-
-<- { "timestamp": { "seconds": 1418751345,
-                    "microseconds": 272147 },
-     "event": "DEVICE_TRAY_MOVED",
-     "data": { "device": "ide1-cd0",
-               "id": "ide0-1-0",
-               "tray-open": false } }
-
-<- { "return": {} }
-
-x-blockdev-remove-medium
-------------------------
-
-Removes a medium (a block driver state tree) from a block device. That block
-device's tray must currently be open (unless there is no attached guest device).
-
-If the tray is open and there is no medium inserted, this will be a no-op.
-
-This command is still a work in progress and is considered experimental.
-Stay away from it unless you want to help with its development.
-
-Arguments:
-
-- "device": block device name (deprecated, use @id instead)
-            (json-string, optional)
-- "id": the name or QOM path of the guest device (json-string, optional)
-
-Example:
-
--> { "execute": "x-blockdev-remove-medium",
-     "arguments": { "id": "ide0-1-0" } }
-
-<- { "error": { "class": "GenericError",
-                "desc": "Tray of device 'ide0-1-0' is not open" } }
-
--> { "execute": "blockdev-open-tray",
-     "arguments": { "id": "ide0-1-0" } }
-
-<- { "timestamp": { "seconds": 1418751627,
-                    "microseconds": 549958 },
-     "event": "DEVICE_TRAY_MOVED",
-     "data": { "device": "ide1-cd0",
-               "id": "ide0-1-0",
-               "tray-open": true } }
-
-<- { "return": {} }
-
--> { "execute": "x-blockdev-remove-medium",
-     "arguments": { "device": "ide0-1-0" } }
-
-<- { "return": {} }
-
-x-blockdev-insert-medium
-------------------------
-
-Inserts a medium (a block driver state tree) into a block device. That block
-device's tray must currently be open (unless there is no attached guest device)
-and there must be no medium inserted already.
-
-This command is still a work in progress and is considered experimental.
-Stay away from it unless you want to help with its development.
-
-Arguments:
-
-- "device": block device name (deprecated, use @id instead)
-            (json-string, optional)
-- "id": the name or QOM path of the guest device (json-string, optional)
-- "node-name": root node of the BDS tree to insert into the block device
-
-Example:
-
--> { "execute": "blockdev-add",
-     "arguments": { { "node-name": "node0",
-                      "driver": "raw",
-                      "file": { "driver": "file",
-                                "filename": "fedora.iso" } } }
-
-<- { "return": {} }
-
--> { "execute": "x-blockdev-insert-medium",
-     "arguments": { "id": "ide0-1-0",
-                    "node-name": "node0" } }
-
-<- { "return": {} }
-
-x-blockdev-change
------------------
-
-Dynamically reconfigure the block driver state graph. It can be used
-to add, remove, insert or replace a graph node. Currently only the
-Quorum driver implements this feature to add or remove its child. This
-is useful to fix a broken quorum child.
-
-If @node is specified, it will be inserted under @parent. @child
-may not be specified in this case. If both @parent and @child are
-specified but @node is not, @child will be detached from @parent.
-
-Arguments:
-- "parent": the id or name of the parent node (json-string)
-- "child": the name of a child under the given parent node (json-string, optional)
-- "node": the name of the node that will be added (json-string, optional)
-
-Note: this command is experimental, and not a stable API. It doesn't
-support all kinds of operations, all kinds of children, nor all block
-drivers.
-
-Warning: The data in a new quorum child MUST be consistent with that of
-the rest of the array.
-
-Example:
-
-Add a new node to a quorum
--> { "execute": "blockdev-add",
-     "arguments": { "driver": "raw",
-                    "node-name": "new_node",
-                    "file": { "driver": "file",
-                              "filename": "test.raw" } } }
-<- { "return": {} }
--> { "execute": "x-blockdev-change",
-     "arguments": { "parent": "disk1",
-                    "node": "new_node" } }
-<- { "return": {} }
-
-Delete a quorum's node
--> { "execute": "x-blockdev-change",
-     "arguments": { "parent": "disk1",
-                    "child": "children.1" } }
-<- { "return": {} }
-
-query-named-block-nodes
------------------------
-
-Return a list of BlockDeviceInfo for all the named block driver nodes
-
-Example:
-
--> { "execute": "query-named-block-nodes" }
-<- { "return": [ { "ro":false,
-                   "drv":"qcow2",
-                   "encrypted":false,
-                   "file":"disks/test.qcow2",
-                   "node-name": "my-node",
-                   "backing_file_depth":1,
-                   "bps":1000000,
-                   "bps_rd":0,
-                   "bps_wr":0,
-                   "iops":1000000,
-                   "iops_rd":0,
-                   "iops_wr":0,
-                   "bps_max": 8000000,
-                   "bps_rd_max": 0,
-                   "bps_wr_max": 0,
-                   "iops_max": 0,
-                   "iops_rd_max": 0,
-                   "iops_wr_max": 0,
-                   "iops_size": 0,
-                   "write_threshold": 0,
-                   "image":{
-                      "filename":"disks/test.qcow2",
-                      "format":"qcow2",
-                      "virtual-size":2048000,
-                      "backing_file":"base.qcow2",
-                      "full-backing-filename":"disks/base.qcow2",
-                      "backing-filename-format":"qcow2",
-                      "snapshots":[
-                         {
-                            "id": "1",
-                            "name": "snapshot1",
-                            "vm-state-size": 0,
-                            "date-sec": 10000200,
-                            "date-nsec": 12,
-                            "vm-clock-sec": 206,
-                            "vm-clock-nsec": 30
-                         }
-                      ],
-                      "backing-image":{
-                          "filename":"disks/base.qcow2",
-                          "format":"qcow2",
-                          "virtual-size":2048000
-                      }
-                   } } ] }
-
-blockdev-change-medium
-----------------------
-
-Changes the medium inserted into a block device by ejecting the current medium
-and loading a new image file which is inserted as the new medium.
-
-Arguments:
-
-- "device": block device name (deprecated, use @id instead)
-            (json-string, optional)
-- "id": the name or QOM path of the guest device (json-string, optional)
-- "filename": filename of the new image (json-string)
-- "format": format of the new image (json-string, optional)
-- "read-only-mode": new read-only mode (json-string, optional)
-          - Possible values: "retain" (default), "read-only", "read-write"
-
-Examples:
-
-1. Change a removable medium
-
--> { "execute": "blockdev-change-medium",
-             "arguments": { "id": "ide0-1-0",
-                            "filename": "/srv/images/Fedora-12-x86_64-DVD.iso",
-                            "format": "raw" } }
-<- { "return": {} }
-
-2. Load a read-only medium into a writable drive
-
--> { "execute": "blockdev-change-medium",
-             "arguments": { "id": "floppyA",
-                            "filename": "/srv/images/ro.img",
-                            "format": "raw",
-                            "read-only-mode": "retain" } }
-
-<- { "error":
-     { "class": "GenericError",
-       "desc": "Could not open '/srv/images/ro.img': Permission denied" } }
-
--> { "execute": "blockdev-change-medium",
-             "arguments": { "id": "floppyA",
-                            "filename": "/srv/images/ro.img",
-                            "format": "raw",
-                            "read-only-mode": "read-only" } }
-
-<- { "return": {} }
-
-query-memdev
-------------
-
-Show memory devices information.
-
-
-Example (1):
-
--> { "execute": "query-memdev" }
-<- { "return": [
-       {
-         "size": 536870912,
-         "merge": false,
-         "dump": true,
-         "prealloc": false,
-         "host-nodes": [0, 1],
-         "policy": "bind"
-       },
-       {
-         "size": 536870912,
-         "merge": false,
-         "dump": true,
-         "prealloc": true,
-         "host-nodes": [2, 3],
-         "policy": "preferred"
-       }
-     ]
-   }
-
-query-memory-devices
---------------------
-
-Return a list of memory devices.
-
-Example:
--> { "execute": "query-memory-devices" }
-<- { "return": [ { "data":
-                      { "addr": 5368709120,
-                        "hotpluggable": true,
-                        "hotplugged": true,
-                        "id": "d1",
-                        "memdev": "/objects/memX",
-                        "node": 0,
-                        "size": 1073741824,
-                        "slot": 0},
-                   "type": "dimm"
-                 } ] }
-
-query-acpi-ospm-status
-----------------------
-
-Return list of ACPIOSTInfo for devices that support status reporting
-via ACPI _OST method.
-
-Example:
--> { "execute": "query-acpi-ospm-status" }
-<- { "return": [ { "device": "d1", "slot": "0", "slot-type": "DIMM", "source": 1, "status": 0},
-                 { "slot": "1", "slot-type": "DIMM", "source": 0, "status": 0},
-                 { "slot": "2", "slot-type": "DIMM", "source": 0, "status": 0},
-                 { "slot": "3", "slot-type": "DIMM", "source": 0, "status": 0}
-   ]}
-
-rtc-reset-reinjection
----------------------
-
-Reset the RTC interrupt reinjection backlog.
-
-Arguments: None.
-
-Example:
-
--> { "execute": "rtc-reset-reinjection" }
-<- { "return": {} }
-
-trace-event-get-state
----------------------
-
-Query the state of events.
-
-Arguments:
-
-- "name": Event name pattern (json-string).
-- "vcpu": The vCPU to query, any vCPU by default (json-int, optional).
-
-An event is returned if:
-- its name matches the "name" pattern, and
-- if "vcpu" is given, the event has the "vcpu" property.
-
-Therefore, if "vcpu" is given, the operation will only match per-vCPU events,
-returning their state on the specified vCPU. Special case: if "name" is an exact
-match, "vcpu" is given and the event does not have the "vcpu" property, an error
-is returned.
-
-Example:
-
--> { "execute": "trace-event-get-state", "arguments": { "name": "qemu_memalign" } }
-<- { "return": [ { "name": "qemu_memalign", "state": "disabled" } ] }
-
-trace-event-set-state
----------------------
-
-Set the state of events.
-
-Arguments:
-
-- "name": Event name pattern (json-string).
-- "enable": Whether to enable or disable the event (json-bool).
-- "ignore-unavailable": Whether to ignore errors for events that cannot be
-  changed (json-bool, optional).
-- "vcpu": The vCPU to act upon, all vCPUs by default (json-int, optional).
-
-An event's state is modified if:
-- its name matches the "name" pattern, and
-- if "vcpu" is given, the event has the "vcpu" property.
-
-Therefore, if "vcpu" is given, the operation will only match per-vCPU events,
-setting their state on the specified vCPU. Special case: if "name" is an exact
-match, "vcpu" is given and the event does not have the "vcpu" property, an error
-is returned.
-
-Example:
-
--> { "execute": "trace-event-set-state", "arguments": { "name": "qemu_memalign", "enable": "true" } }
-<- { "return": {} }
-
-input-send-event
-----------------
-
-Send input event to guest.
-
-Arguments:
-
-- "device": display device (json-string, optional)
-- "head": display head (json-int, optional)
-- "events": list of input events
-
-The consoles are visible in the qom tree, under
-/backend/console[$index]. They have a device link and head property, so
-it is possible to map which console belongs to which device and display.
-
-Example (1):
-
-Press left mouse button.
-
--> { "execute": "input-send-event",
-    "arguments": { "device": "video0",
-                   "events": [ { "type": "btn",
-                   "data" : { "down": true, "button": "left" } } ] } }
-<- { "return": {} }
-
--> { "execute": "input-send-event",
-    "arguments": { "device": "video0",
-                   "events": [ { "type": "btn",
-                   "data" : { "down": false, "button": "left" } } ] } }
-<- { "return": {} }
-
-Example (2):
-
-Press ctrl-alt-del.
-
--> { "execute": "input-send-event",
-     "arguments": { "events": [
-        { "type": "key", "data" : { "down": true,
-          "key": {"type": "qcode", "data": "ctrl" } } },
-        { "type": "key", "data" : { "down": true,
-          "key": {"type": "qcode", "data": "alt" } } },
-        { "type": "key", "data" : { "down": true,
-          "key": {"type": "qcode", "data": "delete" } } } ] } }
-<- { "return": {} }
-
-Example (3):
-
-Move mouse pointer to absolute coordinates (20000, 400).
-
--> { "execute": "input-send-event" ,
-  "arguments": { "events": [
-               { "type": "abs", "data" : { "axis": "x", "value" : 20000 } },
-               { "type": "abs", "data" : { "axis": "y", "value" : 400 } } ] } }
-<- { "return": {} }
-
-block-set-write-threshold
-------------
-
-Change the write threshold for a block drive. The threshold is an offset,
-thus must be non-negative. Default is no write threshold.
-Setting the threshold to zero disables it.
-
-Arguments:
-
-- "node-name": the node name in the block driver state graph (json-string)
-- "write-threshold": the write threshold in bytes (json-int)
-
-Example:
-
--> { "execute": "block-set-write-threshold",
-  "arguments": { "node-name": "mydev",
-                 "write-threshold": 17179869184 } }
-<- { "return": {} }
-
-Show rocker switch
-------------------
-
-Arguments:
-
-- "name": switch name
-
-Example:
-
--> { "execute": "query-rocker", "arguments": { "name": "sw1" } }
-<- { "return": {"name": "sw1", "ports": 2, "id": 1327446905938}}
-
-Show rocker switch ports
-------------------------
-
-Arguments:
-
-- "name": switch name
-
-Example:
-
--> { "execute": "query-rocker-ports", "arguments": { "name": "sw1" } }
-<- { "return": [ {"duplex": "full", "enabled": true, "name": "sw1.1",
-                  "autoneg": "off", "link-up": true, "speed": 10000},
-                 {"duplex": "full", "enabled": true, "name": "sw1.2",
-                  "autoneg": "off", "link-up": true, "speed": 10000}
-   ]}
-
-Show rocker switch OF-DPA flow tables
--------------------------------------
-
-Arguments:
-
-- "name": switch name
-- "tbl-id": (optional) flow table ID
-
-Example:
-
--> { "execute": "query-rocker-of-dpa-flows", "arguments": { "name": "sw1" } }
-<- { "return": [ {"key": {"in-pport": 0, "priority": 1, "tbl-id": 0},
-                  "hits": 138,
-                  "cookie": 0,
-                  "action": {"goto-tbl": 10},
-                  "mask": {"in-pport": 4294901760}
-                 },
-                 {...more...},
-   ]}
-
-Show rocker OF-DPA group tables
--------------------------------
-
-Arguments:
-
-- "name": switch name
-- "type": (optional) group type
-
-Example:
-
--> { "execute": "query-rocker-of-dpa-groups", "arguments": { "name": "sw1" } }
-<- { "return": [ {"type": 0, "out-pport": 2, "pport": 2, "vlan-id": 3841,
-                  "pop-vlan": 1, "id": 251723778},
-                 {"type": 0, "out-pport": 0, "pport": 0, "vlan-id": 3841,
-                  "pop-vlan": 1, "id": 251723776},
-                 {"type": 0, "out-pport": 1, "pport": 1, "vlan-id": 3840,
-                  "pop-vlan": 1, "id": 251658241},
-                 {"type": 0, "out-pport": 0, "pport": 0, "vlan-id": 3840,
-                  "pop-vlan": 1, "id": 251658240}
-   ]}
-
-query-gic-capabilities
----------------
-
-Return a list of GICCapability objects, describing supported GIC
-(Generic Interrupt Controller) versions.
-
-Arguments: None
-
-Example:
-
--> { "execute": "query-gic-capabilities" }
-<- { "return": [{ "version": 2, "emulated": true, "kernel": false },
-                { "version": 3, "emulated": false, "kernel": true } ] }
-
-Show existing/possible CPUs
----------------------------
-
-Arguments: None.
-
-Example for pseries machine type started with
--smp 2,cores=2,maxcpus=4 -cpu POWER8:
-
--> { "execute": "query-hotpluggable-cpus" }
-<- {"return": [
-     { "props": { "core-id": 8 }, "type": "POWER8-spapr-cpu-core",
-       "vcpus-count": 1 },
-     { "props": { "core-id": 0 }, "type": "POWER8-spapr-cpu-core",
-       "vcpus-count": 1, "qom-path": "/machine/unattached/device[0]"}
-   ]}'
-
-Example for pc machine type started with
--smp 1,maxcpus=2:
-    -> { "execute": "query-hotpluggable-cpus" }
-    <- {"return": [
-         {
-            "type": "qemu64-x86_64-cpu", "vcpus-count": 1,
-            "props": {"core-id": 0, "socket-id": 1, "thread-id": 0}
-         },
-         {
-            "qom-path": "/machine/unattached/device[0]",
-            "type": "qemu64-x86_64-cpu", "vcpus-count": 1,
-            "props": {"core-id": 0, "socket-id": 0, "thread-id": 0}
-         }
-       ]}
diff --git a/docs/qmp-events.txt b/docs/qmp-events.txt
deleted file mode 100644
index e0a2365c63..0000000000
--- a/docs/qmp-events.txt
+++ /dev/null
@@ -1,731 +0,0 @@
-                   QEMU Machine Protocol Events
-                   ============================
-
-ACPI_DEVICE_OST
----------------
-
-Emitted when guest executes ACPI _OST method.
-
- - data: ACPIOSTInfo type as described in qapi-schema.json
-
-{ "event": "ACPI_DEVICE_OST",
-     "data": { "device": "d1", "slot": "0", "slot-type": "DIMM", "source": 1, "status": 0 } }
-
-BALLOON_CHANGE
---------------
-
-Emitted when the guest changes the actual BALLOON level. This
-value is equivalent to the 'actual' field return by the
-'query-balloon' command
-
-Data:
-
-- "actual": actual level of the guest memory balloon in bytes (json-number)
-
-Example:
-
-{ "event": "BALLOON_CHANGE",
-    "data": { "actual": 944766976 },
-    "timestamp": { "seconds": 1267020223, "microseconds": 435656 } }
-
-Note: this event is rate-limited.
-
-BLOCK_IMAGE_CORRUPTED
----------------------
-
-Emitted when a disk image is being marked corrupt. The image can be
-identified by its device or node name. The 'device' field is always
-present for compatibility reasons, but it can be empty ("") if the
-image does not have a device name associated.
-
-Data:
-
-- "device":    Device name (json-string)
-- "node-name": Node name (json-string, optional)
-- "msg":       Informative message (e.g., reason for the corruption)
-               (json-string)
-- "offset":    If the corruption resulted from an image access, this
-               is the host's access offset into the image
-               (json-int, optional)
-- "size":      If the corruption resulted from an image access, this
-               is the access size (json-int, optional)
-
-Example:
-
-{ "event": "BLOCK_IMAGE_CORRUPTED",
-    "data": { "device": "ide0-hd0", "node-name": "node0",
-        "msg": "Prevented active L1 table overwrite", "offset": 196608,
-        "size": 65536 },
-    "timestamp": { "seconds": 1378126126, "microseconds": 966463 } }
-
-BLOCK_IO_ERROR
---------------
-
-Emitted when a disk I/O error occurs.
-
-Data:
-
-- "device": device name. This is always present for compatibility
-            reasons, but it can be empty ("") if the image does not
-            have a device name associated. (json-string)
-- "node-name": node name. Note that errors may be reported for the root node
-               that is directly attached to a guest device rather than for the
-               node where the error occurred. (json-string)
-- "operation": I/O operation (json-string, "read" or "write")
-- "action": action that has been taken, it's one of the following (json-string):
-    "ignore": error has been ignored
-    "report": error has been reported to the device
-    "stop": the VM is going to stop because of the error
-
-Example:
-
-{ "event": "BLOCK_IO_ERROR",
-    "data": { "device": "ide0-hd1",
-              "node-name": "#block212",
-              "operation": "write",
-              "action": "stop" },
-    "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
-
-Note: If action is "stop", a STOP event will eventually follow the
-BLOCK_IO_ERROR event.
-
-BLOCK_JOB_CANCELLED
--------------------
-
-Emitted when a block job has been cancelled.
-
-Data:
-
-- "type":     Job type (json-string; "stream" for image streaming
-                                     "commit" for block commit)
-- "device":   Job identifier. Originally the device name but other
-              values are allowed since QEMU 2.7 (json-string)
-- "len":      Maximum progress value (json-int)
-- "offset":   Current progress value (json-int)
-              On success this is equal to len.
-              On failure this is less than len.
-- "speed":    Rate limit, bytes per second (json-int)
-
-Example:
-
-{ "event": "BLOCK_JOB_CANCELLED",
-     "data": { "type": "stream", "device": "virtio-disk0",
-               "len": 10737418240, "offset": 134217728,
-               "speed": 0 },
-     "timestamp": { "seconds": 1267061043, "microseconds": 959568 } }
-
-BLOCK_JOB_COMPLETED
--------------------
-
-Emitted when a block job has completed.
-
-Data:
-
-- "type":     Job type (json-string; "stream" for image streaming
-                                     "commit" for block commit)
-- "device":   Job identifier. Originally the device name but other
-              values are allowed since QEMU 2.7 (json-string)
-- "len":      Maximum progress value (json-int)
-- "offset":   Current progress value (json-int)
-              On success this is equal to len.
-              On failure this is less than len.
-- "speed":    Rate limit, bytes per second (json-int)
-- "error":    Error message (json-string, optional)
-              Only present on failure.  This field contains a human-readable
-              error message.  There are no semantics other than that streaming
-              has failed and clients should not try to interpret the error
-              string.
-
-Example:
-
-{ "event": "BLOCK_JOB_COMPLETED",
-     "data": { "type": "stream", "device": "virtio-disk0",
-               "len": 10737418240, "offset": 10737418240,
-               "speed": 0 },
-     "timestamp": { "seconds": 1267061043, "microseconds": 959568 } }
-
-BLOCK_JOB_ERROR
----------------
-
-Emitted when a block job encounters an error.
-
-Data:
-
-- "device": Job identifier. Originally the device name but other
-            values are allowed since QEMU 2.7 (json-string)
-- "operation": I/O operation (json-string, "read" or "write")
-- "action": action that has been taken, it's one of the following (json-string):
-    "ignore": error has been ignored, the job may fail later
-    "report": error will be reported and the job canceled
-    "stop": error caused job to be paused
-
-Example:
-
-{ "event": "BLOCK_JOB_ERROR",
-    "data": { "device": "ide0-hd1",
-              "operation": "write",
-              "action": "stop" },
-    "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
-
-BLOCK_JOB_READY
----------------
-
-Emitted when a block job is ready to complete.
-
-Data:
-
-- "type":     Job type (json-string; "stream" for image streaming
-                                     "commit" for block commit)
-- "device":   Job identifier. Originally the device name but other
-              values are allowed since QEMU 2.7 (json-string)
-- "len":      Maximum progress value (json-int)
-- "offset":   Current progress value (json-int)
-              On success this is equal to len.
-              On failure this is less than len.
-- "speed":    Rate limit, bytes per second (json-int)
-
-Example:
-
-{ "event": "BLOCK_JOB_READY",
-    "data": { "device": "drive0", "type": "mirror", "speed": 0,
-              "len": 2097152, "offset": 2097152 }
-    "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
-
-Note: The "ready to complete" status is always reset by a BLOCK_JOB_ERROR
-event.
-
-DEVICE_DELETED
---------------
-
-Emitted whenever the device removal completion is acknowledged
-by the guest.
-At this point, it's safe to reuse the specified device ID.
-Device removal can be initiated by the guest or by HMP/QMP commands.
-
-Data:
-
-- "device": device name (json-string, optional)
-- "path": device path (json-string)
-
-{ "event": "DEVICE_DELETED",
-  "data": { "device": "virtio-net-pci-0",
-            "path": "/machine/peripheral/virtio-net-pci-0" },
-  "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
-
-DEVICE_TRAY_MOVED
------------------
-
-It's emitted whenever the tray of a removable device is moved by the guest
-or by HMP/QMP commands.
-
-Data:
-
-- "device": Block device name. This is always present for compatibility
-            reasons, but it can be empty ("") if the image does not have a
-            device name associated. (json-string)
-- "id": The name or QOM path of the guest device (json-string)
-- "tray-open": true if the tray has been opened or false if it has been closed
-               (json-bool)
-
-{ "event": "DEVICE_TRAY_MOVED",
-  "data": { "device": "ide1-cd0",
-            "id": "/machine/unattached/device[22]",
-            "tray-open": true
-  },
-  "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
-
-DUMP_COMPLETED
---------------
-
-Emitted when the guest has finished one memory dump.
-
-Data:
-
-- "result": DumpQueryResult type described in qapi-schema.json
-- "error": Error message when dump failed. This is only a
-  human-readable string provided when dump failed. It should not be
-  parsed in any way (json-string, optional)
-
-Example:
-
-{ "event": "DUMP_COMPLETED",
-  "data": {"result": {"total": 1090650112, "status": "completed",
-                      "completed": 1090650112} } }
-
-GUEST_PANICKED
---------------
-
-Emitted when guest OS panic is detected.
-
-Data:
-
-- "action": Action that has been taken (json-string, currently always "pause").
-
-Example:
-
-{ "event": "GUEST_PANICKED",
-     "data": { "action": "pause" } }
-
-MEM_UNPLUG_ERROR
---------------------
-Emitted when memory hot unplug error occurs.
-
-Data:
-
-- "device": device name (json-string)
-- "msg": Informative message (e.g., reason for the error) (json-string)
-
-Example:
-
-{ "event": "MEM_UNPLUG_ERROR"
-  "data": { "device": "dimm1",
-            "msg": "acpi: device unplug for unsupported device"
-  },
-  "timestamp": { "seconds": 1265044230, "microseconds": 450486 } }
-
-NIC_RX_FILTER_CHANGED
----------------------
-
-The event is emitted once until the query command is executed,
-the first event will always be emitted.
-
-Data:
-
-- "name": net client name (json-string)
-- "path": device path (json-string)
-
-{ "event": "NIC_RX_FILTER_CHANGED",
-  "data": { "name": "vnet0",
-            "path": "/machine/peripheral/vnet0/virtio-backend" },
-  "timestamp": { "seconds": 1368697518, "microseconds": 326866 } }
-}
-
-POWERDOWN
----------
-
-Emitted when the Virtual Machine is powered down through the power
-control system, such as via ACPI.
-
-Data: None.
-
-Example:
-
-{ "event": "POWERDOWN",
-    "timestamp": { "seconds": 1267040730, "microseconds": 682951 } }
-
-QUORUM_FAILURE
---------------
-
-Emitted by the Quorum block driver if it fails to establish a quorum.
-
-Data:
-
-- "reference":     device name if defined else node name.
-- "sector-num":    Number of the first sector of the failed read operation.
-- "sectors-count": Failed read operation sector count.
-
-Example:
-
-{ "event": "QUORUM_FAILURE",
-     "data": { "reference": "usr1", "sector-num": 345435, "sectors-count": 5 },
-     "timestamp": { "seconds": 1344522075, "microseconds": 745528 } }
-
-Note: this event is rate-limited.
-
-QUORUM_REPORT_BAD
------------------
-
-Emitted to report a corruption of a Quorum file.
-
-Data:
-
-- "type":          Quorum operation type
-- "error":         Error message (json-string, optional)
-                   Only present on failure.  This field contains a human-readable
-                   error message.  There are no semantics other than that the
-                   block layer reported an error and clients should not try to
-                   interpret the error string.
-- "node-name":     The graph node name of the block driver state.
-- "sector-num":    Number of the first sector of the failed read operation.
-- "sectors-count": Failed read operation sector count.
-
-Example:
-
-Read operation:
-{ "event": "QUORUM_REPORT_BAD",
-     "data": { "node-name": "node0", "sector-num": 345435, "sectors-count": 5,
-               "type": "read" },
-     "timestamp": { "seconds": 1344522075, "microseconds": 745528 } }
-
-Flush operation:
-{ "event": "QUORUM_REPORT_BAD",
-     "data": { "node-name": "node0", "sector-num": 0, "sectors-count": 2097120,
-               "type": "flush", "error": "Broken pipe" },
-     "timestamp": { "seconds": 1456406829, "microseconds": 291763 } }
-
-Note: this event is rate-limited.
-
-RESET
------
-
-Emitted when the Virtual Machine is reset.
-
-Data: None.
-
-Example:
-
-{ "event": "RESET",
-    "timestamp": { "seconds": 1267041653, "microseconds": 9518 } }
-
-RESUME
-------
-
-Emitted when the Virtual Machine resumes execution.
-
-Data: None.
-
-Example:
-
-{ "event": "RESUME",
-    "timestamp": { "seconds": 1271770767, "microseconds": 582542 } }
-
-RTC_CHANGE
-----------
-
-Emitted when the guest changes the RTC time.
-
-Data:
-
-- "offset": Offset between base RTC clock (as specified by -rtc base), and
-new RTC clock value (json-number)
-
-Example:
-
-{ "event": "RTC_CHANGE",
-    "data": { "offset": 78 },
-    "timestamp": { "seconds": 1267020223, "microseconds": 435656 } }
-
-Note: this event is rate-limited.
-
-SHUTDOWN
---------
-
-Emitted when the Virtual Machine has shut down, indicating that qemu
-is about to exit.
-
-Data: None.
-
-Example:
-
-{ "event": "SHUTDOWN",
-    "timestamp": { "seconds": 1267040730, "microseconds": 682951 } }
-
-Note: If the command-line option "-no-shutdown" has been specified, a STOP
-event will eventually follow the SHUTDOWN event.
-
-SPICE_CONNECTED
----------------
-
-Emitted when a SPICE client connects.
-
-Data:
-
-- "server": Server information (json-object)
-  - "host": IP address (json-string)
-  - "port": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-- "client": Client information (json-object)
-  - "host": IP address (json-string)
-  - "port": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-
-Example:
-
-{ "timestamp": {"seconds": 1290688046, "microseconds": 388707},
-  "event": "SPICE_CONNECTED",
-  "data": {
-    "server": { "port": "5920", "family": "ipv4", "host": "127.0.0.1"},
-    "client": {"port": "52873", "family": "ipv4", "host": "127.0.0.1"}
-}}
-
-SPICE_DISCONNECTED
-------------------
-
-Emitted when a SPICE client disconnects.
-
-Data:
-
-- "server": Server information (json-object)
-  - "host": IP address (json-string)
-  - "port": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-- "client": Client information (json-object)
-  - "host": IP address (json-string)
-  - "port": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-
-Example:
-
-{ "timestamp": {"seconds": 1290688046, "microseconds": 388707},
-  "event": "SPICE_DISCONNECTED",
-  "data": {
-    "server": { "port": "5920", "family": "ipv4", "host": "127.0.0.1"},
-    "client": {"port": "52873", "family": "ipv4", "host": "127.0.0.1"}
-}}
-
-SPICE_INITIALIZED
------------------
-
-Emitted after initial handshake and authentication takes place (if any)
-and the SPICE channel is up and running
-
-Data:
-
-- "server": Server information (json-object)
-  - "host": IP address (json-string)
-  - "port": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-  - "auth": authentication method (json-string, optional)
-- "client": Client information (json-object)
-  - "host": IP address (json-string)
-  - "port": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-  - "connection-id": spice connection id.  All channels with the same id
-                     belong to the same spice session (json-int)
-  - "channel-type": channel type.  "1" is the main control channel, filter for
-                    this one if you want track spice sessions only (json-int)
-  - "channel-id": channel id.  Usually "0", might be different needed when
-                  multiple channels of the same type exist, such as multiple
-                  display channels in a multihead setup (json-int)
-  - "tls": whevener the channel is encrypted (json-bool)
-
-Example:
-
-{ "timestamp": {"seconds": 1290688046, "microseconds": 417172},
-  "event": "SPICE_INITIALIZED",
-  "data": {"server": {"auth": "spice", "port": "5921",
-                      "family": "ipv4", "host": "127.0.0.1"},
-           "client": {"port": "49004", "family": "ipv4", "channel-type": 3,
-                      "connection-id": 1804289383, "host": "127.0.0.1",
-                      "channel-id": 0, "tls": true}
-}}
-
-SPICE_MIGRATE_COMPLETED
------------------------
-
-Emitted when SPICE migration has completed
-
-Data: None.
-
-Example:
-
-{ "timestamp": {"seconds": 1290688046, "microseconds": 417172},
-  "event": "SPICE_MIGRATE_COMPLETED" }
-
-MIGRATION
----------
-
-Emitted when a migration event happens
-
-Data: None.
-
- - "status": migration status
-     See MigrationStatus in ~/qapi-schema.json for possible values
-
-Example:
-
-{"timestamp": {"seconds": 1432121972, "microseconds": 744001},
- "event": "MIGRATION", "data": {"status": "completed"}}
-
-MIGRATION_PASS
---------------
-
-Emitted from the source side of a migration at the start of each pass
-(when it syncs the dirty bitmap)
-
-Data: None.
-
-  - "pass": An incrementing count (starting at 1 on the first pass)
-
-Example:
-{"timestamp": {"seconds": 1449669631, "microseconds": 239225},
- "event": "MIGRATION_PASS", "data": {"pass": 2}}
-
-STOP
-----
-
-Emitted when the Virtual Machine is stopped.
-
-Data: None.
-
-Example:
-
-{ "event": "STOP",
-    "timestamp": { "seconds": 1267041730, "microseconds": 281295 } }
-
-SUSPEND
--------
-
-Emitted when guest enters S3 state.
-
-Data: None.
-
-Example:
-
-{ "event": "SUSPEND",
-     "timestamp": { "seconds": 1344456160, "microseconds": 309119 } }
-
-SUSPEND_DISK
-------------
-
-Emitted when the guest makes a request to enter S4 state.
-
-Data: None.
-
-Example:
-
-{ "event": "SUSPEND_DISK",
-     "timestamp": { "seconds": 1344456160, "microseconds": 309119 } }
-
-Note: QEMU shuts down when entering S4 state.
-
-VNC_CONNECTED
--------------
-
-Emitted when a VNC client establishes a connection.
-
-Data:
-
-- "server": Server information (json-object)
-  - "host": IP address (json-string)
-  - "service": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-  - "auth": authentication method (json-string, optional)
-- "client": Client information (json-object)
-  - "host": IP address (json-string)
-  - "service": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-
-Example:
-
-{ "event": "VNC_CONNECTED",
-    "data": {
-        "server": { "auth": "sasl", "family": "ipv4",
-                    "service": "5901", "host": "0.0.0.0" },
-        "client": { "family": "ipv4", "service": "58425",
-                    "host": "127.0.0.1" } },
-    "timestamp": { "seconds": 1262976601, "microseconds": 975795 } }
-
-
-Note: This event is emitted before any authentication takes place, thus
-the authentication ID is not provided.
-
-VNC_DISCONNECTED
-----------------
-
-Emitted when the connection is closed.
-
-Data:
-
-- "server": Server information (json-object)
-  - "host": IP address (json-string)
-  - "service": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-  - "auth": authentication method (json-string, optional)
-- "client": Client information (json-object)
-  - "host": IP address (json-string)
-  - "service": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-  - "x509_dname": TLS dname (json-string, optional)
-  - "sasl_username": SASL username (json-string, optional)
-
-Example:
-
-{ "event": "VNC_DISCONNECTED",
-    "data": {
-        "server": { "auth": "sasl", "family": "ipv4",
-                    "service": "5901", "host": "0.0.0.0" },
-        "client": { "family": "ipv4", "service": "58425",
-                    "host": "127.0.0.1", "sasl_username": "luiz" } },
-    "timestamp": { "seconds": 1262976601, "microseconds": 975795 } }
-
-VNC_INITIALIZED
----------------
-
-Emitted after authentication takes place (if any) and the VNC session is
-made active.
-
-Data:
-
-- "server": Server information (json-object)
-  - "host": IP address (json-string)
-  - "service": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-  - "auth": authentication method (json-string, optional)
-- "client": Client information (json-object)
-  - "host": IP address (json-string)
-  - "service": port number (json-string)
-  - "family": address family (json-string, "ipv4" or "ipv6")
-  - "x509_dname": TLS dname (json-string, optional)
-  - "sasl_username": SASL username (json-string, optional)
-
-Example:
-
-{ "event": "VNC_INITIALIZED",
-    "data": {
-        "server": { "auth": "sasl", "family": "ipv4",
-                    "service": "5901", "host": "0.0.0.0"},
-        "client": { "family": "ipv4", "service": "46089",
-                    "host": "127.0.0.1", "sasl_username": "luiz" } },
-        "timestamp": { "seconds": 1263475302, "microseconds": 150772 } }
-
-VSERPORT_CHANGE
----------------
-
-Emitted when the guest opens or closes a virtio-serial port.
-
-Data:
-
-- "id": device identifier of the virtio-serial port (json-string)
-- "open": true if the guest has opened the virtio-serial port (json-bool)
-
-Example:
-
-{ "event": "VSERPORT_CHANGE",
-    "data": { "id": "channel0", "open": true },
-    "timestamp": { "seconds": 1401385907, "microseconds": 422329 } }
-
-Note: this event is rate-limited separately for each "id".
-
-WAKEUP
-------
-
-Emitted when the guest has woken up from S3 and is running.
-
-Data: None.
-
-Example:
-
-{ "event": "WAKEUP",
-     "timestamp": { "seconds": 1344522075, "microseconds": 745528 } }
-
-WATCHDOG
---------
-
-Emitted when the watchdog device's timer is expired.
-
-Data:
-
-- "action": Action that has been taken, it's one of the following (json-string):
-            "reset", "shutdown", "poweroff", "pause", "debug", or "none"
-
-Example:
-
-{ "event": "WATCHDOG",
-     "data": { "action": "reset" },
-     "timestamp": { "seconds": 1267061043, "microseconds": 959568 } }
-
-Note: If action is "reset", "shutdown", or "pause" the WATCHDOG event is
-followed respectively by the RESET, SHUTDOWN, or STOP events.
-
-Note: this event is rate-limited.
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 15/21] qapi: add qapi2texi script
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (13 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 14/21] (SQUASHED) move doc to schema Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-11 14:26   ` Markus Armbruster
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 16/21] docs: add qemu logo to pdf Marc-André Lureau
                   ` (6 subsequent siblings)
  21 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

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 description:

  ##
  # @symbol:
  #
  # Symbol body ditto ergo sum. Foo bar
  # baz ding.
  #
  # @param1: the frob to frobnicate
  # @param2: #optional how hard to frobnicate
  #
  # Returns: the frobnicated frob.
  #          If frob isn't frobnicatable, GenericError.
  #
  # 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 | tag_section | freeform_comment }
member = "# @" name ':' [ text ] "\n" freeform_comment
tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ]  "\n" freeform_comment
text = free text with markup

Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
both as freeform_comment and as symbol_comment.  The actual parser
recognizes symbol_comment.

See docs/qapi-code-gen.txt for more details.

Deficiencies:
- the generated QMP documentation includes internal types
- union-type support is lacking
- type information is lacking in generated documentation
- doc comment error message positions are imprecise, they point
  to the beginning of the comment.
- see other TODO/FIXME in this commit

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi.py                                    | 247 ++++++++++++++++++-
 scripts/qapi2texi.py                               | 266 +++++++++++++++++++++
 docs/qapi-code-gen.txt                             | 174 +++++++++++---
 tests/Makefile.include                             |  20 ++
 tests/qapi-schema/alternate-any.err                |   2 +-
 tests/qapi-schema/alternate-any.json               |   4 +
 tests/qapi-schema/alternate-array.err              |   2 +-
 tests/qapi-schema/alternate-array.json             |   7 +
 tests/qapi-schema/alternate-base.err               |   2 +-
 tests/qapi-schema/alternate-base.json              |   7 +
 tests/qapi-schema/alternate-clash.err              |   2 +-
 tests/qapi-schema/alternate-clash.json             |   4 +
 tests/qapi-schema/alternate-conflict-dict.err      |   2 +-
 tests/qapi-schema/alternate-conflict-dict.json     |  10 +
 tests/qapi-schema/alternate-conflict-string.err    |   2 +-
 tests/qapi-schema/alternate-conflict-string.json   |   7 +
 tests/qapi-schema/alternate-empty.err              |   2 +-
 tests/qapi-schema/alternate-empty.json             |   4 +
 tests/qapi-schema/alternate-nested.err             |   2 +-
 tests/qapi-schema/alternate-nested.json            |   7 +
 tests/qapi-schema/alternate-unknown.err            |   2 +-
 tests/qapi-schema/alternate-unknown.json           |   4 +
 tests/qapi-schema/args-alternate.err               |   2 +-
 tests/qapi-schema/args-alternate.json              |   8 +
 tests/qapi-schema/args-any.err                     |   2 +-
 tests/qapi-schema/args-any.json                    |   4 +
 tests/qapi-schema/args-array-empty.err             |   2 +-
 tests/qapi-schema/args-array-empty.json            |   4 +
 tests/qapi-schema/args-array-unknown.err           |   2 +-
 tests/qapi-schema/args-array-unknown.json          |   4 +
 tests/qapi-schema/args-bad-boxed.err               |   2 +-
 tests/qapi-schema/args-bad-boxed.json              |   4 +
 tests/qapi-schema/args-boxed-anon.err              |   2 +-
 tests/qapi-schema/args-boxed-anon.json             |   4 +
 tests/qapi-schema/args-boxed-empty.err             |   2 +-
 tests/qapi-schema/args-boxed-empty.json            |   8 +
 tests/qapi-schema/args-boxed-string.err            |   2 +-
 tests/qapi-schema/args-boxed-string.json           |   4 +
 tests/qapi-schema/args-int.err                     |   2 +-
 tests/qapi-schema/args-int.json                    |   4 +
 tests/qapi-schema/args-invalid.err                 |   2 +-
 tests/qapi-schema/args-invalid.json                |   3 +
 tests/qapi-schema/args-member-array-bad.err        |   2 +-
 tests/qapi-schema/args-member-array-bad.json       |   4 +
 tests/qapi-schema/args-member-case.err             |   2 +-
 tests/qapi-schema/args-member-case.json            |   4 +
 tests/qapi-schema/args-member-unknown.err          |   2 +-
 tests/qapi-schema/args-member-unknown.json         |   4 +
 tests/qapi-schema/args-name-clash.err              |   2 +-
 tests/qapi-schema/args-name-clash.json             |   4 +
 tests/qapi-schema/args-union.err                   |   2 +-
 tests/qapi-schema/args-union.json                  |   7 +
 tests/qapi-schema/args-unknown.err                 |   2 +-
 tests/qapi-schema/args-unknown.json                |   4 +
 tests/qapi-schema/bad-base.err                     |   2 +-
 tests/qapi-schema/bad-base.json                    |   7 +
 tests/qapi-schema/bad-data.err                     |   2 +-
 tests/qapi-schema/bad-data.json                    |   4 +
 tests/qapi-schema/bad-ident.err                    |   2 +-
 tests/qapi-schema/bad-ident.json                   |   4 +
 tests/qapi-schema/bad-type-bool.err                |   2 +-
 tests/qapi-schema/bad-type-bool.json               |   4 +
 tests/qapi-schema/bad-type-dict.err                |   2 +-
 tests/qapi-schema/bad-type-dict.json               |   4 +
 tests/qapi-schema/base-cycle-direct.err            |   2 +-
 tests/qapi-schema/base-cycle-direct.json           |   4 +
 tests/qapi-schema/base-cycle-indirect.err          |   2 +-
 tests/qapi-schema/base-cycle-indirect.json         |   7 +
 tests/qapi-schema/command-int.err                  |   2 +-
 tests/qapi-schema/command-int.json                 |   4 +
 tests/qapi-schema/comments.json                    |   4 +
 tests/qapi-schema/comments.out                     |   3 +
 tests/qapi-schema/doc-bad-args.err                 |   1 +
 tests/qapi-schema/doc-bad-args.exit                |   1 +
 tests/qapi-schema/doc-bad-args.json                |   8 +
 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              |   6 +
 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          |   7 +
 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       |   8 +
 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        |   8 +
 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               |   6 +
 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           |   8 +
 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            |   5 +
 tests/qapi-schema/doc-empty-symbol.out             |   0
 tests/qapi-schema/doc-interleaved-section.err      |   1 +
 tests/qapi-schema/doc-interleaved-section.exit     |   1 +
 tests/qapi-schema/doc-interleaved-section.json     |  21 ++
 tests/qapi-schema/doc-interleaved-section.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             |   5 +
 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            |   5 +
 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          |   7 +
 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         |   6 +
 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           |   5 +
 tests/qapi-schema/doc-invalid-start.out            |   0
 tests/qapi-schema/doc-missing-colon.err            |   1 +
 tests/qapi-schema/doc-missing-colon.exit           |   1 +
 tests/qapi-schema/doc-missing-colon.json           |   5 +
 tests/qapi-schema/doc-missing-colon.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            |   5 +
 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           |   6 +
 tests/qapi-schema/doc-missing-space.out            |   0
 tests/qapi-schema/doc-optional.err                 |   1 +
 tests/qapi-schema/doc-optional.exit                |   1 +
 tests/qapi-schema/doc-optional.json                |   7 +
 tests/qapi-schema/doc-optional.out                 |   0
 tests/qapi-schema/double-type.err                  |   2 +-
 tests/qapi-schema/double-type.json                 |   4 +
 tests/qapi-schema/enum-bad-name.err                |   2 +-
 tests/qapi-schema/enum-bad-name.json               |   4 +
 tests/qapi-schema/enum-bad-prefix.err              |   2 +-
 tests/qapi-schema/enum-bad-prefix.json             |   4 +
 tests/qapi-schema/enum-clash-member.err            |   2 +-
 tests/qapi-schema/enum-clash-member.json           |   4 +
 tests/qapi-schema/enum-dict-member.err             |   2 +-
 tests/qapi-schema/enum-dict-member.json            |   4 +
 tests/qapi-schema/enum-member-case.err             |   2 +-
 tests/qapi-schema/enum-member-case.json            |   7 +
 tests/qapi-schema/enum-missing-data.err            |   2 +-
 tests/qapi-schema/enum-missing-data.json           |   4 +
 tests/qapi-schema/enum-wrong-data.err              |   2 +-
 tests/qapi-schema/enum-wrong-data.json             |   4 +
 tests/qapi-schema/event-boxed-empty.err            |   2 +-
 tests/qapi-schema/event-boxed-empty.json           |   4 +
 tests/qapi-schema/event-case.json                  |   4 +
 tests/qapi-schema/event-case.out                   |   3 +
 tests/qapi-schema/event-nest-struct.err            |   2 +-
 tests/qapi-schema/event-nest-struct.json           |   4 +
 tests/qapi-schema/flat-union-array-branch.err      |   2 +-
 tests/qapi-schema/flat-union-array-branch.json     |  12 +
 tests/qapi-schema/flat-union-bad-base.err          |   2 +-
 tests/qapi-schema/flat-union-bad-base.json         |  13 +
 tests/qapi-schema/flat-union-bad-discriminator.err |   2 +-
 .../qapi-schema/flat-union-bad-discriminator.json  |  16 ++
 tests/qapi-schema/flat-union-base-any.err          |   2 +-
 tests/qapi-schema/flat-union-base-any.json         |  13 +
 tests/qapi-schema/flat-union-base-union.err        |   2 +-
 tests/qapi-schema/flat-union-base-union.json       |  16 ++
 tests/qapi-schema/flat-union-clash-member.err      |   2 +-
 tests/qapi-schema/flat-union-clash-member.json     |  16 ++
 tests/qapi-schema/flat-union-empty.err             |   2 +-
 tests/qapi-schema/flat-union-empty.json            |  10 +
 tests/qapi-schema/flat-union-incomplete-branch.err |   2 +-
 .../qapi-schema/flat-union-incomplete-branch.json  |  10 +
 tests/qapi-schema/flat-union-inline.err            |   2 +-
 tests/qapi-schema/flat-union-inline.json           |  10 +
 tests/qapi-schema/flat-union-int-branch.err        |   2 +-
 tests/qapi-schema/flat-union-int-branch.json       |  13 +
 .../qapi-schema/flat-union-invalid-branch-key.err  |   2 +-
 .../qapi-schema/flat-union-invalid-branch-key.json |  15 ++
 .../flat-union-invalid-discriminator.err           |   2 +-
 .../flat-union-invalid-discriminator.json          |  15 ++
 tests/qapi-schema/flat-union-no-base.err           |   2 +-
 tests/qapi-schema/flat-union-no-base.json          |  13 +
 .../flat-union-optional-discriminator.err          |   2 +-
 .../flat-union-optional-discriminator.json         |  13 +
 .../flat-union-string-discriminator.err            |   2 +-
 .../flat-union-string-discriminator.json           |  15 ++
 tests/qapi-schema/ident-with-escape.json           |   4 +
 tests/qapi-schema/ident-with-escape.out            |   3 +
 tests/qapi-schema/include-relpath-sub.json         |   3 +
 tests/qapi-schema/include-relpath.out              |   3 +
 tests/qapi-schema/include-repetition.out           |   3 +
 tests/qapi-schema/include-simple-sub.json          |   3 +
 tests/qapi-schema/include-simple.out               |   3 +
 tests/qapi-schema/indented-expr.json               |   6 +
 tests/qapi-schema/indented-expr.out                |   6 +
 tests/qapi-schema/missing-type.err                 |   2 +-
 tests/qapi-schema/missing-type.json                |   4 +
 tests/qapi-schema/nested-struct-data.err           |   2 +-
 tests/qapi-schema/nested-struct-data.json          |   4 +
 tests/qapi-schema/qapi-schema-test.json            | 213 +++++++++++++++++
 tests/qapi-schema/qapi-schema-test.out             | 212 ++++++++++++++++
 tests/qapi-schema/redefined-builtin.err            |   2 +-
 tests/qapi-schema/redefined-builtin.json           |   4 +
 tests/qapi-schema/redefined-command.err            |   2 +-
 tests/qapi-schema/redefined-command.json           |   7 +
 tests/qapi-schema/redefined-event.err              |   2 +-
 tests/qapi-schema/redefined-event.json             |   7 +
 tests/qapi-schema/redefined-type.err               |   2 +-
 tests/qapi-schema/redefined-type.json              |   7 +
 tests/qapi-schema/reserved-command-q.err           |   2 +-
 tests/qapi-schema/reserved-command-q.json          |   7 +
 tests/qapi-schema/reserved-enum-q.err              |   2 +-
 tests/qapi-schema/reserved-enum-q.json             |   4 +
 tests/qapi-schema/reserved-member-has.err          |   2 +-
 tests/qapi-schema/reserved-member-has.json         |   4 +
 tests/qapi-schema/reserved-member-q.err            |   2 +-
 tests/qapi-schema/reserved-member-q.json           |   4 +
 tests/qapi-schema/reserved-member-u.err            |   2 +-
 tests/qapi-schema/reserved-member-u.json           |   4 +
 tests/qapi-schema/reserved-member-underscore.err   |   2 +-
 tests/qapi-schema/reserved-member-underscore.json  |   4 +
 tests/qapi-schema/reserved-type-kind.err           |   2 +-
 tests/qapi-schema/reserved-type-kind.json          |   4 +
 tests/qapi-schema/reserved-type-list.err           |   2 +-
 tests/qapi-schema/reserved-type-list.json          |   4 +
 tests/qapi-schema/returns-alternate.err            |   2 +-
 tests/qapi-schema/returns-alternate.json           |   7 +
 tests/qapi-schema/returns-array-bad.err            |   2 +-
 tests/qapi-schema/returns-array-bad.json           |   4 +
 tests/qapi-schema/returns-dict.err                 |   2 +-
 tests/qapi-schema/returns-dict.json                |   4 +
 tests/qapi-schema/returns-unknown.err              |   2 +-
 tests/qapi-schema/returns-unknown.json             |   4 +
 tests/qapi-schema/returns-whitelist.err            |   2 +-
 tests/qapi-schema/returns-whitelist.json           |  16 ++
 tests/qapi-schema/struct-base-clash-deep.err       |   2 +-
 tests/qapi-schema/struct-base-clash-deep.json      |  10 +
 tests/qapi-schema/struct-base-clash.err            |   2 +-
 tests/qapi-schema/struct-base-clash.json           |   7 +
 tests/qapi-schema/struct-data-invalid.err          |   2 +-
 tests/qapi-schema/struct-data-invalid.json         |   3 +
 tests/qapi-schema/struct-member-invalid.err        |   2 +-
 tests/qapi-schema/struct-member-invalid.json       |   3 +
 tests/qapi-schema/test-qapi.py                     |  12 +
 tests/qapi-schema/type-bypass-bad-gen.err          |   2 +-
 tests/qapi-schema/type-bypass-bad-gen.json         |   4 +
 tests/qapi-schema/unicode-str.err                  |   2 +-
 tests/qapi-schema/unicode-str.json                 |   4 +
 tests/qapi-schema/union-base-no-discriminator.err  |   2 +-
 tests/qapi-schema/union-base-no-discriminator.json |  12 +
 tests/qapi-schema/union-branch-case.err            |   2 +-
 tests/qapi-schema/union-branch-case.json           |   4 +
 tests/qapi-schema/union-clash-branches.err         |   2 +-
 tests/qapi-schema/union-clash-branches.json        |   4 +
 tests/qapi-schema/union-empty.err                  |   2 +-
 tests/qapi-schema/union-empty.json                 |   4 +
 tests/qapi-schema/union-invalid-base.err           |   2 +-
 tests/qapi-schema/union-invalid-base.json          |  10 +
 tests/qapi-schema/union-optional-branch.err        |   2 +-
 tests/qapi-schema/union-optional-branch.json       |   4 +
 tests/qapi-schema/union-unknown.err                |   2 +-
 tests/qapi-schema/union-unknown.json               |   4 +
 tests/qapi-schema/unknown-escape.err               |   2 +-
 tests/qapi-schema/unknown-escape.json              |   4 +
 tests/qapi-schema/unknown-expr-key.err             |   2 +-
 tests/qapi-schema/unknown-expr-key.json            |   4 +
 276 files changed, 2006 insertions(+), 127 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-interleaved-section.err
 create mode 100644 tests/qapi-schema/doc-interleaved-section.exit
 create mode 100644 tests/qapi-schema/doc-interleaved-section.json
 create mode 100644 tests/qapi-schema/doc-interleaved-section.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-colon.err
 create mode 100644 tests/qapi-schema/doc-missing-colon.exit
 create mode 100644 tests/qapi-schema/doc-missing-colon.json
 create mode 100644 tests/qapi-schema/doc-missing-colon.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
 create mode 100644 tests/qapi-schema/doc-optional.err
 create mode 100644 tests/qapi-schema/doc-optional.exit
 create mode 100644 tests/qapi-schema/doc-optional.json
 create mode 100644 tests/qapi-schema/doc-optional.out

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 3d5f9e1eaf..a92a86f428 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -125,6 +125,122 @@ class QAPISemError(QAPIError):
                            info['parent'], msg)
 
 
+class QAPIDoc(object):
+    class Section(object):
+        def __init__(self, name=None):
+            # optional section name (argument/member or section name)
+            self.name = name
+            # the list of lines 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, info):
+        # self.parser is used to report errors with QAPIParseError.  The
+        # resulting error position depends on the state of the parser.
+        # It happens to be the beginning of the comment.  More or less
+        # servicable, but action at a distance.
+        self.parser = parser
+        self.info = info
+        self.symbol = None
+        self.body = QAPIDoc.Section()
+        # dict mapping parameter name to ArgSection
+        self.args = OrderedDict()
+        # a list of Section
+        self.sections = []
+        # the current section
+        self.section = self.body
+        # associated expression (to be set by expression parser)
+        self.expr = None
+
+    def has_section(self, name):
+        """Return True if we have a section with this name."""
+        for i in self.sections:
+            if i.name == name:
+                return True
+        return False
+
+    def append(self, line):
+        """Parse a comment line and add it to the documentation."""
+        line = line[1:]
+        if not line:
+            self._append_freeform(line)
+            return
+
+        if line[0] != ' ':
+            raise QAPIParseError(self.parser, "Missing space after #")
+        line = line[1:]
+
+        # FIXME not nice: things like '#  @foo:' and '# @foo: ' aren't
+        # recognized, and get silently treated as ordinary text
+        if self.symbol:
+            self._append_symbol_line(line)
+        elif not self.body.content and line.startswith("@"):
+            if not line.endswith(":"):
+                raise QAPIParseError(self.parser, "Line should end with :")
+            self.symbol = line[1:-1]
+            # FIXME invalid names other than the empty string aren't flagged
+            if not self.symbol:
+                raise QAPIParseError(self.parser, "Invalid name")
+        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:",
+                      "TODO:"):
+            line = line[len(name)+1:]
+            self._start_section(name[:-1])
+
+        self._append_freeform(line)
+
+    def _start_args_section(self, name):
+        # FIXME invalid names other than the empty string aren't flagged
+        if not name:
+            raise QAPIParseError(self.parser, "Invalid parameter name")
+        if name in self.args:
+            raise QAPIParseError(self.parser,
+                                 "'%s' parameter name duplicated" % name)
+        if self.sections:
+            raise QAPIParseError(self.parser,
+                                 "'%s' parameter documentation is interleaved "
+                                 "with other sections" % name)
+        self.section = QAPIDoc.ArgSection(name)
+        self.args[name] = self.section
+
+    def _start_section(self, name=""):
+        if name in ("Returns", "Since") and self.has_section(name):
+            raise QAPIParseError(self.parser,
+                                 "Duplicated '%s' section" % name)
+        self.section = QAPIDoc.Section(name)
+        self.sections.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()):
+            self._start_section()
+        if (in_arg or not self.section.name 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):
@@ -140,11 +256,17 @@ 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 == '#':
+                doc = self.get_doc(info)
+                self.docs.append(doc)
+                continue
+
             expr = self.get_expr(False)
             if isinstance(expr, dict) and "include" in expr:
                 if len(expr) != 1:
@@ -162,6 +284,7 @@ class QAPISchemaParser(object):
                         raise QAPISemError(info, "Inclusion loop for %s"
                                            % include)
                     inf = inf['parent']
+
                 # skip multiple include of the same file
                 if incl_abs_fname in previously_included:
                     continue
@@ -172,12 +295,19 @@ 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
+                        self.docs[-1].info['file'] == fname 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 accept(self, skip_comment=True):
         while True:
             self.tok = self.src[self.cursor]
             self.pos = self.cursor
@@ -185,7 +315,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 == "'":
@@ -319,6 +455,28 @@ class QAPISchemaParser(object):
             raise QAPIParseError(self, 'Expected "{", "[" or string')
         return expr
 
+    def get_doc(self, info):
+        if self.val != '##':
+            raise QAPIParseError(self, "Junk after '##' at start of "
+                                 "documentation comment")
+
+        doc = QAPIDoc(self, info)
+        self.accept(False)
+        while self.tok == '#':
+            if self.val.startswith('##'):
+                # End of doc comment
+                if self.val != '##':
+                    raise QAPIParseError(self, "Junk after '##' at end of "
+                                         "documentation comment")
+                self.accept()
+                return doc
+            else:
+                doc.append(self.val)
+            self.accept(False)
+
+        raise QAPIParseError(self, "Documentation comment must end with '##'")
+
+
 #
 # Semantic analysis of schema expressions
 # TODO fold into QAPISchema
@@ -703,6 +861,11 @@ def check_exprs(exprs):
     for expr_elem in exprs:
         expr = expr_elem['expr']
         info = expr_elem['info']
+
+        if 'doc' not in expr_elem:
+            raise QAPISemError(info,
+                               "Expression missing documentation comment")
+
         if 'enum' in expr:
             check_keys(expr_elem, 'enum', ['data'], ['prefix'])
             add_enum(expr['enum'], info, expr['data'])
@@ -761,6 +924,84 @@ def check_exprs(exprs):
     return exprs
 
 
+def check_freeform_doc(doc):
+    if doc.symbol:
+        raise QAPISemError(doc.info,
+                           "Documention for '%s' is not followed"
+                           " by the definition" % doc.symbol)
+
+    body = str(doc.body)
+    if re.search(r'@\S+:', body, re.MULTILINE):
+        raise QAPISemError(doc.info,
+                           "Free-form documentation block must not contain"
+                           " @NAME: sections")
+
+
+def check_definition_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 QAPISemError(info, "Definition of '%s' follows documentation"
+                           " for '%s'" % (name, doc.symbol))
+    if doc.has_section('Returns') and 'command' not in expr:
+        raise QAPISemError(info, "'Returns:' is only valid for commands")
+
+    if meta == 'union':
+        args = expr.get('base', [])
+    else:
+        args = expr.get('data', [])
+    if isinstance(args, dict):
+        args = args.keys()
+
+    if meta == 'alternate' or \
+       (meta == 'union' and not expr.get('discriminator')):
+        args.append('type')
+
+    if isinstance(args, list):
+        for arg in args:
+            if arg[0] == '*':
+                opt = True
+                desc = doc.args.get(arg[1:])
+            else:
+                opt = False
+                desc = doc.args.get(arg)
+            if not desc:
+                continue
+            desc_opt = "#optional" in str(desc)
+            if desc_opt and not opt:
+                raise QAPISemError(info, "Description has #optional, "
+                                   "but the declaration doesn't")
+            if not desc_opt and opt:
+                # silently fix the doc
+                desc.append("#optional")
+
+    doc_args = set(doc.args.keys())
+    args = set([name.strip('*') for name in args])
+    if not doc_args.issubset(args):
+        raise QAPISemError(info, "The following documented members are not in "
+                           "the declaration: %s" % ", ".join(doc_args - args))
+
+
+def check_docs(docs):
+    for doc in docs:
+        for section in doc.args.values() + doc.sections:
+            content = str(section)
+            if not content or content.isspace():
+                raise QAPISemError(doc.info,
+                                   "Empty doc section '%s'" % section.name)
+
+        if not doc.expr:
+            check_freeform_doc(doc)
+        else:
+            check_definition_doc(doc, doc.expr, doc.info)
+
+    return docs
+
+
 #
 # Schema compiler frontend
 #
@@ -1229,7 +1470,9 @@ class QAPISchemaEvent(QAPISchemaEntity):
 class QAPISchema(object):
     def __init__(self, fname):
         try:
-            self.exprs = check_exprs(QAPISchemaParser(open(fname, "r")).exprs)
+            parser = QAPISchemaParser(open(fname, "r"))
+            self.exprs = check_exprs(parser.exprs)
+            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 0000000000..436749ec7b
--- /dev/null
+++ b/scripts/qapi2texi.py
@@ -0,0 +1,266 @@
+#!/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 = """
+@deftypefn {type} {{}} {name}
+
+{body}
+
+@end deftypefn
+
+""".format
+
+ENUM_FMT = """
+@deftp Enum {name}
+
+{body}
+
+@end deftp
+
+""".format
+
+STRUCT_FMT = """
+@deftp {{{type}}} {name}
+
+{body}
+
+@end deftp
+
+""".format
+
+EXAMPLE_FMT = """@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'\b_([^_\n]+)_\b', 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"""
+    # TODO: Neglects to escape @ characters.
+    # We should probably escape them in subst_braces(), and rename the
+    # function to subst_special() or subs_texi_special().  If we do that, we
+    # need to delay it until after subst_vars() in texi_format().
+    doc = subst_braces(doc).strip('\n')
+    return EXAMPLE_FMT(code=doc)
+
+
+def texi_format(doc):
+    """
+    Format documentation
+
+    Lines starting with:
+    - |: generates an @example
+    - =: generates @section
+    - ==: generates @subsection
+    - 1. or 1): generates an @enumerate @item
+    - */-: 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 == ""
+
+        # FIXME: Doing this in a single if / elif chain is
+        # problematic.  For instance, a line without markup terminates
+        # a list if it follows a blank line (reaches the final elif),
+        # but a line with some *other* markup, such as a = title
+        # doesn't.
+        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("^[*-] ", line):
+            if not inlist:
+                lines.append("@itemize %s" % {'*': "@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_body(doc):
+    """
+    Format the body of a symbol documentation:
+    - main body
+    - table of arguments
+    - followed by "Returns/Notes/Since/Example" sections
+    """
+    body = texi_format(str(doc.body)) + "\n"
+    if doc.args:
+        body += "@table @asis\n"
+        for arg, section in doc.args.iteritems():
+            desc = str(section)
+            opt = ''
+            if "#optional" in desc:
+                desc = desc.replace("#optional", "")
+                opt = ' (optional)'
+            body += "@item @code{'%s'}%s\n%s\n" % (arg, opt,
+                                                   texi_format(desc))
+        body += "@end table\n"
+
+    for section in doc.sections:
+        name, doc = (section.name, str(section))
+        func = texi_format
+        if name.startswith("Example"):
+            func = texi_example
+
+        if name:
+            body += "\n@quotation %s\n%s\n@end quotation" % \
+                    (name, func(doc))
+        else:
+            body += func(doc)
+
+    return body
+
+
+def texi_alternate(expr, doc):
+    """Format an alternate to texi"""
+    body = texi_body(doc)
+    return STRUCT_FMT(type="Alternate",
+                      name=doc.symbol,
+                      body=body)
+
+
+def texi_union(expr, doc):
+    """Format a union to texi"""
+    discriminator = expr.get("discriminator")
+    if discriminator:
+        union = "Flat Union"
+    else:
+        union = "Simple Union"
+
+    body = texi_body(doc)
+    return STRUCT_FMT(type=union,
+                      name=doc.symbol,
+                      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"""
+    body = texi_body(doc)
+    return STRUCT_FMT(type="Struct",
+                      name=doc.symbol,
+                      body=body)
+
+
+def texi_command(expr, doc):
+    """Format a command to texi"""
+    body = texi_body(doc)
+    return COMMAND_FMT(type="Command",
+                       name=doc.symbol,
+                       body=body)
+
+
+def texi_event(expr, doc):
+    """Format an event to texi"""
+    body = texi_body(doc)
+    return COMMAND_FMT(type="Event",
+                       name=doc.symbol,
+                       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}[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])
+    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 2841c5144a..b29f996fdc 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -44,40 +44,148 @@ Input must be ASCII (although QMP supports full Unicode strings, the
 QAPI parser does not).  At present, there is no place where a QAPI
 schema requires the use of JSON numbers or null.
 
+
+=== Comments ===
+
 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
-x.y.z)' comment.  For example:
-
-    ##
-    # @BlockStats:
-    #
-    # Statistics of a virtual block device or a block backing device.
-    #
-    # @device: #optional If the stats are for a virtual block device, the name
-    #          corresponding to the virtual block device.
-    #
-    # @stats:  A @BlockDeviceStats for the device.
-    #
-    # @parent: #optional This describes the file block device if it has one.
-    #
-    # @backing: #optional This describes the backing block device if it has one.
-    #           (Since 2.0)
-    #
-    # Since: 0.14.0
-    ##
-    { 'struct': 'BlockStats',
-      'data': {'*device': 'str', 'stats': 'BlockDeviceStats',
-               '*parent': 'BlockStats',
-               '*backing': 'BlockStats'} }
+newline is ignored.
+
+A multi-line comment that starts and ends with a '##' line is a
+documentation comment.  These are parsed by the documentation
+generator, which recognizes certain markup detailed below.
+
+
+==== Documentation markup ====
+
+Comment text starting with '=' is a section title:
+
+    # = Section title
+
+Double the '=' for a subsection title:
+
+    # == Subection title
+
+'|' denotes examples:
+
+    # | Text of the example, may span
+    # | multiple lines
+
+'*' starts an itemized list:
+
+    # * First item, may span
+    #   multiple lines
+    # * Second item
+
+You can also use '-' instead of '*'.
+
+A decimal number followed by '.' starts a numbered list:
+
+    # 1. First item, may span
+    # multiple lines
+    # 2. Second item
+
+The actual number doesn't matter.  You could even use '*' instead of
+'2.' for the second item.
+
+FIXME what exactly ends a list
+
+Additional whitespace between the initial '#' and the comment text is
+permitted.
+
+*foo* and _foo_ are for strong and emphasis styles respectively (they
+do not work over multiple lines). @foo is used to reference a name in
+the schema.
+
+Example:
+
+##
+# = 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
+#
+##
+
+
+==== Expression documentation ====
+
+Each expression that isn't an include directive must be preceded by a
+documentation block.  Such blocks are called expression documentation
+blocks.
+
+The first line of the documentation names the expression, then the
+documentation body is provided, then individual documentation about
+each member of 'data' is provided. Finally, several tagged sections
+can be added.
+
+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.
+
+A tagged section starts with one of the following words:
+"Note:"/"Notes:", "Since:", "Example"/"Examples", "Returns:", "TODO:".
+
+A 'Since: x.y.z' tag lists the release that introduced the expression.
+
+For example:
+
+##
+# @BlockStats:
+#
+# Statistics of a virtual block device or a block backing device.
+#
+# @device: #optional If the stats are for a virtual block device, the name
+#          corresponding to the virtual block device.
+#
+# @node-name: #optional The node name of the device. (since 2.3)
+#
+# ... more members ...
+#
+# Since: 0.14.0
+##
+{ 'struct': 'BlockStats',
+  'data': {'*device': 'str', '*node-name': 'str',
+           ... more members ... } }
+
+##
+# @query-blockstats:
+#
+# Query the @BlockStats for all virtual block devices.
+#
+# @query-nodes: #optional If true, the command will query all the
+#               block nodes ... explain, explain ...  (since 2.3)
+#
+# Returns: A list of @BlockStats for each virtual block devices.
+#
+# Since: 0.14.0
+#
+# Example:
+#
+# -> { "execute": "query-blockstats" }
+# <- {
+#      ... lots of output ...
+#    }
+#
+##
+{ 'command': 'query-blockstats',
+  'data': { '*query-nodes': 'bool' },
+  'returns': ['BlockStats'] }
+
+==== Free-form documentation ====
+
+A documentation block that isn't an expression documentation block is
+a free-form documentation block.  These may be used to provide
+additional text and structuring content.
+
+
+=== Schema overview ===
 
 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
diff --git a/tests/Makefile.include b/tests/Makefile.include
index e98d3b6bb3..05c0b430f6 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -350,6 +350,24 @@ 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-interleaved-section.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-colon.json
+qapi-schema += doc-missing-expr.json
+qapi-schema += doc-missing-space.json
+qapi-schema += doc-optional.json
 qapi-schema += double-data.json
 qapi-schema += double-type.json
 qapi-schema += duplicate-key.json
@@ -443,6 +461,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/tests/qapi-schema/alternate-any.err b/tests/qapi-schema/alternate-any.err
index aaa0154731..395c8ab583 100644
--- a/tests/qapi-schema/alternate-any.err
+++ b/tests/qapi-schema/alternate-any.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-any.json:2: Alternate 'Alt' member 'one' cannot use type 'any'
+tests/qapi-schema/alternate-any.json:6: Alternate 'Alt' member 'one' cannot use type 'any'
diff --git a/tests/qapi-schema/alternate-any.json b/tests/qapi-schema/alternate-any.json
index e47a73a116..c958776767 100644
--- a/tests/qapi-schema/alternate-any.json
+++ b/tests/qapi-schema/alternate-any.json
@@ -1,4 +1,8 @@
 # we do not allow the 'any' type as an alternate branch
+
+##
+# @Alt:
+##
 { 'alternate': 'Alt',
   'data': { 'one': 'any',
             'two': 'int' } }
diff --git a/tests/qapi-schema/alternate-array.err b/tests/qapi-schema/alternate-array.err
index 7b930c64ab..09628e9755 100644
--- a/tests/qapi-schema/alternate-array.err
+++ b/tests/qapi-schema/alternate-array.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-array.json:5: Member 'two' of alternate 'Alt' cannot be an array
+tests/qapi-schema/alternate-array.json:12: Member 'two' of alternate 'Alt' cannot be an array
diff --git a/tests/qapi-schema/alternate-array.json b/tests/qapi-schema/alternate-array.json
index f241aac122..c2f98ad608 100644
--- a/tests/qapi-schema/alternate-array.json
+++ b/tests/qapi-schema/alternate-array.json
@@ -1,7 +1,14 @@
 # we do not allow array branches in alternates
+
+##
+# @One:
+##
 # TODO: should we support this?
 { 'struct': 'One',
   'data': { 'name': 'str' } }
+##
+# @Alt:
+##
 { 'alternate': 'Alt',
   'data': { 'one': 'One',
             'two': [ 'int' ] } }
diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err
index 30d8a34373..3b679140e0 100644
--- a/tests/qapi-schema/alternate-base.err
+++ b/tests/qapi-schema/alternate-base.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-base.json:4: Unknown key 'base' in alternate 'Alt'
+tests/qapi-schema/alternate-base.json:11: Unknown key 'base' in alternate 'Alt'
diff --git a/tests/qapi-schema/alternate-base.json b/tests/qapi-schema/alternate-base.json
index 529430ecf2..9612b7925d 100644
--- a/tests/qapi-schema/alternate-base.json
+++ b/tests/qapi-schema/alternate-base.json
@@ -1,6 +1,13 @@
 # we reject alternate with base type
+
+##
+# @Base:
+##
 { 'struct': 'Base',
   'data': { 'string': 'str' } }
+##
+# @Alt:
+##
 { 'alternate': 'Alt',
   'base': 'Base',
   'data': { 'number': 'int' } }
diff --git a/tests/qapi-schema/alternate-clash.err b/tests/qapi-schema/alternate-clash.err
index 604d8495eb..f07c3e8ad3 100644
--- a/tests/qapi-schema/alternate-clash.err
+++ b/tests/qapi-schema/alternate-clash.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-clash.json:7: 'a_b' (branch of Alt1) collides with 'a-b' (branch of Alt1)
+tests/qapi-schema/alternate-clash.json:11: 'a_b' (branch of Alt1) collides with 'a-b' (branch of Alt1)
diff --git a/tests/qapi-schema/alternate-clash.json b/tests/qapi-schema/alternate-clash.json
index 6d73bc527b..97ca7c80e7 100644
--- a/tests/qapi-schema/alternate-clash.json
+++ b/tests/qapi-schema/alternate-clash.json
@@ -4,5 +4,9 @@
 # TODO: In the future, if alternates are simplified to not generate
 # the implicit Alt1Kind enum, we would still have a collision with the
 # resulting C union trying to have two members named 'a_b'.
+
+##
+# @Alt1:
+##
 { 'alternate': 'Alt1',
   'data': { 'a-b': 'str', 'a_b': 'int' } }
diff --git a/tests/qapi-schema/alternate-conflict-dict.err b/tests/qapi-schema/alternate-conflict-dict.err
index 0f411f4faf..7cb023fdd8 100644
--- a/tests/qapi-schema/alternate-conflict-dict.err
+++ b/tests/qapi-schema/alternate-conflict-dict.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-conflict-dict.json:6: Alternate 'Alt' member 'two' can't be distinguished from member 'one'
+tests/qapi-schema/alternate-conflict-dict.json:16: Alternate 'Alt' member 'two' can't be distinguished from member 'one'
diff --git a/tests/qapi-schema/alternate-conflict-dict.json b/tests/qapi-schema/alternate-conflict-dict.json
index d566cca816..9f9d97fa2e 100644
--- a/tests/qapi-schema/alternate-conflict-dict.json
+++ b/tests/qapi-schema/alternate-conflict-dict.json
@@ -1,8 +1,18 @@
 # we reject alternates with multiple object branches
+
+##
+# @One:
+##
 { 'struct': 'One',
   'data': { 'name': 'str' } }
+##
+# @Two:
+##
 { 'struct': 'Two',
   'data': { 'value': 'int' } }
+##
+# @Alt:
+##
 { 'alternate': 'Alt',
   'data': { 'one': 'One',
             'two': 'Two' } }
diff --git a/tests/qapi-schema/alternate-conflict-string.err b/tests/qapi-schema/alternate-conflict-string.err
index fc523b0879..6dbbacd1d2 100644
--- a/tests/qapi-schema/alternate-conflict-string.err
+++ b/tests/qapi-schema/alternate-conflict-string.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-conflict-string.json:4: Alternate 'Alt' member 'two' can't be distinguished from member 'one'
+tests/qapi-schema/alternate-conflict-string.json:11: Alternate 'Alt' member 'two' can't be distinguished from member 'one'
diff --git a/tests/qapi-schema/alternate-conflict-string.json b/tests/qapi-schema/alternate-conflict-string.json
index 72f04a820a..12aafab808 100644
--- a/tests/qapi-schema/alternate-conflict-string.json
+++ b/tests/qapi-schema/alternate-conflict-string.json
@@ -1,6 +1,13 @@
 # we reject alternates with multiple string-like branches
+
+##
+# @Enum:
+##
 { 'enum': 'Enum',
   'data': [ 'hello', 'world' ] }
+##
+# @Alt:
+##
 { 'alternate': 'Alt',
   'data': { 'one': 'str',
             'two': 'Enum' } }
diff --git a/tests/qapi-schema/alternate-empty.err b/tests/qapi-schema/alternate-empty.err
index bb06c5bfec..8245ce3103 100644
--- a/tests/qapi-schema/alternate-empty.err
+++ b/tests/qapi-schema/alternate-empty.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-empty.json:2: Alternate 'Alt' should have at least two branches in 'data'
+tests/qapi-schema/alternate-empty.json:6: Alternate 'Alt' should have at least two branches in 'data'
diff --git a/tests/qapi-schema/alternate-empty.json b/tests/qapi-schema/alternate-empty.json
index fff15baf16..db54405240 100644
--- a/tests/qapi-schema/alternate-empty.json
+++ b/tests/qapi-schema/alternate-empty.json
@@ -1,2 +1,6 @@
 # alternates must list at least two types to be useful
+
+##
+# @Alt:
+##
 { 'alternate': 'Alt', 'data': { 'i': 'int' } }
diff --git a/tests/qapi-schema/alternate-nested.err b/tests/qapi-schema/alternate-nested.err
index 4d1187e60e..1804ffbf47 100644
--- a/tests/qapi-schema/alternate-nested.err
+++ b/tests/qapi-schema/alternate-nested.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-nested.json:4: Member 'nested' of alternate 'Alt2' cannot use alternate type 'Alt1'
+tests/qapi-schema/alternate-nested.json:11: Member 'nested' of alternate 'Alt2' cannot use alternate type 'Alt1'
diff --git a/tests/qapi-schema/alternate-nested.json b/tests/qapi-schema/alternate-nested.json
index 8e22186491..9f83ebe2e0 100644
--- a/tests/qapi-schema/alternate-nested.json
+++ b/tests/qapi-schema/alternate-nested.json
@@ -1,5 +1,12 @@
 # we reject a nested alternate branch
+
+##
+# @Alt1:
+##
 { 'alternate': 'Alt1',
   'data': { 'name': 'str', 'value': 'int' } }
+##
+# @Alt2:
+##
 { 'alternate': 'Alt2',
   'data': { 'nested': 'Alt1', 'b': 'bool' } }
diff --git a/tests/qapi-schema/alternate-unknown.err b/tests/qapi-schema/alternate-unknown.err
index dea45dc730..cf5b9b6830 100644
--- a/tests/qapi-schema/alternate-unknown.err
+++ b/tests/qapi-schema/alternate-unknown.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-unknown.json:2: Member 'unknown' of alternate 'Alt' uses unknown type 'MissingType'
+tests/qapi-schema/alternate-unknown.json:6: Member 'unknown' of alternate 'Alt' uses unknown type 'MissingType'
diff --git a/tests/qapi-schema/alternate-unknown.json b/tests/qapi-schema/alternate-unknown.json
index 08c80dced0..941ba1fac4 100644
--- a/tests/qapi-schema/alternate-unknown.json
+++ b/tests/qapi-schema/alternate-unknown.json
@@ -1,3 +1,7 @@
 # we reject an alternate with unknown type in branch
+
+##
+# @Alt:
+##
 { 'alternate': 'Alt',
   'data': { 'unknown': 'MissingType', 'i': 'int' } }
diff --git a/tests/qapi-schema/args-alternate.err b/tests/qapi-schema/args-alternate.err
index 3086eae56b..2e6bf54245 100644
--- a/tests/qapi-schema/args-alternate.err
+++ b/tests/qapi-schema/args-alternate.err
@@ -1 +1 @@
-tests/qapi-schema/args-alternate.json:3: 'data' for command 'oops' cannot use alternate type 'Alt'
+tests/qapi-schema/args-alternate.json:11: 'data' for command 'oops' cannot use alternate type 'Alt'
diff --git a/tests/qapi-schema/args-alternate.json b/tests/qapi-schema/args-alternate.json
index 69e94d4819..49d0211a03 100644
--- a/tests/qapi-schema/args-alternate.json
+++ b/tests/qapi-schema/args-alternate.json
@@ -1,3 +1,11 @@
 # we do not allow alternate arguments
+
+##
+# @Alt:
+##
 { 'alternate': 'Alt', 'data': { 'case1': 'int', 'case2': 'str' } }
+
+##
+# @oops:
+##
 { 'command': 'oops', 'data': 'Alt' }
diff --git a/tests/qapi-schema/args-any.err b/tests/qapi-schema/args-any.err
index bf9b5e0730..955504b10f 100644
--- a/tests/qapi-schema/args-any.err
+++ b/tests/qapi-schema/args-any.err
@@ -1 +1 @@
-tests/qapi-schema/args-any.json:2: 'data' for command 'oops' cannot use built-in type 'any'
+tests/qapi-schema/args-any.json:6: 'data' for command 'oops' cannot use built-in type 'any'
diff --git a/tests/qapi-schema/args-any.json b/tests/qapi-schema/args-any.json
index 58fe5e470e..f494479cc9 100644
--- a/tests/qapi-schema/args-any.json
+++ b/tests/qapi-schema/args-any.json
@@ -1,2 +1,6 @@
 # we do not allow an 'any' argument
+
+##
+# @oops:
+##
 { 'command': 'oops', 'data': 'any' }
diff --git a/tests/qapi-schema/args-array-empty.err b/tests/qapi-schema/args-array-empty.err
index cb7ed33b3f..e85f7918ab 100644
--- a/tests/qapi-schema/args-array-empty.err
+++ b/tests/qapi-schema/args-array-empty.err
@@ -1 +1 @@
-tests/qapi-schema/args-array-empty.json:2: Member 'empty' of 'data' for command 'oops': array type must contain single type name
+tests/qapi-schema/args-array-empty.json:6: Member 'empty' of 'data' for command 'oops': array type must contain single type name
diff --git a/tests/qapi-schema/args-array-empty.json b/tests/qapi-schema/args-array-empty.json
index 652dcfb24a..78a0b88221 100644
--- a/tests/qapi-schema/args-array-empty.json
+++ b/tests/qapi-schema/args-array-empty.json
@@ -1,2 +1,6 @@
 # we reject an array for data if it does not contain a known type
+
+##
+# @oops:
+##
 { 'command': 'oops', 'data': { 'empty': [ ] } }
diff --git a/tests/qapi-schema/args-array-unknown.err b/tests/qapi-schema/args-array-unknown.err
index cd7a0f98d7..77788de099 100644
--- a/tests/qapi-schema/args-array-unknown.err
+++ b/tests/qapi-schema/args-array-unknown.err
@@ -1 +1 @@
-tests/qapi-schema/args-array-unknown.json:2: Member 'array' of 'data' for command 'oops' uses unknown type 'NoSuchType'
+tests/qapi-schema/args-array-unknown.json:6: Member 'array' of 'data' for command 'oops' uses unknown type 'NoSuchType'
diff --git a/tests/qapi-schema/args-array-unknown.json b/tests/qapi-schema/args-array-unknown.json
index 6f3e883315..f680fc10d3 100644
--- a/tests/qapi-schema/args-array-unknown.json
+++ b/tests/qapi-schema/args-array-unknown.json
@@ -1,2 +1,6 @@
 # we reject an array for data if it does not contain a known type
+
+##
+# @oops:
+##
 { 'command': 'oops', 'data': { 'array': [ 'NoSuchType' ] } }
diff --git a/tests/qapi-schema/args-bad-boxed.err b/tests/qapi-schema/args-bad-boxed.err
index ad0d417321..87a906137a 100644
--- a/tests/qapi-schema/args-bad-boxed.err
+++ b/tests/qapi-schema/args-bad-boxed.err
@@ -1 +1 @@
-tests/qapi-schema/args-bad-boxed.json:2: 'boxed' of command 'foo' should only use true value
+tests/qapi-schema/args-bad-boxed.json:6: 'boxed' of command 'foo' should only use true value
diff --git a/tests/qapi-schema/args-bad-boxed.json b/tests/qapi-schema/args-bad-boxed.json
index dea0cd0aa5..4c0b28f291 100644
--- a/tests/qapi-schema/args-bad-boxed.json
+++ b/tests/qapi-schema/args-bad-boxed.json
@@ -1,2 +1,6 @@
 # 'boxed' should only appear with value true
+
+##
+# @foo:
+##
 { 'command': 'foo', 'boxed': false }
diff --git a/tests/qapi-schema/args-boxed-anon.err b/tests/qapi-schema/args-boxed-anon.err
index f24f345218..3cfac0b923 100644
--- a/tests/qapi-schema/args-boxed-anon.err
+++ b/tests/qapi-schema/args-boxed-anon.err
@@ -1 +1 @@
-tests/qapi-schema/args-boxed-anon.json:2: 'data' for command 'foo' should be a type name
+tests/qapi-schema/args-boxed-anon.json:6: 'data' for command 'foo' should be a type name
diff --git a/tests/qapi-schema/args-boxed-anon.json b/tests/qapi-schema/args-boxed-anon.json
index 95f60da2ed..2358e6abb1 100644
--- a/tests/qapi-schema/args-boxed-anon.json
+++ b/tests/qapi-schema/args-boxed-anon.json
@@ -1,2 +1,6 @@
 # 'boxed' can only be used with named types
+
+##
+# @foo:
+##
 { 'command': 'foo', 'boxed': true, 'data': { 'string': 'str' } }
diff --git a/tests/qapi-schema/args-boxed-empty.err b/tests/qapi-schema/args-boxed-empty.err
index 039603e85c..963f495a9d 100644
--- a/tests/qapi-schema/args-boxed-empty.err
+++ b/tests/qapi-schema/args-boxed-empty.err
@@ -1 +1 @@
-tests/qapi-schema/args-boxed-empty.json:3: Cannot use 'boxed' with empty type
+tests/qapi-schema/args-boxed-empty.json:11: Cannot use 'boxed' with empty type
diff --git a/tests/qapi-schema/args-boxed-empty.json b/tests/qapi-schema/args-boxed-empty.json
index 52717e065f..8e8cc26525 100644
--- a/tests/qapi-schema/args-boxed-empty.json
+++ b/tests/qapi-schema/args-boxed-empty.json
@@ -1,3 +1,11 @@
 # 'boxed' requires a non-empty type
+
+##
+# @Empty:
+##
 { 'struct': 'Empty', 'data': {} }
+
+##
+# @foo:
+##
 { 'command': 'foo', 'boxed': true, 'data': 'Empty' }
diff --git a/tests/qapi-schema/args-boxed-string.err b/tests/qapi-schema/args-boxed-string.err
index d326b48aef..7623755208 100644
--- a/tests/qapi-schema/args-boxed-string.err
+++ b/tests/qapi-schema/args-boxed-string.err
@@ -1 +1 @@
-tests/qapi-schema/args-boxed-string.json:2: 'data' for command 'foo' cannot use built-in type 'str'
+tests/qapi-schema/args-boxed-string.json:6: 'data' for command 'foo' cannot use built-in type 'str'
diff --git a/tests/qapi-schema/args-boxed-string.json b/tests/qapi-schema/args-boxed-string.json
index f91a1502e7..aecdf97ce9 100644
--- a/tests/qapi-schema/args-boxed-string.json
+++ b/tests/qapi-schema/args-boxed-string.json
@@ -1,2 +1,6 @@
 # 'boxed' requires a complex (not built-in) type
+
+##
+# @foo:
+##
 { 'command': 'foo', 'boxed': true, 'data': 'str' }
diff --git a/tests/qapi-schema/args-int.err b/tests/qapi-schema/args-int.err
index dc1d2504ff..38b3202b09 100644
--- a/tests/qapi-schema/args-int.err
+++ b/tests/qapi-schema/args-int.err
@@ -1 +1 @@
-tests/qapi-schema/args-int.json:2: 'data' for command 'oops' cannot use built-in type 'int'
+tests/qapi-schema/args-int.json:6: 'data' for command 'oops' cannot use built-in type 'int'
diff --git a/tests/qapi-schema/args-int.json b/tests/qapi-schema/args-int.json
index a334d92e8c..7f4e1b7aa6 100644
--- a/tests/qapi-schema/args-int.json
+++ b/tests/qapi-schema/args-int.json
@@ -1,2 +1,6 @@
 # we reject commands where data is not an array or complex type
+
+##
+# @oops:
+##
 { 'command': 'oops', 'data': 'int' }
diff --git a/tests/qapi-schema/args-invalid.err b/tests/qapi-schema/args-invalid.err
index fe1e94975b..5d3568d7c3 100644
--- a/tests/qapi-schema/args-invalid.err
+++ b/tests/qapi-schema/args-invalid.err
@@ -1 +1 @@
-tests/qapi-schema/args-invalid.json:1: 'data' for command 'foo' should be a dictionary or type name
+tests/qapi-schema/args-invalid.json:4: 'data' for command 'foo' should be a dictionary or type name
diff --git a/tests/qapi-schema/args-invalid.json b/tests/qapi-schema/args-invalid.json
index db0981341b..1a7e63bb23 100644
--- a/tests/qapi-schema/args-invalid.json
+++ b/tests/qapi-schema/args-invalid.json
@@ -1,2 +1,5 @@
+##
+# @foo:
+##
 { 'command': 'foo',
   'data': false }
diff --git a/tests/qapi-schema/args-member-array-bad.err b/tests/qapi-schema/args-member-array-bad.err
index 881b4d954f..825ffca9bf 100644
--- a/tests/qapi-schema/args-member-array-bad.err
+++ b/tests/qapi-schema/args-member-array-bad.err
@@ -1 +1 @@
-tests/qapi-schema/args-member-array-bad.json:2: Member 'member' of 'data' for command 'oops': array type must contain single type name
+tests/qapi-schema/args-member-array-bad.json:6: Member 'member' of 'data' for command 'oops': array type must contain single type name
diff --git a/tests/qapi-schema/args-member-array-bad.json b/tests/qapi-schema/args-member-array-bad.json
index b2ff144ec6..e934f5c457 100644
--- a/tests/qapi-schema/args-member-array-bad.json
+++ b/tests/qapi-schema/args-member-array-bad.json
@@ -1,2 +1,6 @@
 # we reject data if it does not contain a valid array type
+
+##
+# @oops:
+##
 { 'command': 'oops', 'data': { 'member': [ { 'nested': 'str' } ] } }
diff --git a/tests/qapi-schema/args-member-case.err b/tests/qapi-schema/args-member-case.err
index 19c4426601..a3fb2bdd60 100644
--- a/tests/qapi-schema/args-member-case.err
+++ b/tests/qapi-schema/args-member-case.err
@@ -1 +1 @@
-tests/qapi-schema/args-member-case.json:2: 'Arg' (parameter of no-way-this-will-get-whitelisted) should not use uppercase
+tests/qapi-schema/args-member-case.json:6: 'Arg' (parameter of no-way-this-will-get-whitelisted) should not use uppercase
diff --git a/tests/qapi-schema/args-member-case.json b/tests/qapi-schema/args-member-case.json
index 93439bee8b..811e658d66 100644
--- a/tests/qapi-schema/args-member-case.json
+++ b/tests/qapi-schema/args-member-case.json
@@ -1,2 +1,6 @@
 # Member names should be 'lower-case' unless the struct/command is whitelisted
+
+##
+# @no-way-this-will-get-whitelisted:
+##
 { 'command': 'no-way-this-will-get-whitelisted', 'data': { 'Arg': 'int' } }
diff --git a/tests/qapi-schema/args-member-unknown.err b/tests/qapi-schema/args-member-unknown.err
index f6f82828ce..3db452b95a 100644
--- a/tests/qapi-schema/args-member-unknown.err
+++ b/tests/qapi-schema/args-member-unknown.err
@@ -1 +1 @@
-tests/qapi-schema/args-member-unknown.json:2: Member 'member' of 'data' for command 'oops' uses unknown type 'NoSuchType'
+tests/qapi-schema/args-member-unknown.json:6: Member 'member' of 'data' for command 'oops' uses unknown type 'NoSuchType'
diff --git a/tests/qapi-schema/args-member-unknown.json b/tests/qapi-schema/args-member-unknown.json
index 342a41ec90..e2fef9c46f 100644
--- a/tests/qapi-schema/args-member-unknown.json
+++ b/tests/qapi-schema/args-member-unknown.json
@@ -1,2 +1,6 @@
 # we reject data if it does not contain a known type
+
+##
+# @oops:
+##
 { 'command': 'oops', 'data': { 'member': 'NoSuchType' } }
diff --git a/tests/qapi-schema/args-name-clash.err b/tests/qapi-schema/args-name-clash.err
index d953e8d241..23988cb5ca 100644
--- a/tests/qapi-schema/args-name-clash.err
+++ b/tests/qapi-schema/args-name-clash.err
@@ -1 +1 @@
-tests/qapi-schema/args-name-clash.json:4: 'a_b' (parameter of oops) collides with 'a-b' (parameter of oops)
+tests/qapi-schema/args-name-clash.json:8: 'a_b' (parameter of oops) collides with 'a-b' (parameter of oops)
diff --git a/tests/qapi-schema/args-name-clash.json b/tests/qapi-schema/args-name-clash.json
index 61423cb893..991323b78d 100644
--- a/tests/qapi-schema/args-name-clash.json
+++ b/tests/qapi-schema/args-name-clash.json
@@ -1,4 +1,8 @@
 # C member name collision
 # Reject members that clash when mapped to C names (we would have two 'a_b'
 # members).
+
+##
+# @oops:
+##
 { 'command': 'oops', 'data': { 'a-b': 'str', 'a_b': 'str' } }
diff --git a/tests/qapi-schema/args-union.err b/tests/qapi-schema/args-union.err
index f8ad223dde..ce0a34e16c 100644
--- a/tests/qapi-schema/args-union.err
+++ b/tests/qapi-schema/args-union.err
@@ -1 +1 @@
-tests/qapi-schema/args-union.json:3: 'data' for command 'oops' cannot use union type 'Uni'
+tests/qapi-schema/args-union.json:10: 'data' for command 'oops' cannot use union type 'Uni'
diff --git a/tests/qapi-schema/args-union.json b/tests/qapi-schema/args-union.json
index 2fcaeaae16..57284b43c5 100644
--- a/tests/qapi-schema/args-union.json
+++ b/tests/qapi-schema/args-union.json
@@ -1,3 +1,10 @@
 # use of union arguments requires 'boxed':true
+
+##
+# @Uni:
+##
 { 'union': 'Uni', 'data': { 'case1': 'int', 'case2': 'str' } }
+##
+# oops:
+##
 { 'command': 'oops', 'data': 'Uni' }
diff --git a/tests/qapi-schema/args-unknown.err b/tests/qapi-schema/args-unknown.err
index 4d91ec869f..ba6c6cf326 100644
--- a/tests/qapi-schema/args-unknown.err
+++ b/tests/qapi-schema/args-unknown.err
@@ -1 +1 @@
-tests/qapi-schema/args-unknown.json:2: 'data' for command 'oops' uses unknown type 'NoSuchType'
+tests/qapi-schema/args-unknown.json:6: 'data' for command 'oops' uses unknown type 'NoSuchType'
diff --git a/tests/qapi-schema/args-unknown.json b/tests/qapi-schema/args-unknown.json
index 32aba43b3f..12666dc020 100644
--- a/tests/qapi-schema/args-unknown.json
+++ b/tests/qapi-schema/args-unknown.json
@@ -1,2 +1,6 @@
 # we reject data if it does not contain a known type
+
+##
+# @oops:
+##
 { 'command': 'oops', 'data': 'NoSuchType' }
diff --git a/tests/qapi-schema/bad-base.err b/tests/qapi-schema/bad-base.err
index 154274bdd3..e668761c65 100644
--- a/tests/qapi-schema/bad-base.err
+++ b/tests/qapi-schema/bad-base.err
@@ -1 +1 @@
-tests/qapi-schema/bad-base.json:3: 'base' for struct 'MyType' cannot use union type 'Union'
+tests/qapi-schema/bad-base.json:10: 'base' for struct 'MyType' cannot use union type 'Union'
diff --git a/tests/qapi-schema/bad-base.json b/tests/qapi-schema/bad-base.json
index a634331cdd..c3faa8242b 100644
--- a/tests/qapi-schema/bad-base.json
+++ b/tests/qapi-schema/bad-base.json
@@ -1,3 +1,10 @@
 # we reject a base that is not a struct
+
+##
+# @Union:
+##
 { 'union': 'Union', 'data': { 'a': 'int', 'b': 'str' } }
+##
+# @MyType:
+##
 { 'struct': 'MyType', 'base': 'Union', 'data': { 'c': 'int' } }
diff --git a/tests/qapi-schema/bad-data.err b/tests/qapi-schema/bad-data.err
index 8523ac4f46..c1b9e35313 100644
--- a/tests/qapi-schema/bad-data.err
+++ b/tests/qapi-schema/bad-data.err
@@ -1 +1 @@
-tests/qapi-schema/bad-data.json:2: 'data' for command 'oops' cannot be an array
+tests/qapi-schema/bad-data.json:6: 'data' for command 'oops' cannot be an array
diff --git a/tests/qapi-schema/bad-data.json b/tests/qapi-schema/bad-data.json
index 832eeb76f4..51c444f4f8 100644
--- a/tests/qapi-schema/bad-data.json
+++ b/tests/qapi-schema/bad-data.json
@@ -1,2 +1,6 @@
 # we ensure 'data' is a dictionary for all but enums
+
+##
+# @oops:
+##
 { 'command': 'oops', 'data': [ ] }
diff --git a/tests/qapi-schema/bad-ident.err b/tests/qapi-schema/bad-ident.err
index c4190602b5..b757aa21e7 100644
--- a/tests/qapi-schema/bad-ident.err
+++ b/tests/qapi-schema/bad-ident.err
@@ -1 +1 @@
-tests/qapi-schema/bad-ident.json:2: 'struct' does not allow optional name '*oops'
+tests/qapi-schema/bad-ident.json:6: 'struct' does not allow optional name '*oops'
diff --git a/tests/qapi-schema/bad-ident.json b/tests/qapi-schema/bad-ident.json
index 763627ad23..b43df7a3e0 100644
--- a/tests/qapi-schema/bad-ident.json
+++ b/tests/qapi-schema/bad-ident.json
@@ -1,2 +1,6 @@
 # we reject creating a type name with bad name
+
+##
+# @*oops:
+##
 { 'struct': '*oops', 'data': { 'i': 'int' } }
diff --git a/tests/qapi-schema/bad-type-bool.err b/tests/qapi-schema/bad-type-bool.err
index 62fd70baaf..72e026b46c 100644
--- a/tests/qapi-schema/bad-type-bool.err
+++ b/tests/qapi-schema/bad-type-bool.err
@@ -1 +1 @@
-tests/qapi-schema/bad-type-bool.json:2: 'struct' key must have a string value
+tests/qapi-schema/bad-type-bool.json:6: 'struct' key must have a string value
diff --git a/tests/qapi-schema/bad-type-bool.json b/tests/qapi-schema/bad-type-bool.json
index bde17b56c4..1f9eddf938 100644
--- a/tests/qapi-schema/bad-type-bool.json
+++ b/tests/qapi-schema/bad-type-bool.json
@@ -1,2 +1,6 @@
 # we reject an expression with a metatype that is not a string
+
+##
+# @true:
+##
 { 'struct': true, 'data': { } }
diff --git a/tests/qapi-schema/bad-type-dict.err b/tests/qapi-schema/bad-type-dict.err
index 0b2a2aeac4..d0d1f607e5 100644
--- a/tests/qapi-schema/bad-type-dict.err
+++ b/tests/qapi-schema/bad-type-dict.err
@@ -1 +1 @@
-tests/qapi-schema/bad-type-dict.json:2: 'command' key must have a string value
+tests/qapi-schema/bad-type-dict.json:6: 'command' key must have a string value
diff --git a/tests/qapi-schema/bad-type-dict.json b/tests/qapi-schema/bad-type-dict.json
index 2a91b241f8..5952caab28 100644
--- a/tests/qapi-schema/bad-type-dict.json
+++ b/tests/qapi-schema/bad-type-dict.json
@@ -1,2 +1,6 @@
 # we reject an expression with a metatype that is not a string
+
+##
+# @foo:
+##
 { 'command': { } }
diff --git a/tests/qapi-schema/base-cycle-direct.err b/tests/qapi-schema/base-cycle-direct.err
index 9c68f6543d..dd7f5aace6 100644
--- a/tests/qapi-schema/base-cycle-direct.err
+++ b/tests/qapi-schema/base-cycle-direct.err
@@ -1 +1 @@
-tests/qapi-schema/base-cycle-direct.json:2: Object Loopy contains itself
+tests/qapi-schema/base-cycle-direct.json:6: Object Loopy contains itself
diff --git a/tests/qapi-schema/base-cycle-direct.json b/tests/qapi-schema/base-cycle-direct.json
index 4fc66d0516..9780f7e2ca 100644
--- a/tests/qapi-schema/base-cycle-direct.json
+++ b/tests/qapi-schema/base-cycle-direct.json
@@ -1,2 +1,6 @@
 # we reject a loop in base classes
+
+##
+# @Loopy:
+##
 { 'struct': 'Loopy', 'base': 'Loopy', 'data': {} }
diff --git a/tests/qapi-schema/base-cycle-indirect.err b/tests/qapi-schema/base-cycle-indirect.err
index fc92fe47f8..f4198e4a40 100644
--- a/tests/qapi-schema/base-cycle-indirect.err
+++ b/tests/qapi-schema/base-cycle-indirect.err
@@ -1 +1 @@
-tests/qapi-schema/base-cycle-indirect.json:2: Object Base1 contains itself
+tests/qapi-schema/base-cycle-indirect.json:6: Object Base1 contains itself
diff --git a/tests/qapi-schema/base-cycle-indirect.json b/tests/qapi-schema/base-cycle-indirect.json
index 28667721a3..99926c4609 100644
--- a/tests/qapi-schema/base-cycle-indirect.json
+++ b/tests/qapi-schema/base-cycle-indirect.json
@@ -1,3 +1,10 @@
 # we reject a loop in base classes
+
+##
+# @Base1:
+##
 { 'struct': 'Base1', 'base': 'Base2', 'data': {} }
+##
+# @Base2:
+##
 { 'struct': 'Base2', 'base': 'Base1', 'data': {} }
diff --git a/tests/qapi-schema/command-int.err b/tests/qapi-schema/command-int.err
index 0f9300679b..3c834a97ab 100644
--- a/tests/qapi-schema/command-int.err
+++ b/tests/qapi-schema/command-int.err
@@ -1 +1 @@
-tests/qapi-schema/command-int.json:2: built-in 'int' is already defined
+tests/qapi-schema/command-int.json:6: built-in 'int' is already defined
diff --git a/tests/qapi-schema/command-int.json b/tests/qapi-schema/command-int.json
index 9a62554fc6..5b51bf148b 100644
--- a/tests/qapi-schema/command-int.json
+++ b/tests/qapi-schema/command-int.json
@@ -1,2 +1,6 @@
 # we reject collisions between commands and types
+
+##
+# @int:
+##
 { 'command': 'int', 'data': { 'character': 'str' } }
diff --git a/tests/qapi-schema/comments.json b/tests/qapi-schema/comments.json
index e643f3a74c..d31ef0d90a 100644
--- a/tests/qapi-schema/comments.json
+++ b/tests/qapi-schema/comments.json
@@ -1,4 +1,8 @@
 # Unindented comment
+
+##
+# @Status:
+##
 { 'enum': 'Status',             # Comment to the right of code
   # Indented comment
   'data': [ 'good', 'bad', 'ugly' ] }
diff --git a/tests/qapi-schema/comments.out b/tests/qapi-schema/comments.out
index 5d7c13cad1..ab3d74f49f 100644
--- a/tests/qapi-schema/comments.out
+++ b/tests/qapi-schema/comments.out
@@ -2,3 +2,6 @@ enum QType ['none', 'qnull', 'qint', 'qstring', 'qdict', 'qlist', 'qfloat', 'qbo
     prefix QTYPE
 enum Status ['good', 'bad', 'ugly']
 object q_empty
+doc symbol=Status expr=('enum', 'Status')
+    body=
+
diff --git a/tests/qapi-schema/doc-bad-args.err b/tests/qapi-schema/doc-bad-args.err
new file mode 100644
index 0000000000..5d44d9b668
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-args.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-bad-args.json:3: The following documented members are not in the declaration: b
diff --git a/tests/qapi-schema/doc-bad-args.exit b/tests/qapi-schema/doc-bad-args.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /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 0000000000..048e0fc5ef
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-args.json
@@ -0,0 +1,8 @@
+# Arguments listed in the doc comment must exist in the actual schema
+
+##
+# @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 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-bad-symbol.err b/tests/qapi-schema/doc-bad-symbol.err
new file mode 100644
index 0000000000..ac4e5667cb
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-symbol.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-bad-symbol.json:3: 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 0000000000..d00491fd7e
--- /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 0000000000..a7c15b3b8f
--- /dev/null
+++ b/tests/qapi-schema/doc-bad-symbol.json
@@ -0,0 +1,6 @@
+# Documentation symbol mismatch with expression
+
+##
+# @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 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-duplicated-arg.err b/tests/qapi-schema/doc-duplicated-arg.err
new file mode 100644
index 0000000000..1c3f8e0a54
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-arg.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-duplicated-arg.json:6:1: 'a' parameter name duplicated
diff --git a/tests/qapi-schema/doc-duplicated-arg.exit b/tests/qapi-schema/doc-duplicated-arg.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /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 0000000000..035cae9745
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-arg.json
@@ -0,0 +1,7 @@
+# Do not allow duplicated argument
+
+##
+# @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 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-duplicated-return.err b/tests/qapi-schema/doc-duplicated-return.err
new file mode 100644
index 0000000000..e48039f8e5
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-return.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-duplicated-return.json:7: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 0000000000..d00491fd7e
--- /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 0000000000..b44b5ae979
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-return.json
@@ -0,0 +1,8 @@
+# Do not allow duplicated Returns section
+
+##
+# @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 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-duplicated-since.err b/tests/qapi-schema/doc-duplicated-since.err
new file mode 100644
index 0000000000..3fb890744a
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-since.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-duplicated-since.json:7: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 0000000000..d00491fd7e
--- /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 0000000000..343cd872cb
--- /dev/null
+++ b/tests/qapi-schema/doc-duplicated-since.json
@@ -0,0 +1,8 @@
+# Do not allow duplicated Since section
+
+##
+# @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 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-empty-arg.err b/tests/qapi-schema/doc-empty-arg.err
new file mode 100644
index 0000000000..2895518fa7
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-arg.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-empty-arg.json:5:1: Invalid parameter name
diff --git a/tests/qapi-schema/doc-empty-arg.exit b/tests/qapi-schema/doc-empty-arg.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /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 0000000000..8f76ede8f3
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-arg.json
@@ -0,0 +1,6 @@
+# An invalid empty argument name
+
+##
+# @foo:
+# @:
+##
diff --git a/tests/qapi-schema/doc-empty-arg.out b/tests/qapi-schema/doc-empty-arg.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-empty-section.err b/tests/qapi-schema/doc-empty-section.err
new file mode 100644
index 0000000000..00ad625e17
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-section.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-empty-section.json:3: 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 0000000000..d00491fd7e
--- /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 0000000000..f3384e9a3b
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-section.json
@@ -0,0 +1,8 @@
+# Tagged-section must not be empty
+
+##
+# @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 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-empty-symbol.err b/tests/qapi-schema/doc-empty-symbol.err
new file mode 100644
index 0000000000..1936ad094f
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-symbol.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-empty-symbol.json:4:1: Invalid name
diff --git a/tests/qapi-schema/doc-empty-symbol.exit b/tests/qapi-schema/doc-empty-symbol.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /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 0000000000..fb8fddc4ae
--- /dev/null
+++ b/tests/qapi-schema/doc-empty-symbol.json
@@ -0,0 +1,5 @@
+# Invalid documentation symbol
+
+##
+# @:
+##
diff --git a/tests/qapi-schema/doc-empty-symbol.out b/tests/qapi-schema/doc-empty-symbol.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-interleaved-section.err b/tests/qapi-schema/doc-interleaved-section.err
new file mode 100644
index 0000000000..3487b16582
--- /dev/null
+++ b/tests/qapi-schema/doc-interleaved-section.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-interleaved-section.json:15:1: 'foobar' parameter documentation is interleaved with other sections
diff --git a/tests/qapi-schema/doc-interleaved-section.exit b/tests/qapi-schema/doc-interleaved-section.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/doc-interleaved-section.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-interleaved-section.json b/tests/qapi-schema/doc-interleaved-section.json
new file mode 100644
index 0000000000..adb29e98da
--- /dev/null
+++ b/tests/qapi-schema/doc-interleaved-section.json
@@ -0,0 +1,21 @@
+# Arguments and sections must not be interleaved
+
+##
+# @TestStruct:
+#
+# body
+#
+# @integer: foo
+#           blah
+#
+#           bao
+#
+# Note: a section.
+#
+# @foobar: catch this
+#
+# Since: 2.3
+#
+##
+{ 'struct': 'TestStruct',
+  'data': { 'integer': 'int', 'foobar': 'int' } }
diff --git a/tests/qapi-schema/doc-interleaved-section.out b/tests/qapi-schema/doc-interleaved-section.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-invalid-end.err b/tests/qapi-schema/doc-invalid-end.err
new file mode 100644
index 0000000000..2bda28cb54
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-end.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-invalid-end.json:5: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 0000000000..d00491fd7e
--- /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 0000000000..3583b23b18
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-end.json
@@ -0,0 +1,5 @@
+# Documentation must end with '##'
+
+##
+# 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 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-invalid-end2.err b/tests/qapi-schema/doc-invalid-end2.err
new file mode 100644
index 0000000000..6fad9c789e
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-end2.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-invalid-end2.json:5: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 0000000000..d00491fd7e
--- /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 0000000000..fa2d39d7c2
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-end2.json
@@ -0,0 +1,5 @@
+# Documentation must end with '##'
+
+##
+#
+## invalid
diff --git a/tests/qapi-schema/doc-invalid-end2.out b/tests/qapi-schema/doc-invalid-end2.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-invalid-return.err b/tests/qapi-schema/doc-invalid-return.err
new file mode 100644
index 0000000000..5aaba33bb4
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-return.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-invalid-return.json:3: 'Returns:' is only valid for commands
diff --git a/tests/qapi-schema/doc-invalid-return.exit b/tests/qapi-schema/doc-invalid-return.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /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 0000000000..1ba45de414
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-return.json
@@ -0,0 +1,7 @@
+# Events can't have 'Returns' section
+
+##
+# @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 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-invalid-section.err b/tests/qapi-schema/doc-invalid-section.err
new file mode 100644
index 0000000000..85bb67b829
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-section.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-invalid-section.json:3: Free-form documentation block must not 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 0000000000..d00491fd7e
--- /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 0000000000..0578b8ae25
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-section.json
@@ -0,0 +1,6 @@
+# Free-form documentation doesn't have tagged-sections
+
+##
+# 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 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-invalid-start.err b/tests/qapi-schema/doc-invalid-start.err
new file mode 100644
index 0000000000..149af2bfac
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-start.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-invalid-start.json:3: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 0000000000..d00491fd7e
--- /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 0000000000..4f6c15a38c
--- /dev/null
+++ b/tests/qapi-schema/doc-invalid-start.json
@@ -0,0 +1,5 @@
+# Documentation must start with '##'
+
+## invalid
+#
+##
diff --git a/tests/qapi-schema/doc-invalid-start.out b/tests/qapi-schema/doc-invalid-start.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-missing-colon.err b/tests/qapi-schema/doc-missing-colon.err
new file mode 100644
index 0000000000..817398b8e4
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-colon.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-missing-colon.json:4:1: Line should end with :
diff --git a/tests/qapi-schema/doc-missing-colon.exit b/tests/qapi-schema/doc-missing-colon.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-colon.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-missing-colon.json b/tests/qapi-schema/doc-missing-colon.json
new file mode 100644
index 0000000000..d88c06c6dd
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-colon.json
@@ -0,0 +1,5 @@
+# The symbol section must end with ':'
+
+##
+# @missing-colon
+##
diff --git a/tests/qapi-schema/doc-missing-colon.out b/tests/qapi-schema/doc-missing-colon.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-missing-expr.err b/tests/qapi-schema/doc-missing-expr.err
new file mode 100644
index 0000000000..c0e687cadd
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-expr.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-missing-expr.json:3: Documention for 'bar' 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 0000000000..d00491fd7e
--- /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 0000000000..06ad7df8d6
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-expr.json
@@ -0,0 +1,5 @@
+# Expression documentation must be followed by the actual expression
+
+##
+# @bar:
+##
diff --git a/tests/qapi-schema/doc-missing-expr.out b/tests/qapi-schema/doc-missing-expr.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-missing-space.err b/tests/qapi-schema/doc-missing-space.err
new file mode 100644
index 0000000000..d6b46ffd77
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-space.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-missing-space.json:5: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 0000000000..d00491fd7e
--- /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 0000000000..beb276bc64
--- /dev/null
+++ b/tests/qapi-schema/doc-missing-space.json
@@ -0,0 +1,6 @@
+# Documentation line must have a leading space
+
+##
+# 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 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/doc-optional.err b/tests/qapi-schema/doc-optional.err
new file mode 100644
index 0000000000..20d405af79
--- /dev/null
+++ b/tests/qapi-schema/doc-optional.err
@@ -0,0 +1 @@
+tests/qapi-schema/doc-optional.json:3: Description has #optional, but the declaration doesn't
diff --git a/tests/qapi-schema/doc-optional.exit b/tests/qapi-schema/doc-optional.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/doc-optional.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/doc-optional.json b/tests/qapi-schema/doc-optional.json
new file mode 100644
index 0000000000..06c855ec94
--- /dev/null
+++ b/tests/qapi-schema/doc-optional.json
@@ -0,0 +1,7 @@
+# Description #optional should match declaration
+
+##
+# @foo:
+# @a: a #optional
+##
+{ 'command': 'foo', 'data': {'a': 'int'} }
diff --git a/tests/qapi-schema/doc-optional.out b/tests/qapi-schema/doc-optional.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/double-type.err b/tests/qapi-schema/double-type.err
index f9613c6d6b..424df9bedd 100644
--- a/tests/qapi-schema/double-type.err
+++ b/tests/qapi-schema/double-type.err
@@ -1 +1 @@
-tests/qapi-schema/double-type.json:2: Unknown key 'command' in struct 'bar'
+tests/qapi-schema/double-type.json:6: Unknown key 'command' in struct 'bar'
diff --git a/tests/qapi-schema/double-type.json b/tests/qapi-schema/double-type.json
index 911fa7af50..ab59523ff7 100644
--- a/tests/qapi-schema/double-type.json
+++ b/tests/qapi-schema/double-type.json
@@ -1,2 +1,6 @@
 # we reject an expression with ambiguous metatype
+
+##
+# @foo:
+##
 { 'command': 'foo', 'struct': 'bar', 'data': { } }
diff --git a/tests/qapi-schema/enum-bad-name.err b/tests/qapi-schema/enum-bad-name.err
index 9c3c1002b7..157d1b0d69 100644
--- a/tests/qapi-schema/enum-bad-name.err
+++ b/tests/qapi-schema/enum-bad-name.err
@@ -1 +1 @@
-tests/qapi-schema/enum-bad-name.json:2: Member of enum 'MyEnum' uses invalid name 'not^possible'
+tests/qapi-schema/enum-bad-name.json:6: Member of enum 'MyEnum' uses invalid name 'not^possible'
diff --git a/tests/qapi-schema/enum-bad-name.json b/tests/qapi-schema/enum-bad-name.json
index 8506562b31..978cb88994 100644
--- a/tests/qapi-schema/enum-bad-name.json
+++ b/tests/qapi-schema/enum-bad-name.json
@@ -1,2 +1,6 @@
 # we ensure all enum names can map to C
+
+##
+# @MyEnum:
+##
 { 'enum': 'MyEnum', 'data': [ 'not^possible' ] }
diff --git a/tests/qapi-schema/enum-bad-prefix.err b/tests/qapi-schema/enum-bad-prefix.err
index 399f5f7af5..918915f7ab 100644
--- a/tests/qapi-schema/enum-bad-prefix.err
+++ b/tests/qapi-schema/enum-bad-prefix.err
@@ -1 +1 @@
-tests/qapi-schema/enum-bad-prefix.json:2: Enum 'MyEnum' requires a string for 'prefix'
+tests/qapi-schema/enum-bad-prefix.json:6: Enum 'MyEnum' requires a string for 'prefix'
diff --git a/tests/qapi-schema/enum-bad-prefix.json b/tests/qapi-schema/enum-bad-prefix.json
index 996f628f6d..25f17a7b08 100644
--- a/tests/qapi-schema/enum-bad-prefix.json
+++ b/tests/qapi-schema/enum-bad-prefix.json
@@ -1,2 +1,6 @@
 # The prefix must be a string type
+
+##
+# @MyEnum:
+##
 { 'enum': 'MyEnum', 'data': [ 'one' ], 'prefix': [ 'fish' ] }
diff --git a/tests/qapi-schema/enum-clash-member.err b/tests/qapi-schema/enum-clash-member.err
index 5403c78507..25249b63c4 100644
--- a/tests/qapi-schema/enum-clash-member.err
+++ b/tests/qapi-schema/enum-clash-member.err
@@ -1 +1 @@
-tests/qapi-schema/enum-clash-member.json:2: 'one_two' (member of MyEnum) collides with 'one-two' (member of MyEnum)
+tests/qapi-schema/enum-clash-member.json:6: 'one_two' (member of MyEnum) collides with 'one-two' (member of MyEnum)
diff --git a/tests/qapi-schema/enum-clash-member.json b/tests/qapi-schema/enum-clash-member.json
index b6928b8bfd..fd52751941 100644
--- a/tests/qapi-schema/enum-clash-member.json
+++ b/tests/qapi-schema/enum-clash-member.json
@@ -1,2 +1,6 @@
 # we reject enums where members will clash when mapped to C enum
+
+##
+# @MyEnum:
+##
 { 'enum': 'MyEnum', 'data': [ 'one-two', 'one_two' ] }
diff --git a/tests/qapi-schema/enum-dict-member.err b/tests/qapi-schema/enum-dict-member.err
index 8ca146ea59..9b7d2f111d 100644
--- a/tests/qapi-schema/enum-dict-member.err
+++ b/tests/qapi-schema/enum-dict-member.err
@@ -1 +1 @@
-tests/qapi-schema/enum-dict-member.json:2: Member of enum 'MyEnum' requires a string name
+tests/qapi-schema/enum-dict-member.json:6: Member of enum 'MyEnum' requires a string name
diff --git a/tests/qapi-schema/enum-dict-member.json b/tests/qapi-schema/enum-dict-member.json
index 79672e0f09..69d30f0c1e 100644
--- a/tests/qapi-schema/enum-dict-member.json
+++ b/tests/qapi-schema/enum-dict-member.json
@@ -1,2 +1,6 @@
 # we reject any enum member that is not a string
+
+##
+# @MyEnum:
+##
 { 'enum': 'MyEnum', 'data': [ { 'value': 'str' } ] }
diff --git a/tests/qapi-schema/enum-member-case.err b/tests/qapi-schema/enum-member-case.err
index b652e9aacc..df96e2205a 100644
--- a/tests/qapi-schema/enum-member-case.err
+++ b/tests/qapi-schema/enum-member-case.err
@@ -1 +1 @@
-tests/qapi-schema/enum-member-case.json:3: 'Value' (member of NoWayThisWillGetWhitelisted) should not use uppercase
+tests/qapi-schema/enum-member-case.json:10: 'Value' (member of NoWayThisWillGetWhitelisted) should not use uppercase
diff --git a/tests/qapi-schema/enum-member-case.json b/tests/qapi-schema/enum-member-case.json
index 2096b350ca..d2e4aba39d 100644
--- a/tests/qapi-schema/enum-member-case.json
+++ b/tests/qapi-schema/enum-member-case.json
@@ -1,3 +1,10 @@
 # Member names should be 'lower-case' unless the enum is whitelisted
+
+##
+# @UuidInfo:
+##
 { 'enum': 'UuidInfo', 'data': [ 'Value' ] } # UuidInfo is whitelisted
+##
+# @NoWayThisWillGetWhitelisted:
+##
 { 'enum': 'NoWayThisWillGetWhitelisted', 'data': [ 'Value' ] }
diff --git a/tests/qapi-schema/enum-missing-data.err b/tests/qapi-schema/enum-missing-data.err
index ba4873ae69..de4b9e8281 100644
--- a/tests/qapi-schema/enum-missing-data.err
+++ b/tests/qapi-schema/enum-missing-data.err
@@ -1 +1 @@
-tests/qapi-schema/enum-missing-data.json:2: Key 'data' is missing from enum 'MyEnum'
+tests/qapi-schema/enum-missing-data.json:6: Key 'data' is missing from enum 'MyEnum'
diff --git a/tests/qapi-schema/enum-missing-data.json b/tests/qapi-schema/enum-missing-data.json
index 558fd35e93..d7601f91fb 100644
--- a/tests/qapi-schema/enum-missing-data.json
+++ b/tests/qapi-schema/enum-missing-data.json
@@ -1,2 +1,6 @@
 # we require that all QAPI enums have a data array
+
+##
+# @MyEnum:
+##
 { 'enum': 'MyEnum' }
diff --git a/tests/qapi-schema/enum-wrong-data.err b/tests/qapi-schema/enum-wrong-data.err
index 11b43471cf..c44e9b59dc 100644
--- a/tests/qapi-schema/enum-wrong-data.err
+++ b/tests/qapi-schema/enum-wrong-data.err
@@ -1 +1 @@
-tests/qapi-schema/enum-wrong-data.json:2: Enum 'MyEnum' requires an array for 'data'
+tests/qapi-schema/enum-wrong-data.json:6: Enum 'MyEnum' requires an array for 'data'
diff --git a/tests/qapi-schema/enum-wrong-data.json b/tests/qapi-schema/enum-wrong-data.json
index 7b3e255c14..4b9e97878b 100644
--- a/tests/qapi-schema/enum-wrong-data.json
+++ b/tests/qapi-schema/enum-wrong-data.json
@@ -1,2 +1,6 @@
 # we require that all qapi enums have an array for data
+
+##
+# @MyEnum:
+##
 { 'enum': 'MyEnum', 'data': { 'value': 'str' } }
diff --git a/tests/qapi-schema/event-boxed-empty.err b/tests/qapi-schema/event-boxed-empty.err
index 68ec6f2d2b..defe656e32 100644
--- a/tests/qapi-schema/event-boxed-empty.err
+++ b/tests/qapi-schema/event-boxed-empty.err
@@ -1 +1 @@
-tests/qapi-schema/event-boxed-empty.json:2: Use of 'boxed' requires 'data'
+tests/qapi-schema/event-boxed-empty.json:6: Use of 'boxed' requires 'data'
diff --git a/tests/qapi-schema/event-boxed-empty.json b/tests/qapi-schema/event-boxed-empty.json
index cb145f1433..63b870b31b 100644
--- a/tests/qapi-schema/event-boxed-empty.json
+++ b/tests/qapi-schema/event-boxed-empty.json
@@ -1,2 +1,6 @@
 # 'boxed' requires a non-empty type
+
+##
+# @FOO:
+##
 { 'event': 'FOO', 'boxed': true }
diff --git a/tests/qapi-schema/event-case.json b/tests/qapi-schema/event-case.json
index 3a92d8b610..6b05c5d247 100644
--- a/tests/qapi-schema/event-case.json
+++ b/tests/qapi-schema/event-case.json
@@ -1,3 +1,7 @@
 # TODO: might be nice to enforce naming conventions; but until then this works
 # even though events should usually be ALL_CAPS
+
+##
+# @oops:
+##
 { 'event': 'oops' }
diff --git a/tests/qapi-schema/event-case.out b/tests/qapi-schema/event-case.out
index 5a0f2bf805..5a36293c8f 100644
--- a/tests/qapi-schema/event-case.out
+++ b/tests/qapi-schema/event-case.out
@@ -3,3 +3,6 @@ enum QType ['none', 'qnull', 'qint', 'qstring', 'qdict', 'qlist', 'qfloat', 'qbo
 event oops None
    boxed=False
 object q_empty
+doc symbol=oops expr=('event', 'oops')
+    body=
+
diff --git a/tests/qapi-schema/event-nest-struct.err b/tests/qapi-schema/event-nest-struct.err
index 5a42701b8f..17a6c3c7b9 100644
--- a/tests/qapi-schema/event-nest-struct.err
+++ b/tests/qapi-schema/event-nest-struct.err
@@ -1 +1 @@
-tests/qapi-schema/event-nest-struct.json:1: Member 'a' of 'data' for event 'EVENT_A' should be a type name
+tests/qapi-schema/event-nest-struct.json:5: Member 'a' of 'data' for event 'EVENT_A' should be a type name
diff --git a/tests/qapi-schema/event-nest-struct.json b/tests/qapi-schema/event-nest-struct.json
index ee6f3ecb6f..328e0a64d3 100644
--- a/tests/qapi-schema/event-nest-struct.json
+++ b/tests/qapi-schema/event-nest-struct.json
@@ -1,2 +1,6 @@
+##
+# @EVENT_A:
+# event-nest-struct
+##
 { 'event': 'EVENT_A',
   'data': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' } }
diff --git a/tests/qapi-schema/flat-union-array-branch.err b/tests/qapi-schema/flat-union-array-branch.err
index 8ea91eadb2..e456094993 100644
--- a/tests/qapi-schema/flat-union-array-branch.err
+++ b/tests/qapi-schema/flat-union-array-branch.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-array-branch.json:8: Member 'value1' of union 'TestUnion' cannot be an array
+tests/qapi-schema/flat-union-array-branch.json:20: Member 'value1' of union 'TestUnion' cannot be an array
diff --git a/tests/qapi-schema/flat-union-array-branch.json b/tests/qapi-schema/flat-union-array-branch.json
index 0b98820a8f..51dde10392 100644
--- a/tests/qapi-schema/flat-union-array-branch.json
+++ b/tests/qapi-schema/flat-union-array-branch.json
@@ -1,10 +1,22 @@
+##
+# @TestEnum:
+##
 # we require flat union branches to be a struct
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
+##
+# @Base:
+##
 { 'struct': 'Base',
   'data': { 'enum1': 'TestEnum' } }
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'Base',
   'discriminator': 'enum1',
diff --git a/tests/qapi-schema/flat-union-bad-base.err b/tests/qapi-schema/flat-union-bad-base.err
index bee24a217a..072ffbaadd 100644
--- a/tests/qapi-schema/flat-union-bad-base.err
+++ b/tests/qapi-schema/flat-union-bad-base.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-bad-base.json:8: 'string' (member of TestTypeA) collides with 'string' (base of TestUnion)
+tests/qapi-schema/flat-union-bad-base.json:21: 'string' (member of TestTypeA) collides with 'string' (base of TestUnion)
diff --git a/tests/qapi-schema/flat-union-bad-base.json b/tests/qapi-schema/flat-union-bad-base.json
index 74dd421708..7713e7f0ad 100644
--- a/tests/qapi-schema/flat-union-bad-base.json
+++ b/tests/qapi-schema/flat-union-bad-base.json
@@ -1,10 +1,23 @@
 # we allow anonymous base, but enforce no duplicate keys
+
+##
+# @TestEnum:
+##
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
+##
+# @TestTypeA:
+##
 { 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': { 'enum1': 'TestEnum', 'string': 'str' },
   'discriminator': 'enum1',
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.err b/tests/qapi-schema/flat-union-bad-discriminator.err
index c38cc8e4df..1be4e7b23a 100644
--- a/tests/qapi-schema/flat-union-bad-discriminator.err
+++ b/tests/qapi-schema/flat-union-bad-discriminator.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-bad-discriminator.json:11: Discriminator of flat union 'TestUnion' requires a string name
+tests/qapi-schema/flat-union-bad-discriminator.json:27: Discriminator of flat union 'TestUnion' requires a string name
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.json b/tests/qapi-schema/flat-union-bad-discriminator.json
index cd10b9d901..ef92f9b583 100644
--- a/tests/qapi-schema/flat-union-bad-discriminator.json
+++ b/tests/qapi-schema/flat-union-bad-discriminator.json
@@ -1,13 +1,29 @@
 # we require the discriminator to be a string naming a base-type member
 # this tests the old syntax for anonymous unions before we added alternates
+
+##
+# @TestEnum:
+##
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
+##
+# @TestBase:
+##
 { 'struct': 'TestBase',
   'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
+##
+# @TestTypeA:
+##
 { 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'TestBase',
   'discriminator': {},
diff --git a/tests/qapi-schema/flat-union-base-any.err b/tests/qapi-schema/flat-union-base-any.err
index 646f1c9cd1..c1ea2d76b3 100644
--- a/tests/qapi-schema/flat-union-base-any.err
+++ b/tests/qapi-schema/flat-union-base-any.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-base-any.json:8: 'base' for union 'TestUnion' cannot use built-in type 'any'
+tests/qapi-schema/flat-union-base-any.json:21: 'base' for union 'TestUnion' cannot use built-in type 'any'
diff --git a/tests/qapi-schema/flat-union-base-any.json b/tests/qapi-schema/flat-union-base-any.json
index fe66b713ef..3dfb02fa30 100644
--- a/tests/qapi-schema/flat-union-base-any.json
+++ b/tests/qapi-schema/flat-union-base-any.json
@@ -1,10 +1,23 @@
 # we require the base to be an existing struct
+
+##
+# @TestEnum:
+##
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
+##
+# @TestTypeA:
+##
 { 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'any',
   'discriminator': 'enum1',
diff --git a/tests/qapi-schema/flat-union-base-union.err b/tests/qapi-schema/flat-union-base-union.err
index f138395e45..ccc5e85876 100644
--- a/tests/qapi-schema/flat-union-base-union.err
+++ b/tests/qapi-schema/flat-union-base-union.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-base-union.json:14: 'base' for union 'TestUnion' cannot use union type 'UnionBase'
+tests/qapi-schema/flat-union-base-union.json:30: 'base' for union 'TestUnion' cannot use union type 'UnionBase'
diff --git a/tests/qapi-schema/flat-union-base-union.json b/tests/qapi-schema/flat-union-base-union.json
index 98b4eba181..c63c6130b8 100644
--- a/tests/qapi-schema/flat-union-base-union.json
+++ b/tests/qapi-schema/flat-union-base-union.json
@@ -2,15 +2,31 @@
 # TODO: It would be possible to allow a union as a base, as long as all
 # permutations of QMP names exposed by base do not clash with any QMP
 # member names added by local variants.
+
+##
+# @TestEnum:
+##
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
+##
+# @TestTypeA:
+##
 { 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
+##
+# @UnionBase:
+##
 { 'union': 'UnionBase',
   'data': { 'kind1': 'TestTypeA',
             'kind2': 'TestTypeB' } }
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'UnionBase',
   'discriminator': 'type',
diff --git a/tests/qapi-schema/flat-union-clash-member.err b/tests/qapi-schema/flat-union-clash-member.err
index 2adf69755a..fe12a07e2d 100644
--- a/tests/qapi-schema/flat-union-clash-member.err
+++ b/tests/qapi-schema/flat-union-clash-member.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-clash-member.json:11: 'name' (member of Branch1) collides with 'name' (member of Base)
+tests/qapi-schema/flat-union-clash-member.json:27: 'name' (member of Branch1) collides with 'name' (member of Base)
diff --git a/tests/qapi-schema/flat-union-clash-member.json b/tests/qapi-schema/flat-union-clash-member.json
index 9efc7719b8..9000b94f16 100644
--- a/tests/qapi-schema/flat-union-clash-member.json
+++ b/tests/qapi-schema/flat-union-clash-member.json
@@ -1,13 +1,29 @@
 # We check for no duplicate keys between branch members and base
 # base's member 'name' clashes with Branch1's
+
+##
+# @TestEnum:
+##
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
+##
+# @Base:
+##
 { 'struct': 'Base',
   'data': { 'enum1': 'TestEnum', '*name': 'str' } }
+##
+# @Branch1:
+##
 { 'struct': 'Branch1',
   'data': { 'name': 'str' } }
+##
+# @Branch2:
+##
 { 'struct': 'Branch2',
   'data': { 'value': 'int' } }
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'Base',
   'discriminator': 'enum1',
diff --git a/tests/qapi-schema/flat-union-empty.err b/tests/qapi-schema/flat-union-empty.err
index 15754f54eb..ead7bd4fcb 100644
--- a/tests/qapi-schema/flat-union-empty.err
+++ b/tests/qapi-schema/flat-union-empty.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-empty.json:4: Union 'Union' cannot have empty 'data'
+tests/qapi-schema/flat-union-empty.json:14: Union 'Union' cannot have empty 'data'
diff --git a/tests/qapi-schema/flat-union-empty.json b/tests/qapi-schema/flat-union-empty.json
index 77f1d9abfb..afa8988205 100644
--- a/tests/qapi-schema/flat-union-empty.json
+++ b/tests/qapi-schema/flat-union-empty.json
@@ -1,4 +1,14 @@
 # flat unions cannot be empty
+
+##
+# @Empty:
+##
 { 'enum': 'Empty', 'data': [ ] }
+##
+# @Base:
+##
 { 'struct': 'Base', 'data': { 'type': 'Empty' } }
+##
+# @Union:
+##
 { 'union': 'Union', 'base': 'Base', 'discriminator': 'type', 'data': { } }
diff --git a/tests/qapi-schema/flat-union-incomplete-branch.err b/tests/qapi-schema/flat-union-incomplete-branch.err
index e826bf0789..c655bbfb4a 100644
--- a/tests/qapi-schema/flat-union-incomplete-branch.err
+++ b/tests/qapi-schema/flat-union-incomplete-branch.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-incomplete-branch.json:6: Union 'TestUnion' data missing 'value2' branch
+tests/qapi-schema/flat-union-incomplete-branch.json:16: Union 'TestUnion' data missing 'value2' branch
diff --git a/tests/qapi-schema/flat-union-incomplete-branch.json b/tests/qapi-schema/flat-union-incomplete-branch.json
index 25a411bc83..dea03775c7 100644
--- a/tests/qapi-schema/flat-union-incomplete-branch.json
+++ b/tests/qapi-schema/flat-union-incomplete-branch.json
@@ -1,8 +1,18 @@
 # we require all branches of the union to be covered
+
+##
+# @TestEnum:
+##
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
+##
+# @TestTypeA:
+##
 { 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': { 'type': 'TestEnum' },
   'discriminator': 'type',
diff --git a/tests/qapi-schema/flat-union-inline.err b/tests/qapi-schema/flat-union-inline.err
index 2333358d28..c2c3f7604b 100644
--- a/tests/qapi-schema/flat-union-inline.err
+++ b/tests/qapi-schema/flat-union-inline.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-inline.json:7: Member 'value1' of union 'TestUnion' should be a type name
+tests/qapi-schema/flat-union-inline.json:17: Member 'value1' of union 'TestUnion' should be a type name
diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json
index 62c7cda617..400f0817a1 100644
--- a/tests/qapi-schema/flat-union-inline.json
+++ b/tests/qapi-schema/flat-union-inline.json
@@ -1,9 +1,19 @@
 # we require branches to be a struct name
 # TODO: should we allow anonymous inline branch types?
+
+##
+# @TestEnum:
+##
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
+##
+# @Base:
+##
 { 'struct': 'Base',
   'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'Base',
   'discriminator': 'enum1',
diff --git a/tests/qapi-schema/flat-union-int-branch.err b/tests/qapi-schema/flat-union-int-branch.err
index faf01573b7..299cbb24b2 100644
--- a/tests/qapi-schema/flat-union-int-branch.err
+++ b/tests/qapi-schema/flat-union-int-branch.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-int-branch.json:8: Member 'value1' of union 'TestUnion' cannot use built-in type 'int'
+tests/qapi-schema/flat-union-int-branch.json:21: Member 'value1' of union 'TestUnion' cannot use built-in type 'int'
diff --git a/tests/qapi-schema/flat-union-int-branch.json b/tests/qapi-schema/flat-union-int-branch.json
index 9370c349e8..9603e172f8 100644
--- a/tests/qapi-schema/flat-union-int-branch.json
+++ b/tests/qapi-schema/flat-union-int-branch.json
@@ -1,10 +1,23 @@
 # we require flat union branches to be a struct
+
+##
+# @TestEnum:
+##
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
+##
+# @Base:
+##
 { 'struct': 'Base',
   'data': { 'enum1': 'TestEnum' } }
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'Base',
   'discriminator': 'enum1',
diff --git a/tests/qapi-schema/flat-union-invalid-branch-key.err b/tests/qapi-schema/flat-union-invalid-branch-key.err
index ccf72d2dfe..455f2dc083 100644
--- a/tests/qapi-schema/flat-union-invalid-branch-key.err
+++ b/tests/qapi-schema/flat-union-invalid-branch-key.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-invalid-branch-key.json:13: Discriminator value 'value_wrong' is not found in enum 'TestEnum'
+tests/qapi-schema/flat-union-invalid-branch-key.json:28: Discriminator value 'value_wrong' is not found in enum 'TestEnum'
diff --git a/tests/qapi-schema/flat-union-invalid-branch-key.json b/tests/qapi-schema/flat-union-invalid-branch-key.json
index 95ff7746bf..00f28966ff 100644
--- a/tests/qapi-schema/flat-union-invalid-branch-key.json
+++ b/tests/qapi-schema/flat-union-invalid-branch-key.json
@@ -1,15 +1,30 @@
+##
+# @TestEnum:
+##
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
 
+##
+# @TestBase:
+##
 { 'struct': 'TestBase',
   'data': { 'enum1': 'TestEnum' } }
 
+##
+# @TestTypeA:
+##
 { 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
 
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
 
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'TestBase',
   'discriminator': 'enum1',
diff --git a/tests/qapi-schema/flat-union-invalid-discriminator.err b/tests/qapi-schema/flat-union-invalid-discriminator.err
index 5f4055614e..f0e427b0a7 100644
--- a/tests/qapi-schema/flat-union-invalid-discriminator.err
+++ b/tests/qapi-schema/flat-union-invalid-discriminator.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-invalid-discriminator.json:13: Discriminator 'enum_wrong' is not a member of base struct 'TestBase'
+tests/qapi-schema/flat-union-invalid-discriminator.json:28: Discriminator 'enum_wrong' is not a member of base struct 'TestBase'
diff --git a/tests/qapi-schema/flat-union-invalid-discriminator.json b/tests/qapi-schema/flat-union-invalid-discriminator.json
index 48b94c3a4d..c8700c7d71 100644
--- a/tests/qapi-schema/flat-union-invalid-discriminator.json
+++ b/tests/qapi-schema/flat-union-invalid-discriminator.json
@@ -1,15 +1,30 @@
+##
+# @TestEnum:
+##
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
 
+##
+# @TestBase:
+##
 { 'struct': 'TestBase',
   'data': { 'enum1': 'TestEnum' } }
 
+##
+# @TestTypeA:
+##
 { 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
 
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
 
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'TestBase',
   'discriminator': 'enum_wrong',
diff --git a/tests/qapi-schema/flat-union-no-base.err b/tests/qapi-schema/flat-union-no-base.err
index 841c93b554..a2d0a81aa0 100644
--- a/tests/qapi-schema/flat-union-no-base.err
+++ b/tests/qapi-schema/flat-union-no-base.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a base
+tests/qapi-schema/flat-union-no-base.json:22: Flat union 'TestUnion' must have a base
diff --git a/tests/qapi-schema/flat-union-no-base.json b/tests/qapi-schema/flat-union-no-base.json
index ffc4c6f0e6..641f68aea4 100644
--- a/tests/qapi-schema/flat-union-no-base.json
+++ b/tests/qapi-schema/flat-union-no-base.json
@@ -1,11 +1,24 @@
 # flat unions require a base
 # TODO: simple unions should be able to use an enum discriminator
+
+##
+# @TestTypeA:
+##
 { 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
+##
+# @Enum:
+##
 { 'enum': 'Enum',
   'data': [ 'value1', 'value2' ] }
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'discriminator': 'Enum',
   'data': { 'value1': 'TestTypeA',
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.err b/tests/qapi-schema/flat-union-optional-discriminator.err
index aaabedb3bd..e15f8564dd 100644
--- a/tests/qapi-schema/flat-union-optional-discriminator.err
+++ b/tests/qapi-schema/flat-union-optional-discriminator.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-optional-discriminator.json:6: Discriminator of flat union 'MyUnion' does not allow optional name '*switch'
+tests/qapi-schema/flat-union-optional-discriminator.json:19: Discriminator of flat union 'MyUnion' does not allow optional name '*switch'
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.json b/tests/qapi-schema/flat-union-optional-discriminator.json
index 08a8f7ef8b..9f19af5789 100644
--- a/tests/qapi-schema/flat-union-optional-discriminator.json
+++ b/tests/qapi-schema/flat-union-optional-discriminator.json
@@ -1,8 +1,21 @@
 # we require the discriminator to be non-optional
+
+##
+# @Enum:
+##
 { 'enum': 'Enum', 'data': [ 'one', 'two' ] }
+##
+# @Base:
+##
 { 'struct': 'Base',
   'data': { '*switch': 'Enum' } }
+##
+# @Branch:
+##
 { 'struct': 'Branch', 'data': { 'name': 'str' } }
+##
+# @MyUnion:
+##
 { 'union': 'MyUnion',
   'base': 'Base',
   'discriminator': '*switch',
diff --git a/tests/qapi-schema/flat-union-string-discriminator.err b/tests/qapi-schema/flat-union-string-discriminator.err
index 200016bd5c..bc0c133aa9 100644
--- a/tests/qapi-schema/flat-union-string-discriminator.err
+++ b/tests/qapi-schema/flat-union-string-discriminator.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-string-discriminator.json:13: Discriminator 'kind' must be of enumeration type
+tests/qapi-schema/flat-union-string-discriminator.json:28: Discriminator 'kind' must be of enumeration type
diff --git a/tests/qapi-schema/flat-union-string-discriminator.json b/tests/qapi-schema/flat-union-string-discriminator.json
index 8af60333b6..47a17d2e4a 100644
--- a/tests/qapi-schema/flat-union-string-discriminator.json
+++ b/tests/qapi-schema/flat-union-string-discriminator.json
@@ -1,15 +1,30 @@
+##
+# @TestEnum:
+##
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
 
+##
+# @TestBase:
+##
 { 'struct': 'TestBase',
   'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
 
+##
+# @TestTypeA:
+##
 { 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
 
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
 
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'TestBase',
   'discriminator': 'kind',
diff --git a/tests/qapi-schema/ident-with-escape.json b/tests/qapi-schema/ident-with-escape.json
index 56617501e7..c03404bee3 100644
--- a/tests/qapi-schema/ident-with-escape.json
+++ b/tests/qapi-schema/ident-with-escape.json
@@ -1,4 +1,8 @@
 # we allow escape sequences in strings, if they map back to ASCII
 # { 'command': 'fooA', 'data': { 'bar1': 'str' } }
+
+##
+# @fooA:
+##
 { 'c\u006fmmand': '\u0066\u006f\u006FA',
   'd\u0061ta': { '\u0062\u0061\u00721': '\u0073\u0074\u0072' } }
diff --git a/tests/qapi-schema/ident-with-escape.out b/tests/qapi-schema/ident-with-escape.out
index 1d2722c02e..4a98933b28 100644
--- a/tests/qapi-schema/ident-with-escape.out
+++ b/tests/qapi-schema/ident-with-escape.out
@@ -5,3 +5,6 @@ command fooA q_obj_fooA-arg -> None
 object q_empty
 object q_obj_fooA-arg
     member bar1: str optional=False
+doc symbol=fooA expr=('command', 'fooA')
+    body=
+
diff --git a/tests/qapi-schema/include-relpath-sub.json b/tests/qapi-schema/include-relpath-sub.json
index 4bd4af4162..b4bd8a23d7 100644
--- a/tests/qapi-schema/include-relpath-sub.json
+++ b/tests/qapi-schema/include-relpath-sub.json
@@ -1,2 +1,5 @@
+##
+# @Status:
+##
 { 'enum': 'Status',
   'data': [ 'good', 'bad', 'ugly' ] }
diff --git a/tests/qapi-schema/include-relpath.out b/tests/qapi-schema/include-relpath.out
index 5d7c13cad1..ab3d74f49f 100644
--- a/tests/qapi-schema/include-relpath.out
+++ b/tests/qapi-schema/include-relpath.out
@@ -2,3 +2,6 @@ enum QType ['none', 'qnull', 'qint', 'qstring', 'qdict', 'qlist', 'qfloat', 'qbo
     prefix QTYPE
 enum Status ['good', 'bad', 'ugly']
 object q_empty
+doc symbol=Status expr=('enum', 'Status')
+    body=
+
diff --git a/tests/qapi-schema/include-repetition.out b/tests/qapi-schema/include-repetition.out
index 5d7c13cad1..ab3d74f49f 100644
--- a/tests/qapi-schema/include-repetition.out
+++ b/tests/qapi-schema/include-repetition.out
@@ -2,3 +2,6 @@ enum QType ['none', 'qnull', 'qint', 'qstring', 'qdict', 'qlist', 'qfloat', 'qbo
     prefix QTYPE
 enum Status ['good', 'bad', 'ugly']
 object q_empty
+doc symbol=Status expr=('enum', 'Status')
+    body=
+
diff --git a/tests/qapi-schema/include-simple-sub.json b/tests/qapi-schema/include-simple-sub.json
index 4bd4af4162..b4bd8a23d7 100644
--- a/tests/qapi-schema/include-simple-sub.json
+++ b/tests/qapi-schema/include-simple-sub.json
@@ -1,2 +1,5 @@
+##
+# @Status:
+##
 { 'enum': 'Status',
   'data': [ 'good', 'bad', 'ugly' ] }
diff --git a/tests/qapi-schema/include-simple.out b/tests/qapi-schema/include-simple.out
index 5d7c13cad1..ab3d74f49f 100644
--- a/tests/qapi-schema/include-simple.out
+++ b/tests/qapi-schema/include-simple.out
@@ -2,3 +2,6 @@ enum QType ['none', 'qnull', 'qint', 'qstring', 'qdict', 'qlist', 'qfloat', 'qbo
     prefix QTYPE
 enum Status ['good', 'bad', 'ugly']
 object q_empty
+doc symbol=Status expr=('enum', 'Status')
+    body=
+
diff --git a/tests/qapi-schema/indented-expr.json b/tests/qapi-schema/indented-expr.json
index 7115d3131e..d759be1877 100644
--- a/tests/qapi-schema/indented-expr.json
+++ b/tests/qapi-schema/indented-expr.json
@@ -1,2 +1,8 @@
+##
+# @eins:
+##
 { 'command' : 'eins' }
+##
+# @zwei:
+##
  { 'command' : 'zwei' }
diff --git a/tests/qapi-schema/indented-expr.out b/tests/qapi-schema/indented-expr.out
index e8171c935f..4ef4d7f48d 100644
--- a/tests/qapi-schema/indented-expr.out
+++ b/tests/qapi-schema/indented-expr.out
@@ -5,3 +5,9 @@ command eins None -> None
 object q_empty
 command zwei None -> None
    gen=True success_response=True boxed=False
+doc symbol=eins expr=('command', 'eins')
+    body=
+
+doc symbol=zwei expr=('command', 'zwei')
+    body=
+
diff --git a/tests/qapi-schema/missing-type.err b/tests/qapi-schema/missing-type.err
index b3e7b14e42..74c4ef7324 100644
--- a/tests/qapi-schema/missing-type.err
+++ b/tests/qapi-schema/missing-type.err
@@ -1 +1 @@
-tests/qapi-schema/missing-type.json:2: Expression is missing metatype
+tests/qapi-schema/missing-type.json:6: Expression is missing metatype
diff --git a/tests/qapi-schema/missing-type.json b/tests/qapi-schema/missing-type.json
index ff5349d3fe..c2fc62d0af 100644
--- a/tests/qapi-schema/missing-type.json
+++ b/tests/qapi-schema/missing-type.json
@@ -1,2 +1,6 @@
 # we reject an expression with missing metatype
+
+##
+# @foo:
+##
 { 'data': { } }
diff --git a/tests/qapi-schema/nested-struct-data.err b/tests/qapi-schema/nested-struct-data.err
index da767bade2..379bd1d3f4 100644
--- a/tests/qapi-schema/nested-struct-data.err
+++ b/tests/qapi-schema/nested-struct-data.err
@@ -1 +1 @@
-tests/qapi-schema/nested-struct-data.json:2: Member 'a' of 'data' for command 'foo' should be a type name
+tests/qapi-schema/nested-struct-data.json:6: Member 'a' of 'data' for command 'foo' should be a type name
diff --git a/tests/qapi-schema/nested-struct-data.json b/tests/qapi-schema/nested-struct-data.json
index efbe773ded..6106e15e86 100644
--- a/tests/qapi-schema/nested-struct-data.json
+++ b/tests/qapi-schema/nested-struct-data.json
@@ -1,3 +1,7 @@
 # inline subtypes collide with our desired future use of defaults
+
+##
+# @foo:
+##
 { 'command': 'foo',
   'data': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' } }
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 17194637ba..f4d8cc4230 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -3,67 +3,153 @@
 # 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:
+#
+# body with @var
+#
+# @integer: foo
+#           blah
+#
+#           bao
+#
+# @boolean: bar
+# @string: baz
+#
+# Example:
+#
+# -> { "execute": ... }
+# <- { "return": ... }
+#
+# Since: 2.3
+# Note: a note
+#
+##
 { 'struct': 'TestStruct',
   'data': { 'integer': 'int', 'boolean': 'bool', 'string': 'str' } }
 
+##
+# @NestedEnumsOne:
 # for testing enums
+##
 { 'struct': 'NestedEnumsOne',
   'data': { 'enum1': 'EnumOne',   # Intentional forward reference
             '*enum2': 'EnumOne', 'enum3': 'EnumOne', '*enum4': 'EnumOne' } }
 
+##
+# @MyEnum:
 # An empty enum, although unusual, is currently acceptable
+##
 { 'enum': 'MyEnum', 'data': [ ] }
 
+##
+# @Empty1:
 # Likewise for an empty struct, including an empty base
+##
 { 'struct': 'Empty1', 'data': { } }
+##
+# @Empty2:
+##
 { 'struct': 'Empty2', 'base': 'Empty1', 'data': { } }
 
+##
+# @user_def_cmd0:
+##
 { 'command': 'user_def_cmd0', 'data': 'Empty2', 'returns': 'Empty2' }
 
+##
+# @QEnumTwo:
 # for testing override of default naming heuristic
+##
 { 'enum': 'QEnumTwo',
   'prefix': 'QENUM_TWO',
   'data': [ 'value1', 'value2' ] }
 
+##
+# @UserDefOne:
 # for testing nested structs
+##
 { 'struct': 'UserDefOne',
   'base': 'UserDefZero',        # intentional forward reference
   'data': { 'string': 'str',
             '*enum1': 'EnumOne' } }   # intentional forward reference
 
+##
+# @EnumOne:
+##
 { 'enum': 'EnumOne',
   'data': [ 'value1', 'value2', 'value3' ] }
 
+##
+# @UserDefZero:
+##
 { 'struct': 'UserDefZero',
   'data': { 'integer': 'int' } }
 
+##
+# @UserDefTwoDictDict:
+##
 { 'struct': 'UserDefTwoDictDict',
   'data': { 'userdef': 'UserDefOne', 'string': 'str' } }
 
+##
+# @UserDefTwoDict:
+##
 { 'struct': 'UserDefTwoDict',
   'data': { 'string1': 'str',
             'dict2': 'UserDefTwoDictDict',
             '*dict3': 'UserDefTwoDictDict' } }
 
+##
+# @UserDefTwo:
+##
 { 'struct': 'UserDefTwo',
   'data': { 'string0': 'str',
             'dict1': 'UserDefTwoDict' } }
 
+##
+# @ForceArrays:
 # dummy struct to force generation of array types not otherwise mentioned
+##
 { 'struct': 'ForceArrays',
   'data': { 'unused1':['UserDefOne'], 'unused2':['UserDefTwo'],
             'unused3':['TestStruct'] } }
 
+##
+# @UserDefA:
 # for testing unions
 # Among other things, test that a name collision between branches does
 # not cause any problems (since only one branch can be in use at a time),
 # by intentionally using two branches that both have a C member 'a_b'
+##
 { 'struct': 'UserDefA',
   'data': { 'boolean': 'bool', '*a_b': 'int' } }
 
+##
+# @UserDefB:
+##
 { 'struct': 'UserDefB',
   'data': { 'intb': 'int', '*a-b': 'bool' } }
 
+##
+# @UserDefFlatUnion:
+##
 { 'union': 'UserDefFlatUnion',
   'base': 'UserDefUnionBase',   # intentional forward reference
   'discriminator': 'enum1',
@@ -71,35 +157,71 @@
             'value2' : 'UserDefB',
             'value3' : 'UserDefB' } }
 
+##
+# @UserDefUnionBase:
+##
 { 'struct': 'UserDefUnionBase',
   'base': 'UserDefZero',
   'data': { 'string': 'str', 'enum1': 'EnumOne' } }
 
+##
+# @UserDefFlatUnion2:
 # this variant of UserDefFlatUnion defaults to a union that uses members with
 # allocated types to test corner cases in the cleanup/dealloc visitor
+##
 { 'union': 'UserDefFlatUnion2',
   'base': { '*integer': 'int', 'string': 'str', 'enum1': 'QEnumTwo' },
   'discriminator': 'enum1',
   'data': { 'value1' : 'UserDefC', # intentional forward reference
             'value2' : 'UserDefB' } }
 
+##
+# @WrapAlternate:
+##
 { 'struct': 'WrapAlternate',
   'data': { 'alt': 'UserDefAlternate' } }
+##
+# @UserDefAlternate:
+##
 { 'alternate': 'UserDefAlternate',
   'data': { 'udfu': 'UserDefFlatUnion', 's': 'str', 'i': 'int' } }
 
+##
+# @UserDefC:
+##
 { 'struct': 'UserDefC',
   'data': { 'string1': 'str', 'string2': 'str' } }
 
 # for testing use of 'number' within alternates
+##
+# @AltStrBool:
+##
 { 'alternate': 'AltStrBool', 'data': { 's': 'str', 'b': 'bool' } }
+##
+# @AltStrNum:
+##
 { 'alternate': 'AltStrNum', 'data': { 's': 'str', 'n': 'number' } }
+##
+# @AltNumStr:
+##
 { 'alternate': 'AltNumStr', 'data': { 'n': 'number', 's': 'str' } }
+##
+# @AltStrInt:
+##
 { 'alternate': 'AltStrInt', 'data': { 's': 'str', 'i': 'int' } }
+##
+# @AltIntNum:
+##
 { 'alternate': 'AltIntNum', 'data': { 'i': 'int', 'n': 'number' } }
+##
+# @AltNumInt:
+##
 { 'alternate': 'AltNumInt', 'data': { 'n': 'number', 'i': 'int' } }
 
+##
+# @UserDefNativeListUnion:
 # for testing native lists
+##
 { 'union': 'UserDefNativeListUnion',
   'data': { 'integer': ['int'],
             's8': ['int8'],
@@ -117,19 +239,61 @@
             'any': ['any'] } }
 
 # testing commands
+##
+# @user_def_cmd:
+##
 { 'command': 'user_def_cmd', 'data': {} }
+##
+# @user_def_cmd1:
+##
 { 'command': 'user_def_cmd1', 'data': {'ud1a': 'UserDefOne'} }
+##
+# @user_def_cmd2:
+##
 { 'command': 'user_def_cmd2',
   'data': {'ud1a': 'UserDefOne', '*ud1b': 'UserDefOne'},
   'returns': 'UserDefTwo' }
 
+##
+# Another comment
+##
+
+##
+# @guest-get-time:
+#
+# @guest-get-time body
+#
+# @a: an integer
+# @b: #optional integer
+#
+# 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' }
+##
+# @guest-sync:
+##
 { 'command': 'guest-sync', 'data': { 'arg': 'any' }, 'returns': 'any' }
+##
+# @boxed-struct:
+##
 { 'command': 'boxed-struct', 'boxed': true, 'data': 'UserDefZero' }
+##
+# @boxed-union:
+##
 { 'command': 'boxed-union', 'data': 'UserDefNativeListUnion', 'boxed': true }
 
+##
+# @UserDefOptions:
+#
 # For testing integer range flattening in opts-visitor. The following schema
 # corresponds to the option format:
 #
@@ -137,6 +301,7 @@
 #
 # For simplicity, this example doesn't use [type=]discriminator nor optargs
 # specific to discriminator values.
+##
 { 'struct': 'UserDefOptions',
   'data': {
     '*i64' : [ 'int'    ],
@@ -146,35 +311,83 @@
     '*u64x':   'uint64'  } }
 
 # testing event
+##
+# @EventStructOne:
+##
 { 'struct': 'EventStructOne',
   'data': { 'struct1': 'UserDefOne', 'string': 'str', '*enum2': 'EnumOne' } }
 
+##
+# @EVENT_A:
+##
 { 'event': 'EVENT_A' }
+##
+# @EVENT_B:
+##
 { 'event': 'EVENT_B',
   'data': { } }
+##
+# @EVENT_C:
+##
 { 'event': 'EVENT_C',
   'data': { '*a': 'int', '*b': 'UserDefOne', 'c': 'str' } }
+##
+# @EVENT_D:
+##
 { 'event': 'EVENT_D',
   'data': { 'a' : 'EventStructOne', 'b' : 'str', '*c': 'str', '*enum3': 'EnumOne' } }
+##
+# @EVENT_E:
+##
 { 'event': 'EVENT_E', 'boxed': true, 'data': 'UserDefZero' }
+##
+# @EVENT_F:
+##
 { 'event': 'EVENT_F', 'boxed': true, 'data': 'UserDefAlternate' }
 
 # test that we correctly compile downstream extensions, as well as munge
 # ticklish names
+##
+# @__org.qemu_x-Enum:
+##
 { 'enum': '__org.qemu_x-Enum', 'data': [ '__org.qemu_x-value' ] }
+##
+# @__org.qemu_x-Base:
+##
 { 'struct': '__org.qemu_x-Base',
   'data': { '__org.qemu_x-member1': '__org.qemu_x-Enum' } }
+##
+# @__org.qemu_x-Struct:
+##
 { 'struct': '__org.qemu_x-Struct', 'base': '__org.qemu_x-Base',
   'data': { '__org.qemu_x-member2': 'str', '*wchar-t': 'int' } }
+##
+# @__org.qemu_x-Union1:
+##
 { 'union': '__org.qemu_x-Union1', 'data': { '__org.qemu_x-branch': 'str' } }
+##
+# @__org.qemu_x-Struct2:
+##
 { 'struct': '__org.qemu_x-Struct2',
   'data': { 'array': ['__org.qemu_x-Union1'] } }
+##
+# @__org.qemu_x-Union2:
+##
 { 'union': '__org.qemu_x-Union2', 'base': '__org.qemu_x-Base',
   'discriminator': '__org.qemu_x-member1',
   'data': { '__org.qemu_x-value': '__org.qemu_x-Struct2' } }
+##
+# @__org.qemu_x-Alt:
+##
 { 'alternate': '__org.qemu_x-Alt',
   'data': { '__org.qemu_x-branch': 'str', 'b': '__org.qemu_x-Base' } }
+##
+# @__ORG.QEMU_X-EVENT:
+##
 { 'event': '__ORG.QEMU_X-EVENT', 'data': '__org.qemu_x-Struct' }
+##
+# @__org.qemu_x-command:
+##
 { 'command': '__org.qemu_x-command',
   'data': { 'a': ['__org.qemu_x-Enum'], 'b': ['__org.qemu_x-Struct'],
             'c': '__org.qemu_x-Union2', 'd': '__org.qemu_x-Alt' },
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 9d99c4eebb..1b44be8045 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -232,3 +232,215 @@ 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
+    section=Example
+-> { "execute": ... }
+<- { "return": ... }
+    section=Since
+2.3
+    section=Note
+a note
+    body=
+body with @var
+doc symbol=NestedEnumsOne expr=('struct', 'NestedEnumsOne')
+    body=
+for testing enums
+doc symbol=MyEnum expr=('enum', 'MyEnum')
+    body=
+An empty enum, although unusual, is currently acceptable
+doc symbol=Empty1 expr=('struct', 'Empty1')
+    body=
+Likewise for an empty struct, including an empty base
+doc symbol=Empty2 expr=('struct', 'Empty2')
+    body=
+
+doc symbol=user_def_cmd0 expr=('command', 'user_def_cmd0')
+    body=
+
+doc symbol=QEnumTwo expr=('enum', 'QEnumTwo')
+    body=
+for testing override of default naming heuristic
+doc symbol=UserDefOne expr=('struct', 'UserDefOne')
+    body=
+for testing nested structs
+doc symbol=EnumOne expr=('enum', 'EnumOne')
+    body=
+
+doc symbol=UserDefZero expr=('struct', 'UserDefZero')
+    body=
+
+doc symbol=UserDefTwoDictDict expr=('struct', 'UserDefTwoDictDict')
+    body=
+
+doc symbol=UserDefTwoDict expr=('struct', 'UserDefTwoDict')
+    body=
+
+doc symbol=UserDefTwo expr=('struct', 'UserDefTwo')
+    body=
+
+doc symbol=ForceArrays expr=('struct', 'ForceArrays')
+    body=
+dummy struct to force generation of array types not otherwise mentioned
+doc symbol=UserDefA expr=('struct', 'UserDefA')
+    body=
+for testing unions
+Among other things, test that a name collision between branches does
+not cause any problems (since only one branch can be in use at a time),
+by intentionally using two branches that both have a C member 'a_b'
+doc symbol=UserDefB expr=('struct', 'UserDefB')
+    body=
+
+doc symbol=UserDefFlatUnion expr=('union', 'UserDefFlatUnion')
+    body=
+
+doc symbol=UserDefUnionBase expr=('struct', 'UserDefUnionBase')
+    body=
+
+doc symbol=UserDefFlatUnion2 expr=('union', 'UserDefFlatUnion2')
+    body=
+this variant of UserDefFlatUnion defaults to a union that uses members with
+allocated types to test corner cases in the cleanup/dealloc visitor
+doc symbol=WrapAlternate expr=('struct', 'WrapAlternate')
+    body=
+
+doc symbol=UserDefAlternate expr=('alternate', 'UserDefAlternate')
+    body=
+
+doc symbol=UserDefC expr=('struct', 'UserDefC')
+    body=
+
+doc symbol=AltStrBool expr=('alternate', 'AltStrBool')
+    body=
+
+doc symbol=AltStrNum expr=('alternate', 'AltStrNum')
+    body=
+
+doc symbol=AltNumStr expr=('alternate', 'AltNumStr')
+    body=
+
+doc symbol=AltStrInt expr=('alternate', 'AltStrInt')
+    body=
+
+doc symbol=AltIntNum expr=('alternate', 'AltIntNum')
+    body=
+
+doc symbol=AltNumInt expr=('alternate', 'AltNumInt')
+    body=
+
+doc symbol=UserDefNativeListUnion expr=('union', 'UserDefNativeListUnion')
+    body=
+for testing native lists
+doc symbol=user_def_cmd expr=('command', 'user_def_cmd')
+    body=
+
+doc symbol=user_def_cmd1 expr=('command', 'user_def_cmd1')
+    body=
+
+doc symbol=user_def_cmd2 expr=('command', 'user_def_cmd2')
+    body=
+
+doc freeform
+    body=
+Another comment
+doc symbol=guest-get-time expr=('command', 'guest-get-time')
+    arg=a
+an integer
+    arg=b
+#optional integer
+    section=Returns
+returns something
+    section=Example
+-> { "execute": "guest-get-time", ... }
+<- { "return": "42" }
+    body=
+@guest-get-time body
+doc symbol=guest-sync expr=('command', 'guest-sync')
+    body=
+
+doc symbol=boxed-struct expr=('command', 'boxed-struct')
+    body=
+
+doc symbol=boxed-union expr=('command', 'boxed-union')
+    body=
+
+doc symbol=UserDefOptions expr=('struct', 'UserDefOptions')
+    body=
+For testing integer range flattening in opts-visitor. The following schema
+corresponds to the option format:
+
+-userdef i64=3-6,i64=-5--1,u64=2,u16=1,u16=7-12
+
+For simplicity, this example doesn't use [type=]discriminator nor optargs
+specific to discriminator values.
+doc symbol=EventStructOne expr=('struct', 'EventStructOne')
+    body=
+
+doc symbol=EVENT_A expr=('event', 'EVENT_A')
+    body=
+
+doc symbol=EVENT_B expr=('event', 'EVENT_B')
+    body=
+
+doc symbol=EVENT_C expr=('event', 'EVENT_C')
+    body=
+
+doc symbol=EVENT_D expr=('event', 'EVENT_D')
+    body=
+
+doc symbol=EVENT_E expr=('event', 'EVENT_E')
+    body=
+
+doc symbol=EVENT_F expr=('event', 'EVENT_F')
+    body=
+
+doc symbol=__org.qemu_x-Enum expr=('enum', '__org.qemu_x-Enum')
+    body=
+
+doc symbol=__org.qemu_x-Base expr=('struct', '__org.qemu_x-Base')
+    body=
+
+doc symbol=__org.qemu_x-Struct expr=('struct', '__org.qemu_x-Struct')
+    body=
+
+doc symbol=__org.qemu_x-Union1 expr=('union', '__org.qemu_x-Union1')
+    body=
+
+doc symbol=__org.qemu_x-Struct2 expr=('struct', '__org.qemu_x-Struct2')
+    body=
+
+doc symbol=__org.qemu_x-Union2 expr=('union', '__org.qemu_x-Union2')
+    body=
+
+doc symbol=__org.qemu_x-Alt expr=('alternate', '__org.qemu_x-Alt')
+    body=
+
+doc symbol=__ORG.QEMU_X-EVENT expr=('event', '__ORG.QEMU_X-EVENT')
+    body=
+
+doc symbol=__org.qemu_x-command expr=('command', '__org.qemu_x-command')
+    body=
+
diff --git a/tests/qapi-schema/redefined-builtin.err b/tests/qapi-schema/redefined-builtin.err
index b2757225c4..ee0a2adf0b 100644
--- a/tests/qapi-schema/redefined-builtin.err
+++ b/tests/qapi-schema/redefined-builtin.err
@@ -1 +1 @@
-tests/qapi-schema/redefined-builtin.json:2: built-in 'size' is already defined
+tests/qapi-schema/redefined-builtin.json:6: built-in 'size' is already defined
diff --git a/tests/qapi-schema/redefined-builtin.json b/tests/qapi-schema/redefined-builtin.json
index 45b8a550ad..6d3a940d5e 100644
--- a/tests/qapi-schema/redefined-builtin.json
+++ b/tests/qapi-schema/redefined-builtin.json
@@ -1,2 +1,6 @@
 # we reject types that duplicate builtin names
+
+##
+# @size:
+##
 { 'struct': 'size', 'data': { 'myint': 'size' } }
diff --git a/tests/qapi-schema/redefined-command.err b/tests/qapi-schema/redefined-command.err
index 82ae256e63..1e297c43ba 100644
--- a/tests/qapi-schema/redefined-command.err
+++ b/tests/qapi-schema/redefined-command.err
@@ -1 +1 @@
-tests/qapi-schema/redefined-command.json:3: command 'foo' is already defined
+tests/qapi-schema/redefined-command.json:10: command 'foo' is already defined
diff --git a/tests/qapi-schema/redefined-command.json b/tests/qapi-schema/redefined-command.json
index 247e401948..3a8cb9024c 100644
--- a/tests/qapi-schema/redefined-command.json
+++ b/tests/qapi-schema/redefined-command.json
@@ -1,3 +1,10 @@
 # we reject commands defined more than once
+
+##
+# @foo:
+##
 { 'command': 'foo', 'data': { 'one': 'str' } }
+##
+# @foo:
+##
 { 'command': 'foo', 'data': { '*two': 'str' } }
diff --git a/tests/qapi-schema/redefined-event.err b/tests/qapi-schema/redefined-event.err
index 35429cb481..912c785119 100644
--- a/tests/qapi-schema/redefined-event.err
+++ b/tests/qapi-schema/redefined-event.err
@@ -1 +1 @@
-tests/qapi-schema/redefined-event.json:3: event 'EVENT_A' is already defined
+tests/qapi-schema/redefined-event.json:10: event 'EVENT_A' is already defined
diff --git a/tests/qapi-schema/redefined-event.json b/tests/qapi-schema/redefined-event.json
index 7717e91c18..ec7aeea0f0 100644
--- a/tests/qapi-schema/redefined-event.json
+++ b/tests/qapi-schema/redefined-event.json
@@ -1,3 +1,10 @@
 # we reject duplicate events
+
+##
+# @EVENT_A:
+##
 { 'event': 'EVENT_A', 'data': { 'myint': 'int' } }
+##
+# @EVENT_A:
+##
 { 'event': 'EVENT_A', 'data': { 'myint': 'int' } }
diff --git a/tests/qapi-schema/redefined-type.err b/tests/qapi-schema/redefined-type.err
index 06ea78c478..28d87c098c 100644
--- a/tests/qapi-schema/redefined-type.err
+++ b/tests/qapi-schema/redefined-type.err
@@ -1 +1 @@
-tests/qapi-schema/redefined-type.json:3: struct 'foo' is already defined
+tests/qapi-schema/redefined-type.json:10: struct 'foo' is already defined
diff --git a/tests/qapi-schema/redefined-type.json b/tests/qapi-schema/redefined-type.json
index a09e768bae..7a8f3e1ec8 100644
--- a/tests/qapi-schema/redefined-type.json
+++ b/tests/qapi-schema/redefined-type.json
@@ -1,3 +1,10 @@
 # we reject types defined more than once
+
+##
+# @foo:
+##
 { 'struct': 'foo', 'data': { 'one': 'str' } }
+##
+# @foo:
+##
 { 'enum': 'foo', 'data': [ 'two' ] }
diff --git a/tests/qapi-schema/reserved-command-q.err b/tests/qapi-schema/reserved-command-q.err
index f939e044eb..5e17f3169b 100644
--- a/tests/qapi-schema/reserved-command-q.err
+++ b/tests/qapi-schema/reserved-command-q.err
@@ -1 +1 @@
-tests/qapi-schema/reserved-command-q.json:5: 'command' uses invalid name 'q-unix'
+tests/qapi-schema/reserved-command-q.json:12: 'command' uses invalid name 'q-unix'
diff --git a/tests/qapi-schema/reserved-command-q.json b/tests/qapi-schema/reserved-command-q.json
index 99f8aae314..bba0860c99 100644
--- a/tests/qapi-schema/reserved-command-q.json
+++ b/tests/qapi-schema/reserved-command-q.json
@@ -1,5 +1,12 @@
 # C entity name collision
 # We reject names like 'q-unix', because they can collide with the mangled
 # name for 'unix' in generated C.
+
+##
+# @unix:
+##
 { 'command': 'unix' }
+##
+# @q-unix:
+##
 { 'command': 'q-unix' }
diff --git a/tests/qapi-schema/reserved-enum-q.err b/tests/qapi-schema/reserved-enum-q.err
index e1c3480ee2..acb2df811d 100644
--- a/tests/qapi-schema/reserved-enum-q.err
+++ b/tests/qapi-schema/reserved-enum-q.err
@@ -1 +1 @@
-tests/qapi-schema/reserved-enum-q.json:4: Member of enum 'Foo' uses invalid name 'q-Unix'
+tests/qapi-schema/reserved-enum-q.json:8: Member of enum 'Foo' uses invalid name 'q-Unix'
diff --git a/tests/qapi-schema/reserved-enum-q.json b/tests/qapi-schema/reserved-enum-q.json
index 3593a765ea..6c7e7177c3 100644
--- a/tests/qapi-schema/reserved-enum-q.json
+++ b/tests/qapi-schema/reserved-enum-q.json
@@ -1,4 +1,8 @@
 # C entity name collision
 # We reject names like 'q-unix', because they can collide with the mangled
 # name for 'unix' in generated C.
+
+##
+# @Foo:
+##
 { 'enum': 'Foo', 'data': [ 'unix', 'q-Unix' ] }
diff --git a/tests/qapi-schema/reserved-member-has.err b/tests/qapi-schema/reserved-member-has.err
index e755771446..9ace796055 100644
--- a/tests/qapi-schema/reserved-member-has.err
+++ b/tests/qapi-schema/reserved-member-has.err
@@ -1 +1 @@
-tests/qapi-schema/reserved-member-has.json:5: Member of 'data' for command 'oops' uses reserved name 'has-a'
+tests/qapi-schema/reserved-member-has.json:9: Member of 'data' for command 'oops' uses reserved name 'has-a'
diff --git a/tests/qapi-schema/reserved-member-has.json b/tests/qapi-schema/reserved-member-has.json
index 45b9109bdc..f0d8905ca2 100644
--- a/tests/qapi-schema/reserved-member-has.json
+++ b/tests/qapi-schema/reserved-member-has.json
@@ -2,4 +2,8 @@
 # We reject names like 'has-a', because they can collide with the flag
 # for an optional 'a' in generated C.
 # TODO we could munge the optional flag name to avoid the collision.
+
+##
+# @oops:
+##
 { 'command': 'oops', 'data': { '*a': 'str', 'has-a': 'str' } }
diff --git a/tests/qapi-schema/reserved-member-q.err b/tests/qapi-schema/reserved-member-q.err
index f3d5dd7818..1709a88462 100644
--- a/tests/qapi-schema/reserved-member-q.err
+++ b/tests/qapi-schema/reserved-member-q.err
@@ -1 +1 @@
-tests/qapi-schema/reserved-member-q.json:4: Member of 'data' for struct 'Foo' uses invalid name 'q-unix'
+tests/qapi-schema/reserved-member-q.json:8: Member of 'data' for struct 'Foo' uses invalid name 'q-unix'
diff --git a/tests/qapi-schema/reserved-member-q.json b/tests/qapi-schema/reserved-member-q.json
index 62fed8fddf..f51e312917 100644
--- a/tests/qapi-schema/reserved-member-q.json
+++ b/tests/qapi-schema/reserved-member-q.json
@@ -1,4 +1,8 @@
 # C member name collision
 # We reject names like 'q-unix', because they can collide with the mangled
 # name for 'unix' in generated C.
+
+##
+# @Foo:
+##
 { 'struct': 'Foo', 'data': { 'unix':'int', 'q-unix':'bool' } }
diff --git a/tests/qapi-schema/reserved-member-u.err b/tests/qapi-schema/reserved-member-u.err
index 87d42296cc..6ec69a712a 100644
--- a/tests/qapi-schema/reserved-member-u.err
+++ b/tests/qapi-schema/reserved-member-u.err
@@ -1 +1 @@
-tests/qapi-schema/reserved-member-u.json:7: Member of 'data' for struct 'Oops' uses reserved name 'u'
+tests/qapi-schema/reserved-member-u.json:11: Member of 'data' for struct 'Oops' uses reserved name 'u'
diff --git a/tests/qapi-schema/reserved-member-u.json b/tests/qapi-schema/reserved-member-u.json
index 1eaf0f301c..3a578e5b56 100644
--- a/tests/qapi-schema/reserved-member-u.json
+++ b/tests/qapi-schema/reserved-member-u.json
@@ -4,4 +4,8 @@
 # This is true even for non-unions, because it is possible to convert a
 # struct to flat union while remaining backwards compatible in QMP.
 # TODO - we could munge the member name to 'q_u' to avoid the collision
+
+##
+# @Oops:
+##
 { 'struct': 'Oops', 'data': { 'u': 'str' } }
diff --git a/tests/qapi-schema/reserved-member-underscore.err b/tests/qapi-schema/reserved-member-underscore.err
index 65ff0da8ce..c9aefee3a8 100644
--- a/tests/qapi-schema/reserved-member-underscore.err
+++ b/tests/qapi-schema/reserved-member-underscore.err
@@ -1 +1 @@
-tests/qapi-schema/reserved-member-underscore.json:4: Member of 'data' for struct 'Oops' uses invalid name '_oops'
+tests/qapi-schema/reserved-member-underscore.json:8: Member of 'data' for struct 'Oops' uses invalid name '_oops'
diff --git a/tests/qapi-schema/reserved-member-underscore.json b/tests/qapi-schema/reserved-member-underscore.json
index 4a3a017638..cc34b54b02 100644
--- a/tests/qapi-schema/reserved-member-underscore.json
+++ b/tests/qapi-schema/reserved-member-underscore.json
@@ -1,4 +1,8 @@
 # C member name collision
 # We reject use of a single leading underscore in all names (names must
 # begin with a letter or a downstream extension double-underscore prefix).
+
+##
+# @Oops:
+##
 { 'struct': 'Oops', 'data': { '_oops': 'str' } }
diff --git a/tests/qapi-schema/reserved-type-kind.err b/tests/qapi-schema/reserved-type-kind.err
index 0a38efaad8..8698073062 100644
--- a/tests/qapi-schema/reserved-type-kind.err
+++ b/tests/qapi-schema/reserved-type-kind.err
@@ -1 +1 @@
-tests/qapi-schema/reserved-type-kind.json:2: enum 'UnionKind' should not end in 'Kind'
+tests/qapi-schema/reserved-type-kind.json:6: enum 'UnionKind' should not end in 'Kind'
diff --git a/tests/qapi-schema/reserved-type-kind.json b/tests/qapi-schema/reserved-type-kind.json
index 9ecaba12bc..a094941561 100644
--- a/tests/qapi-schema/reserved-type-kind.json
+++ b/tests/qapi-schema/reserved-type-kind.json
@@ -1,2 +1,6 @@
 # we reject types that would conflict with implicit union enum
+
+##
+# @UnionKind:
+##
 { 'enum': 'UnionKind', 'data': [ 'oops' ] }
diff --git a/tests/qapi-schema/reserved-type-list.err b/tests/qapi-schema/reserved-type-list.err
index 4510fa6d90..ec0531c4b9 100644
--- a/tests/qapi-schema/reserved-type-list.err
+++ b/tests/qapi-schema/reserved-type-list.err
@@ -1 +1 @@
-tests/qapi-schema/reserved-type-list.json:5: struct 'FooList' should not end in 'List'
+tests/qapi-schema/reserved-type-list.json:9: struct 'FooList' should not end in 'List'
diff --git a/tests/qapi-schema/reserved-type-list.json b/tests/qapi-schema/reserved-type-list.json
index 98d53bf808..6effb78e7f 100644
--- a/tests/qapi-schema/reserved-type-list.json
+++ b/tests/qapi-schema/reserved-type-list.json
@@ -2,4 +2,8 @@
 # We reserve names ending in 'List' for use by array types.
 # TODO - we could choose array names to avoid collision with user types,
 # in order to let this compile
+
+##
+# @FooList:
+##
 { 'struct': 'FooList', 'data': { 's': 'str' } }
diff --git a/tests/qapi-schema/returns-alternate.err b/tests/qapi-schema/returns-alternate.err
index dfbb419cac..2b81623ca3 100644
--- a/tests/qapi-schema/returns-alternate.err
+++ b/tests/qapi-schema/returns-alternate.err
@@ -1 +1 @@
-tests/qapi-schema/returns-alternate.json:3: 'returns' for command 'oops' cannot use alternate type 'Alt'
+tests/qapi-schema/returns-alternate.json:10: 'returns' for command 'oops' cannot use alternate type 'Alt'
diff --git a/tests/qapi-schema/returns-alternate.json b/tests/qapi-schema/returns-alternate.json
index 972390c06b..005bf2d148 100644
--- a/tests/qapi-schema/returns-alternate.json
+++ b/tests/qapi-schema/returns-alternate.json
@@ -1,3 +1,10 @@
 # we reject returns if it is an alternate type
+
+##
+# @Alt:
+##
 { 'alternate': 'Alt', 'data': { 'a': 'int', 'b': 'str' } }
+##
+# @oops:
+##
 { 'command': 'oops', 'returns': 'Alt' }
diff --git a/tests/qapi-schema/returns-array-bad.err b/tests/qapi-schema/returns-array-bad.err
index 138095ccde..b53bdb0ade 100644
--- a/tests/qapi-schema/returns-array-bad.err
+++ b/tests/qapi-schema/returns-array-bad.err
@@ -1 +1 @@
-tests/qapi-schema/returns-array-bad.json:2: 'returns' for command 'oops': array type must contain single type name
+tests/qapi-schema/returns-array-bad.json:6: 'returns' for command 'oops': array type must contain single type name
diff --git a/tests/qapi-schema/returns-array-bad.json b/tests/qapi-schema/returns-array-bad.json
index 09b0b1f182..30528fed29 100644
--- a/tests/qapi-schema/returns-array-bad.json
+++ b/tests/qapi-schema/returns-array-bad.json
@@ -1,2 +1,6 @@
 # we reject an array return that is not a single type
+
+##
+# @oops:
+##
 { 'command': 'oops', 'returns': [ 'str', 'str' ] }
diff --git a/tests/qapi-schema/returns-dict.err b/tests/qapi-schema/returns-dict.err
index eb2d0c4661..1570a35d49 100644
--- a/tests/qapi-schema/returns-dict.err
+++ b/tests/qapi-schema/returns-dict.err
@@ -1 +1 @@
-tests/qapi-schema/returns-dict.json:2: 'returns' for command 'oops' should be a type name
+tests/qapi-schema/returns-dict.json:6: 'returns' for command 'oops' should be a type name
diff --git a/tests/qapi-schema/returns-dict.json b/tests/qapi-schema/returns-dict.json
index 1cfef3ede7..6a3ed0f34d 100644
--- a/tests/qapi-schema/returns-dict.json
+++ b/tests/qapi-schema/returns-dict.json
@@ -1,2 +1,6 @@
 # we reject inline struct return type
+
+##
+# @oops:
+##
 { 'command': 'oops', 'returns': { 'a': 'str' } }
diff --git a/tests/qapi-schema/returns-unknown.err b/tests/qapi-schema/returns-unknown.err
index 1f43e3ac9f..d76bcfe455 100644
--- a/tests/qapi-schema/returns-unknown.err
+++ b/tests/qapi-schema/returns-unknown.err
@@ -1 +1 @@
-tests/qapi-schema/returns-unknown.json:2: 'returns' for command 'oops' uses unknown type 'NoSuchType'
+tests/qapi-schema/returns-unknown.json:6: 'returns' for command 'oops' uses unknown type 'NoSuchType'
diff --git a/tests/qapi-schema/returns-unknown.json b/tests/qapi-schema/returns-unknown.json
index 25bd498bff..3837f0e607 100644
--- a/tests/qapi-schema/returns-unknown.json
+++ b/tests/qapi-schema/returns-unknown.json
@@ -1,2 +1,6 @@
 # we reject returns if it does not contain a known type
+
+##
+# @oops:
+##
 { 'command': 'oops', 'returns': 'NoSuchType' }
diff --git a/tests/qapi-schema/returns-whitelist.err b/tests/qapi-schema/returns-whitelist.err
index f47c1ee7ca..e77ea2da3f 100644
--- a/tests/qapi-schema/returns-whitelist.err
+++ b/tests/qapi-schema/returns-whitelist.err
@@ -1 +1 @@
-tests/qapi-schema/returns-whitelist.json:10: 'returns' for command 'no-way-this-will-get-whitelisted' cannot use built-in type 'int'
+tests/qapi-schema/returns-whitelist.json:26: 'returns' for command 'no-way-this-will-get-whitelisted' cannot use built-in type 'int'
diff --git a/tests/qapi-schema/returns-whitelist.json b/tests/qapi-schema/returns-whitelist.json
index e8b3cea396..0bc952db87 100644
--- a/tests/qapi-schema/returns-whitelist.json
+++ b/tests/qapi-schema/returns-whitelist.json
@@ -1,11 +1,27 @@
 # we enforce that 'returns' be a dict or array of dict unless whitelisted
+
+##
+# @human-monitor-command:
+##
 { 'command': 'human-monitor-command',
   'data': {'command-line': 'str', '*cpu-index': 'int'},
   'returns': 'str' }
+##
+# @TpmModel:
+##
 { 'enum': 'TpmModel', 'data': [ 'tpm-tis' ] }
+##
+# @query-tpm-models:
+##
 { 'command': 'query-tpm-models', 'returns': ['TpmModel'] }
+##
+# @guest-get-time:
+##
 { 'command': 'guest-get-time',
   'returns': 'int' }
 
+##
+# @no-way-this-will-get-whitelisted:
+##
 { 'command': 'no-way-this-will-get-whitelisted',
   'returns': [ 'int' ] }
diff --git a/tests/qapi-schema/struct-base-clash-deep.err b/tests/qapi-schema/struct-base-clash-deep.err
index e2d7943f21..1b7c0e9d12 100644
--- a/tests/qapi-schema/struct-base-clash-deep.err
+++ b/tests/qapi-schema/struct-base-clash-deep.err
@@ -1 +1 @@
-tests/qapi-schema/struct-base-clash-deep.json:10: 'name' (member of Sub) collides with 'name' (member of Base)
+tests/qapi-schema/struct-base-clash-deep.json:20: 'name' (member of Sub) collides with 'name' (member of Base)
diff --git a/tests/qapi-schema/struct-base-clash-deep.json b/tests/qapi-schema/struct-base-clash-deep.json
index fa873ab5d4..646d680ad6 100644
--- a/tests/qapi-schema/struct-base-clash-deep.json
+++ b/tests/qapi-schema/struct-base-clash-deep.json
@@ -2,11 +2,21 @@
 # Here, 'name' would have to appear twice on the wire, locally and
 # indirectly for the grandparent base; the collision doesn't care that
 # one instance is optional.
+
+##
+# @Base:
+##
 { 'struct': 'Base',
   'data': { 'name': 'str' } }
+##
+# @Mid:
+##
 { 'struct': 'Mid',
   'base': 'Base',
   'data': { 'value': 'int' } }
+##
+# @Sub:
+##
 { 'struct': 'Sub',
   'base': 'Mid',
   'data': { '*name': 'str' } }
diff --git a/tests/qapi-schema/struct-base-clash.err b/tests/qapi-schema/struct-base-clash.err
index c52f33d27b..5fe6393efa 100644
--- a/tests/qapi-schema/struct-base-clash.err
+++ b/tests/qapi-schema/struct-base-clash.err
@@ -1 +1 @@
-tests/qapi-schema/struct-base-clash.json:5: 'name' (member of Sub) collides with 'name' (member of Base)
+tests/qapi-schema/struct-base-clash.json:12: 'name' (member of Sub) collides with 'name' (member of Base)
diff --git a/tests/qapi-schema/struct-base-clash.json b/tests/qapi-schema/struct-base-clash.json
index 11aec80fe5..a8539958b5 100644
--- a/tests/qapi-schema/struct-base-clash.json
+++ b/tests/qapi-schema/struct-base-clash.json
@@ -1,7 +1,14 @@
 # Reject attempts to duplicate QMP members
 # Here, 'name' would have to appear twice on the wire, locally and for base.
+
+##
+# @Base:
+##
 { 'struct': 'Base',
   'data': { 'name': 'str' } }
+##
+# @Sub:
+##
 { 'struct': 'Sub',
   'base': 'Base',
   'data': { 'name': 'str' } }
diff --git a/tests/qapi-schema/struct-data-invalid.err b/tests/qapi-schema/struct-data-invalid.err
index 6644f4c2ad..27163355bd 100644
--- a/tests/qapi-schema/struct-data-invalid.err
+++ b/tests/qapi-schema/struct-data-invalid.err
@@ -1 +1 @@
-tests/qapi-schema/struct-data-invalid.json:1: 'data' for struct 'foo' should be a dictionary or type name
+tests/qapi-schema/struct-data-invalid.json:4: 'data' for struct 'foo' should be a dictionary or type name
diff --git a/tests/qapi-schema/struct-data-invalid.json b/tests/qapi-schema/struct-data-invalid.json
index 9adbc3bb6b..aa817bda34 100644
--- a/tests/qapi-schema/struct-data-invalid.json
+++ b/tests/qapi-schema/struct-data-invalid.json
@@ -1,2 +1,5 @@
+##
+# @foo:
+##
 { 'struct': 'foo',
   'data': false }
diff --git a/tests/qapi-schema/struct-member-invalid.err b/tests/qapi-schema/struct-member-invalid.err
index 69a326d450..f2b105ba88 100644
--- a/tests/qapi-schema/struct-member-invalid.err
+++ b/tests/qapi-schema/struct-member-invalid.err
@@ -1 +1 @@
-tests/qapi-schema/struct-member-invalid.json:1: Member 'a' of 'data' for struct 'foo' should be a type name
+tests/qapi-schema/struct-member-invalid.json:4: Member 'a' of 'data' for struct 'foo' should be a type name
diff --git a/tests/qapi-schema/struct-member-invalid.json b/tests/qapi-schema/struct-member-invalid.json
index 8f172f7a87..10c74262d3 100644
--- a/tests/qapi-schema/struct-member-invalid.json
+++ b/tests/qapi-schema/struct-member-invalid.json
@@ -1,2 +1,5 @@
+##
+# @foo:
+##
 { 'struct': 'foo',
   'data': { 'a': false } }
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index ef74e2c4c8..39b55b994a 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.sections:
+        print '    section=%s\n%s' % (section.name, section)
+    print '    body=\n%s' % doc.body
diff --git a/tests/qapi-schema/type-bypass-bad-gen.err b/tests/qapi-schema/type-bypass-bad-gen.err
index a83c3c655d..bd5431f60b 100644
--- a/tests/qapi-schema/type-bypass-bad-gen.err
+++ b/tests/qapi-schema/type-bypass-bad-gen.err
@@ -1 +1 @@
-tests/qapi-schema/type-bypass-bad-gen.json:2: 'gen' of command 'foo' should only use false value
+tests/qapi-schema/type-bypass-bad-gen.json:6: 'gen' of command 'foo' should only use false value
diff --git a/tests/qapi-schema/type-bypass-bad-gen.json b/tests/qapi-schema/type-bypass-bad-gen.json
index e8dec34249..7162c1a0ca 100644
--- a/tests/qapi-schema/type-bypass-bad-gen.json
+++ b/tests/qapi-schema/type-bypass-bad-gen.json
@@ -1,2 +1,6 @@
 # 'gen' should only appear with value false
+
+##
+# @foo:
+##
 { 'command': 'foo', 'gen': 'whatever' }
diff --git a/tests/qapi-schema/unicode-str.err b/tests/qapi-schema/unicode-str.err
index f621cd6448..92ee277370 100644
--- a/tests/qapi-schema/unicode-str.err
+++ b/tests/qapi-schema/unicode-str.err
@@ -1 +1 @@
-tests/qapi-schema/unicode-str.json:2: 'command' uses invalid name 'é'
+tests/qapi-schema/unicode-str.json:6: 'command' uses invalid name 'é'
diff --git a/tests/qapi-schema/unicode-str.json b/tests/qapi-schema/unicode-str.json
index 5253a1b9f3..75a08b3d93 100644
--- a/tests/qapi-schema/unicode-str.json
+++ b/tests/qapi-schema/unicode-str.json
@@ -1,2 +1,6 @@
 # we don't support full Unicode strings, yet
+
+##
+# @e:
+##
 { 'command': 'é' }
diff --git a/tests/qapi-schema/union-base-no-discriminator.err b/tests/qapi-schema/union-base-no-discriminator.err
index 8b7a24260f..ca6ee92357 100644
--- a/tests/qapi-schema/union-base-no-discriminator.err
+++ b/tests/qapi-schema/union-base-no-discriminator.err
@@ -1 +1 @@
-tests/qapi-schema/union-base-no-discriminator.json:11: Simple union 'TestUnion' must not have a base
+tests/qapi-schema/union-base-no-discriminator.json:23: Simple union 'TestUnion' must not have a base
diff --git a/tests/qapi-schema/union-base-no-discriminator.json b/tests/qapi-schema/union-base-no-discriminator.json
index 1409cf5c9e..cc6bac1424 100644
--- a/tests/qapi-schema/union-base-no-discriminator.json
+++ b/tests/qapi-schema/union-base-no-discriminator.json
@@ -1,13 +1,25 @@
+##
+# @TestTypeA:
+##
 # we reject simple unions with a base (or flat unions without discriminator)
 { 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
 
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
 
+##
+# @Base:
+##
 { 'struct': 'Base',
   'data': { 'string': 'str' } }
 
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'Base',
   'data': { 'value1': 'TestTypeA',
diff --git a/tests/qapi-schema/union-branch-case.err b/tests/qapi-schema/union-branch-case.err
index 11521901d8..9095bae565 100644
--- a/tests/qapi-schema/union-branch-case.err
+++ b/tests/qapi-schema/union-branch-case.err
@@ -1 +1 @@
-tests/qapi-schema/union-branch-case.json:2: 'Branch' (branch of NoWayThisWillGetWhitelisted) should not use uppercase
+tests/qapi-schema/union-branch-case.json:6: 'Branch' (branch of NoWayThisWillGetWhitelisted) should not use uppercase
diff --git a/tests/qapi-schema/union-branch-case.json b/tests/qapi-schema/union-branch-case.json
index e6565dc3b3..6de131548c 100644
--- a/tests/qapi-schema/union-branch-case.json
+++ b/tests/qapi-schema/union-branch-case.json
@@ -1,2 +1,6 @@
 # Branch names should be 'lower-case' unless the union is whitelisted
+
+##
+# @NoWayThisWillGetWhitelisted:
+##
 { 'union': 'NoWayThisWillGetWhitelisted', 'data': { 'Branch': 'int' } }
diff --git a/tests/qapi-schema/union-clash-branches.err b/tests/qapi-schema/union-clash-branches.err
index e5b21135bb..640caeab8c 100644
--- a/tests/qapi-schema/union-clash-branches.err
+++ b/tests/qapi-schema/union-clash-branches.err
@@ -1 +1 @@
-tests/qapi-schema/union-clash-branches.json:4: 'a_b' (branch of TestUnion) collides with 'a-b' (branch of TestUnion)
+tests/qapi-schema/union-clash-branches.json:8: 'a_b' (branch of TestUnion) collides with 'a-b' (branch of TestUnion)
diff --git a/tests/qapi-schema/union-clash-branches.json b/tests/qapi-schema/union-clash-branches.json
index 3bece8c948..6615665dfe 100644
--- a/tests/qapi-schema/union-clash-branches.json
+++ b/tests/qapi-schema/union-clash-branches.json
@@ -1,5 +1,9 @@
 # Union branch name collision
 # Reject a union that would result in a collision in generated C names (this
 # would try to generate two members 'a_b').
+
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'data': { 'a-b': 'int', 'a_b': 'str' } }
diff --git a/tests/qapi-schema/union-empty.err b/tests/qapi-schema/union-empty.err
index 12c20221bd..749bc76fc5 100644
--- a/tests/qapi-schema/union-empty.err
+++ b/tests/qapi-schema/union-empty.err
@@ -1 +1 @@
-tests/qapi-schema/union-empty.json:2: Union 'Union' cannot have empty 'data'
+tests/qapi-schema/union-empty.json:6: Union 'Union' cannot have empty 'data'
diff --git a/tests/qapi-schema/union-empty.json b/tests/qapi-schema/union-empty.json
index 1f0b13ca21..c9b0a1ef33 100644
--- a/tests/qapi-schema/union-empty.json
+++ b/tests/qapi-schema/union-empty.json
@@ -1,2 +1,6 @@
 # unions cannot be empty
+
+##
+# @Union:
+##
 { 'union': 'Union', 'data': { } }
diff --git a/tests/qapi-schema/union-invalid-base.err b/tests/qapi-schema/union-invalid-base.err
index 03d7b97a93..41e238f453 100644
--- a/tests/qapi-schema/union-invalid-base.err
+++ b/tests/qapi-schema/union-invalid-base.err
@@ -1 +1 @@
-tests/qapi-schema/union-invalid-base.json:8: 'base' for union 'TestUnion' cannot use built-in type 'int'
+tests/qapi-schema/union-invalid-base.json:18: 'base' for union 'TestUnion' cannot use built-in type 'int'
diff --git a/tests/qapi-schema/union-invalid-base.json b/tests/qapi-schema/union-invalid-base.json
index 92be39df69..fd837cb80b 100644
--- a/tests/qapi-schema/union-invalid-base.json
+++ b/tests/qapi-schema/union-invalid-base.json
@@ -1,10 +1,20 @@
 # a union base type must be a struct
+
+##
+# @TestTypeA:
+##
 { 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
 
+##
+# @TestTypeB:
+##
 { 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
 
+##
+# @TestUnion:
+##
 { 'union': 'TestUnion',
   'base': 'int',
   'discriminator': 'int',
diff --git a/tests/qapi-schema/union-optional-branch.err b/tests/qapi-schema/union-optional-branch.err
index 3ada1334dc..60523c07e4 100644
--- a/tests/qapi-schema/union-optional-branch.err
+++ b/tests/qapi-schema/union-optional-branch.err
@@ -1 +1 @@
-tests/qapi-schema/union-optional-branch.json:2: Member of union 'Union' does not allow optional name '*a'
+tests/qapi-schema/union-optional-branch.json:6: Member of union 'Union' does not allow optional name '*a'
diff --git a/tests/qapi-schema/union-optional-branch.json b/tests/qapi-schema/union-optional-branch.json
index 591615fc68..7d2ee4c730 100644
--- a/tests/qapi-schema/union-optional-branch.json
+++ b/tests/qapi-schema/union-optional-branch.json
@@ -1,2 +1,6 @@
 # union branches cannot be optional
+
+##
+# @Union:
+##
 { 'union': 'Union', 'data': { '*a': 'int', 'b': 'str' } }
diff --git a/tests/qapi-schema/union-unknown.err b/tests/qapi-schema/union-unknown.err
index 54fe456f9c..5568302205 100644
--- a/tests/qapi-schema/union-unknown.err
+++ b/tests/qapi-schema/union-unknown.err
@@ -1 +1 @@
-tests/qapi-schema/union-unknown.json:2: Member 'unknown' of union 'Union' uses unknown type 'MissingType'
+tests/qapi-schema/union-unknown.json:6: Member 'unknown' of union 'Union' uses unknown type 'MissingType'
diff --git a/tests/qapi-schema/union-unknown.json b/tests/qapi-schema/union-unknown.json
index aa7e8143d8..5042b23197 100644
--- a/tests/qapi-schema/union-unknown.json
+++ b/tests/qapi-schema/union-unknown.json
@@ -1,3 +1,7 @@
 # we reject a union with unknown type in branch
+
+##
+# @Union:
+##
 { 'union': 'Union',
   'data': { 'unknown': 'MissingType' } }
diff --git a/tests/qapi-schema/unknown-escape.err b/tests/qapi-schema/unknown-escape.err
index 000e30ddf3..1a4ead632b 100644
--- a/tests/qapi-schema/unknown-escape.err
+++ b/tests/qapi-schema/unknown-escape.err
@@ -1 +1 @@
-tests/qapi-schema/unknown-escape.json:3:21: Unknown escape \x
+tests/qapi-schema/unknown-escape.json:7:21: Unknown escape \x
diff --git a/tests/qapi-schema/unknown-escape.json b/tests/qapi-schema/unknown-escape.json
index 8e6891e52a..e3ae6793f2 100644
--- a/tests/qapi-schema/unknown-escape.json
+++ b/tests/qapi-schema/unknown-escape.json
@@ -1,3 +1,7 @@
 # we only recognize JSON escape sequences, plus our \' extension (no \x)
+
+##
+# @foo:
+##
 # { 'command': 'foo', 'data': {} }
 { 'command': 'foo', 'dat\x61':{} }
diff --git a/tests/qapi-schema/unknown-expr-key.err b/tests/qapi-schema/unknown-expr-key.err
index 12f5ed5b43..b19a668bd6 100644
--- a/tests/qapi-schema/unknown-expr-key.err
+++ b/tests/qapi-schema/unknown-expr-key.err
@@ -1 +1 @@
-tests/qapi-schema/unknown-expr-key.json:2: Unknown key 'bogus' in struct 'bar'
+tests/qapi-schema/unknown-expr-key.json:6: Unknown key 'bogus' in struct 'bar'
diff --git a/tests/qapi-schema/unknown-expr-key.json b/tests/qapi-schema/unknown-expr-key.json
index 3b2be00cc4..1b764c7b9d 100644
--- a/tests/qapi-schema/unknown-expr-key.json
+++ b/tests/qapi-schema/unknown-expr-key.json
@@ -1,2 +1,6 @@
 # we reject an expression with unknown top-level keys
+
+##
+# @bar:
+##
 { 'struct': 'bar', 'data': { 'string': 'str'}, 'bogus': { } }
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 16/21] docs: add qemu logo to pdf
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (14 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 15/21] qapi: add qapi2texi script Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 17/21] build-sys: use --no-split for info Marc-André Lureau
                   ` (5 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Add a logo to texi2pdf output. Other formats (info/html) are left as
future improvements.

The PDF (needed by texi2pdf for vectorized images) was generated from
pc-bios/qemu_logo.svg like this:

inkscape --export-pdf=docs/qemu_logo.pdf pc-bios/qemu_logo.svg

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 docs/qemu-ga-ref.texi  |   4 ++++
 docs/qemu-qmp-ref.texi |   4 ++++
 docs/qemu_logo.pdf     | Bin 0 -> 9117 bytes
 3 files changed, 8 insertions(+)
 create mode 100644 docs/qemu_logo.pdf

diff --git a/docs/qemu-ga-ref.texi b/docs/qemu-ga-ref.texi
index b8898027dc..87cc8d01a5 100644
--- a/docs/qemu-ga-ref.texi
+++ b/docs/qemu-ga-ref.texi
@@ -6,6 +6,10 @@
 
 @settitle QEMU Guest Agent Protocol Reference
 
+@iftex
+@center @image{docs/qemu_logo}
+@end iftex
+
 @copying
 This is the QEMU Guest Agent Protocol reference manual.
 
diff --git a/docs/qemu-qmp-ref.texi b/docs/qemu-qmp-ref.texi
index efb3370a24..818e52573b 100644
--- a/docs/qemu-qmp-ref.texi
+++ b/docs/qemu-qmp-ref.texi
@@ -6,6 +6,10 @@
 
 @settitle QEMU QMP Reference Manual
 
+@iftex
+@center @image{docs/qemu_logo}
+@end iftex
+
 @copying
 This is the QEMU QMP reference manual.
 
diff --git a/docs/qemu_logo.pdf b/docs/qemu_logo.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..294cb7dec50de73c786925671300fb0abdf9d641
GIT binary patch
literal 9117
zcmd5?2|QF^`%elfQWRNlwoqesGqx7H>`T^?!C)*ini-<BC~Nl6W=WPxi^$S1Q4vy>
zlq^4#U7MYvi2uDarj$PK|9#)j`*}a_T%U38bDnd~^E~G{-*e9Qj*O|64h*S?<(4TL
z&Yk5(0|<cP<iV|`2EYx8WH-7yfB{A3+yDT;bx2-xA{G4aMW7S4h|UxjBKOXn+%!6s
zNbu(NPw}?OkJ*n~H}rXvLPD>LewJPiPq*yuGb;qMxXf2Yrrc>;>)5rxe(}mw!_IBZ
zBWn5r%A#k#rGWQNo&)#KDAvR8x0=tc_AK7FZkNj5=6K3XBu`00v0e4uQlqTX<bbK(
z5l%ap<hk*j0Hy6sy2Tj^ZCrgkhexBQ)yk$ul*@YTF4>a{EK^pC6URTA>12kjw8+SN
zFL;DT>nnVNJ9Uq*i!-qH#ln^D>ACfRJ_j`?HMf@?@jNTxflU55Zj$}?+0HD(+jedv
zsK^#=)qCfsdYUJ$wa)c?4(M-vH?`g0kBxo1J)9j`)_>X;B@~wx>R4K+FqOBp`!xB!
zYKk&q;3}T*uAA-Y$KeWcRpI5+{BuFY&Y#omOBB^5Vs^djKT6SmH9b!`dlmmaNI~J^
zjhaIL<6~y+qD@B~K30`}Of;%hE1bESUHuGxc_QqQg4HY2nclzcN$iJ3-3#cc0hp{c
zy<3R6;a%}Tr^$xj_YZd`?t32RE2i$=QC7C+f&?{Ht6y(`IGs*^kgG1CxPJQO;rX-K
zqbvRQ-n}mIZI9i->s9SXlwd0Z-)STpY7g`9DQu+Vcix|ylVCSlqiqSJKCGLROGORi
z;~vdL4}>0hReysnT{AIsU5Z=eLjIN9LeG(#e>oLhxsg(nPCXa|sBAxwcvHo$7qz`L
zzF`mF+!cqM>Bn3jyHyX}d#uXcUE<oUZ(m@@uaTPiUbNU(k<2b+%ZA>)Nqdckppx$%
z9tC}RYDDtvV8xkqOBHjhW=1uKc7gM`fjT2i$CVqd1WajXh4ZG~Fo(5ux(Q^;S5-bK
zNn(qtkEuUX-|kNw(tZw%jR}6;;H-4&+FQHLdyx1w=eKl{Gfbac=L;<lc6Sa?vJ_p{
ztr)sFK3^qBu*MU1B-?Jw5dZsZQ}p#qT-iI~jz|sFDfm_DTz<dym<U#uTiw~Kps-zC
z)Fxe=m;T<;hcj_i_RAe7R62MU0Ef9-Wh%03Z|ay64OZ0bpB{V+yX(KL<!YB>e~?hj
z?(4f_tdx|(I)fX-(!c8lWJpMzX6LS4Qx{$`F=meR!Q1T~eXPqX96>(6f^#T+X4P{x
zJ7+{w-KxEw_B^GEhSDO1JcG6ww|tVHemwOBU*2~3q|GI{2V0JEcDO6K2y1TlS=)bT
zm4ZRAs2E$v+a1DTIf1}U!UcH_r~GQ~*Xhxw2k=<uvphOgTa(Hhc#pn+o*w>?qU%+d
zWyVR<-GG(a%7(!Nx~Jp(R@-a#zSo$qHs&{TUt5Q-Bp-Rj71(Y~|NF3nPiyLJY{r<7
zLJ-VRcOx7A(8*O5oK9QWKAdsVnHSE{W;bnEpo~2feFA&S7roKc#Woy2-otTl%VunR
zabGKE;)441crKUVgF|_yMZ-IdbKUsd>LN#Pv1NbX8!sFwc86>9Wn5eS%?<UNrHwt-
znZjy@EbDqjB3^Uxb3M8kmVXbW8i&gay8--lpY3*#gkPnfj)>3cQi7=TRk_t$f(16+
zkiH~e|5<FtakOmZ>X?VQW|D_n){%QJ;IzVeIHHxq8p$WRMQTz#w5PAfrQC5`{lSU<
z>6lPkrP2zCAiSbEX2r(67JB%1LK}BpIbpaBqkA^imU^iw?Q9BcqY3`+b9N?O^|$=f
z0{EWBM}|AJjSfbIE0n0e!>XiOJUabGQZ^w)CuRoUSJR+Hl=4^LuJgNosy^7V`Mac~
zYk9rn#<B>C1t1oASf}}pySTiteAizyd9I}=WkLm%Ik*bG3v83fR#y|<EatOEa_S+$
zp>+MoLdCIf-}Zj{fE?c<nHnA5^)RE#dcU;*M;f7EZJI)?zmK~5>#$4q#{>7Cm>U~E
z-*tR$a{S%Y_y&_ZC)f;gtWI(h$u5hy3*JG94Z$?@9|US78m|l~K#!;&fX;w$ZGXD1
z1s#NMKurx2fFC#k0X_h5%{>4TfIAbwZxGZM%V<h~5)1z!T^b(1v!0n+a0_>W3yJK;
zXoKdC1+Y*#sOgRaaE#kUJsk>}zNA-I7y^a>l$SIH>Ma_LM1W$}GGrOxLj>Ta1UDkX
z15*N(2sVUdRfHQ6T}T8Cia%fviVy$_k5t6qFbE|8udIks#vrgb-~gkhCTIXyl?FhI
z9CJm}rBZx-mKeqCiy>u2zyg3<QVC?557e)7086Y7(5b!*i=eP*f?8TcKaw-iTvr29
zS@hYQNTc{toryF68V7S|wPd*nIlyvJ$CvC(CsD|Z?okY5wJ6>M5}Dx>l4U<)(M1GH
zfc#Bh3?P?w@pJ6KHT$3M;zxgs<`wbEI1E~8vCCh3fQEW8=RlvKBw9=ch9l_3STO=b
z@uE;Id<f3qQ2r7qu#j0!p}NpOcaTUWMJ!Soi^BoRctsQ*g}_3Q`lA?&Rm5YJ(KsyA
zxW%THM{cnI%zZHUU?0%PF~1mzu~^KC5ZD)EteD~@BgWbrF$?}RZ*{2zR_cJ&;hL2H
zA$OUnxy)S_pe)%6A{ld=k)MCs3qqbTXONAFlLyh6&d{{*b)qw-unvXl&8!RFF!Yd&
zM-ArW1?Mdujl!d`01ByyM4^<BH~^`HQ^aGCI6M+S;*=Ee2<4xt_+xHCjesA>&L8l$
zDEnEm2yzT~V?y1|oy=0^RAt_?@^@KvnmZB=;wqFFKhbe<k}rh|$OvW#mP*0K;eW;v
zsN@$YfdoI%WJ%h9oq>Np_S#J*7*jpCRU^Csfw7Wn=w7)(x^I>7@@0WBWQ%hYh53O#
z5U8`zhiR`7f#%5=4$x3TUvCnbLL<=`ISbbXkjz5>Xuu+TEwBGe=;0PnbKvl?W)fpv
zVKfgdGmM)(`WApI!HdQ?L$FX0S}|C5u_h$LzJH90V0wVpBFsauwN&scW+9>V>)+~P
zN%JfO{<UeA@yuNM9UvGUFpI$X|Ifl92BnBc;;|SMfJcMJG!%%4;IvpG#o-l|5jZqP
z`9BPTjJ11l9{e14B(z{JrW5GPzvknTY-XhS4-j19&VSd*AA1?lo}W&lmTo^5Pr;}k
zi^3m|!H_@9dGzl<9})pBdMKPSn48cjqd?fhVnM{iD<YLKcyQ=JY{a54pp2P=|83|~
z1{mZ1zXyGVmJH~VVOwRn9QqhTwwS9wp%1lmSM*OnA0ru-?%bB*2eba4ppVfFbCU_J
zIsZTOp;+4sXil+`?jLh6P|`6qkpKonhF=zlVe7R%`>M9QRqq-(meT1)<*%6y_34w^
zGXdmB0D|izgEjanS4GSAZ#2iqOz_EFmELzFU4SoIh>uS`L)(xP8KlLfvHEqooSA^T
zQPK^e9ogaj;mPyU;i)-ds!bRDRGL1Fyg4yFD>1t;ds+R#J5+;EldU@Yq;-qs>xa|j
z{Mabcs73PrfN8IVBGtQIQP0YQw^=nC-@LE=pl_XX%YaJ}u|0M2X6UtpV%)L%JFg${
z(5`t&nKrBT%6c(zBckWXTa~aeuZr(CJzZaXdZ6hkS$Zb#UBi~7O;Hi#isUu39twla
zb~?5kd}OJiJE_V=U((L-NNm_?yz{cF@J;jRjJ@P6_uD(}N3TiKIkP#fIJKkS$tW*q
zHiqtod=Wn)_2#14TLB7Cm256!^I}Kkbc$uPRclBA8fG2G(-##OuK4#eNgLP3ZSAlJ
zg?`1aTXJcndy=8$Js&<a?7T}kxLKs_Sd7P@Uu;2}?Wa}n$PCh>9Pf(n&4%9X*0G)<
zaPfrHkHIS1!?wP?XV-h~{OXgZSuWad>um*#^(^+`tk3NTNXzYDFWJXl5HuVS=O=<u
z^ffrwSRj-lP!Uh2Tn;=#@HzZtr!aA|kzD?fe$vgSemT#YvcDJKFAuQId!BpWn|$={
zW^bcnnU2iQbnokar<IIFoW(M`ZY9|neD?4T^$nfBUffySIUm*H<=xV^O3*P^NPKPV
zr@i94*<MuP#VhqXr0sLc+V<67Bi!&VDeEk%DXyHqaGSCvb6s12|5vpn3+@^Do$)5U
zm#^!-9;A5`*vYFNEs68WimXU;=GyOtroEgE>NL1Nb~nXQf)X4pe%|Zw?Yjlp@x?;w
zmo1)4k;>G2IA;i7Kn3(Y&P<QY7VTrH)Psh`eh!MtpYsHEO%{)bevLd|_m1bIOp!&=
zjq^36Co#UC{2U1;r`vBj^aKQkf6Xedc+goMtaR?1^x)Q*NSI4m;L1DQlQXejpS0Ju
zA3AYqSJT1nuCuT1+{2YFv>iDv1>Y9%MkYYu+2n%k!brulr-$BFwvH89wjR6zw@`n)
ze$JxyM9~<9Fl;ZhU`1JgpRo4d^rC0qRLlvDzO7#D=r}`@lg9GBp|h|+$xpIh1zvga
zMZOoy>BTxpHR(Mxr8f!)zaUt&9l&shN0&wqyKH+_5(H1e)jT73H<|h8PEz5!q$Z03
ztt34v`SOi0;oezDUCRiL7DLQI)g3M2t9yO(PbLLR^*L9-L^(ZsGXoSnUtRLw8aohT
z$Jyary(`i019oEHSA|b5sihY%_ww#vYM7oZ2n66g9W@`-R9_^l8SuD2ho0Y{U3x}L
zC*rj&zkRjTnxcH(GqcjFwXSO}CBYI>?NrJ>9JLiZ8a5MU^sXoMVmRB~gpXT_FB)B|
zqaQNzvkPnyFv~vR_-4+*JHao=SRmR%+T^O&+fZIJ#V?Un(%C~kJj#02J{k8yu`b&v
z8FD2%_66?z5b|l|MAPlzW;^%j2W@NY1Or;h&6^v`Xs0Lxs`5_No5ycwbgF1q7RVYT
z$Pq4w(4O#miY3N6p(WPe<%HP|mFDH4g`U(_4f<5u>>lZfx@MzmFKH_!;azs#s=^0r
z|2R-uz2;))q^}GtStcHPrZ(@KQ)^)fWup3B0WVC#qD{CUw;$#u<4GuZztTnTXt_uH
zsoWLkdZsa+L1tG5yJt2o1ieaR+tPLFcx-c5-I?Un$?veTvML!#hjU7`!LF;k>Luga
zKOd;bW!s`%B{RSI_}B0XgQgfcxb$G~M&Zy6`4PJ^8WTgxlxkOe*Ga<7Zom26K+V8>
z?K%Ieo7W*sByLZQze&PnzZnsqQ#L5R*zvhOv!R;u4et8I%lB~GwRp14%;x~@*LIm3
zKkx$skrxlTK2V+S5#^uqvwU{{R{fE*BHPWOqh<wBS6U4D3qu~^47Ug6??|oBY&wuh
zQy2OoY2qpWI+8F=`aBhsF64ibEH*Z;7P1NPY!-dmR3TF&Z2CdvC%2CwPrHv>q}AC)
z$ma9C)Uvqr*eRmA=>xC08`a6Ey*2BYp|JBS_mNByoe6h-P}gh_|0;_-;^ZrRI9X1O
z<1m%;Xt=*|!aJAYser!j4Yv~g32VYz_~EBM?TChMIU)oHPj(m?B<tT!j7RX;+Z3z&
zC5djXf(@2r>4)U3rfFY#)+0+Sf2%U(ur|AqhSdBbx4-V>m9c@4_=#GT{X!A&bt%>}
z`+HI<4%!{E>UqKKCNQ5E{6fI^@TZAFje9X8=`&M_io5Eo<;5-w&G>6a&Dr!wv7f;B
zoPDexvVPM})0xv6=PPEpA6d_&2Jt_gea^pCbTrcWQ$l|}-`k@&pbI@lUZK05v)_z;
zv~)OOw6c5HI5w~&%y@lF>87o*GvijP-bzQ~cto#$UC%jIEyep)N5NrNf5M$>J*K)@
zYW<qJ(hgGNo)P}dVb;9G&#eV`#pMz>?GNQSh;n@ySzp6W#PbQvvisZi+}k>2KN3GH
z@I|&;CPbZk_`zD22SIOMRMoy&-S*ijr86m7u%mRM3SU@MLQEFCttxi6IXabh2yjys
zuoG|yul}Mpr_<BQ{m8tnh4WFAHf>$nwSY8SUQ)?zjx?$7?|Sn?N?^V-Ar%EAe;q<W
zMH6?j!0}w?%nA#U{S)#bJc?diXST_knF!mM$bL%5iz)HtXnED!D%-s#1bfqdKscn?
zh)DI_Zq*ujmB{wDW$4v(|NVqb!!i4~V-JMrpu7yZzx0}+j=hf66}8d7m89)|p(JdR
zm8rN@rQ?0h+LbdGE6hW}1jH?EGV*MCf@aw#*g|I6XL2eeLuT(DqDURjXiPVihzwCV
ze#`WlPi)0X_FX8%LBhzJ<o^934{(HrA%EqkkB9v0P>2HF!}6~iYU0qSF->+X#|XwJ
ztb!gQA!li=;^M1xRo;^5?4ftwVv~l7#vU_e?ej^zhA+*`#9`Zo%^J$?stMow^q0{U
z!<Y3vN%s^wQOXW)GY}UW`~4FY5{3S<C2Z_*9F-197Lb)CoLY~IiA}-@3z?i6&zBPy
z+#bmjCGsh$&k5NQIl7Ht7;h32q4U!H&W;Jfl-zmI;VL;nG6FGi<g)0N=ugLl2<c-g
z=XcQiR0KoUudV6f2z{CIWF;4`Z*6)-qvrwYb&=K*giD?UZ$!D{w%j#Wc5m$p8<pQL
z7E;=YzbLqS1h`xmtx0|$l%+ZVNN_Yqrg(+bjc?s1cUP?Dd8c%N0E#qUXC(Cg@jf%5
zI5U7#s3x4ZCTe9?m=BT5T5i91bnQ3)0iwR%q*Pq+d`{xYsd;_9&lwW5=f^qBJN4FZ
zKU)7x<?PwG3w+!8+4X#Mp9R}U7dBqZ*Qiw8AZRb$_mR)Q*g3M%`bw-IvL9Bx8#A84
zJ{H0^pq+K<e4ceV|EZ3F_z}6kIznpMt;>AQR+1jaM|0WPX|0p|7%~zpF5tI1`P9C0
zNnes#oQjJn7dzL>kPk_hGMZ}T?+ZrFXBLe$W@M{<KarWY;J0F-{A|rUHy`5G_do2Z
zS*#Ga_e3e75iC9c#i9b2?p}YM{{O@lEoCzfnAVb?0DPsBn~oatZjkmS*O(_c1nSv0
z4|6)&h-L}%RdX&J6AU-c;rxPC%_MxyJ~3QY`*@dmBfD!}LvpZzO@fZlA!?eKiDOE`
zbgqv2%^G}snD;aM^ji*DnK6@<=Tns0$}S*!)7Vy=FyTm;<R5wn*A(ILjM|0Id;!?B
z2JJDRT5sxl9rbwo*G7?w18;e?W;<ruB`b2iJayIQJZ)epoG`(UlGoyV)gE&Nw~%+O
zTIlw}oXY;mi%KEsT1BLyE8jW?6VFs1dvHC8vdiluR)%UrvADg-qM@%Z5LsDqV^omR
zP8<9~Kv_%*6j1aJf(AU~|70ncrwoWc*CNuKsU#mdg~~WkKq6y;H^`|Pm>S#aYHimd
zdJwF9EkL>&ra|#?`Jtpk@FsZ$0CLQ708)@=7J3ofKti0!u0YK#l5sHbNU99LFd%(|
zR0auFB$CCl!u9C{FOu^fvYQtXKrkBFLvw~Gd1def0v$;pBcW#)3XNq5GzmU>M3S33
z$l8O=Fhmw~qPG=*XV!-v^jHs&5n#FyQD8X07%V+`vA72)$9~3y*&d5zv;?_bsEi@i
zAkc_Vd;d5-Br1)r=}w@s!eB^Pe8w|vg<AL-cz`i9%#)oq8GHv0z59S$lgN9>G!jd|
z=-ry+LU)HaZHP|z&s<7)C?-oUWo2mQExqta)FKrGx1{VNlc3l10AqeGjU_~9uvjo=
zETK0nG=|{@BqkLPwQ9~{)hNEuTbCuT7f?$N!kXYu0KlvS<eUj4Dg^)uT#TX;Am>h}
z`>4P{@(&ccQ3*coBxjl;h3Y2H%sx{p#l_bd{7Jz-Uu_9XvpgL%31Dtg+!#(U+*y20
z^s|ybmFUWi06^eE{P+M$SS-joy8??ckZ%J~4*CFOmJEqSER!J+5V`uB9tNbUf0Ln*
z2+-7D_0UKtjee71miK|dAV9w2S3MjayG({u!Y{W)8O(&=>Y@<P+l^n_17QaQq2FZK
z<?W#n7%(G#)k7ncmi2)~L3HtNdT59||4pWZUS<m#ue7WW40?H-F=+I%_+c>kWpT!#
zme<9h(989(%VUbef|&Q4&3F`cnSFRP4x9=s-{@2Vh+b62FK8@CM~G0aL8xd>q0j*o
jl(S6i&?mc6z?YSbXU0-Upwb!Hi3k1VmXXmm(c%6--IUSm

literal 0
HcmV?d00001

-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 17/21] build-sys: use --no-split for info
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (15 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 16/21] docs: add qemu logo to pdf Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 18/21] build-sys: remove dvi doc generation Marc-André Lureau
                   ` (4 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Splitting the info files doesn't bring much benefits these days.
This fixes also untracked generated info files from git ignore.

Let's use MAKEINFOFLAGS for common flags, --number-sections is already
the default anyway, so adding it doesn't change the info output.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 Makefile | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/Makefile b/Makefile
index 361773634d..0bc470d974 100644
--- a/Makefile
+++ b/Makefile
@@ -529,17 +529,17 @@ ui/console-gl.o: $(SRC_PATH)/ui/console-gl.c \
 
 # documentation
 MAKEINFO=makeinfo
-MAKEINFOFLAGS=--no-headers --no-split --number-sections
+MAKEINFOFLAGS=--no-split --number-sections
 TEXIFLAG=$(if $(V),,--quiet)
 %.dvi: %.texi
 	$(call quiet-command,texi2dvi $(TEXIFLAG) -I . $<,"GEN","$@")
 
 %.html: %.texi
-	$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --html $< -o $@, \
-	"GEN","$@")
+	$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --no-headers \
+	--html $< -o $@,"GEN","$@")
 
 %.info: %.texi
-	$(call quiet-command,$(MAKEINFO) $< -o $@,"GEN","$@")
+	$(call quiet-command,$(MAKEINFO) $(MAKEINFOFLAGS) $< -o $@,"GEN","$@")
 
 %.pdf: %.texi
 	$(call quiet-command,texi2pdf $(TEXIFLAG) -I . $<,"GEN","$@")
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 18/21] build-sys: remove dvi doc generation
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (16 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 17/21] build-sys: use --no-split for info Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 19/21] build-sys: use a generic TEXI2MAN rule Marc-André Lureau
                   ` (3 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

There is no clear reason to have rules to generate dvi format
documentation, pdf is generally better supported nowadays.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 .gitignore |  1 -
 Makefile   | 12 ++++--------
 2 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/.gitignore b/.gitignore
index 3d7848cb7e..6f175b391e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,7 +60,6 @@
 *.a
 *.aux
 *.cp
-*.dvi
 *.exe
 *.msi
 *.dll
diff --git a/Makefile b/Makefile
index 0bc470d974..f2b9ef0784 100644
--- a/Makefile
+++ b/Makefile
@@ -80,7 +80,7 @@ GENERATED_HEADERS += module_block.h
 Makefile: ;
 configure: ;
 
-.PHONY: all clean cscope distclean dvi html info install install-doc \
+.PHONY: all clean cscope distclean html info install install-doc \
 	pdf recurse-all speed test dist msi FORCE
 
 $(call set-vpath, $(SRC_PATH))
@@ -389,7 +389,7 @@ distclean: clean
 	rm -f config-all-devices.mak config-all-disas.mak config.status
 	rm -f po/*.mo tests/qemu-iotests/common.env
 	rm -f roms/seabios/config.mak roms/vgabios/config.mak
-	rm -f qemu-doc.info qemu-doc.aux qemu-doc.cp qemu-doc.cps qemu-doc.dvi
+	rm -f qemu-doc.info qemu-doc.aux qemu-doc.cp qemu-doc.cps
 	rm -f qemu-doc.fn qemu-doc.fns qemu-doc.info qemu-doc.ky qemu-doc.kys
 	rm -f qemu-doc.log qemu-doc.pdf qemu-doc.pg qemu-doc.toc qemu-doc.tp
 	rm -f qemu-doc.vr
@@ -531,9 +531,6 @@ ui/console-gl.o: $(SRC_PATH)/ui/console-gl.c \
 MAKEINFO=makeinfo
 MAKEINFOFLAGS=--no-split --number-sections
 TEXIFLAG=$(if $(V),,--quiet)
-%.dvi: %.texi
-	$(call quiet-command,texi2dvi $(TEXIFLAG) -I . $<,"GEN","$@")
-
 %.html: %.texi
 	$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --no-headers \
 	--html $< -o $@,"GEN","$@")
@@ -587,12 +584,11 @@ qemu-ga.8: qemu-ga.texi
 	  $(POD2MAN) --section=8 --center=" " --release=" " qemu-ga.pod > $@, \
 	  "GEN","$@")
 
-dvi: qemu-doc.dvi
 html: qemu-doc.html
 info: qemu-doc.info
 pdf: qemu-doc.pdf
 
-qemu-doc.dvi qemu-doc.html qemu-doc.info qemu-doc.pdf: \
+qemu-doc.html qemu-doc.info qemu-doc.pdf: \
 	qemu-img.texi qemu-nbd.texi qemu-options.texi qemu-option-trace.texi \
 	qemu-monitor.texi qemu-img-cmds.texi qemu-ga.texi \
 	qemu-monitor-info.texi
@@ -689,7 +685,7 @@ help:
 	@echo  '  docker          - Help about targets running tests inside Docker containers'
 	@echo  ''
 	@echo  'Documentation targets:'
-	@echo  '  dvi html info pdf'
+	@echo  '  html info pdf'
 	@echo  '                  - Build documentation in specified format'
 	@echo  ''
 ifdef CONFIG_WIN32
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 19/21] build-sys: use a generic TEXI2MAN rule
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (17 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 18/21] build-sys: remove dvi doc generation Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 20/21] build-sys: add txt documentation rules Marc-André Lureau
                   ` (2 subsequent siblings)
  21 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

The recipe for making a man page from .texi is duplicated several
times over.  Capture it in suitable pattern rules instead.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 Makefile  | 24 ------------------------
 rules.mak | 10 ++++++++++
 2 files changed, 10 insertions(+), 24 deletions(-)

diff --git a/Makefile b/Makefile
index f2b9ef0784..d18bac1c31 100644
--- a/Makefile
+++ b/Makefile
@@ -554,35 +554,11 @@ qemu-img-cmds.texi: $(SRC_PATH)/qemu-img-cmds.hx $(SRC_PATH)/scripts/hxtool
 	$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
 
 qemu.1: qemu-doc.texi qemu-options.texi qemu-monitor.texi qemu-monitor-info.texi
-	$(call quiet-command, \
-	  perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu.pod && \
-	  $(POD2MAN) --section=1 --center=" " --release=" " qemu.pod > $@, \
-	  "GEN","$@")
 qemu.1: qemu-option-trace.texi
-
 qemu-img.1: qemu-img.texi qemu-option-trace.texi qemu-img-cmds.texi
-	$(call quiet-command, \
-	  perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-img.pod && \
-	  $(POD2MAN) --section=1 --center=" " --release=" " qemu-img.pod > $@, \
-	  "GEN","$@")
-
 fsdev/virtfs-proxy-helper.1: fsdev/virtfs-proxy-helper.texi
-	$(call quiet-command, \
-	  perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< fsdev/virtfs-proxy-helper.pod && \
-	  $(POD2MAN) --section=1 --center=" " --release=" " fsdev/virtfs-proxy-helper.pod > $@, \
-	  "GEN","$@")
-
 qemu-nbd.8: qemu-nbd.texi qemu-option-trace.texi
-	$(call quiet-command, \
-	  perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-nbd.pod && \
-	  $(POD2MAN) --section=8 --center=" " --release=" " qemu-nbd.pod > $@, \
-	  "GEN","$@")
-
 qemu-ga.8: qemu-ga.texi
-	$(call quiet-command, \
-	  perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-ga.pod && \
-	  $(POD2MAN) --section=8 --center=" " --release=" " qemu-ga.pod > $@, \
-	  "GEN","$@")
 
 html: qemu-doc.html
 info: qemu-doc.info
diff --git a/rules.mak b/rules.mak
index f4839d2c38..c9739961a7 100644
--- a/rules.mak
+++ b/rules.mak
@@ -361,3 +361,13 @@ define unnest-vars
         $(eval -include $(patsubst %.o,%.d,$(patsubst %.mo,%.d,$($v))))
         $(eval $v := $(filter-out %/,$($v))))
 endef
+
+TEXI2MAN = $(call quiet-command, \
+	perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< $@.pod && \
+	$(POD2MAN) --section=$(subst .,,$(suffix $@)) --center=" " --release=" " $@.pod > $@, \
+	"GEN","$@")
+
+%.1:
+	$(call TEXI2MAN)
+%.8:
+	$(call TEXI2MAN)
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 20/21] build-sys: add txt documentation rules
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (18 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 19/21] build-sys: use a generic TEXI2MAN rule Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-12 10:09   ` Markus Armbruster
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 21/21] build-sys: add qapi doc generation targets Marc-André Lureau
  2017-01-11 14:40 ` [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Markus Armbruster
  21 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Build plain text documentation, and install it.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
 .gitignore |  1 +
 Makefile   | 12 +++++++++---
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index 6f175b391e..e16bddc070 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,6 +40,7 @@
 /qmp-marshal.c
 /qemu-doc.html
 /qemu-doc.info
+/qemu-doc.txt
 /qemu-img
 /qemu-nbd
 /qemu-options.def
diff --git a/Makefile b/Makefile
index d18bac1c31..37d45ee21b 100644
--- a/Makefile
+++ b/Makefile
@@ -81,7 +81,7 @@ Makefile: ;
 configure: ;
 
 .PHONY: all clean cscope distclean html info install install-doc \
-	pdf recurse-all speed test dist msi FORCE
+	pdf txt recurse-all speed test dist msi FORCE
 
 $(call set-vpath, $(SRC_PATH))
 
@@ -90,7 +90,7 @@ LIBS+=-lz $(LIBS_TOOLS)
 HELPERS-$(CONFIG_LINUX) = qemu-bridge-helper$(EXESUF)
 
 ifdef BUILD_DOCS
-DOCS=qemu-doc.html qemu.1 qemu-img.1 qemu-nbd.8 qemu-ga.8
+DOCS=qemu-doc.html qemu-doc.txt qemu.1 qemu-img.1 qemu-nbd.8 qemu-ga.8
 ifdef CONFIG_VIRTFS
 DOCS+=fsdev/virtfs-proxy-helper.1
 endif
@@ -431,6 +431,7 @@ endif
 install-doc: $(DOCS)
 	$(INSTALL_DIR) "$(DESTDIR)$(qemu_docdir)"
 	$(INSTALL_DATA) qemu-doc.html "$(DESTDIR)$(qemu_docdir)"
+	$(INSTALL_DATA) qemu-doc.txt "$(DESTDIR)$(qemu_docdir)"
 ifdef CONFIG_POSIX
 	$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
 	$(INSTALL_DATA) qemu.1 "$(DESTDIR)$(mandir)/man1"
@@ -538,6 +539,10 @@ TEXIFLAG=$(if $(V),,--quiet)
 %.info: %.texi
 	$(call quiet-command,$(MAKEINFO) $(MAKEINFOFLAGS) $< -o $@,"GEN","$@")
 
+%.txt: %.texi
+	$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --no-headers \
+	--plaintext $< -o $@,"GEN   $@")
+
 %.pdf: %.texi
 	$(call quiet-command,texi2pdf $(TEXIFLAG) -I . $<,"GEN","$@")
 
@@ -563,6 +568,7 @@ qemu-ga.8: qemu-ga.texi
 html: qemu-doc.html
 info: qemu-doc.info
 pdf: qemu-doc.pdf
+txt: qemu-doc.txt
 
 qemu-doc.html qemu-doc.info qemu-doc.pdf: \
 	qemu-img.texi qemu-nbd.texi qemu-options.texi qemu-option-trace.texi \
@@ -661,7 +667,7 @@ help:
 	@echo  '  docker          - Help about targets running tests inside Docker containers'
 	@echo  ''
 	@echo  'Documentation targets:'
-	@echo  '  html info pdf'
+	@echo  '  html info pdf txt'
 	@echo  '                  - Build documentation in specified format'
 	@echo  ''
 ifdef CONFIG_WIN32
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [Qemu-devel] [PATCH v7 21/21] build-sys: add qapi doc generation targets
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (19 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 20/21] build-sys: add txt documentation rules Marc-André Lureau
@ 2017-01-09 14:34 ` Marc-André Lureau
  2017-01-11 14:28   ` Markus Armbruster
  2017-01-11 14:40 ` [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Markus Armbruster
  21 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-09 14:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: eblake, armbru, Marc-André Lureau

Generate and install the man, txt and html versions of QAPI
documentation (generate and install qemu-doc.txt too).

Add it also to optional pdf/info targets.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 .gitignore         |  9 +++++++++
 Makefile           | 43 ++++++++++++++++++++++++++++++++++++-------
 configure          |  2 +-
 docs/qmp-intro.txt |  3 +--
 rules.mak          |  2 ++
 5 files changed, 49 insertions(+), 10 deletions(-)

diff --git a/.gitignore b/.gitignore
index e16bddc070..511527bd8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -109,6 +109,15 @@
 /pc-bios/optionrom/kvmvapic.img
 /pc-bios/s390-ccw/s390-ccw.elf
 /pc-bios/s390-ccw/s390-ccw.img
+/docs/qemu-ga-ref.html
+/docs/qemu-ga-ref.txt
+/docs/qemu-qmp-ref.html
+/docs/qemu-qmp-ref.txt
+docs/qemu-ga-ref.info*
+docs/qemu-qmp-ref.info*
+/qemu-ga-qapi.texi
+/qemu-qapi.texi
+*.tps
 .stgit-*
 cscope.*
 tags
diff --git a/Makefile b/Makefile
index 37d45ee21b..3597e95d28 100644
--- a/Makefile
+++ b/Makefile
@@ -91,6 +91,8 @@ HELPERS-$(CONFIG_LINUX) = qemu-bridge-helper$(EXESUF)
 
 ifdef BUILD_DOCS
 DOCS=qemu-doc.html qemu-doc.txt qemu.1 qemu-img.1 qemu-nbd.8 qemu-ga.8
+DOCS+=docs/qemu-qmp-ref.html docs/qemu-qmp-ref.txt docs/qemu-qmp-ref.7
+DOCS+=docs/qemu-ga-ref.html docs/qemu-ga-ref.txt docs/qemu-ga-ref.7
 ifdef CONFIG_VIRTFS
 DOCS+=fsdev/virtfs-proxy-helper.1
 endif
@@ -266,6 +268,7 @@ qemu-ga$(EXESUF): QEMU_CFLAGS += -I qga/qapi-generated
 gen-out-type = $(subst .,-,$(suffix $@))
 
 qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
+qapi-py += $(SRC_PATH)/scripts/qapi2texi.py
 
 qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h :\
 $(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
@@ -395,6 +398,11 @@ distclean: clean
 	rm -f qemu-doc.vr
 	rm -f config.log
 	rm -f linux-headers/asm
+	rm -f qemu-ga-qapi.texi qemu-qapi.texi
+	rm -f docs/qemu-qmp-ref.7 docs/qemu-ga-ref.7
+	rm -f docs/qemu-qmp-ref.txt docs/qemu-ga-ref.txt
+	rm -f docs/qemu-qmp-ref.pdf docs/qemu-ga-ref.pdf
+	rm -f docs/qemu-qmp-ref.html docs/qemu-ga-ref.html
 	for d in $(TARGET_DIRS); do \
 	rm -rf $$d || exit 1 ; \
         done
@@ -432,9 +440,13 @@ install-doc: $(DOCS)
 	$(INSTALL_DIR) "$(DESTDIR)$(qemu_docdir)"
 	$(INSTALL_DATA) qemu-doc.html "$(DESTDIR)$(qemu_docdir)"
 	$(INSTALL_DATA) qemu-doc.txt "$(DESTDIR)$(qemu_docdir)"
+	$(INSTALL_DATA) docs/qemu-qmp-ref.html "$(DESTDIR)$(qemu_docdir)"
+	$(INSTALL_DATA) docs/qemu-qmp-ref.txt "$(DESTDIR)$(qemu_docdir)"
 ifdef CONFIG_POSIX
 	$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
 	$(INSTALL_DATA) qemu.1 "$(DESTDIR)$(mandir)/man1"
+	$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man7"
+	$(INSTALL_DATA) docs/qemu-qmp-ref.7 "$(DESTDIR)$(mandir)/man7"
 ifneq ($(TOOLS),)
 	$(INSTALL_DATA) qemu-img.1 "$(DESTDIR)$(mandir)/man1"
 	$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man8"
@@ -442,6 +454,9 @@ ifneq ($(TOOLS),)
 endif
 ifneq (,$(findstring qemu-ga,$(TOOLS)))
 	$(INSTALL_DATA) qemu-ga.8 "$(DESTDIR)$(mandir)/man8"
+	$(INSTALL_DATA) docs/qemu-ga-ref.html "$(DESTDIR)$(qemu_docdir)"
+	$(INSTALL_DATA) docs/qemu-ga-ref.txt "$(DESTDIR)$(qemu_docdir)"
+	$(INSTALL_DATA) docs/qemu-ga-ref.7 "$(DESTDIR)$(mandir)/man7"
 endif
 endif
 ifdef CONFIG_VIRTFS
@@ -529,9 +544,10 @@ ui/console-gl.o: $(SRC_PATH)/ui/console-gl.c \
 	ui/shader/texture-blit-vert.h ui/shader/texture-blit-frag.h
 
 # documentation
-MAKEINFO=makeinfo
+MAKEINFO=makeinfo -D 'VERSION $(VERSION)'
 MAKEINFOFLAGS=--no-split --number-sections
-TEXIFLAG=$(if $(V),,--quiet)
+TEXIFLAG=$(if $(V),,--quiet) --command='@set VERSION $(VERSION)'
+
 %.html: %.texi
 	$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --no-headers \
 	--html $< -o $@,"GEN","$@")
@@ -544,7 +560,7 @@ TEXIFLAG=$(if $(V),,--quiet)
 	--plaintext $< -o $@,"GEN   $@")
 
 %.pdf: %.texi
-	$(call quiet-command,texi2pdf $(TEXIFLAG) -I . $<,"GEN","$@")
+	$(call quiet-command,texi2pdf $(TEXIFLAG) -I $(SRC_PATH) -I . $< -o $@,"GEN","$@")
 
 qemu-options.texi: $(SRC_PATH)/qemu-options.hx $(SRC_PATH)/scripts/hxtool
 	$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
@@ -558,6 +574,12 @@ qemu-monitor-info.texi: $(SRC_PATH)/hmp-commands-info.hx $(SRC_PATH)/scripts/hxt
 qemu-img-cmds.texi: $(SRC_PATH)/qemu-img-cmds.hx $(SRC_PATH)/scripts/hxtool
 	$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
 
+qemu-qapi.texi: $(qapi-modules) $(qapi-py)
+	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN" "$@")
+
+qemu-ga-qapi.texi: $(SRC_PATH)/qga/qapi-schema.json $(qapi-py)
+	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
+
 qemu.1: qemu-doc.texi qemu-options.texi qemu-monitor.texi qemu-monitor-info.texi
 qemu.1: qemu-option-trace.texi
 qemu-img.1: qemu-img.texi qemu-option-trace.texi qemu-img-cmds.texi
@@ -565,16 +587,23 @@ fsdev/virtfs-proxy-helper.1: fsdev/virtfs-proxy-helper.texi
 qemu-nbd.8: qemu-nbd.texi qemu-option-trace.texi
 qemu-ga.8: qemu-ga.texi
 
-html: qemu-doc.html
-info: qemu-doc.info
-pdf: qemu-doc.pdf
-txt: qemu-doc.txt
+html: qemu-doc.html docs/qemu-qmp-ref.html docs/qemu-ga-ref.html
+info: qemu-doc.info docs/qemu-qmp-ref.info docs/qemu-ga-ref.info
+pdf: qemu-doc.pdf docs/qemu-qmp-ref.pdf docs/qemu-ga-ref.pdf
+txt: qemu-doc.txt docs/qemu-qmp-ref.txt docs/qemu-ga-ref.txt
 
 qemu-doc.html qemu-doc.info qemu-doc.pdf: \
 	qemu-img.texi qemu-nbd.texi qemu-options.texi qemu-option-trace.texi \
 	qemu-monitor.texi qemu-img-cmds.texi qemu-ga.texi \
 	qemu-monitor-info.texi
 
+docs/qemu-ga-ref.dvi docs/qemu-ga-ref.html docs/qemu-ga-ref.info docs/qemu-ga-ref.pdf docs/qemu-ga-ref.txt docs/qemu-ga-ref.7: \
+docs/qemu-ga-ref.texi qemu-ga-qapi.texi
+
+docs/qemu-qmp-ref.dvi docs/qemu-qmp-ref.html docs/qemu-qmp-ref.info docs/qemu-qmp-ref.pdf docs/qemu-qmp-ref.txt docs/qemu-qmp-ref.7: \
+docs/qemu-qmp-ref.texi qemu-qapi.texi
+
+
 ifdef CONFIG_WIN32
 
 INSTALLER = qemu-setup-$(VERSION)$(EXESUF)
diff --git a/configure b/configure
index 3770d7c263..b2ae46e464 100755
--- a/configure
+++ b/configure
@@ -6183,7 +6183,7 @@ fi
 
 # build tree in object directory in case the source is not in the current directory
 DIRS="tests tests/tcg tests/tcg/cris tests/tcg/lm32 tests/libqos tests/qapi-schema tests/tcg/xtensa tests/qemu-iotests"
-DIRS="$DIRS fsdev"
+DIRS="$DIRS docs fsdev"
 DIRS="$DIRS pc-bios/optionrom pc-bios/spapr-rtas pc-bios/s390-ccw"
 DIRS="$DIRS roms/seabios roms/vgabios"
 DIRS="$DIRS qapi-generated"
diff --git a/docs/qmp-intro.txt b/docs/qmp-intro.txt
index f6a3a031e9..60deafbae6 100644
--- a/docs/qmp-intro.txt
+++ b/docs/qmp-intro.txt
@@ -16,8 +16,7 @@ QMP is JSON[1] based and features the following:
 For detailed information on QMP's usage, please, refer to the following files:
 
 o qmp-spec.txt      QEMU Machine Protocol current specification
-o qmp-commands.txt  QMP supported commands (auto-generated at build-time)
-o qmp-events.txt    List of available asynchronous events
+o qemu-qmp-ref.html QEMU QMP commands and events (auto-generated at build-time)
 
 [1] http://www.json.org
 
diff --git a/rules.mak b/rules.mak
index c9739961a7..73de30c7b3 100644
--- a/rules.mak
+++ b/rules.mak
@@ -369,5 +369,7 @@ TEXI2MAN = $(call quiet-command, \
 
 %.1:
 	$(call TEXI2MAN)
+%.7:
+	$(call TEXI2MAN)
 %.8:
 	$(call TEXI2MAN)
-- 
2.11.0

^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 01/21] qapi: replace 'o' for list items
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 01/21] qapi: replace 'o' for list items Marc-André Lureau
@ 2017-01-10  8:49   ` Markus Armbruster
  0 siblings, 0 replies; 40+ messages in thread
From: Markus Armbruster @ 2017-01-10  8:49 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Replace with '*', the common form for list items.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Reviewed-by: Markus Armbruster <armbru@redhat.com>

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 02/21] qapi: move QKeyCode doc body at the top
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 02/21] qapi: move QKeyCode doc body at the top Marc-André Lureau
@ 2017-01-10  8:51   ` Markus Armbruster
  0 siblings, 0 replies; 40+ messages in thread
From: Markus Armbruster @ 2017-01-10  8:51 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Following documentation guidelines.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Reviewed-by: Markus Armbruster <armbru@redhat.com>

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 03/21] qapi: make TODOs named-sections
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 03/21] qapi: make TODOs named-sections Marc-André Lureau
@ 2017-01-10  9:11   ` Markus Armbruster
  0 siblings, 0 replies; 40+ messages in thread
From: Markus Armbruster @ 2017-01-10  9:11 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Have the TODO in the TAG: format, so they will stand out in the
> generated documentation.

I expect TODO without colon to creep back.  Right now, we have three
with and three without colon.  Whack-a-mole is a stupid game, but since
these moles are fairly harmless, I don't care.

The commit message is sub-optimal, though.  At this point, there is no
generated documentation, and we have no concept of "named-sections".
Let's instead say

  qapi: Format TODO comments more consistently

  Consistently put a colon after TODO.  This will make the TODOs stand
  out in the documentation we're going to generate.

With that
Reviewed-by: Markus Armbruster <armbru@redhat.com>

Alternatively, delay the patch until after we generate documentation.

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 07/21] qapi: avoid interleaving sections and parameters
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 07/21] qapi: avoid interleaving sections and parameters Marc-André Lureau
@ 2017-01-10 10:32   ` Markus Armbruster
  0 siblings, 0 replies; 40+ messages in thread
From: Markus Armbruster @ 2017-01-10 10:32 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Follow documentation guideline, body, parameters then additional
> sections.

Commit message wording nit: we don't have a concept of "sections", yet.

I don't think the guidelines demand such an order at this point.  Here's
the applicable paragraph from docs/qapi-code-gen.txt:

    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
    x.y.z)' comment.  For example:

A pedantical reader would conclude only "Since" can follow the "members
of 'data'".

I'm feeling less pedantical, and conclude that additional information
not mentioned in this paragraph can be stuck wherever it reads well.
Matches existing practice.

That said, I don't mind moving stuff around if it helps the doc
generator later on, and doesn't make things less readable.

> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  qapi-schema.json     | 10 +++++-----
>  qapi/event.json      |  4 ++--
>  qapi/introspect.json |  3 +--
>  3 files changed, 8 insertions(+), 9 deletions(-)
>
> diff --git a/qapi-schema.json b/qapi-schema.json
> index ac6352fcf9..10a3506528 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -4729,17 +4729,17 @@
>  # it should be passed by management with device_add command when
>  # a CPU is being hotplugged.
>  #
> +# @node-id: #optional NUMA node ID the CPU belongs to
> +# @socket-id: #optional socket number within node/board the CPU belongs to
> +# @core-id: #optional core number within socket the CPU belongs to
> +# @thread-id: #optional thread number within core the CPU belongs to
> +#
>  # Note: currently there are 4 properties that could be present
>  # but management should be prepared to pass through other
>  # properties with device_add command to allow for future
>  # interface extension. This also requires the filed names to be kept in
>  # sync with the properties passed to -device/device_add.
>  #
> -# @node-id: #optional NUMA node ID the CPU belongs to
> -# @socket-id: #optional socket number within node/board the CPU belongs to
> -# @core-id: #optional core number within socket the CPU belongs to
> -# @thread-id: #optional thread number within core the CPU belongs to
> -#
>  # Since: 2.7
>  ##
>  { 'struct': 'CpuInstanceProperties',

No objection.

> diff --git a/qapi/event.json b/qapi/event.json
> index 37bf34ed6d..c7689b211d 100644
> --- a/qapi/event.json
> +++ b/qapi/event.json
> @@ -272,9 +272,9 @@
>  #
>  # Emitted when guest executes ACPI _OST method.
>  #
> -# Since: 2.1
> -#
>  # @info: ACPIOSTInfo type as described in qapi-schema.json
> +#
> +# Since: 2.1
>  ##
>  { 'event': 'ACPI_DEVICE_OST',
>       'data': { 'info': 'ACPIOSTInfo' } }

Consistency improvement.

> diff --git a/qapi/introspect.json b/qapi/introspect.json
> index 464097a235..f6adc439bb 100644
> --- a/qapi/introspect.json
> +++ b/qapi/introspect.json
> @@ -78,14 +78,13 @@
>  # @SchemaInfo:
>  #
>  # @name: the entity's name, inherited from @base.
> +#        The SchemaInfo is always referenced by this name.
>  #        Commands and events have the name defined in the QAPI schema.
>  #        Unlike command and event names, type names are not part of
>  #        the wire ABI.  Consequently, type names are meaningless
>  #        strings here, although they are still guaranteed unique
>  #        regardless of @meta-type.
>  #
> -# All references to other SchemaInfo are by name.
> -#
>  # @meta-type: the entity's meta type, inherited from @base.
>  #
>  # Additional members depend on the value of @meta-type.

Okay.

PATCH 02 "qapi: move QKeyCode doc body at the top" could be squashed
into this one, I think.

Overall, no objections, except the commit message's first line
forward-references the "sections" concept, and its rationale is a bit
weak.

What about this:

    qapi: Reorder doc comments for future doc generator

    The doc generator we're going to add expects a fairly rigid doc
    comment structure.  Reorder / rephrase some to please it.

To be clear: I still doubt this rigid order is a good idea.  But as long
as I can't point to actual harm, I'm willing to accept it, in the
interest of getting stuff done.

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 08/21] qapi: move experimental note down
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 08/21] qapi: move experimental note down Marc-André Lureau
@ 2017-01-10 12:08   ` Markus Armbruster
  2017-01-10 12:56     ` Marc-André Lureau
  0 siblings, 1 reply; 40+ messages in thread
From: Markus Armbruster @ 2017-01-10 12:08 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Use a better 'Note:' section, move it below parameters following
> guidelines.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Again, I don't mind moving stuff around if it helps getting this series
wrapped, and doesn't make things less readable.  My only nit is the
commit message's claim about the guidelines.  Let's sidestep it:

    Move these notes down and prefix with "Note:", to please the doc
    generator we're going to add.

Okay?

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 12/21] qapi.py: fix line break before binary operator pep8
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 12/21] qapi.py: fix line break before binary operator pep8 Marc-André Lureau
@ 2017-01-10 12:23   ` Markus Armbruster
  2017-01-10 12:55     ` Marc-André Lureau
  0 siblings, 1 reply; 40+ messages in thread
From: Markus Armbruster @ 2017-01-10 12:23 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Python code style accepts both form, but pep8 complains. Better to clean
> up the single warning for now, so new errors stand out more easily.
>
> Fix scripts/qapi.py:1539:21: W503 line break before binary operator

PEP 8 permits both, but prefers line break before binary operator.
Thus, this warning is clearly bogus.  What version of pep8 do you use?
Mine doesn't warn.

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 12/21] qapi.py: fix line break before binary operator pep8
  2017-01-10 12:23   ` Markus Armbruster
@ 2017-01-10 12:55     ` Marc-André Lureau
  2017-01-10 16:04       ` Markus Armbruster
  0 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-10 12:55 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Marc-André Lureau, qemu-devel

Hi

----- Original Message -----
> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
> 
> > Python code style accepts both form, but pep8 complains. Better to clean
> > up the single warning for now, so new errors stand out more easily.
> >
> > Fix scripts/qapi.py:1539:21: W503 line break before binary operator
> 
> PEP 8 permits both, but prefers line break before binary operator.
> Thus, this warning is clearly bogus.  What version of pep8 do you use?
> Mine doesn't warn.

I tried both 1.6.2 and 1.7.0

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 08/21] qapi: move experimental note down
  2017-01-10 12:08   ` Markus Armbruster
@ 2017-01-10 12:56     ` Marc-André Lureau
  0 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-10 12:56 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Marc-André Lureau, qemu-devel

Hi

----- Original Message -----
> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
> 
> > Use a better 'Note:' section, move it below parameters following
> > guidelines.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Again, I don't mind moving stuff around if it helps getting this series
> wrapped, and doesn't make things less readable.  My only nit is the
> commit message's claim about the guidelines.  Let's sidestep it:
> 
>     Move these notes down and prefix with "Note:", to please the doc
>     generator we're going to add.
> 
> Okay?
> 

ok, thanks

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 12/21] qapi.py: fix line break before binary operator pep8
  2017-01-10 12:55     ` Marc-André Lureau
@ 2017-01-10 16:04       ` Markus Armbruster
  2017-01-10 16:25         ` Marc-André Lureau
  0 siblings, 1 reply; 40+ messages in thread
From: Markus Armbruster @ 2017-01-10 16:04 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Marc-André Lureau, qemu-devel

Marc-André Lureau <mlureau@redhat.com> writes:

> Hi
>
> ----- Original Message -----
>> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
>> 
>> > Python code style accepts both form, but pep8 complains. Better to clean
>> > up the single warning for now, so new errors stand out more easily.
>> >
>> > Fix scripts/qapi.py:1539:21: W503 line break before binary operator
>> 
>> PEP 8 permits both, but prefers line break before binary operator.
>> Thus, this warning is clearly bogus.  What version of pep8 do you use?
>> Mine doesn't warn.
>
> I tried both 1.6.2 and 1.7.0

I tried again, and I can now see the warning.  No idea what I did wrong
the first time.

Turns out PEP8 used to demand line break after the operator, but no
more[1].  The pep8 program hasn't caught up, but it will[2]: its current
git HEAD already suppresses W503.  There's work in progress[3] on a new
W504 to warn on line break after binary operator.

We will have to live with a mix of old and new pep8 / pycodestyle
versions.  Changing code to make new versions instead of old versions
complain instead is not a good idea.  We should simply ignore the
obsolete warning from old versions.

pep8 lets you do that with --ignore=W503.  Sadly, --ignore overwrites
the default, so you have to --ignore=E121,E123,E126,E226,E24,E704,W503.
Or have a file tox.ini with

    [pep8]
    ignore=E121,E123,E126,E226,E24,E704,W503


[1] https://hg.python.org/peps/rev/3857909d7956
[2] https://github.com/PyCQA/pycodestyle/issues/498
[3] https://github.com/PyCQA/pycodestyle/pull/502

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 12/21] qapi.py: fix line break before binary operator pep8
  2017-01-10 16:04       ` Markus Armbruster
@ 2017-01-10 16:25         ` Marc-André Lureau
  0 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-10 16:25 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel

Hi

On Tue, Jan 10, 2017 at 5:04 PM Markus Armbruster <armbru@redhat.com> wrote:

> Marc-André Lureau <mlureau@redhat.com> writes:
>
> > Hi
> >
> > ----- Original Message -----
> >> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
> >>
> >> > Python code style accepts both form, but pep8 complains. Better to
> clean
> >> > up the single warning for now, so new errors stand out more easily.
> >> >
> >> > Fix scripts/qapi.py:1539:21: W503 line break before binary operator
> >>
> >> PEP 8 permits both, but prefers line break before binary operator.
> >> Thus, this warning is clearly bogus.  What version of pep8 do you use?
> >> Mine doesn't warn.
> >
> > I tried both 1.6.2 and 1.7.0
>
> I tried again, and I can now see the warning.  No idea what I did wrong
> the first time.
>
> Turns out PEP8 used to demand line break after the operator, but no
> more[1].  The pep8 program hasn't caught up, but it will[2]: its current
> git HEAD already suppresses W503.  There's work in progress[3] on a new
> W504 to warn on line break after binary operator.
>
> We will have to live with a mix of old and new pep8 / pycodestyle
> versions.  Changing code to make new versions instead of old versions
> complain instead is not a good idea.  We should simply ignore the
> obsolete warning from old versions.
>
>
ok, feel free to drop this patch then

thanks


> pep8 lets you do that with --ignore=W503.  Sadly, --ignore overwrites
> the default, so you have to --ignore=E121,E123,E126,E226,E24,E704,W503.
> Or have a file tox.ini with
>
>     [pep8]
>     ignore=E121,E123,E126,E226,E24,E704,W503
>
>
> [1] https://hg.python.org/peps/rev/3857909d7956
> [2] https://github.com/PyCQA/pycodestyle/issues/498
> [3] https://github.com/PyCQA/pycodestyle/pull/502
>
> --
Marc-André Lureau

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 15/21] qapi: add qapi2texi script
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 15/21] qapi: add qapi2texi script Marc-André Lureau
@ 2017-01-11 14:26   ` Markus Armbruster
  2017-01-11 16:21     ` Marc-André Lureau
  0 siblings, 1 reply; 40+ messages in thread
From: Markus Armbruster @ 2017-01-11 14:26 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> 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 description:
>
>   ##
>   # @symbol:
>   #
>   # Symbol body ditto ergo sum. Foo bar
>   # baz ding.
>   #
>   # @param1: the frob to frobnicate
>   # @param2: #optional how hard to frobnicate
>   #
>   # Returns: the frobnicated frob.
>   #          If frob isn't frobnicatable, GenericError.
>   #
>   # 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 | tag_section | freeform_comment }
> member = "# @" name ':' [ text ] "\n" freeform_comment
> tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:", "Examples:" ) [ text ]  "\n" freeform_comment
> text = free text with markup
>
> Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
> both as freeform_comment and as symbol_comment.  The actual parser
> recognizes symbol_comment.
>
> See docs/qapi-code-gen.txt for more details.
>
> Deficiencies:

Perhaps "Deficiencies and limitations".

> - the generated QMP documentation includes internal types
> - union-type support is lacking

"union type" (no dash).

> - type information is lacking in generated documentation
> - doc comment error message positions are imprecise, they point
>   to the beginning of the comment.
> - see other TODO/FIXME in this commit

Suggest: - a few minor issues, all marked TODO/FIXME in the code

>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
[diffstat snipped...]
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 3d5f9e1eaf..a92a86f428 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -125,6 +125,122 @@ class QAPISemError(QAPIError):
>                             info['parent'], msg)
>  
>  
> +class QAPIDoc(object):
> +    class Section(object):
> +        def __init__(self, name=None):
> +            # optional section name (argument/member or section name)
> +            self.name = name
> +            # the list of lines 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, info):
> +        # self.parser is used to report errors with QAPIParseError.  The
> +        # resulting error position depends on the state of the parser.
> +        # It happens to be the beginning of the comment.  More or less
> +        # servicable, but action at a distance.
> +        self.parser = parser
> +        self.info = info
> +        self.symbol = None
> +        self.body = QAPIDoc.Section()
> +        # dict mapping parameter name to ArgSection
> +        self.args = OrderedDict()
> +        # a list of Section
> +        self.sections = []
> +        # the current section
> +        self.section = self.body
> +        # associated expression (to be set by expression parser)
> +        self.expr = None
> +
> +    def has_section(self, name):
> +        """Return True if we have a section with this name."""
> +        for i in self.sections:
> +            if i.name == name:
> +                return True
> +        return False
> +
> +    def append(self, line):
> +        """Parse a comment line and add it to the documentation."""
> +        line = line[1:]
> +        if not line:
> +            self._append_freeform(line)
> +            return
> +
> +        if line[0] != ' ':
> +            raise QAPIParseError(self.parser, "Missing space after #")
> +        line = line[1:]
> +
> +        # FIXME not nice: things like '#  @foo:' and '# @foo: ' aren't
> +        # recognized, and get silently treated as ordinary text
> +        if self.symbol:
> +            self._append_symbol_line(line)
> +        elif not self.body.content and line.startswith("@"):
> +            if not line.endswith(":"):
> +                raise QAPIParseError(self.parser, "Line should end with :")
> +            self.symbol = line[1:-1]
> +            # FIXME invalid names other than the empty string aren't flagged
> +            if not self.symbol:
> +                raise QAPIParseError(self.parser, "Invalid name")
> +        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:",
> +                      "TODO:"):
> +            line = line[len(name)+1:]
> +            self._start_section(name[:-1])
> +
> +        self._append_freeform(line)
> +
> +    def _start_args_section(self, name):
> +        # FIXME invalid names other than the empty string aren't flagged
> +        if not name:
> +            raise QAPIParseError(self.parser, "Invalid parameter name")
> +        if name in self.args:
> +            raise QAPIParseError(self.parser,
> +                                 "'%s' parameter name duplicated" % name)
> +        if self.sections:
> +            raise QAPIParseError(self.parser,
> +                                 "'%s' parameter documentation is interleaved "
> +                                 "with other sections" % name)

This error message is rather confusing.  Ideally, we'd point to the the
source code that made us add to self.sections, but that's not in the
cards right now.  Here's my best try:

               raise QAPIParseError(self.parser,
                                    "'@%s:' can't follow '%s' section"
                                    % (name, self.sections[0].name))

> +        self.section = QAPIDoc.ArgSection(name)
> +        self.args[name] = self.section
> +
> +    def _start_section(self, name=""):

Note: @name changed to default to "" since v6 for the benefit of
_append_freeform() below.

> +        if name in ("Returns", "Since") and self.has_section(name):
> +            raise QAPIParseError(self.parser,
> +                                 "Duplicated '%s' section" % name)
> +        self.section = QAPIDoc.Section(name)
> +        self.sections.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()):

PEP8 recommends breaking lines before operators in new code.

> +            self._start_section()

Changed from v6's

               raise QAPIParseError(self.parser, "Invalid section indentation")

May well be an improvement (I'm certainly no fan of this error myself),
but it throws my review off the fast track: now I have to figure out how
the new code works.  Can you give me a hint on what this change does?

Or should we take a shortcut and revert to v6 here?  We can always
improve on top.

> +        if (in_arg or not self.section.name or
> +                not self.section.name.startswith("Example")):

Again, break before the operator.

> +            line = line.strip()
> +        self.section.append(line)
> +
> +
>  class QAPISchemaParser(object):
>  
>      def __init__(self, fp, previously_included=[], incl_info=None):
> @@ -140,11 +256,17 @@ 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 == '#':
> +                doc = self.get_doc(info)
> +                self.docs.append(doc)
> +                continue
> +
>              expr = self.get_expr(False)
>              if isinstance(expr, dict) and "include" in expr:
>                  if len(expr) != 1:
> @@ -162,6 +284,7 @@ class QAPISchemaParser(object):
>                          raise QAPISemError(info, "Inclusion loop for %s"
>                                             % include)
>                      inf = inf['parent']
> +
>                  # skip multiple include of the same file
>                  if incl_abs_fname in previously_included:
>                      continue
> @@ -172,12 +295,19 @@ 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
> +                        self.docs[-1].info['file'] == fname and
> +                        not self.docs[-1].expr):

Again, break before the operator.

> +                    self.docs[-1].expr = expr
> +                    expr_elem['doc'] = self.docs[-1]
> +
>                  self.exprs.append(expr_elem)
>  
> -    def accept(self):
> +    def accept(self, skip_comment=True):
>          while True:
>              self.tok = self.src[self.cursor]
>              self.pos = self.cursor
> @@ -185,7 +315,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 == "'":

Copied from review of v3, so I don't forget:

Comment tokens are thrown away as before, except when the parser asks
for them by passing skip_comment=False, or when the comment token starts
with ##.  The parser asks while parsing a doc comment, in get_doc().

This is a backchannel from the parser to the lexer.  I'd rather avoid
such lexer hacks, but I guess we can address that on top.

A comment starting with ## inside an expression is now a syntax error.
For instance, input

    {
    ##

yields

    /dev/stdin:2:1: Expected string or "}"

Rather unfriendly error message, but we can fix that on top.

> @@ -319,6 +455,28 @@ class QAPISchemaParser(object):
>              raise QAPIParseError(self, 'Expected "{", "[" or string')
>          return expr
>  
> +    def get_doc(self, info):
> +        if self.val != '##':
> +            raise QAPIParseError(self, "Junk after '##' at start of "
> +                                 "documentation comment")
> +
> +        doc = QAPIDoc(self, info)
> +        self.accept(False)
> +        while self.tok == '#':
> +            if self.val.startswith('##'):
> +                # End of doc comment
> +                if self.val != '##':
> +                    raise QAPIParseError(self, "Junk after '##' at end of "
> +                                         "documentation comment")
> +                self.accept()
> +                return doc
> +            else:
> +                doc.append(self.val)
> +            self.accept(False)
> +
> +        raise QAPIParseError(self, "Documentation comment must end with '##'")
> +
> +
>  #
>  # Semantic analysis of schema expressions
>  # TODO fold into QAPISchema
> @@ -703,6 +861,11 @@ def check_exprs(exprs):
>      for expr_elem in exprs:
>          expr = expr_elem['expr']
>          info = expr_elem['info']
> +
> +        if 'doc' not in expr_elem:
> +            raise QAPISemError(info,
> +                               "Expression missing documentation comment")
> +
>          if 'enum' in expr:
>              check_keys(expr_elem, 'enum', ['data'], ['prefix'])
>              add_enum(expr['enum'], info, expr['data'])
> @@ -761,6 +924,84 @@ def check_exprs(exprs):
>      return exprs
>  
>  
> +def check_freeform_doc(doc):
> +    if doc.symbol:
> +        raise QAPISemError(doc.info,
> +                           "Documention for '%s' is not followed"
> +                           " by the definition" % doc.symbol)
> +
> +    body = str(doc.body)
> +    if re.search(r'@\S+:', body, re.MULTILINE):
> +        raise QAPISemError(doc.info,
> +                           "Free-form documentation block must not contain"
> +                           " @NAME: sections")
> +
> +
> +def check_definition_doc(doc, expr, info):

Lots of churn in this function.  It can certainly use improvement, as
pointed out in prior reviews, but it triggers still more review.  Based
on the tests, I guess it's for finding documented parameters that don't
actually exist, and to diagnose optional mismatch.  Anything else?

Or should we take a shortcut and revert to v6 here?  We can always
improve on top.  I think v6 would need a # TODO Should ensure #optional
matches the schema.

> +    for i in ('enum', 'union', 'alternate', 'struct', 'command', 'event'):
> +        if i in expr:
> +            meta = i
> +            break
> +
> +    name = expr[meta]
> +    if doc.symbol != name:
> +        raise QAPISemError(info, "Definition of '%s' follows documentation"
> +                           " for '%s'" % (name, doc.symbol))
> +    if doc.has_section('Returns') and 'command' not in expr:
> +        raise QAPISemError(info, "'Returns:' is only valid for commands")

Copied from review of v5, so I don't forget:

We accept 'Returns:' even when the command doesn't return anything,
because we currently use it to document errors, too.  Can't say I like
that, but it's out of scope here.

> +
> +    if meta == 'union':
> +        args = expr.get('base', [])
> +    else:
> +        args = expr.get('data', [])
> +    if isinstance(args, dict):
> +        args = args.keys()
> +
> +    if meta == 'alternate' or \
> +       (meta == 'union' and not expr.get('discriminator')):
> +        args.append('type')
> +
> +    if isinstance(args, list):
> +        for arg in args:
> +            if arg[0] == '*':
> +                opt = True
> +                desc = doc.args.get(arg[1:])
> +            else:
> +                opt = False
> +                desc = doc.args.get(arg)
> +            if not desc:
> +                continue
> +            desc_opt = "#optional" in str(desc)
> +            if desc_opt and not opt:
> +                raise QAPISemError(info, "Description has #optional, "
> +                                   "but the declaration doesn't")
> +            if not desc_opt and opt:
> +                # silently fix the doc
> +                desc.append("#optional")
> +
> +    doc_args = set(doc.args.keys())
> +    args = set([name.strip('*') for name in args])
> +    if not doc_args.issubset(args):
> +        raise QAPISemError(info, "The following documented members are not in "
> +                           "the declaration: %s" % ", ".join(doc_args - args))

Copied from review of v5, so I don't forget:

As explained in review of v3, this is only a subset of the real set of
members.  Computing the exact set is impractical when working with the
abstract syntax tree.  I believe we'll eventually have to rewrite this
code to work with the QAPISchemaEntity instead.

> +
> +
> +def check_docs(docs):
> +    for doc in docs:
> +        for section in doc.args.values() + doc.sections:
> +            content = str(section)
> +            if not content or content.isspace():
> +                raise QAPISemError(doc.info,
> +                                   "Empty doc section '%s'" % section.name)
> +
> +        if not doc.expr:
> +            check_freeform_doc(doc)
> +        else:
> +            check_definition_doc(doc, doc.expr, doc.info)
> +
> +    return docs
> +
> +
>  #
>  # Schema compiler frontend
>  #
> @@ -1229,7 +1470,9 @@ class QAPISchemaEvent(QAPISchemaEntity):
>  class QAPISchema(object):
>      def __init__(self, fname):
>          try:
> -            self.exprs = check_exprs(QAPISchemaParser(open(fname, "r")).exprs)
> +            parser = QAPISchemaParser(open(fname, "r"))
> +            self.exprs = check_exprs(parser.exprs)
> +            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 0000000000..436749ec7b
> --- /dev/null
> +++ b/scripts/qapi2texi.py
> @@ -0,0 +1,266 @@
> +#!/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 = """
> +@deftypefn {type} {{}} {name}
> +
> +{body}
> +
> +@end deftypefn
> +
> +""".format
> +
> +ENUM_FMT = """
> +@deftp Enum {name}
> +
> +{body}
> +
> +@end deftp
> +
> +""".format
> +
> +STRUCT_FMT = """
> +@deftp {{{type}}} {name}
> +
> +{body}
> +
> +@end deftp
> +
> +""".format
> +
> +EXAMPLE_FMT = """@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'\b_([^_\n]+)_\b', 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"""
> +    # TODO: Neglects to escape @ characters.
> +    # We should probably escape them in subst_braces(), and rename the
> +    # function to subst_special() or subs_texi_special().  If we do that, we
> +    # need to delay it until after subst_vars() in texi_format().
> +    doc = subst_braces(doc).strip('\n')
> +    return EXAMPLE_FMT(code=doc)
> +
> +
> +def texi_format(doc):
> +    """
> +    Format documentation
> +
> +    Lines starting with:
> +    - |: generates an @example
> +    - =: generates @section
> +    - ==: generates @subsection
> +    - 1. or 1): generates an @enumerate @item
> +    - */-: 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 == ""
> +
> +        # FIXME: Doing this in a single if / elif chain is
> +        # problematic.  For instance, a line without markup terminates
> +        # a list if it follows a blank line (reaches the final elif),
> +        # but a line with some *other* markup, such as a = title
> +        # doesn't.
> +        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):

[.] is an unusual way to say \.

Perhaps we should consistently use r"..." for strings used as regular
expressions.

> +            if not inlist:
> +                lines.append("@enumerate")
> +                inlist = "enumerate"
> +            line = line[line.find(" ")+1:]
> +            lines.append("@item")
> +        elif re.match("^[*-] ", line):
> +            if not inlist:
> +                lines.append("@itemize %s" % {'*': "@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_body(doc):
> +    """
> +    Format the body of a symbol documentation:
> +    - main body
> +    - table of arguments
> +    - followed by "Returns/Notes/Since/Example" sections
> +    """
> +    body = texi_format(str(doc.body)) + "\n"
> +    if doc.args:
> +        body += "@table @asis\n"
> +        for arg, section in doc.args.iteritems():
> +            desc = str(section)
> +            opt = ''
> +            if "#optional" in desc:
> +                desc = desc.replace("#optional", "")
> +                opt = ' (optional)'
> +            body += "@item @code{'%s'}%s\n%s\n" % (arg, opt,
> +                                                   texi_format(desc))
> +        body += "@end table\n"
> +
> +    for section in doc.sections:
> +        name, doc = (section.name, str(section))
> +        func = texi_format
> +        if name.startswith("Example"):
> +            func = texi_example
> +
> +        if name:

@quotation produces confusing .txt and .html output, as discussed in
review of v6.  Either drop it, or record the problem in a comment, e.g.

               # FIXME the indentation produced by @quotation in .txt and
               # .html output is confusing

> +            body += "\n@quotation %s\n%s\n@end quotation" % \
> +                    (name, func(doc))
> +        else:
> +            body += func(doc)
> +
> +    return body
> +
> +
> +def texi_alternate(expr, doc):
> +    """Format an alternate to texi"""
> +    body = texi_body(doc)
> +    return STRUCT_FMT(type="Alternate",
> +                      name=doc.symbol,
> +                      body=body)
> +
> +
> +def texi_union(expr, doc):
> +    """Format a union to texi"""
> +    discriminator = expr.get("discriminator")
> +    if discriminator:
> +        union = "Flat Union"
> +    else:
> +        union = "Simple Union"

Not sure flat vs. simple matters for users of generated documentation,
but let's not worry about that now.

> +
> +    body = texi_body(doc)
> +    return STRUCT_FMT(type=union,
> +                      name=doc.symbol,
> +                      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"""
> +    body = texi_body(doc)
> +    return STRUCT_FMT(type="Struct",
> +                      name=doc.symbol,
> +                      body=body)
> +
> +
> +def texi_command(expr, doc):
> +    """Format a command to texi"""
> +    body = texi_body(doc)
> +    return COMMAND_FMT(type="Command",
> +                       name=doc.symbol,
> +                       body=body)
> +
> +
> +def texi_event(expr, doc):
> +    """Format an event to texi"""
> +    body = texi_body(doc)
> +    return COMMAND_FMT(type="Event",
> +                       name=doc.symbol,
> +                       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}[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])
> +    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 2841c5144a..b29f996fdc 100644
> --- a/docs/qapi-code-gen.txt
> +++ b/docs/qapi-code-gen.txt
> @@ -44,40 +44,148 @@ Input must be ASCII (although QMP supports full Unicode strings, the
>  QAPI parser does not).  At present, there is no place where a QAPI
>  schema requires the use of JSON numbers or null.
>  
> +
> +=== Comments ===
> +
>  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
> -x.y.z)' comment.  For example:
> -
> -    ##
> -    # @BlockStats:
> -    #
> -    # Statistics of a virtual block device or a block backing device.
> -    #
> -    # @device: #optional If the stats are for a virtual block device, the name
> -    #          corresponding to the virtual block device.
> -    #
> -    # @stats:  A @BlockDeviceStats for the device.
> -    #
> -    # @parent: #optional This describes the file block device if it has one.
> -    #
> -    # @backing: #optional This describes the backing block device if it has one.
> -    #           (Since 2.0)
> -    #
> -    # Since: 0.14.0
> -    ##
> -    { 'struct': 'BlockStats',
> -      'data': {'*device': 'str', 'stats': 'BlockDeviceStats',
> -               '*parent': 'BlockStats',
> -               '*backing': 'BlockStats'} }
> +newline is ignored.
> +
> +A multi-line comment that starts and ends with a '##' line is a
> +documentation comment.  These are parsed by the documentation
> +generator, which recognizes certain markup detailed below.
> +
> +
> +==== Documentation markup ====
> +
> +Comment text starting with '=' is a section title:
> +
> +    # = Section title
> +
> +Double the '=' for a subsection title:
> +
> +    # == Subection title
> +
> +'|' denotes examples:
> +
> +    # | Text of the example, may span
> +    # | multiple lines
> +
> +'*' starts an itemized list:
> +
> +    # * First item, may span
> +    #   multiple lines
> +    # * Second item
> +
> +You can also use '-' instead of '*'.
> +
> +A decimal number followed by '.' starts a numbered list:
> +
> +    # 1. First item, may span
> +    # multiple lines
> +    # 2. Second item
> +
> +The actual number doesn't matter.  You could even use '*' instead of
> +'2.' for the second item.
> +
> +FIXME what exactly ends a list

Can you say offhand what ends a list?  If yes, can we resolve this FIXME
now rather than later?

> +
> +Additional whitespace between the initial '#' and the comment text is
> +permitted.
> +
> +*foo* and _foo_ are for strong and emphasis styles respectively (they
> +do not work over multiple lines). @foo is used to reference a name in
> +the schema.
> +
> +Example:
> +
> +##
> +# = 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
> +#
> +##
> +
> +
> +==== Expression documentation ====
> +
> +Each expression that isn't an include directive must be preceded by a
> +documentation block.  Such blocks are called expression documentation
> +blocks.
> +
> +The first line of the documentation names the expression, then the
> +documentation body is provided, then individual documentation about
> +each member of 'data' is provided. Finally, several tagged sections
> +can be added.

In my review of v6, I wrote:

    I'm afraid this is more aspiration than specification: the parser
    accepts these things in almost any order.

    Could be filed under "deficiencies" for now.

To file under "deficiencies", we need to add a FIXME or TODO either
here, or in the parser.  Assuming you haven't tightened the parser
meanwhile (hope you haven't; we need to control the churn).

    Member of 'data' won't remain accurate.  Consider:

        { 'union': 'GlusterServer',
          'base': { 'type': 'GlusterTransport' },
          'discriminator': 'type',
          'data': { 'unix': 'UnixSocketAddress',
                    'tcp': 'InetSocketAddress' } }

    Its doc comment currently doesn't contain argument sections.  It should
    eventually contain @type:, @unix: and @tcp:.  Only the latter two are
    members of 'data'.

    I should propose something better, but I'm getting perilously close to
    the christmas break already.  Later.

Also: passive voice is meh (not your idea; you adapted the existing
text).

What about:

   The documentation block consists of a first line naming the
   expression, an optional overview, a description of each argument (for
   commands and events) or member (for structs, unions and alternates),
   and optional tagged sections.

I still don't like the use of "section" for both the = things and the
tagged sections, but it's not important enough to spend time on it now.

> +
> +Optional members are tagged with the phrase '#optional', often with

If we adopt my text, we should say "arguments / members" here.

> +their default value; and extensions added after the expression was
> +first released are also given a '(since x.y.z)' comment.
> +
> +A tagged section starts with one of the following words:
> +"Note:"/"Notes:", "Since:", "Example"/"Examples", "Returns:", "TODO:".

What ends such a section?  The start of the next one / end of the
comment block?

I suspect we could avoid PATCH 03 by recognizing "TODO" in addition to
"TODO:".  Might also be more robust.  But let's ignore this now.

> +
> +A 'Since: x.y.z' tag lists the release that introduced the expression.

Nitpick: 'Since: x.y.z' is a tagged section, not a tag.

> +
> +For example:
> +
> +##
> +# @BlockStats:
> +#
> +# Statistics of a virtual block device or a block backing device.
> +#
> +# @device: #optional If the stats are for a virtual block device, the name
> +#          corresponding to the virtual block device.
> +#
> +# @node-name: #optional The node name of the device. (since 2.3)
> +#
> +# ... more members ...
> +#
> +# Since: 0.14.0
> +##
> +{ 'struct': 'BlockStats',
> +  'data': {'*device': 'str', '*node-name': 'str',
> +           ... more members ... } }
> +
> +##
> +# @query-blockstats:
> +#
> +# Query the @BlockStats for all virtual block devices.
> +#
> +# @query-nodes: #optional If true, the command will query all the
> +#               block nodes ... explain, explain ...  (since 2.3)
> +#
> +# Returns: A list of @BlockStats for each virtual block devices.
> +#
> +# Since: 0.14.0
> +#
> +# Example:
> +#
> +# -> { "execute": "query-blockstats" }
> +# <- {
> +#      ... lots of output ...
> +#    }
> +#
> +##
> +{ 'command': 'query-blockstats',
> +  'data': { '*query-nodes': 'bool' },
> +  'returns': ['BlockStats'] }
> +
> +==== Free-form documentation ====
> +
> +A documentation block that isn't an expression documentation block is
> +a free-form documentation block.  These may be used to provide
> +additional text and structuring content.
> +
> +
> +=== Schema overview ===
>  
>  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
[Tests look okay, snipped...]

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 21/21] build-sys: add qapi doc generation targets
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 21/21] build-sys: add qapi doc generation targets Marc-André Lureau
@ 2017-01-11 14:28   ` Markus Armbruster
  0 siblings, 0 replies; 40+ messages in thread
From: Markus Armbruster @ 2017-01-11 14:28 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Generate and install the man, txt and html versions of QAPI
> documentation (generate and install qemu-doc.txt too).
>
> Add it also to optional pdf/info targets.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Reviewed-by: Markus Armbruster <armbru@redhat.com>

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed)
  2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
                   ` (20 preceding siblings ...)
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 21/21] build-sys: add qapi doc generation targets Marc-André Lureau
@ 2017-01-11 14:40 ` Markus Armbruster
  2017-01-11 14:43   ` Marc-André Lureau
  21 siblings, 1 reply; 40+ messages in thread
From: Markus Armbruster @ 2017-01-11 14:40 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Add a qapi2texi script to generate the documentation from the qapi
> schemas.
>
> The SQUASHED patch in this series is a squashed version of the
> documentation move from qmp-commands.txt to the schemas. The whole
> version (not sent on the ML to avoid spamming) is in the following git
> branch: https://github.com/elmarco/qemu/commits/qapi-doc
>
> PDF preview:
> https://fedorapeople.org/~elmarco/qemu-qmp-ref.pdf (v7 without type info)

PATCH 15 "qapi: add qapi2texi script" might need another respin,
depending on discussion.  The rest looks ready to me, with a few commit
message tweaks.

I could take PATCH 01-08,11 right away if that helps.

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed)
  2017-01-11 14:40 ` [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Markus Armbruster
@ 2017-01-11 14:43   ` Marc-André Lureau
  0 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-11 14:43 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: qemu-devel

Hi

On Wed, Jan 11, 2017 at 3:41 PM Markus Armbruster <armbru@redhat.com> wrote:

> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
>
> > Add a qapi2texi script to generate the documentation from the qapi
> > schemas.
> >
> > The SQUASHED patch in this series is a squashed version of the
> > documentation move from qmp-commands.txt to the schemas. The whole
> > version (not sent on the ML to avoid spamming) is in the following git
> > branch: https://github.com/elmarco/qemu/commits/qapi-doc
> >
> > PDF preview:
> > https://fedorapeople.org/~elmarco/qemu-qmp-ref.pdf (v7 without type
> info)
>
> PATCH 15 "qapi: add qapi2texi script" might need another respin,
> depending on discussion.  The rest looks ready to me, with a few commit
> message tweaks.
>
> I could take PATCH 01-08,11 right away if that helps.
>

That will certainly help if you could take patches (including the one with
minor nits if any), then I can rebase on top of your branch (qapi-next ?)
-- 
Marc-André Lureau

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 15/21] qapi: add qapi2texi script
  2017-01-11 14:26   ` Markus Armbruster
@ 2017-01-11 16:21     ` Marc-André Lureau
  2017-01-12 13:17       ` Markus Armbruster
  0 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-11 16:21 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Marc-André Lureau, qemu-devel

Hi

----- Original Message -----
> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
> 
> > 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 description:
> >
> >   ##
> >   # @symbol:
> >   #
> >   # Symbol body ditto ergo sum. Foo bar
> >   # baz ding.
> >   #
> >   # @param1: the frob to frobnicate
> >   # @param2: #optional how hard to frobnicate
> >   #
> >   # Returns: the frobnicated frob.
> >   #          If frob isn't frobnicatable, GenericError.
> >   #
> >   # 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 | tag_section | freeform_comment
> > }
> > member = "# @" name ':' [ text ] "\n" freeform_comment
> > tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:",
> > "Examples:" ) [ text ]  "\n" freeform_comment
> > text = free text with markup
> >
> > Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
> > both as freeform_comment and as symbol_comment.  The actual parser
> > recognizes symbol_comment.
> >
> > See docs/qapi-code-gen.txt for more details.
> >
> > Deficiencies:
> 
> Perhaps "Deficiencies and limitations".

ok

> 
> > - the generated QMP documentation includes internal types
> > - union-type support is lacking
> 
> "union type" (no dash).
> 
> > - type information is lacking in generated documentation
> > - doc comment error message positions are imprecise, they point
> >   to the beginning of the comment.
> > - see other TODO/FIXME in this commit
> 
> Suggest: - a few minor issues, all marked TODO/FIXME in the code
> 

ok

> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> [diffstat snipped...]
> > diff --git a/scripts/qapi.py b/scripts/qapi.py
> > index 3d5f9e1eaf..a92a86f428 100644
> > --- a/scripts/qapi.py
> > +++ b/scripts/qapi.py
> > @@ -125,6 +125,122 @@ class QAPISemError(QAPIError):
> >                             info['parent'], msg)
> >  
> >  
> > +class QAPIDoc(object):
> > +    class Section(object):
> > +        def __init__(self, name=None):
> > +            # optional section name (argument/member or section name)
> > +            self.name = name
> > +            # the list of lines 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, info):
> > +        # self.parser is used to report errors with QAPIParseError.  The
> > +        # resulting error position depends on the state of the parser.
> > +        # It happens to be the beginning of the comment.  More or less
> > +        # servicable, but action at a distance.
> > +        self.parser = parser
> > +        self.info = info
> > +        self.symbol = None
> > +        self.body = QAPIDoc.Section()
> > +        # dict mapping parameter name to ArgSection
> > +        self.args = OrderedDict()
> > +        # a list of Section
> > +        self.sections = []
> > +        # the current section
> > +        self.section = self.body
> > +        # associated expression (to be set by expression parser)
> > +        self.expr = None
> > +
> > +    def has_section(self, name):
> > +        """Return True if we have a section with this name."""
> > +        for i in self.sections:
> > +            if i.name == name:
> > +                return True
> > +        return False
> > +
> > +    def append(self, line):
> > +        """Parse a comment line and add it to the documentation."""
> > +        line = line[1:]
> > +        if not line:
> > +            self._append_freeform(line)
> > +            return
> > +
> > +        if line[0] != ' ':
> > +            raise QAPIParseError(self.parser, "Missing space after #")
> > +        line = line[1:]
> > +
> > +        # FIXME not nice: things like '#  @foo:' and '# @foo: ' aren't
> > +        # recognized, and get silently treated as ordinary text
> > +        if self.symbol:
> > +            self._append_symbol_line(line)
> > +        elif not self.body.content and line.startswith("@"):
> > +            if not line.endswith(":"):
> > +                raise QAPIParseError(self.parser, "Line should end with
> > :")
> > +            self.symbol = line[1:-1]
> > +            # FIXME invalid names other than the empty string aren't
> > flagged
> > +            if not self.symbol:
> > +                raise QAPIParseError(self.parser, "Invalid name")
> > +        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:",
> > +                      "TODO:"):
> > +            line = line[len(name)+1:]
> > +            self._start_section(name[:-1])
> > +
> > +        self._append_freeform(line)
> > +
> > +    def _start_args_section(self, name):
> > +        # FIXME invalid names other than the empty string aren't flagged
> > +        if not name:
> > +            raise QAPIParseError(self.parser, "Invalid parameter name")
> > +        if name in self.args:
> > +            raise QAPIParseError(self.parser,
> > +                                 "'%s' parameter name duplicated" % name)
> > +        if self.sections:
> > +            raise QAPIParseError(self.parser,
> > +                                 "'%s' parameter documentation is
> > interleaved "
> > +                                 "with other sections" % name)
> 
> This error message is rather confusing.  Ideally, we'd point to the the
> source code that made us add to self.sections, but that's not in the
> cards right now.  Here's my best try:
> 
>                raise QAPIParseError(self.parser,
>                                     "'@%s:' can't follow '%s' section"
>                                     % (name, self.sections[0].name))
> 

works for me too

> > +        self.section = QAPIDoc.ArgSection(name)
> > +        self.args[name] = self.section
> > +
> > +    def _start_section(self, name=""):
> 
> Note: @name changed to default to "" since v6 for the benefit of
> _append_freeform() below.
> 
> > +        if name in ("Returns", "Since") and self.has_section(name):
> > +            raise QAPIParseError(self.parser,
> > +                                 "Duplicated '%s' section" % name)
> > +        self.section = QAPIDoc.Section(name)
> > +        self.sections.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()):
> 
> PEP8 recommends breaking lines before operators in new code.

ok

> 
> > +            self._start_section()
> 
> Changed from v6's
> 
>                raise QAPIParseError(self.parser, "Invalid section
>                indentation")
> 
> May well be an improvement (I'm certainly no fan of this error myself),
> but it throws my review off the fast track: now I have to figure out how
> the new code works.  Can you give me a hint on what this change does?

In v6, we rejected unindented sections after arguments (see tests/qapi-schema/doc-bad-section.json):

# @arg: blah bla
#       zing bar
#
# This should be rejected

And a patch in v6 reordered the json comments. But you asked for the doc to not be reordered without careful review (both source and output). So those additional paragraphs are added as untitled sections, and the doc generator keep section order. The output will be in the same order as the source.


> Or should we take a shortcut and revert to v6 here?  We can always
> improve on top.

Probably not needed to revert.

> 
> > +        if (in_arg or not self.section.name or
> > +                not self.section.name.startswith("Example")):
> 
> Again, break before the operator.

ok

> 
> > +            line = line.strip()
> > +        self.section.append(line)
> > +
> > +
> >  class QAPISchemaParser(object):
> >  
> >      def __init__(self, fp, previously_included=[], incl_info=None):
> > @@ -140,11 +256,17 @@ 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 == '#':
> > +                doc = self.get_doc(info)
> > +                self.docs.append(doc)
> > +                continue
> > +
> >              expr = self.get_expr(False)
> >              if isinstance(expr, dict) and "include" in expr:
> >                  if len(expr) != 1:
> > @@ -162,6 +284,7 @@ class QAPISchemaParser(object):
> >                          raise QAPISemError(info, "Inclusion loop for %s"
> >                                             % include)
> >                      inf = inf['parent']
> > +
> >                  # skip multiple include of the same file
> >                  if incl_abs_fname in previously_included:
> >                      continue
> > @@ -172,12 +295,19 @@ 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
> > +                        self.docs[-1].info['file'] == fname and
> > +                        not self.docs[-1].expr):
> 
> Again, break before the operator.

ok

> 
> > +                    self.docs[-1].expr = expr
> > +                    expr_elem['doc'] = self.docs[-1]
> > +
> >                  self.exprs.append(expr_elem)
> >  
> > -    def accept(self):
> > +    def accept(self, skip_comment=True):
> >          while True:
> >              self.tok = self.src[self.cursor]
> >              self.pos = self.cursor
> > @@ -185,7 +315,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 == "'":
> 
> Copied from review of v3, so I don't forget:
> 
> Comment tokens are thrown away as before, except when the parser asks
> for them by passing skip_comment=False, or when the comment token starts
> with ##.  The parser asks while parsing a doc comment, in get_doc().
> 
> This is a backchannel from the parser to the lexer.  I'd rather avoid
> such lexer hacks, but I guess we can address that on top.
> 
> A comment starting with ## inside an expression is now a syntax error.
> For instance, input
> 
>     {
>     ##
> 
> yields
> 
>     /dev/stdin:2:1: Expected string or "}"
> 
> Rather unfriendly error message, but we can fix that on top.
> 
> > @@ -319,6 +455,28 @@ class QAPISchemaParser(object):
> >              raise QAPIParseError(self, 'Expected "{", "[" or string')
> >          return expr
> >  
> > +    def get_doc(self, info):
> > +        if self.val != '##':
> > +            raise QAPIParseError(self, "Junk after '##' at start of "
> > +                                 "documentation comment")
> > +
> > +        doc = QAPIDoc(self, info)
> > +        self.accept(False)
> > +        while self.tok == '#':
> > +            if self.val.startswith('##'):
> > +                # End of doc comment
> > +                if self.val != '##':
> > +                    raise QAPIParseError(self, "Junk after '##' at end of
> > "
> > +                                         "documentation comment")
> > +                self.accept()
> > +                return doc
> > +            else:
> > +                doc.append(self.val)
> > +            self.accept(False)
> > +
> > +        raise QAPIParseError(self, "Documentation comment must end with
> > '##'")
> > +
> > +
> >  #
> >  # Semantic analysis of schema expressions
> >  # TODO fold into QAPISchema
> > @@ -703,6 +861,11 @@ def check_exprs(exprs):
> >      for expr_elem in exprs:
> >          expr = expr_elem['expr']
> >          info = expr_elem['info']
> > +
> > +        if 'doc' not in expr_elem:
> > +            raise QAPISemError(info,
> > +                               "Expression missing documentation comment")
> > +
> >          if 'enum' in expr:
> >              check_keys(expr_elem, 'enum', ['data'], ['prefix'])
> >              add_enum(expr['enum'], info, expr['data'])
> > @@ -761,6 +924,84 @@ def check_exprs(exprs):
> >      return exprs
> >  
> >  
> > +def check_freeform_doc(doc):
> > +    if doc.symbol:
> > +        raise QAPISemError(doc.info,
> > +                           "Documention for '%s' is not followed"
> > +                           " by the definition" % doc.symbol)
> > +
> > +    body = str(doc.body)
> > +    if re.search(r'@\S+:', body, re.MULTILINE):
> > +        raise QAPISemError(doc.info,
> > +                           "Free-form documentation block must not
> > contain"
> > +                           " @NAME: sections")
> > +
> > +
> > +def check_definition_doc(doc, expr, info):
> 
> Lots of churn in this function.  It can certainly use improvement, as
> pointed out in prior reviews, but it triggers still more review.  Based
> on the tests, I guess it's for finding documented parameters that don't
> actually exist, and to diagnose optional mismatch.  Anything else?
> 

implicitely adds #optional (since it's no longer in type info, it better be in the description), code simplification, message improvement too.

I'd rather keep this too, and improve on top.

> Or should we take a shortcut and revert to v6 here?  We can always
> improve on top.  I think v6 would need a # TODO Should ensure #optional
> matches the schema.
> 
> > +    for i in ('enum', 'union', 'alternate', 'struct', 'command', 'event'):
> > +        if i in expr:
> > +            meta = i
> > +            break
> > +
> > +    name = expr[meta]
> > +    if doc.symbol != name:
> > +        raise QAPISemError(info, "Definition of '%s' follows
> > documentation"
> > +                           " for '%s'" % (name, doc.symbol))
> > +    if doc.has_section('Returns') and 'command' not in expr:
> > +        raise QAPISemError(info, "'Returns:' is only valid for commands")
> 
> Copied from review of v5, so I don't forget:
> 
> We accept 'Returns:' even when the command doesn't return anything,
> because we currently use it to document errors, too.  Can't say I like
> that, but it's out of scope here.
> 
> > +
> > +    if meta == 'union':
> > +        args = expr.get('base', [])
> > +    else:
> > +        args = expr.get('data', [])
> > +    if isinstance(args, dict):
> > +        args = args.keys()
> > +
> > +    if meta == 'alternate' or \
> > +       (meta == 'union' and not expr.get('discriminator')):
> > +        args.append('type')
> > +
> > +    if isinstance(args, list):
> > +        for arg in args:
> > +            if arg[0] == '*':
> > +                opt = True
> > +                desc = doc.args.get(arg[1:])
> > +            else:
> > +                opt = False
> > +                desc = doc.args.get(arg)
> > +            if not desc:
> > +                continue
> > +            desc_opt = "#optional" in str(desc)
> > +            if desc_opt and not opt:
> > +                raise QAPISemError(info, "Description has #optional, "
> > +                                   "but the declaration doesn't")
> > +            if not desc_opt and opt:
> > +                # silently fix the doc
> > +                desc.append("#optional")
> > +
> > +    doc_args = set(doc.args.keys())
> > +    args = set([name.strip('*') for name in args])
> > +    if not doc_args.issubset(args):
> > +        raise QAPISemError(info, "The following documented members are not
> > in "
> > +                           "the declaration: %s" % ", ".join(doc_args -
> > args))
> 
> Copied from review of v5, so I don't forget:
> 
> As explained in review of v3, this is only a subset of the real set of
> members.  Computing the exact set is impractical when working with the
> abstract syntax tree.  I believe we'll eventually have to rewrite this
> code to work with the QAPISchemaEntity instead.
> 
> > +
> > +
> > +def check_docs(docs):
> > +    for doc in docs:
> > +        for section in doc.args.values() + doc.sections:
> > +            content = str(section)
> > +            if not content or content.isspace():
> > +                raise QAPISemError(doc.info,
> > +                                   "Empty doc section '%s'" %
> > section.name)
> > +
> > +        if not doc.expr:
> > +            check_freeform_doc(doc)
> > +        else:
> > +            check_definition_doc(doc, doc.expr, doc.info)
> > +
> > +    return docs
> > +
> > +
> >  #
> >  # Schema compiler frontend
> >  #
> > @@ -1229,7 +1470,9 @@ class QAPISchemaEvent(QAPISchemaEntity):
> >  class QAPISchema(object):
> >      def __init__(self, fname):
> >          try:
> > -            self.exprs = check_exprs(QAPISchemaParser(open(fname,
> > "r")).exprs)
> > +            parser = QAPISchemaParser(open(fname, "r"))
> > +            self.exprs = check_exprs(parser.exprs)
> > +            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 0000000000..436749ec7b
> > --- /dev/null
> > +++ b/scripts/qapi2texi.py
> > @@ -0,0 +1,266 @@
> > +#!/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 = """
> > +@deftypefn {type} {{}} {name}
> > +
> > +{body}
> > +
> > +@end deftypefn
> > +
> > +""".format
> > +
> > +ENUM_FMT = """
> > +@deftp Enum {name}
> > +
> > +{body}
> > +
> > +@end deftp
> > +
> > +""".format
> > +
> > +STRUCT_FMT = """
> > +@deftp {{{type}}} {name}
> > +
> > +{body}
> > +
> > +@end deftp
> > +
> > +""".format
> > +
> > +EXAMPLE_FMT = """@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'\b_([^_\n]+)_\b', 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"""
> > +    # TODO: Neglects to escape @ characters.
> > +    # We should probably escape them in subst_braces(), and rename the
> > +    # function to subst_special() or subs_texi_special().  If we do that,
> > we
> > +    # need to delay it until after subst_vars() in texi_format().
> > +    doc = subst_braces(doc).strip('\n')
> > +    return EXAMPLE_FMT(code=doc)
> > +
> > +
> > +def texi_format(doc):
> > +    """
> > +    Format documentation
> > +
> > +    Lines starting with:
> > +    - |: generates an @example
> > +    - =: generates @section
> > +    - ==: generates @subsection
> > +    - 1. or 1): generates an @enumerate @item
> > +    - */-: 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 == ""
> > +
> > +        # FIXME: Doing this in a single if / elif chain is
> > +        # problematic.  For instance, a line without markup terminates
> > +        # a list if it follows a blank line (reaches the final elif),
> > +        # but a line with some *other* markup, such as a = title
> > +        # doesn't.
> > +        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):
> 
> [.] is an unusual way to say \.

Right, forgot to change that when removing ) from v6.

> 
> Perhaps we should consistently use r"..." for strings used as regular
> expressions.
> 

No idea, could be changed on top.

> > +            if not inlist:
> > +                lines.append("@enumerate")
> > +                inlist = "enumerate"
> > +            line = line[line.find(" ")+1:]
> > +            lines.append("@item")
> > +        elif re.match("^[*-] ", line):
> > +            if not inlist:
> > +                lines.append("@itemize %s" % {'*': "@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_body(doc):
> > +    """
> > +    Format the body of a symbol documentation:
> > +    - main body
> > +    - table of arguments
> > +    - followed by "Returns/Notes/Since/Example" sections
> > +    """
> > +    body = texi_format(str(doc.body)) + "\n"
> > +    if doc.args:
> > +        body += "@table @asis\n"
> > +        for arg, section in doc.args.iteritems():
> > +            desc = str(section)
> > +            opt = ''
> > +            if "#optional" in desc:
> > +                desc = desc.replace("#optional", "")
> > +                opt = ' (optional)'
> > +            body += "@item @code{'%s'}%s\n%s\n" % (arg, opt,
> > +                                                   texi_format(desc))
> > +        body += "@end table\n"
> > +
> > +    for section in doc.sections:
> > +        name, doc = (section.name, str(section))
> > +        func = texi_format
> > +        if name.startswith("Example"):
> > +            func = texi_example
> > +
> > +        if name:
> 
> @quotation produces confusing .txt and .html output, as discussed in
> review of v6.  Either drop it, or record the problem in a comment, e.g.
> 
>                # FIXME the indentation produced by @quotation in .txt and
>                # .html output is confusing
> 

added

> > +            body += "\n@quotation %s\n%s\n@end quotation" % \
> > +                    (name, func(doc))
> > +        else:
> > +            body += func(doc)
> > +
> > +    return body
> > +
> > +
> > +def texi_alternate(expr, doc):
> > +    """Format an alternate to texi"""
> > +    body = texi_body(doc)
> > +    return STRUCT_FMT(type="Alternate",
> > +                      name=doc.symbol,
> > +                      body=body)
> > +
> > +
> > +def texi_union(expr, doc):
> > +    """Format a union to texi"""
> > +    discriminator = expr.get("discriminator")
> > +    if discriminator:
> > +        union = "Flat Union"
> > +    else:
> > +        union = "Simple Union"
> 
> Not sure flat vs. simple matters for users of generated documentation,
> but let's not worry about that now.
> 
> > +
> > +    body = texi_body(doc)
> > +    return STRUCT_FMT(type=union,
> > +                      name=doc.symbol,
> > +                      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"""
> > +    body = texi_body(doc)
> > +    return STRUCT_FMT(type="Struct",
> > +                      name=doc.symbol,
> > +                      body=body)
> > +
> > +
> > +def texi_command(expr, doc):
> > +    """Format a command to texi"""
> > +    body = texi_body(doc)
> > +    return COMMAND_FMT(type="Command",
> > +                       name=doc.symbol,
> > +                       body=body)
> > +
> > +
> > +def texi_event(expr, doc):
> > +    """Format an event to texi"""
> > +    body = texi_body(doc)
> > +    return COMMAND_FMT(type="Event",
> > +                       name=doc.symbol,
> > +                       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}[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])
> > +    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 2841c5144a..b29f996fdc 100644
> > --- a/docs/qapi-code-gen.txt
> > +++ b/docs/qapi-code-gen.txt
> > @@ -44,40 +44,148 @@ Input must be ASCII (although QMP supports full
> > Unicode strings, the
> >  QAPI parser does not).  At present, there is no place where a QAPI
> >  schema requires the use of JSON numbers or null.
> >  
> > +
> > +=== Comments ===
> > +
> >  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
> > -x.y.z)' comment.  For example:
> > -
> > -    ##
> > -    # @BlockStats:
> > -    #
> > -    # Statistics of a virtual block device or a block backing device.
> > -    #
> > -    # @device: #optional If the stats are for a virtual block device, the
> > name
> > -    #          corresponding to the virtual block device.
> > -    #
> > -    # @stats:  A @BlockDeviceStats for the device.
> > -    #
> > -    # @parent: #optional This describes the file block device if it has
> > one.
> > -    #
> > -    # @backing: #optional This describes the backing block device if it
> > has one.
> > -    #           (Since 2.0)
> > -    #
> > -    # Since: 0.14.0
> > -    ##
> > -    { 'struct': 'BlockStats',
> > -      'data': {'*device': 'str', 'stats': 'BlockDeviceStats',
> > -               '*parent': 'BlockStats',
> > -               '*backing': 'BlockStats'} }
> > +newline is ignored.
> > +
> > +A multi-line comment that starts and ends with a '##' line is a
> > +documentation comment.  These are parsed by the documentation
> > +generator, which recognizes certain markup detailed below.
> > +
> > +
> > +==== Documentation markup ====
> > +
> > +Comment text starting with '=' is a section title:
> > +
> > +    # = Section title
> > +
> > +Double the '=' for a subsection title:
> > +
> > +    # == Subection title
> > +
> > +'|' denotes examples:
> > +
> > +    # | Text of the example, may span
> > +    # | multiple lines
> > +
> > +'*' starts an itemized list:
> > +
> > +    # * First item, may span
> > +    #   multiple lines
> > +    # * Second item
> > +
> > +You can also use '-' instead of '*'.
> > +
> > +A decimal number followed by '.' starts a numbered list:
> > +
> > +    # 1. First item, may span
> > +    # multiple lines
> > +    # 2. Second item
> > +
> > +The actual number doesn't matter.  You could even use '*' instead of
> > +'2.' for the second item.
> > +
> > +FIXME what exactly ends a list
> 
> Can you say offhand what ends a list?  If yes, can we resolve this FIXME
> now rather than later?

well, the code is too permissive, there is a FIXME there already.

In general, I think we want to end a list after a blank line and a "non-item" line. Is the wording ok?

> 
> > +
> > +Additional whitespace between the initial '#' and the comment text is
> > +permitted.
> > +
> > +*foo* and _foo_ are for strong and emphasis styles respectively (they
> > +do not work over multiple lines). @foo is used to reference a name in
> > +the schema.
> > +
> > +Example:
> > +
> > +##
> > +# = 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
> > +#
> > +##
> > +
> > +
> > +==== Expression documentation ====
> > +
> > +Each expression that isn't an include directive must be preceded by a
> > +documentation block.  Such blocks are called expression documentation
> > +blocks.
> > +
> > +The first line of the documentation names the expression, then the
> > +documentation body is provided, then individual documentation about
> > +each member of 'data' is provided. Finally, several tagged sections
> > +can be added.
> 
> In my review of v6, I wrote:
> 
>     I'm afraid this is more aspiration than specification: the parser
>     accepts these things in almost any order.
> 
>     Could be filed under "deficiencies" for now.
> 
> To file under "deficiencies", we need to add a FIXME or TODO either
> here, or in the parser.  Assuming you haven't tightened the parser
> meanwhile (hope you haven't; we need to control the churn).

ok

> 
>     Member of 'data' won't remain accurate.  Consider:
> 
>         { 'union': 'GlusterServer',
>           'base': { 'type': 'GlusterTransport' },
>           'discriminator': 'type',
>           'data': { 'unix': 'UnixSocketAddress',
>                     'tcp': 'InetSocketAddress' } }
> 
>     Its doc comment currently doesn't contain argument sections.  It should
>     eventually contain @type:, @unix: and @tcp:.  Only the latter two are
>     members of 'data'.
> 
>     I should propose something better, but I'm getting perilously close to
>     the christmas break already.  Later.
> 
> Also: passive voice is meh (not your idea; you adapted the existing
> text).
> 
> What about:
> 
>    The documentation block consists of a first line naming the
>    expression, an optional overview, a description of each argument (for
>    commands and events) or member (for structs, unions and alternates),
>    and optional tagged sections.
> 
> I still don't like the use of "section" for both the = things and the
> tagged sections, but it's not important enough to spend time on it now.
> 

ok

> > +
> > +Optional members are tagged with the phrase '#optional', often with
> 
> If we adopt my text, we should say "arguments / members" here.
> 

ok

> > +their default value; and extensions added after the expression was
> > +first released are also given a '(since x.y.z)' comment.
> > +
> > +A tagged section starts with one of the following words:
> > +"Note:"/"Notes:", "Since:", "Example"/"Examples", "Returns:", "TODO:".
> 
> What ends such a section?  The start of the next one / end of the
> comment block?
> 

yes

> I suspect we could avoid PATCH 03 by recognizing "TODO" in addition to
> "TODO:".  Might also be more robust.  But let's ignore this now.
> 
> > +
> > +A 'Since: x.y.z' tag lists the release that introduced the expression.
> 
> Nitpick: 'Since: x.y.z' is a tagged section, not a tag.
> 

ok

> > +
> > +For example:
> > +
> > +##
> > +# @BlockStats:
> > +#
> > +# Statistics of a virtual block device or a block backing device.
> > +#
> > +# @device: #optional If the stats are for a virtual block device, the name
> > +#          corresponding to the virtual block device.
> > +#
> > +# @node-name: #optional The node name of the device. (since 2.3)
> > +#
> > +# ... more members ...
> > +#
> > +# Since: 0.14.0
> > +##
> > +{ 'struct': 'BlockStats',
> > +  'data': {'*device': 'str', '*node-name': 'str',
> > +           ... more members ... } }
> > +
> > +##
> > +# @query-blockstats:
> > +#
> > +# Query the @BlockStats for all virtual block devices.
> > +#
> > +# @query-nodes: #optional If true, the command will query all the
> > +#               block nodes ... explain, explain ...  (since 2.3)
> > +#
> > +# Returns: A list of @BlockStats for each virtual block devices.
> > +#
> > +# Since: 0.14.0
> > +#
> > +# Example:
> > +#
> > +# -> { "execute": "query-blockstats" }
> > +# <- {
> > +#      ... lots of output ...
> > +#    }
> > +#
> > +##
> > +{ 'command': 'query-blockstats',
> > +  'data': { '*query-nodes': 'bool' },
> > +  'returns': ['BlockStats'] }
> > +
> > +==== Free-form documentation ====
> > +
> > +A documentation block that isn't an expression documentation block is
> > +a free-form documentation block.  These may be used to provide
> > +additional text and structuring content.
> > +
> > +
> > +=== Schema overview ===
> >  
> >  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
> [Tests look okay, snipped...]
> 

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 20/21] build-sys: add txt documentation rules
  2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 20/21] build-sys: add txt documentation rules Marc-André Lureau
@ 2017-01-12 10:09   ` Markus Armbruster
  0 siblings, 0 replies; 40+ messages in thread
From: Markus Armbruster @ 2017-01-12 10:09 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Build plain text documentation, and install it.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Reviewed-by: Markus Armbruster <armbru@redhat.com>
> ---
>  .gitignore |  1 +
>  Makefile   | 12 +++++++++---
>  2 files changed, 10 insertions(+), 3 deletions(-)
>
> diff --git a/.gitignore b/.gitignore
> index 6f175b391e..e16bddc070 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -40,6 +40,7 @@
>  /qmp-marshal.c
>  /qemu-doc.html
>  /qemu-doc.info
> +/qemu-doc.txt
>  /qemu-img
>  /qemu-nbd
>  /qemu-options.def
> diff --git a/Makefile b/Makefile
> index d18bac1c31..37d45ee21b 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -81,7 +81,7 @@ Makefile: ;
>  configure: ;
>  
>  .PHONY: all clean cscope distclean html info install install-doc \
> -	pdf recurse-all speed test dist msi FORCE
> +	pdf txt recurse-all speed test dist msi FORCE
>  
>  $(call set-vpath, $(SRC_PATH))
>  
> @@ -90,7 +90,7 @@ LIBS+=-lz $(LIBS_TOOLS)
>  HELPERS-$(CONFIG_LINUX) = qemu-bridge-helper$(EXESUF)
>  
>  ifdef BUILD_DOCS
> -DOCS=qemu-doc.html qemu.1 qemu-img.1 qemu-nbd.8 qemu-ga.8
> +DOCS=qemu-doc.html qemu-doc.txt qemu.1 qemu-img.1 qemu-nbd.8 qemu-ga.8
>  ifdef CONFIG_VIRTFS
>  DOCS+=fsdev/virtfs-proxy-helper.1
>  endif
> @@ -431,6 +431,7 @@ endif
>  install-doc: $(DOCS)
>  	$(INSTALL_DIR) "$(DESTDIR)$(qemu_docdir)"
>  	$(INSTALL_DATA) qemu-doc.html "$(DESTDIR)$(qemu_docdir)"
> +	$(INSTALL_DATA) qemu-doc.txt "$(DESTDIR)$(qemu_docdir)"
>  ifdef CONFIG_POSIX
>  	$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
>  	$(INSTALL_DATA) qemu.1 "$(DESTDIR)$(mandir)/man1"
> @@ -538,6 +539,10 @@ TEXIFLAG=$(if $(V),,--quiet)
>  %.info: %.texi
>  	$(call quiet-command,$(MAKEINFO) $(MAKEINFOFLAGS) $< -o $@,"GEN","$@")
>  
> +%.txt: %.texi
> +	$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --no-headers \
> +	--plaintext $< -o $@,"GEN   $@")

I believe this needs to be "GEN", "$@" now.  See commit 0bdb12c.

> +
>  %.pdf: %.texi
>  	$(call quiet-command,texi2pdf $(TEXIFLAG) -I . $<,"GEN","$@")
>  
> @@ -563,6 +568,7 @@ qemu-ga.8: qemu-ga.texi
>  html: qemu-doc.html
>  info: qemu-doc.info
>  pdf: qemu-doc.pdf
> +txt: qemu-doc.txt
>  
>  qemu-doc.html qemu-doc.info qemu-doc.pdf: \
>  	qemu-img.texi qemu-nbd.texi qemu-options.texi qemu-option-trace.texi \
> @@ -661,7 +667,7 @@ help:
>  	@echo  '  docker          - Help about targets running tests inside Docker containers'
>  	@echo  ''
>  	@echo  'Documentation targets:'
> -	@echo  '  html info pdf'
> +	@echo  '  html info pdf txt'
>  	@echo  '                  - Build documentation in specified format'
>  	@echo  ''
>  ifdef CONFIG_WIN32

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 15/21] qapi: add qapi2texi script
  2017-01-11 16:21     ` Marc-André Lureau
@ 2017-01-12 13:17       ` Markus Armbruster
  2017-01-12 17:36         ` Marc-André Lureau
  0 siblings, 1 reply; 40+ messages in thread
From: Markus Armbruster @ 2017-01-12 13:17 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Marc-André Lureau, qemu-devel

Marc-André Lureau <mlureau@redhat.com> writes:

> Hi
>
> ----- Original Message -----
>> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
>> 
>> > 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 description:
>> >
>> >   ##
>> >   # @symbol:
>> >   #
>> >   # Symbol body ditto ergo sum. Foo bar
>> >   # baz ding.
>> >   #
>> >   # @param1: the frob to frobnicate
>> >   # @param2: #optional how hard to frobnicate
>> >   #
>> >   # Returns: the frobnicated frob.
>> >   #          If frob isn't frobnicatable, GenericError.
>> >   #
>> >   # 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 | tag_section | freeform_comment
>> > }
>> > member = "# @" name ':' [ text ] "\n" freeform_comment
>> > tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:", "Example:",
>> > "Examples:" ) [ text ]  "\n" freeform_comment
>> > text = free text with markup
>> >
>> > Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
>> > both as freeform_comment and as symbol_comment.  The actual parser
>> > recognizes symbol_comment.
>> >
>> > See docs/qapi-code-gen.txt for more details.
>> >
>> > Deficiencies:
>> 
>> Perhaps "Deficiencies and limitations".
>
> ok
>
>> 
>> > - the generated QMP documentation includes internal types
>> > - union-type support is lacking
>> 
>> "union type" (no dash).
>> 
>> > - type information is lacking in generated documentation
>> > - doc comment error message positions are imprecise, they point
>> >   to the beginning of the comment.
>> > - see other TODO/FIXME in this commit
>> 
>> Suggest: - a few minor issues, all marked TODO/FIXME in the code
>> 
>
> ok
>
>> >
>> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>> > ---
>> [diffstat snipped...]
>> > diff --git a/scripts/qapi.py b/scripts/qapi.py
>> > index 3d5f9e1eaf..a92a86f428 100644
>> > --- a/scripts/qapi.py
>> > +++ b/scripts/qapi.py
>> > @@ -125,6 +125,122 @@ class QAPISemError(QAPIError):
>> >                             info['parent'], msg)
>> >  
>> >  
>> > +class QAPIDoc(object):
>> > +    class Section(object):
>> > +        def __init__(self, name=None):
>> > +            # optional section name (argument/member or section name)
>> > +            self.name = name
>> > +            # the list of lines 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, info):
>> > +        # self.parser is used to report errors with QAPIParseError.  The
>> > +        # resulting error position depends on the state of the parser.
>> > +        # It happens to be the beginning of the comment.  More or less
>> > +        # servicable, but action at a distance.
>> > +        self.parser = parser
>> > +        self.info = info
>> > +        self.symbol = None
>> > +        self.body = QAPIDoc.Section()
>> > +        # dict mapping parameter name to ArgSection
>> > +        self.args = OrderedDict()
>> > +        # a list of Section
>> > +        self.sections = []
>> > +        # the current section
>> > +        self.section = self.body
>> > +        # associated expression (to be set by expression parser)
>> > +        self.expr = None
>> > +
>> > +    def has_section(self, name):
>> > +        """Return True if we have a section with this name."""
>> > +        for i in self.sections:
>> > +            if i.name == name:
>> > +                return True
>> > +        return False
>> > +
>> > +    def append(self, line):
>> > +        """Parse a comment line and add it to the documentation."""
>> > +        line = line[1:]
>> > +        if not line:
>> > +            self._append_freeform(line)
>> > +            return
>> > +
>> > +        if line[0] != ' ':
>> > +            raise QAPIParseError(self.parser, "Missing space after #")
>> > +        line = line[1:]
>> > +
>> > +        # FIXME not nice: things like '#  @foo:' and '# @foo: ' aren't
>> > +        # recognized, and get silently treated as ordinary text
>> > +        if self.symbol:
>> > +            self._append_symbol_line(line)
>> > +        elif not self.body.content and line.startswith("@"):
>> > +            if not line.endswith(":"):
>> > +                raise QAPIParseError(self.parser, "Line should end with
>> > :")
>> > +            self.symbol = line[1:-1]
>> > +            # FIXME invalid names other than the empty string aren't
>> > flagged
>> > +            if not self.symbol:
>> > +                raise QAPIParseError(self.parser, "Invalid name")
>> > +        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:",
>> > +                      "TODO:"):
>> > +            line = line[len(name)+1:]
>> > +            self._start_section(name[:-1])
>> > +
>> > +        self._append_freeform(line)
>> > +
>> > +    def _start_args_section(self, name):
>> > +        # FIXME invalid names other than the empty string aren't flagged
>> > +        if not name:
>> > +            raise QAPIParseError(self.parser, "Invalid parameter name")
>> > +        if name in self.args:
>> > +            raise QAPIParseError(self.parser,
>> > +                                 "'%s' parameter name duplicated" % name)
>> > +        if self.sections:
>> > +            raise QAPIParseError(self.parser,
>> > +                                 "'%s' parameter documentation is
>> > interleaved "
>> > +                                 "with other sections" % name)
>> 
>> This error message is rather confusing.  Ideally, we'd point to the the
>> source code that made us add to self.sections, but that's not in the
>> cards right now.  Here's my best try:
>> 
>>                raise QAPIParseError(self.parser,
>>                                     "'@%s:' can't follow '%s' section"
>>                                     % (name, self.sections[0].name))
>> 
>
> works for me too
>
>> > +        self.section = QAPIDoc.ArgSection(name)
>> > +        self.args[name] = self.section
>> > +
>> > +    def _start_section(self, name=""):
>> 
>> Note: @name changed to default to "" since v6 for the benefit of
>> _append_freeform() below.
>> 
>> > +        if name in ("Returns", "Since") and self.has_section(name):
>> > +            raise QAPIParseError(self.parser,
>> > +                                 "Duplicated '%s' section" % name)
>> > +        self.section = QAPIDoc.Section(name)
>> > +        self.sections.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()):
>> 
>> PEP8 recommends breaking lines before operators in new code.
>
> ok
>
>> 
>> > +            self._start_section()
>> 
>> Changed from v6's
>> 
>>                raise QAPIParseError(self.parser, "Invalid section
>>                indentation")
>> 
>> May well be an improvement (I'm certainly no fan of this error myself),
>> but it throws my review off the fast track: now I have to figure out how
>> the new code works.  Can you give me a hint on what this change does?
>
> In v6, we rejected unindented sections after arguments (see tests/qapi-schema/doc-bad-section.json):
>
> # @arg: blah bla
> #       zing bar
> #
> # This should be rejected
>
> And a patch in v6 reordered the json comments. But you asked for the
> doc to not be reordered without careful review (both source and
> output). So those additional paragraphs are added as untitled
> sections, and the doc generator keep section order. The output will be
> in the same order as the source.

Thanks.

>> Or should we take a shortcut and revert to v6 here?  We can always
>> improve on top.
>
> Probably not needed to revert.

I played with it a bit, and it looks like it's working fine.

>> > +        if (in_arg or not self.section.name or
>> > +                not self.section.name.startswith("Example")):
>> 
>> Again, break before the operator.
>
> ok
>
>> 
>> > +            line = line.strip()
>> > +        self.section.append(line)
>> > +
>> > +
>> >  class QAPISchemaParser(object):
>> >  
>> >      def __init__(self, fp, previously_included=[], incl_info=None):
>> > @@ -140,11 +256,17 @@ 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 == '#':
>> > +                doc = self.get_doc(info)
>> > +                self.docs.append(doc)
>> > +                continue
>> > +
>> >              expr = self.get_expr(False)
>> >              if isinstance(expr, dict) and "include" in expr:
>> >                  if len(expr) != 1:
>> > @@ -162,6 +284,7 @@ class QAPISchemaParser(object):
>> >                          raise QAPISemError(info, "Inclusion loop for %s"
>> >                                             % include)
>> >                      inf = inf['parent']
>> > +
>> >                  # skip multiple include of the same file
>> >                  if incl_abs_fname in previously_included:
>> >                      continue
>> > @@ -172,12 +295,19 @@ 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
>> > +                        self.docs[-1].info['file'] == fname and
>> > +                        not self.docs[-1].expr):
>> 
>> Again, break before the operator.
>
> ok
>
>> 
>> > +                    self.docs[-1].expr = expr
>> > +                    expr_elem['doc'] = self.docs[-1]
>> > +
>> >                  self.exprs.append(expr_elem)
>> >  
>> > -    def accept(self):
>> > +    def accept(self, skip_comment=True):
>> >          while True:
>> >              self.tok = self.src[self.cursor]
>> >              self.pos = self.cursor
>> > @@ -185,7 +315,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 == "'":
>> 
>> Copied from review of v3, so I don't forget:
>> 
>> Comment tokens are thrown away as before, except when the parser asks
>> for them by passing skip_comment=False, or when the comment token starts
>> with ##.  The parser asks while parsing a doc comment, in get_doc().
>> 
>> This is a backchannel from the parser to the lexer.  I'd rather avoid
>> such lexer hacks, but I guess we can address that on top.
>> 
>> A comment starting with ## inside an expression is now a syntax error.
>> For instance, input
>> 
>>     {
>>     ##
>> 
>> yields
>> 
>>     /dev/stdin:2:1: Expected string or "}"
>> 
>> Rather unfriendly error message, but we can fix that on top.
>> 
>> > @@ -319,6 +455,28 @@ class QAPISchemaParser(object):
>> >              raise QAPIParseError(self, 'Expected "{", "[" or string')
>> >          return expr
>> >  
>> > +    def get_doc(self, info):
>> > +        if self.val != '##':
>> > +            raise QAPIParseError(self, "Junk after '##' at start of "
>> > +                                 "documentation comment")
>> > +
>> > +        doc = QAPIDoc(self, info)
>> > +        self.accept(False)
>> > +        while self.tok == '#':
>> > +            if self.val.startswith('##'):
>> > +                # End of doc comment
>> > +                if self.val != '##':
>> > +                    raise QAPIParseError(self, "Junk after '##' at end of
>> > "
>> > +                                         "documentation comment")
>> > +                self.accept()
>> > +                return doc
>> > +            else:
>> > +                doc.append(self.val)
>> > +            self.accept(False)
>> > +
>> > +        raise QAPIParseError(self, "Documentation comment must end with
>> > '##'")
>> > +
>> > +
>> >  #
>> >  # Semantic analysis of schema expressions
>> >  # TODO fold into QAPISchema
>> > @@ -703,6 +861,11 @@ def check_exprs(exprs):
>> >      for expr_elem in exprs:
>> >          expr = expr_elem['expr']
>> >          info = expr_elem['info']
>> > +
>> > +        if 'doc' not in expr_elem:
>> > +            raise QAPISemError(info,
>> > +                               "Expression missing documentation comment")
>> > +
>> >          if 'enum' in expr:
>> >              check_keys(expr_elem, 'enum', ['data'], ['prefix'])
>> >              add_enum(expr['enum'], info, expr['data'])
>> > @@ -761,6 +924,84 @@ def check_exprs(exprs):
>> >      return exprs
>> >  
>> >  
>> > +def check_freeform_doc(doc):
>> > +    if doc.symbol:
>> > +        raise QAPISemError(doc.info,
>> > +                           "Documention for '%s' is not followed"
>> > +                           " by the definition" % doc.symbol)
>> > +
>> > +    body = str(doc.body)
>> > +    if re.search(r'@\S+:', body, re.MULTILINE):
>> > +        raise QAPISemError(doc.info,
>> > +                           "Free-form documentation block must not
>> > contain"
>> > +                           " @NAME: sections")
>> > +
>> > +
>> > +def check_definition_doc(doc, expr, info):
>> 
>> Lots of churn in this function.  It can certainly use improvement, as
>> pointed out in prior reviews, but it triggers still more review.  Based
>> on the tests, I guess it's for finding documented parameters that don't
>> actually exist, and to diagnose optional mismatch.  Anything else?
>> 
>
> implicitely adds #optional (since it's no longer in type info, it better be in the description), code simplification, message improvement too.
>
> I'd rather keep this too, and improve on top.

Okay.  Review below.

>> Or should we take a shortcut and revert to v6 here?  We can always
>> improve on top.  I think v6 would need a # TODO Should ensure #optional
>> matches the schema.
>> 
>> > +    for i in ('enum', 'union', 'alternate', 'struct', 'command', 'event'):
>> > +        if i in expr:
>> > +            meta = i
>> > +            break
>> > +
>> > +    name = expr[meta]
>> > +    if doc.symbol != name:
>> > +        raise QAPISemError(info, "Definition of '%s' follows
>> > documentation"
>> > +                           " for '%s'" % (name, doc.symbol))
>> > +    if doc.has_section('Returns') and 'command' not in expr:
>> > +        raise QAPISemError(info, "'Returns:' is only valid for commands")
>> 
>> Copied from review of v5, so I don't forget:
>> 
>> We accept 'Returns:' even when the command doesn't return anything,
>> because we currently use it to document errors, too.  Can't say I like
>> that, but it's out of scope here.
>> 
>> > +
>> > +    if meta == 'union':
>> > +        args = expr.get('base', [])

Note: args is either string or dictionary.

>> > +    else:
>> > +        args = expr.get('data', [])

Note: args is either string (only if command or event), dictionary (only
if struct, alternate, command, event) or list (only if enum)

>> > +    if isinstance(args, dict):
>> > +        args = args.keys()

Now args is either string (only if command or event) or list.

>> > +
>> > +    if meta == 'alternate' or \
>> > +       (meta == 'union' and not expr.get('discriminator')):

PEP8 prefers parenthesis over backslash, and recommends breaking lines
before instead of after binary operators:

          if (meta == 'alternate'
              or (meta == 'union' and not expr.get('discriminator'))):

Note: args can only be list here.

>> > +        args.append('type')

Works because we know it's a list.  It's less than obvious, though.

>> > +
>> > +    if isinstance(args, list):
>> > +        for arg in args:
>> > +            if arg[0] == '*':
>> > +                opt = True
>> > +                desc = doc.args.get(arg[1:])
>> > +            else:
>> > +                opt = False
>> > +                desc = doc.args.get(arg)
>> > +            if not desc:
>> > +                continue
>> > +            desc_opt = "#optional" in str(desc)
>> > +            if desc_opt and not opt:
>> > +                raise QAPISemError(info, "Description has #optional, "
>> > +                                   "but the declaration doesn't")
>> > +            if not desc_opt and opt:
>> > +                # silently fix the doc
>> > +                desc.append("#optional")

#optional is redundant information.  But as long as we use it, it better
be correct, to avoid misleading readers of the schema.  Two sides of
correctness: it's there when arg is optional, and it's not there when
it's not.  You enforce the latter, but you silently fix the former.  I
figure you do that because #optional is currently missing in quite a few
places.

Let's add
                      # TODO either fix the schema and make this an error,
                      # or drop #optional entirely

>> > +
>> > +    doc_args = set(doc.args.keys())
>> > +    args = set([name.strip('*') for name in args])

Now args morphs from string or dict to set.  I'd use a new variable.

If args is a string, it becomes a set of characters, which is not what
you want.  For instance, qapi-schema.json's QCryptoBlockOpenOptions has
'base': 'QCryptoBlockOptionsBase', and args changes from
'QCryptoBlockOptionsBase' to set(['O', 'a', 'C', 'B', 'e', 'i', 'c',
'k', 'l', 'o', 'n', 'Q', 'p', 's', 'r', 't', 'y']) here.

>> > +    if not doc_args.issubset(args):
>> > +        raise QAPISemError(info, "The following documented members are not in "
>> > +                           "the declaration: %s" % ", ".join(doc_args - args))

If args is originally a string, then we happily accept documentation for
its characters.  Quite unlikely to bite in practice, but it's wrong
anyways.

I suspect you could save yourself some grief by returning early when
args is string, say:

    if meta == 'union':
        args = expr.get('base', [])
    else:
        args = expr.get('data', [])
    if isinstance(args, str):
        return
    if isinstance(args, dict):
        args = args.keys()
    assert isinstance(args, list)

    if (meta == 'alternate'
            or (meta == 'union' and not expr.get('discriminator'))):
        args.append('type')

    for arg in args:
        ...

>> Copied from review of v5, so I don't forget:
>> 
>> As explained in review of v3, this is only a subset of the real set of
>> members.  Computing the exact set is impractical when working with the
>> abstract syntax tree.  I believe we'll eventually have to rewrite this
>> code to work with the QAPISchemaEntity instead.
>> 
>> > +
>> > +
>> > +def check_docs(docs):
>> > +    for doc in docs:
>> > +        for section in doc.args.values() + doc.sections:
>> > +            content = str(section)
>> > +            if not content or content.isspace():
>> > +                raise QAPISemError(doc.info,
>> > +                                   "Empty doc section '%s'" %
>> > section.name)
>> > +
>> > +        if not doc.expr:
>> > +            check_freeform_doc(doc)
>> > +        else:
>> > +            check_definition_doc(doc, doc.expr, doc.info)
>> > +
>> > +    return docs
>> > +
>> > +
>> >  #
>> >  # Schema compiler frontend
>> >  #
>> > @@ -1229,7 +1470,9 @@ class QAPISchemaEvent(QAPISchemaEntity):
>> >  class QAPISchema(object):
>> >      def __init__(self, fname):
>> >          try:
>> > -            self.exprs = check_exprs(QAPISchemaParser(open(fname,
>> > "r")).exprs)
>> > +            parser = QAPISchemaParser(open(fname, "r"))
>> > +            self.exprs = check_exprs(parser.exprs)
>> > +            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 0000000000..436749ec7b
>> > --- /dev/null
>> > +++ b/scripts/qapi2texi.py
>> > @@ -0,0 +1,266 @@
>> > +#!/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 = """
>> > +@deftypefn {type} {{}} {name}
>> > +
>> > +{body}
>> > +
>> > +@end deftypefn
>> > +
>> > +""".format
>> > +
>> > +ENUM_FMT = """
>> > +@deftp Enum {name}
>> > +
>> > +{body}
>> > +
>> > +@end deftp
>> > +
>> > +""".format
>> > +
>> > +STRUCT_FMT = """
>> > +@deftp {{{type}}} {name}
>> > +
>> > +{body}
>> > +
>> > +@end deftp
>> > +
>> > +""".format
>> > +
>> > +EXAMPLE_FMT = """@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'\b_([^_\n]+)_\b', 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"""
>> > +    # TODO: Neglects to escape @ characters.
>> > +    # We should probably escape them in subst_braces(), and rename the
>> > +    # function to subst_special() or subs_texi_special().  If we do that,
>> > we
>> > +    # need to delay it until after subst_vars() in texi_format().
>> > +    doc = subst_braces(doc).strip('\n')
>> > +    return EXAMPLE_FMT(code=doc)
>> > +
>> > +
>> > +def texi_format(doc):
>> > +    """
>> > +    Format documentation
>> > +
>> > +    Lines starting with:
>> > +    - |: generates an @example
>> > +    - =: generates @section
>> > +    - ==: generates @subsection
>> > +    - 1. or 1): generates an @enumerate @item
>> > +    - */-: 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 == ""
>> > +
>> > +        # FIXME: Doing this in a single if / elif chain is
>> > +        # problematic.  For instance, a line without markup terminates
>> > +        # a list if it follows a blank line (reaches the final elif),
>> > +        # but a line with some *other* markup, such as a = title
>> > +        # doesn't.
>> > +        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):
>> 
>> [.] is an unusual way to say \.
>
> Right, forgot to change that when removing ) from v6.
>
>> 
>> Perhaps we should consistently use r"..." for strings used as regular
>> expressions.
>> 
>
> No idea, could be changed on top.

Yes.  But if you respin anyway, adding some r wouldn't hurt.

>> > +            if not inlist:
>> > +                lines.append("@enumerate")
>> > +                inlist = "enumerate"
>> > +            line = line[line.find(" ")+1:]
>> > +            lines.append("@item")
>> > +        elif re.match("^[*-] ", line):
>> > +            if not inlist:
>> > +                lines.append("@itemize %s" % {'*': "@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_body(doc):
>> > +    """
>> > +    Format the body of a symbol documentation:
>> > +    - main body
>> > +    - table of arguments
>> > +    - followed by "Returns/Notes/Since/Example" sections
>> > +    """
>> > +    body = texi_format(str(doc.body)) + "\n"
>> > +    if doc.args:
>> > +        body += "@table @asis\n"
>> > +        for arg, section in doc.args.iteritems():
>> > +            desc = str(section)
>> > +            opt = ''
>> > +            if "#optional" in desc:
>> > +                desc = desc.replace("#optional", "")
>> > +                opt = ' (optional)'
>> > +            body += "@item @code{'%s'}%s\n%s\n" % (arg, opt,
>> > +                                                   texi_format(desc))
>> > +        body += "@end table\n"
>> > +
>> > +    for section in doc.sections:
>> > +        name, doc = (section.name, str(section))
>> > +        func = texi_format
>> > +        if name.startswith("Example"):
>> > +            func = texi_example
>> > +
>> > +        if name:
>> 
>> @quotation produces confusing .txt and .html output, as discussed in
>> review of v6.  Either drop it, or record the problem in a comment, e.g.
>> 
>>                # FIXME the indentation produced by @quotation in .txt and
>>                # .html output is confusing
>> 
>
> added

Thanks.

>> > +            body += "\n@quotation %s\n%s\n@end quotation" % \
>> > +                    (name, func(doc))
>> > +        else:
>> > +            body += func(doc)
>> > +
>> > +    return body
>> > +
>> > +
>> > +def texi_alternate(expr, doc):
>> > +    """Format an alternate to texi"""
>> > +    body = texi_body(doc)
>> > +    return STRUCT_FMT(type="Alternate",
>> > +                      name=doc.symbol,
>> > +                      body=body)
>> > +
>> > +
>> > +def texi_union(expr, doc):
>> > +    """Format a union to texi"""
>> > +    discriminator = expr.get("discriminator")
>> > +    if discriminator:
>> > +        union = "Flat Union"
>> > +    else:
>> > +        union = "Simple Union"
>> 
>> Not sure flat vs. simple matters for users of generated documentation,
>> but let's not worry about that now.
>> 
>> > +
>> > +    body = texi_body(doc)
>> > +    return STRUCT_FMT(type=union,
>> > +                      name=doc.symbol,
>> > +                      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"""
>> > +    body = texi_body(doc)
>> > +    return STRUCT_FMT(type="Struct",
>> > +                      name=doc.symbol,
>> > +                      body=body)
>> > +
>> > +
>> > +def texi_command(expr, doc):
>> > +    """Format a command to texi"""
>> > +    body = texi_body(doc)
>> > +    return COMMAND_FMT(type="Command",
>> > +                       name=doc.symbol,
>> > +                       body=body)
>> > +
>> > +
>> > +def texi_event(expr, doc):
>> > +    """Format an event to texi"""
>> > +    body = texi_body(doc)
>> > +    return COMMAND_FMT(type="Event",
>> > +                       name=doc.symbol,
>> > +                       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}[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])
>> > +    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 2841c5144a..b29f996fdc 100644
>> > --- a/docs/qapi-code-gen.txt
>> > +++ b/docs/qapi-code-gen.txt
>> > @@ -44,40 +44,148 @@ Input must be ASCII (although QMP supports full
>> > Unicode strings, the
>> >  QAPI parser does not).  At present, there is no place where a QAPI
>> >  schema requires the use of JSON numbers or null.
>> >  
>> > +
>> > +=== Comments ===
>> > +
>> >  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
>> > -x.y.z)' comment.  For example:
>> > -
>> > -    ##
>> > -    # @BlockStats:
>> > -    #
>> > -    # Statistics of a virtual block device or a block backing device.
>> > -    #
>> > -    # @device: #optional If the stats are for a virtual block device, the
>> > name
>> > -    #          corresponding to the virtual block device.
>> > -    #
>> > -    # @stats:  A @BlockDeviceStats for the device.
>> > -    #
>> > -    # @parent: #optional This describes the file block device if it has
>> > one.
>> > -    #
>> > -    # @backing: #optional This describes the backing block device if it
>> > has one.
>> > -    #           (Since 2.0)
>> > -    #
>> > -    # Since: 0.14.0
>> > -    ##
>> > -    { 'struct': 'BlockStats',
>> > -      'data': {'*device': 'str', 'stats': 'BlockDeviceStats',
>> > -               '*parent': 'BlockStats',
>> > -               '*backing': 'BlockStats'} }
>> > +newline is ignored.
>> > +
>> > +A multi-line comment that starts and ends with a '##' line is a
>> > +documentation comment.  These are parsed by the documentation
>> > +generator, which recognizes certain markup detailed below.
>> > +
>> > +
>> > +==== Documentation markup ====
>> > +
>> > +Comment text starting with '=' is a section title:
>> > +
>> > +    # = Section title
>> > +
>> > +Double the '=' for a subsection title:
>> > +
>> > +    # == Subection title
>> > +
>> > +'|' denotes examples:
>> > +
>> > +    # | Text of the example, may span
>> > +    # | multiple lines
>> > +
>> > +'*' starts an itemized list:
>> > +
>> > +    # * First item, may span
>> > +    #   multiple lines
>> > +    # * Second item
>> > +
>> > +You can also use '-' instead of '*'.
>> > +
>> > +A decimal number followed by '.' starts a numbered list:
>> > +
>> > +    # 1. First item, may span
>> > +    # multiple lines

Should the second line be indented?

>> > +    # 2. Second item
>> > +
>> > +The actual number doesn't matter.  You could even use '*' instead of
>> > +'2.' for the second item.
>> > +
>> > +FIXME what exactly ends a list
>> 
>> Can you say offhand what ends a list?  If yes, can we resolve this FIXME
>> now rather than later?
>
> well, the code is too permissive, there is a FIXME there already.
>
> In general, I think we want to end a list after a blank line and a "non-item" line. Is the wording ok?

Blank line is pretty restrictive, because list items consisting of
multiple paragraphs aren't possible then.

I think writing something like this is fairly natural:

    * First item, may span
      multiple lines.

      Even multiple paragaphs are okay.

    * Second item

      - This is a sublist
      - Second item of sublist

    * Third item

    This sentence is not part of the list.

We don't support sublists, and I'm not saying we should, I'm just
showing how they would fit in.

My point is that indentation is a natural way to delimit list items.

I don't expect you to implement this now.  All I want for this series is
reasonable documentation.  As far as I can tell, texi_format() ends the
list at a blank line, except when it doesn't (see its FIXME).

What about this: replace "FIXME what exactly ends a list" by

    Lists can't be nested.  Blank lines are currently not supported
    within lists.

and add to texi_format()'s FIXME something like

    Make sure to update section "Documentation markup" in
    docs/qapi-code-gen.txt when fixing this.

Okay?

>> > +
>> > +Additional whitespace between the initial '#' and the comment text is
>> > +permitted.
>> > +
>> > +*foo* and _foo_ are for strong and emphasis styles respectively (they
>> > +do not work over multiple lines). @foo is used to reference a name in
>> > +the schema.
>> > +
>> > +Example:
>> > +
>> > +##
>> > +# = 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
>> > +#
>> > +##
>> > +
>> > +
>> > +==== Expression documentation ====
>> > +
>> > +Each expression that isn't an include directive must be preceded by a
>> > +documentation block.  Such blocks are called expression documentation
>> > +blocks.
>> > +
>> > +The first line of the documentation names the expression, then the
>> > +documentation body is provided, then individual documentation about
>> > +each member of 'data' is provided. Finally, several tagged sections
>> > +can be added.
>> 
>> In my review of v6, I wrote:
>> 
>>     I'm afraid this is more aspiration than specification: the parser
>>     accepts these things in almost any order.
>> 
>>     Could be filed under "deficiencies" for now.
>> 
>> To file under "deficiencies", we need to add a FIXME or TODO either
>> here, or in the parser.  Assuming you haven't tightened the parser
>> meanwhile (hope you haven't; we need to control the churn).
>
> ok
>
>> 
>>     Member of 'data' won't remain accurate.  Consider:
>> 
>>         { 'union': 'GlusterServer',
>>           'base': { 'type': 'GlusterTransport' },
>>           'discriminator': 'type',
>>           'data': { 'unix': 'UnixSocketAddress',
>>                     'tcp': 'InetSocketAddress' } }
>> 
>>     Its doc comment currently doesn't contain argument sections.  It should
>>     eventually contain @type:, @unix: and @tcp:.  Only the latter two are
>>     members of 'data'.
>> 
>>     I should propose something better, but I'm getting perilously close to
>>     the christmas break already.  Later.
>> 
>> Also: passive voice is meh (not your idea; you adapted the existing
>> text).
>> 
>> What about:
>> 
>>    The documentation block consists of a first line naming the
>>    expression, an optional overview, a description of each argument (for
>>    commands and events) or member (for structs, unions and alternates),
>>    and optional tagged sections.
>> 
>> I still don't like the use of "section" for both the = things and the
>> tagged sections, but it's not important enough to spend time on it now.
>> 
>
> ok
>
>> > +
>> > +Optional members are tagged with the phrase '#optional', often with
>> 
>> If we adopt my text, we should say "arguments / members" here.
>> 
>
> ok
>
>> > +their default value; and extensions added after the expression was
>> > +first released are also given a '(since x.y.z)' comment.
>> > +
>> > +A tagged section starts with one of the following words:
>> > +"Note:"/"Notes:", "Since:", "Example"/"Examples", "Returns:", "TODO:".
>> 
>> What ends such a section?  The start of the next one / end of the
>> comment block?
>> 
>
> yes
>
>> I suspect we could avoid PATCH 03 by recognizing "TODO" in addition to
>> "TODO:".  Might also be more robust.  But let's ignore this now.
>> 
>> > +
>> > +A 'Since: x.y.z' tag lists the release that introduced the expression.
>> 
>> Nitpick: 'Since: x.y.z' is a tagged section, not a tag.
>> 
>
> ok
>
>> > +
>> > +For example:
>> > +
>> > +##
>> > +# @BlockStats:
>> > +#
>> > +# Statistics of a virtual block device or a block backing device.
>> > +#
>> > +# @device: #optional If the stats are for a virtual block device, the name
>> > +#          corresponding to the virtual block device.
>> > +#
>> > +# @node-name: #optional The node name of the device. (since 2.3)
>> > +#
>> > +# ... more members ...
>> > +#
>> > +# Since: 0.14.0
>> > +##
>> > +{ 'struct': 'BlockStats',
>> > +  'data': {'*device': 'str', '*node-name': 'str',
>> > +           ... more members ... } }
>> > +
>> > +##
>> > +# @query-blockstats:
>> > +#
>> > +# Query the @BlockStats for all virtual block devices.
>> > +#
>> > +# @query-nodes: #optional If true, the command will query all the
>> > +#               block nodes ... explain, explain ...  (since 2.3)
>> > +#
>> > +# Returns: A list of @BlockStats for each virtual block devices.
>> > +#
>> > +# Since: 0.14.0
>> > +#
>> > +# Example:
>> > +#
>> > +# -> { "execute": "query-blockstats" }
>> > +# <- {
>> > +#      ... lots of output ...
>> > +#    }
>> > +#
>> > +##
>> > +{ 'command': 'query-blockstats',
>> > +  'data': { '*query-nodes': 'bool' },
>> > +  'returns': ['BlockStats'] }
>> > +
>> > +==== Free-form documentation ====
>> > +
>> > +A documentation block that isn't an expression documentation block is
>> > +a free-form documentation block.  These may be used to provide
>> > +additional text and structuring content.
>> > +
>> > +
>> > +=== Schema overview ===
>> >  
>> >  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
>> [Tests look okay, snipped...]
>> 

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [Qemu-devel] [PATCH v7 15/21] qapi: add qapi2texi script
  2017-01-12 13:17       ` Markus Armbruster
@ 2017-01-12 17:36         ` Marc-André Lureau
  0 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2017-01-12 17:36 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Marc-André Lureau, qemu-devel

Hi

----- Original Message -----
> Marc-André Lureau <mlureau@redhat.com> writes:
> 
> > Hi
> >
> > ----- Original Message -----
> >> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
> >> 
> >> > 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 description:
> >> >
> >> >   ##
> >> >   # @symbol:
> >> >   #
> >> >   # Symbol body ditto ergo sum. Foo bar
> >> >   # baz ding.
> >> >   #
> >> >   # @param1: the frob to frobnicate
> >> >   # @param2: #optional how hard to frobnicate
> >> >   #
> >> >   # Returns: the frobnicated frob.
> >> >   #          If frob isn't frobnicatable, GenericError.
> >> >   #
> >> >   # 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 | tag_section |
> >> > freeform_comment
> >> > }
> >> > member = "# @" name ':' [ text ] "\n" freeform_comment
> >> > tag_section = "# " ( "Returns:", "Since:", "Note:", "Notes:",
> >> > "Example:",
> >> > "Examples:" ) [ text ]  "\n" freeform_comment
> >> > text = free text with markup
> >> >
> >> > Note that the grammar is ambiguous: a line "# @foo:\n" can be parsed
> >> > both as freeform_comment and as symbol_comment.  The actual parser
> >> > recognizes symbol_comment.
> >> >
> >> > See docs/qapi-code-gen.txt for more details.
> >> >
> >> > Deficiencies:
> >> 
> >> Perhaps "Deficiencies and limitations".
> >
> > ok
> >
> >> 
> >> > - the generated QMP documentation includes internal types
> >> > - union-type support is lacking
> >> 
> >> "union type" (no dash).
> >> 
> >> > - type information is lacking in generated documentation
> >> > - doc comment error message positions are imprecise, they point
> >> >   to the beginning of the comment.
> >> > - see other TODO/FIXME in this commit
> >> 
> >> Suggest: - a few minor issues, all marked TODO/FIXME in the code
> >> 
> >
> > ok
> >
> >> >
> >> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> >> > ---
> >> [diffstat snipped...]
> >> > diff --git a/scripts/qapi.py b/scripts/qapi.py
> >> > index 3d5f9e1eaf..a92a86f428 100644
> >> > --- a/scripts/qapi.py
> >> > +++ b/scripts/qapi.py
> >> > @@ -125,6 +125,122 @@ class QAPISemError(QAPIError):
> >> >                             info['parent'], msg)
> >> >  
> >> >  
> >> > +class QAPIDoc(object):
> >> > +    class Section(object):
> >> > +        def __init__(self, name=None):
> >> > +            # optional section name (argument/member or section name)
> >> > +            self.name = name
> >> > +            # the list of lines 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, info):
> >> > +        # self.parser is used to report errors with QAPIParseError.
> >> > The
> >> > +        # resulting error position depends on the state of the parser.
> >> > +        # It happens to be the beginning of the comment.  More or less
> >> > +        # servicable, but action at a distance.
> >> > +        self.parser = parser
> >> > +        self.info = info
> >> > +        self.symbol = None
> >> > +        self.body = QAPIDoc.Section()
> >> > +        # dict mapping parameter name to ArgSection
> >> > +        self.args = OrderedDict()
> >> > +        # a list of Section
> >> > +        self.sections = []
> >> > +        # the current section
> >> > +        self.section = self.body
> >> > +        # associated expression (to be set by expression parser)
> >> > +        self.expr = None
> >> > +
> >> > +    def has_section(self, name):
> >> > +        """Return True if we have a section with this name."""
> >> > +        for i in self.sections:
> >> > +            if i.name == name:
> >> > +                return True
> >> > +        return False
> >> > +
> >> > +    def append(self, line):
> >> > +        """Parse a comment line and add it to the documentation."""
> >> > +        line = line[1:]
> >> > +        if not line:
> >> > +            self._append_freeform(line)
> >> > +            return
> >> > +
> >> > +        if line[0] != ' ':
> >> > +            raise QAPIParseError(self.parser, "Missing space after #")
> >> > +        line = line[1:]
> >> > +
> >> > +        # FIXME not nice: things like '#  @foo:' and '# @foo: ' aren't
> >> > +        # recognized, and get silently treated as ordinary text
> >> > +        if self.symbol:
> >> > +            self._append_symbol_line(line)
> >> > +        elif not self.body.content and line.startswith("@"):
> >> > +            if not line.endswith(":"):
> >> > +                raise QAPIParseError(self.parser, "Line should end with
> >> > :")
> >> > +            self.symbol = line[1:-1]
> >> > +            # FIXME invalid names other than the empty string aren't
> >> > flagged
> >> > +            if not self.symbol:
> >> > +                raise QAPIParseError(self.parser, "Invalid name")
> >> > +        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:",
> >> > +                      "TODO:"):
> >> > +            line = line[len(name)+1:]
> >> > +            self._start_section(name[:-1])
> >> > +
> >> > +        self._append_freeform(line)
> >> > +
> >> > +    def _start_args_section(self, name):
> >> > +        # FIXME invalid names other than the empty string aren't
> >> > flagged
> >> > +        if not name:
> >> > +            raise QAPIParseError(self.parser, "Invalid parameter name")
> >> > +        if name in self.args:
> >> > +            raise QAPIParseError(self.parser,
> >> > +                                 "'%s' parameter name duplicated" %
> >> > name)
> >> > +        if self.sections:
> >> > +            raise QAPIParseError(self.parser,
> >> > +                                 "'%s' parameter documentation is
> >> > interleaved "
> >> > +                                 "with other sections" % name)
> >> 
> >> This error message is rather confusing.  Ideally, we'd point to the the
> >> source code that made us add to self.sections, but that's not in the
> >> cards right now.  Here's my best try:
> >> 
> >>                raise QAPIParseError(self.parser,
> >>                                     "'@%s:' can't follow '%s' section"
> >>                                     % (name, self.sections[0].name))
> >> 
> >
> > works for me too
> >
> >> > +        self.section = QAPIDoc.ArgSection(name)
> >> > +        self.args[name] = self.section
> >> > +
> >> > +    def _start_section(self, name=""):
> >> 
> >> Note: @name changed to default to "" since v6 for the benefit of
> >> _append_freeform() below.
> >> 
> >> > +        if name in ("Returns", "Since") and self.has_section(name):
> >> > +            raise QAPIParseError(self.parser,
> >> > +                                 "Duplicated '%s' section" % name)
> >> > +        self.section = QAPIDoc.Section(name)
> >> > +        self.sections.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()):
> >> 
> >> PEP8 recommends breaking lines before operators in new code.
> >
> > ok
> >
> >> 
> >> > +            self._start_section()
> >> 
> >> Changed from v6's
> >> 
> >>                raise QAPIParseError(self.parser, "Invalid section
> >>                indentation")
> >> 
> >> May well be an improvement (I'm certainly no fan of this error myself),
> >> but it throws my review off the fast track: now I have to figure out how
> >> the new code works.  Can you give me a hint on what this change does?
> >
> > In v6, we rejected unindented sections after arguments (see
> > tests/qapi-schema/doc-bad-section.json):
> >
> > # @arg: blah bla
> > #       zing bar
> > #
> > # This should be rejected
> >
> > And a patch in v6 reordered the json comments. But you asked for the
> > doc to not be reordered without careful review (both source and
> > output). So those additional paragraphs are added as untitled
> > sections, and the doc generator keep section order. The output will be
> > in the same order as the source.
> 
> Thanks.
> 
> >> Or should we take a shortcut and revert to v6 here?  We can always
> >> improve on top.
> >
> > Probably not needed to revert.
> 
> I played with it a bit, and it looks like it's working fine.
> 
> >> > +        if (in_arg or not self.section.name or
> >> > +                not self.section.name.startswith("Example")):
> >> 
> >> Again, break before the operator.
> >
> > ok
> >
> >> 
> >> > +            line = line.strip()
> >> > +        self.section.append(line)
> >> > +
> >> > +
> >> >  class QAPISchemaParser(object):
> >> >  
> >> >      def __init__(self, fp, previously_included=[], incl_info=None):
> >> > @@ -140,11 +256,17 @@ 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 == '#':
> >> > +                doc = self.get_doc(info)
> >> > +                self.docs.append(doc)
> >> > +                continue
> >> > +
> >> >              expr = self.get_expr(False)
> >> >              if isinstance(expr, dict) and "include" in expr:
> >> >                  if len(expr) != 1:
> >> > @@ -162,6 +284,7 @@ class QAPISchemaParser(object):
> >> >                          raise QAPISemError(info, "Inclusion loop for
> >> >                          %s"
> >> >                                             % include)
> >> >                      inf = inf['parent']
> >> > +
> >> >                  # skip multiple include of the same file
> >> >                  if incl_abs_fname in previously_included:
> >> >                      continue
> >> > @@ -172,12 +295,19 @@ 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
> >> > +                        self.docs[-1].info['file'] == fname and
> >> > +                        not self.docs[-1].expr):
> >> 
> >> Again, break before the operator.
> >
> > ok
> >
> >> 
> >> > +                    self.docs[-1].expr = expr
> >> > +                    expr_elem['doc'] = self.docs[-1]
> >> > +
> >> >                  self.exprs.append(expr_elem)
> >> >  
> >> > -    def accept(self):
> >> > +    def accept(self, skip_comment=True):
> >> >          while True:
> >> >              self.tok = self.src[self.cursor]
> >> >              self.pos = self.cursor
> >> > @@ -185,7 +315,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 == "'":
> >> 
> >> Copied from review of v3, so I don't forget:
> >> 
> >> Comment tokens are thrown away as before, except when the parser asks
> >> for them by passing skip_comment=False, or when the comment token starts
> >> with ##.  The parser asks while parsing a doc comment, in get_doc().
> >> 
> >> This is a backchannel from the parser to the lexer.  I'd rather avoid
> >> such lexer hacks, but I guess we can address that on top.
> >> 
> >> A comment starting with ## inside an expression is now a syntax error.
> >> For instance, input
> >> 
> >>     {
> >>     ##
> >> 
> >> yields
> >> 
> >>     /dev/stdin:2:1: Expected string or "}"
> >> 
> >> Rather unfriendly error message, but we can fix that on top.
> >> 
> >> > @@ -319,6 +455,28 @@ class QAPISchemaParser(object):
> >> >              raise QAPIParseError(self, 'Expected "{", "[" or string')
> >> >          return expr
> >> >  
> >> > +    def get_doc(self, info):
> >> > +        if self.val != '##':
> >> > +            raise QAPIParseError(self, "Junk after '##' at start of "
> >> > +                                 "documentation comment")
> >> > +
> >> > +        doc = QAPIDoc(self, info)
> >> > +        self.accept(False)
> >> > +        while self.tok == '#':
> >> > +            if self.val.startswith('##'):
> >> > +                # End of doc comment
> >> > +                if self.val != '##':
> >> > +                    raise QAPIParseError(self, "Junk after '##' at end
> >> > of
> >> > "
> >> > +                                         "documentation comment")
> >> > +                self.accept()
> >> > +                return doc
> >> > +            else:
> >> > +                doc.append(self.val)
> >> > +            self.accept(False)
> >> > +
> >> > +        raise QAPIParseError(self, "Documentation comment must end with
> >> > '##'")
> >> > +
> >> > +
> >> >  #
> >> >  # Semantic analysis of schema expressions
> >> >  # TODO fold into QAPISchema
> >> > @@ -703,6 +861,11 @@ def check_exprs(exprs):
> >> >      for expr_elem in exprs:
> >> >          expr = expr_elem['expr']
> >> >          info = expr_elem['info']
> >> > +
> >> > +        if 'doc' not in expr_elem:
> >> > +            raise QAPISemError(info,
> >> > +                               "Expression missing documentation
> >> > comment")
> >> > +
> >> >          if 'enum' in expr:
> >> >              check_keys(expr_elem, 'enum', ['data'], ['prefix'])
> >> >              add_enum(expr['enum'], info, expr['data'])
> >> > @@ -761,6 +924,84 @@ def check_exprs(exprs):
> >> >      return exprs
> >> >  
> >> >  
> >> > +def check_freeform_doc(doc):
> >> > +    if doc.symbol:
> >> > +        raise QAPISemError(doc.info,
> >> > +                           "Documention for '%s' is not followed"
> >> > +                           " by the definition" % doc.symbol)
> >> > +
> >> > +    body = str(doc.body)
> >> > +    if re.search(r'@\S+:', body, re.MULTILINE):
> >> > +        raise QAPISemError(doc.info,
> >> > +                           "Free-form documentation block must not
> >> > contain"
> >> > +                           " @NAME: sections")
> >> > +
> >> > +
> >> > +def check_definition_doc(doc, expr, info):
> >> 
> >> Lots of churn in this function.  It can certainly use improvement, as
> >> pointed out in prior reviews, but it triggers still more review.  Based
> >> on the tests, I guess it's for finding documented parameters that don't
> >> actually exist, and to diagnose optional mismatch.  Anything else?
> >> 
> >
> > implicitely adds #optional (since it's no longer in type info, it better be
> > in the description), code simplification, message improvement too.
> >
> > I'd rather keep this too, and improve on top.
> 
> Okay.  Review below.
> 
> >> Or should we take a shortcut and revert to v6 here?  We can always
> >> improve on top.  I think v6 would need a # TODO Should ensure #optional
> >> matches the schema.
> >> 
> >> > +    for i in ('enum', 'union', 'alternate', 'struct', 'command',
> >> > 'event'):
> >> > +        if i in expr:
> >> > +            meta = i
> >> > +            break
> >> > +
> >> > +    name = expr[meta]
> >> > +    if doc.symbol != name:
> >> > +        raise QAPISemError(info, "Definition of '%s' follows
> >> > documentation"
> >> > +                           " for '%s'" % (name, doc.symbol))
> >> > +    if doc.has_section('Returns') and 'command' not in expr:
> >> > +        raise QAPISemError(info, "'Returns:' is only valid for
> >> > commands")
> >> 
> >> Copied from review of v5, so I don't forget:
> >> 
> >> We accept 'Returns:' even when the command doesn't return anything,
> >> because we currently use it to document errors, too.  Can't say I like
> >> that, but it's out of scope here.
> >> 
> >> > +
> >> > +    if meta == 'union':
> >> > +        args = expr.get('base', [])
> 
> Note: args is either string or dictionary.
> 
> >> > +    else:
> >> > +        args = expr.get('data', [])
> 
> Note: args is either string (only if command or event), dictionary (only
> if struct, alternate, command, event) or list (only if enum)
> 
> >> > +    if isinstance(args, dict):
> >> > +        args = args.keys()
> 
> Now args is either string (only if command or event) or list.
> 
> >> > +
> >> > +    if meta == 'alternate' or \
> >> > +       (meta == 'union' and not expr.get('discriminator')):
> 
> PEP8 prefers parenthesis over backslash, and recommends breaking lines
> before instead of after binary operators:

ok

> 
>           if (meta == 'alternate'
>               or (meta == 'union' and not expr.get('discriminator'))):
> 
> Note: args can only be list here.
> 
> >> > +        args.append('type')
> 
> Works because we know it's a list.  It's less than obvious, though.
> 
> >> > +
> >> > +    if isinstance(args, list):
> >> > +        for arg in args:
> >> > +            if arg[0] == '*':
> >> > +                opt = True
> >> > +                desc = doc.args.get(arg[1:])
> >> > +            else:
> >> > +                opt = False
> >> > +                desc = doc.args.get(arg)
> >> > +            if not desc:
> >> > +                continue
> >> > +            desc_opt = "#optional" in str(desc)
> >> > +            if desc_opt and not opt:
> >> > +                raise QAPISemError(info, "Description has #optional, "
> >> > +                                   "but the declaration doesn't")
> >> > +            if not desc_opt and opt:
> >> > +                # silently fix the doc
> >> > +                desc.append("#optional")
> 
> #optional is redundant information.  But as long as we use it, it better
> be correct, to avoid misleading readers of the schema.  Two sides of
> correctness: it's there when arg is optional, and it's not there when
> it's not.  You enforce the latter, but you silently fix the former.  I
> figure you do that because #optional is currently missing in quite a few
> places.
> 
> Let's add
>                       # TODO either fix the schema and make this an error,
>                       # or drop #optional entirely
> 

ok

> >> > +
> >> > +    doc_args = set(doc.args.keys())
> >> > +    args = set([name.strip('*') for name in args])
> 
> Now args morphs from string or dict to set.  I'd use a new variable.
> 
> If args is a string, it becomes a set of characters, which is not what
> you want.  For instance, qapi-schema.json's QCryptoBlockOpenOptions has
> 'base': 'QCryptoBlockOptionsBase', and args changes from
> 'QCryptoBlockOptionsBase' to set(['O', 'a', 'C', 'B', 'e', 'i', 'c',
> 'k', 'l', 'o', 'n', 'Q', 'p', 's', 'r', 't', 'y']) here.
> 
> >> > +    if not doc_args.issubset(args):
> >> > +        raise QAPISemError(info, "The following documented members are
> >> > not in "
> >> > +                           "the declaration: %s" % ", ".join(doc_args -
> >> > args))
> 
> If args is originally a string, then we happily accept documentation for
> its characters.  Quite unlikely to bite in practice, but it's wrong
> anyways.
> 
> I suspect you could save yourself some grief by returning early when
> args is string, say:
> 
>     if meta == 'union':
>         args = expr.get('base', [])
>     else:
>         args = expr.get('data', [])
>     if isinstance(args, str):
>         return
>     if isinstance(args, dict):
>         args = args.keys()
>     assert isinstance(args, list)
> 
>     if (meta == 'alternate'
>             or (meta == 'union' and not expr.get('discriminator'))):
>         args.append('type')
> 
>     for arg in args:
>         ...

ok

> 
> >> Copied from review of v5, so I don't forget:
> >> 
> >> As explained in review of v3, this is only a subset of the real set of
> >> members.  Computing the exact set is impractical when working with the
> >> abstract syntax tree.  I believe we'll eventually have to rewrite this
> >> code to work with the QAPISchemaEntity instead.
> >> 
> >> > +
> >> > +
> >> > +def check_docs(docs):
> >> > +    for doc in docs:
> >> > +        for section in doc.args.values() + doc.sections:
> >> > +            content = str(section)
> >> > +            if not content or content.isspace():
> >> > +                raise QAPISemError(doc.info,
> >> > +                                   "Empty doc section '%s'" %
> >> > section.name)
> >> > +
> >> > +        if not doc.expr:
> >> > +            check_freeform_doc(doc)
> >> > +        else:
> >> > +            check_definition_doc(doc, doc.expr, doc.info)
> >> > +
> >> > +    return docs
> >> > +
> >> > +
> >> >  #
> >> >  # Schema compiler frontend
> >> >  #
> >> > @@ -1229,7 +1470,9 @@ class QAPISchemaEvent(QAPISchemaEntity):
> >> >  class QAPISchema(object):
> >> >      def __init__(self, fname):
> >> >          try:
> >> > -            self.exprs = check_exprs(QAPISchemaParser(open(fname,
> >> > "r")).exprs)
> >> > +            parser = QAPISchemaParser(open(fname, "r"))
> >> > +            self.exprs = check_exprs(parser.exprs)
> >> > +            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 0000000000..436749ec7b
> >> > --- /dev/null
> >> > +++ b/scripts/qapi2texi.py
> >> > @@ -0,0 +1,266 @@
> >> > +#!/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 = """
> >> > +@deftypefn {type} {{}} {name}
> >> > +
> >> > +{body}
> >> > +
> >> > +@end deftypefn
> >> > +
> >> > +""".format
> >> > +
> >> > +ENUM_FMT = """
> >> > +@deftp Enum {name}
> >> > +
> >> > +{body}
> >> > +
> >> > +@end deftp
> >> > +
> >> > +""".format
> >> > +
> >> > +STRUCT_FMT = """
> >> > +@deftp {{{type}}} {name}
> >> > +
> >> > +{body}
> >> > +
> >> > +@end deftp
> >> > +
> >> > +""".format
> >> > +
> >> > +EXAMPLE_FMT = """@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'\b_([^_\n]+)_\b', 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"""
> >> > +    # TODO: Neglects to escape @ characters.
> >> > +    # We should probably escape them in subst_braces(), and rename the
> >> > +    # function to subst_special() or subs_texi_special().  If we do
> >> > that,
> >> > we
> >> > +    # need to delay it until after subst_vars() in texi_format().
> >> > +    doc = subst_braces(doc).strip('\n')
> >> > +    return EXAMPLE_FMT(code=doc)
> >> > +
> >> > +
> >> > +def texi_format(doc):
> >> > +    """
> >> > +    Format documentation
> >> > +
> >> > +    Lines starting with:
> >> > +    - |: generates an @example
> >> > +    - =: generates @section
> >> > +    - ==: generates @subsection
> >> > +    - 1. or 1): generates an @enumerate @item
> >> > +    - */-: 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 == ""
> >> > +
> >> > +        # FIXME: Doing this in a single if / elif chain is
> >> > +        # problematic.  For instance, a line without markup terminates
> >> > +        # a list if it follows a blank line (reaches the final elif),
> >> > +        # but a line with some *other* markup, such as a = title
> >> > +        # doesn't.
> >> > +        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):
> >> 
> >> [.] is an unusual way to say \.
> >
> > Right, forgot to change that when removing ) from v6.
> >
> >> 
> >> Perhaps we should consistently use r"..." for strings used as regular
> >> expressions.
> >> 
> >
> > No idea, could be changed on top.
> 
> Yes.  But if you respin anyway, adding some r wouldn't hurt.

ok

> 
> >> > +            if not inlist:
> >> > +                lines.append("@enumerate")
> >> > +                inlist = "enumerate"
> >> > +            line = line[line.find(" ")+1:]
> >> > +            lines.append("@item")
> >> > +        elif re.match("^[*-] ", line):
> >> > +            if not inlist:
> >> > +                lines.append("@itemize %s" % {'*': "@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_body(doc):
> >> > +    """
> >> > +    Format the body of a symbol documentation:
> >> > +    - main body
> >> > +    - table of arguments
> >> > +    - followed by "Returns/Notes/Since/Example" sections
> >> > +    """
> >> > +    body = texi_format(str(doc.body)) + "\n"
> >> > +    if doc.args:
> >> > +        body += "@table @asis\n"
> >> > +        for arg, section in doc.args.iteritems():
> >> > +            desc = str(section)
> >> > +            opt = ''
> >> > +            if "#optional" in desc:
> >> > +                desc = desc.replace("#optional", "")
> >> > +                opt = ' (optional)'
> >> > +            body += "@item @code{'%s'}%s\n%s\n" % (arg, opt,
> >> > +                                                   texi_format(desc))
> >> > +        body += "@end table\n"
> >> > +
> >> > +    for section in doc.sections:
> >> > +        name, doc = (section.name, str(section))
> >> > +        func = texi_format
> >> > +        if name.startswith("Example"):
> >> > +            func = texi_example
> >> > +
> >> > +        if name:
> >> 
> >> @quotation produces confusing .txt and .html output, as discussed in
> >> review of v6.  Either drop it, or record the problem in a comment, e.g.
> >> 
> >>                # FIXME the indentation produced by @quotation in .txt and
> >>                # .html output is confusing
> >> 
> >
> > added
> 
> Thanks.
> 
> >> > +            body += "\n@quotation %s\n%s\n@end quotation" % \
> >> > +                    (name, func(doc))
> >> > +        else:
> >> > +            body += func(doc)
> >> > +
> >> > +    return body
> >> > +
> >> > +
> >> > +def texi_alternate(expr, doc):
> >> > +    """Format an alternate to texi"""
> >> > +    body = texi_body(doc)
> >> > +    return STRUCT_FMT(type="Alternate",
> >> > +                      name=doc.symbol,
> >> > +                      body=body)
> >> > +
> >> > +
> >> > +def texi_union(expr, doc):
> >> > +    """Format a union to texi"""
> >> > +    discriminator = expr.get("discriminator")
> >> > +    if discriminator:
> >> > +        union = "Flat Union"
> >> > +    else:
> >> > +        union = "Simple Union"
> >> 
> >> Not sure flat vs. simple matters for users of generated documentation,
> >> but let's not worry about that now.
> >> 
> >> > +
> >> > +    body = texi_body(doc)
> >> > +    return STRUCT_FMT(type=union,
> >> > +                      name=doc.symbol,
> >> > +                      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"""
> >> > +    body = texi_body(doc)
> >> > +    return STRUCT_FMT(type="Struct",
> >> > +                      name=doc.symbol,
> >> > +                      body=body)
> >> > +
> >> > +
> >> > +def texi_command(expr, doc):
> >> > +    """Format a command to texi"""
> >> > +    body = texi_body(doc)
> >> > +    return COMMAND_FMT(type="Command",
> >> > +                       name=doc.symbol,
> >> > +                       body=body)
> >> > +
> >> > +
> >> > +def texi_event(expr, doc):
> >> > +    """Format an event to texi"""
> >> > +    body = texi_body(doc)
> >> > +    return COMMAND_FMT(type="Event",
> >> > +                       name=doc.symbol,
> >> > +                       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}[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])
> >> > +    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 2841c5144a..b29f996fdc 100644
> >> > --- a/docs/qapi-code-gen.txt
> >> > +++ b/docs/qapi-code-gen.txt
> >> > @@ -44,40 +44,148 @@ Input must be ASCII (although QMP supports full
> >> > Unicode strings, the
> >> >  QAPI parser does not).  At present, there is no place where a QAPI
> >> >  schema requires the use of JSON numbers or null.
> >> >  
> >> > +
> >> > +=== Comments ===
> >> > +
> >> >  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
> >> > -x.y.z)' comment.  For example:
> >> > -
> >> > -    ##
> >> > -    # @BlockStats:
> >> > -    #
> >> > -    # Statistics of a virtual block device or a block backing device.
> >> > -    #
> >> > -    # @device: #optional If the stats are for a virtual block device,
> >> > the
> >> > name
> >> > -    #          corresponding to the virtual block device.
> >> > -    #
> >> > -    # @stats:  A @BlockDeviceStats for the device.
> >> > -    #
> >> > -    # @parent: #optional This describes the file block device if it has
> >> > one.
> >> > -    #
> >> > -    # @backing: #optional This describes the backing block device if it
> >> > has one.
> >> > -    #           (Since 2.0)
> >> > -    #
> >> > -    # Since: 0.14.0
> >> > -    ##
> >> > -    { 'struct': 'BlockStats',
> >> > -      'data': {'*device': 'str', 'stats': 'BlockDeviceStats',
> >> > -               '*parent': 'BlockStats',
> >> > -               '*backing': 'BlockStats'} }
> >> > +newline is ignored.
> >> > +
> >> > +A multi-line comment that starts and ends with a '##' line is a
> >> > +documentation comment.  These are parsed by the documentation
> >> > +generator, which recognizes certain markup detailed below.
> >> > +
> >> > +
> >> > +==== Documentation markup ====
> >> > +
> >> > +Comment text starting with '=' is a section title:
> >> > +
> >> > +    # = Section title
> >> > +
> >> > +Double the '=' for a subsection title:
> >> > +
> >> > +    # == Subection title
> >> > +
> >> > +'|' denotes examples:
> >> > +
> >> > +    # | Text of the example, may span
> >> > +    # | multiple lines
> >> > +
> >> > +'*' starts an itemized list:
> >> > +
> >> > +    # * First item, may span
> >> > +    #   multiple lines
> >> > +    # * Second item
> >> > +
> >> > +You can also use '-' instead of '*'.
> >> > +
> >> > +A decimal number followed by '.' starts a numbered list:
> >> > +
> >> > +    # 1. First item, may span
> >> > +    # multiple lines
> 
> Should the second line be indented?

ok

> 
> >> > +    # 2. Second item
> >> > +
> >> > +The actual number doesn't matter.  You could even use '*' instead of
> >> > +'2.' for the second item.
> >> > +
> >> > +FIXME what exactly ends a list
> >> 
> >> Can you say offhand what ends a list?  If yes, can we resolve this FIXME
> >> now rather than later?
> >
> > well, the code is too permissive, there is a FIXME there already.
> >
> > In general, I think we want to end a list after a blank line and a
> > "non-item" line. Is the wording ok?
> 
> Blank line is pretty restrictive, because list items consisting of
> multiple paragraphs aren't possible then.
> 
> I think writing something like this is fairly natural:
> 
>     * First item, may span
>       multiple lines.
> 
>       Even multiple paragaphs are okay.
> 
>     * Second item
> 
>       - This is a sublist
>       - Second item of sublist
> 
>     * Third item
> 
>     This sentence is not part of the list.
> 
> We don't support sublists, and I'm not saying we should, I'm just
> showing how they would fit in.
> 
> My point is that indentation is a natural way to delimit list items.
> 
> I don't expect you to implement this now.  All I want for this series is
> reasonable documentation.  As far as I can tell, texi_format() ends the
> list at a blank line, except when it doesn't (see its FIXME).
> 
> What about this: replace "FIXME what exactly ends a list" by
> 
>     Lists can't be nested.  Blank lines are currently not supported
>     within lists.
> 
> and add to texi_format()'s FIXME something like
> 
>     Make sure to update section "Documentation markup" in
>     docs/qapi-code-gen.txt when fixing this.
> 
> Okay?

ok

> 
> >> > +
> >> > +Additional whitespace between the initial '#' and the comment text is
> >> > +permitted.
> >> > +
> >> > +*foo* and _foo_ are for strong and emphasis styles respectively (they
> >> > +do not work over multiple lines). @foo is used to reference a name in
> >> > +the schema.
> >> > +
> >> > +Example:
> >> > +
> >> > +##
> >> > +# = 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
> >> > +#
> >> > +##
> >> > +
> >> > +
> >> > +==== Expression documentation ====
> >> > +
> >> > +Each expression that isn't an include directive must be preceded by a
> >> > +documentation block.  Such blocks are called expression documentation
> >> > +blocks.
> >> > +
> >> > +The first line of the documentation names the expression, then the
> >> > +documentation body is provided, then individual documentation about
> >> > +each member of 'data' is provided. Finally, several tagged sections
> >> > +can be added.
> >> 
> >> In my review of v6, I wrote:
> >> 
> >>     I'm afraid this is more aspiration than specification: the parser
> >>     accepts these things in almost any order.
> >> 
> >>     Could be filed under "deficiencies" for now.
> >> 
> >> To file under "deficiencies", we need to add a FIXME or TODO either
> >> here, or in the parser.  Assuming you haven't tightened the parser
> >> meanwhile (hope you haven't; we need to control the churn).
> >
> > ok
> >
> >> 
> >>     Member of 'data' won't remain accurate.  Consider:
> >> 
> >>         { 'union': 'GlusterServer',
> >>           'base': { 'type': 'GlusterTransport' },
> >>           'discriminator': 'type',
> >>           'data': { 'unix': 'UnixSocketAddress',
> >>                     'tcp': 'InetSocketAddress' } }
> >> 
> >>     Its doc comment currently doesn't contain argument sections.  It
> >>     should
> >>     eventually contain @type:, @unix: and @tcp:.  Only the latter two are
> >>     members of 'data'.
> >> 
> >>     I should propose something better, but I'm getting perilously close to
> >>     the christmas break already.  Later.
> >> 
> >> Also: passive voice is meh (not your idea; you adapted the existing
> >> text).
> >> 
> >> What about:
> >> 
> >>    The documentation block consists of a first line naming the
> >>    expression, an optional overview, a description of each argument (for
> >>    commands and events) or member (for structs, unions and alternates),
> >>    and optional tagged sections.
> >> 
> >> I still don't like the use of "section" for both the = things and the
> >> tagged sections, but it's not important enough to spend time on it now.
> >> 
> >
> > ok
> >
> >> > +
> >> > +Optional members are tagged with the phrase '#optional', often with
> >> 
> >> If we adopt my text, we should say "arguments / members" here.
> >> 
> >
> > ok
> >
> >> > +their default value; and extensions added after the expression was
> >> > +first released are also given a '(since x.y.z)' comment.
> >> > +
> >> > +A tagged section starts with one of the following words:
> >> > +"Note:"/"Notes:", "Since:", "Example"/"Examples", "Returns:", "TODO:".
> >> 
> >> What ends such a section?  The start of the next one / end of the
> >> comment block?
> >> 
> >
> > yes
> >
> >> I suspect we could avoid PATCH 03 by recognizing "TODO" in addition to
> >> "TODO:".  Might also be more robust.  But let's ignore this now.
> >> 
> >> > +
> >> > +A 'Since: x.y.z' tag lists the release that introduced the expression.
> >> 
> >> Nitpick: 'Since: x.y.z' is a tagged section, not a tag.
> >> 
> >
> > ok
> >
> >> > +
> >> > +For example:
> >> > +
> >> > +##
> >> > +# @BlockStats:
> >> > +#
> >> > +# Statistics of a virtual block device or a block backing device.
> >> > +#
> >> > +# @device: #optional If the stats are for a virtual block device, the
> >> > name
> >> > +#          corresponding to the virtual block device.
> >> > +#
> >> > +# @node-name: #optional The node name of the device. (since 2.3)
> >> > +#
> >> > +# ... more members ...
> >> > +#
> >> > +# Since: 0.14.0
> >> > +##
> >> > +{ 'struct': 'BlockStats',
> >> > +  'data': {'*device': 'str', '*node-name': 'str',
> >> > +           ... more members ... } }
> >> > +
> >> > +##
> >> > +# @query-blockstats:
> >> > +#
> >> > +# Query the @BlockStats for all virtual block devices.
> >> > +#
> >> > +# @query-nodes: #optional If true, the command will query all the
> >> > +#               block nodes ... explain, explain ...  (since 2.3)
> >> > +#
> >> > +# Returns: A list of @BlockStats for each virtual block devices.
> >> > +#
> >> > +# Since: 0.14.0
> >> > +#
> >> > +# Example:
> >> > +#
> >> > +# -> { "execute": "query-blockstats" }
> >> > +# <- {
> >> > +#      ... lots of output ...
> >> > +#    }
> >> > +#
> >> > +##
> >> > +{ 'command': 'query-blockstats',
> >> > +  'data': { '*query-nodes': 'bool' },
> >> > +  'returns': ['BlockStats'] }
> >> > +
> >> > +==== Free-form documentation ====
> >> > +
> >> > +A documentation block that isn't an expression documentation block is
> >> > +a free-form documentation block.  These may be used to provide
> >> > +additional text and structuring content.
> >> > +
> >> > +
> >> > +=== Schema overview ===
> >> >  
> >> >  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
> >> [Tests look okay, snipped...]
> >> 
> 

^ permalink raw reply	[flat|nested] 40+ messages in thread

end of thread, other threads:[~2017-01-12 17:36 UTC | newest]

Thread overview: 40+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-01-09 14:34 [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 01/21] qapi: replace 'o' for list items Marc-André Lureau
2017-01-10  8:49   ` Markus Armbruster
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 02/21] qapi: move QKeyCode doc body at the top Marc-André Lureau
2017-01-10  8:51   ` Markus Armbruster
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 03/21] qapi: make TODOs named-sections Marc-André Lureau
2017-01-10  9:11   ` Markus Armbruster
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 04/21] qapi: improve device_add schema Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 05/21] qapi: improve TransactionAction doc Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 06/21] qga/schema: improve guest-set-vcpus Returns: section Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 07/21] qapi: avoid interleaving sections and parameters Marc-André Lureau
2017-01-10 10:32   ` Markus Armbruster
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 08/21] qapi: move experimental note down Marc-André Lureau
2017-01-10 12:08   ` Markus Armbruster
2017-01-10 12:56     ` Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 09/21] qapi: add some sections in docs Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 10/21] docs: add master qapi texi files Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 11/21] qapi: rework qapi Exception Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 12/21] qapi.py: fix line break before binary operator pep8 Marc-André Lureau
2017-01-10 12:23   ` Markus Armbruster
2017-01-10 12:55     ` Marc-André Lureau
2017-01-10 16:04       ` Markus Armbruster
2017-01-10 16:25         ` Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 13/21] texi2pod: learn quotation, deftp and deftypefn Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 14/21] (SQUASHED) move doc to schema Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 15/21] qapi: add qapi2texi script Marc-André Lureau
2017-01-11 14:26   ` Markus Armbruster
2017-01-11 16:21     ` Marc-André Lureau
2017-01-12 13:17       ` Markus Armbruster
2017-01-12 17:36         ` Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 16/21] docs: add qemu logo to pdf Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 17/21] build-sys: use --no-split for info Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 18/21] build-sys: remove dvi doc generation Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 19/21] build-sys: use a generic TEXI2MAN rule Marc-André Lureau
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 20/21] build-sys: add txt documentation rules Marc-André Lureau
2017-01-12 10:09   ` Markus Armbruster
2017-01-09 14:34 ` [Qemu-devel] [PATCH v7 21/21] build-sys: add qapi doc generation targets Marc-André Lureau
2017-01-11 14:28   ` Markus Armbruster
2017-01-11 14:40 ` [Qemu-devel] [PATCH v7 00/21] qapi doc generation (whole version, squashed) Markus Armbruster
2017-01-11 14:43   ` Marc-André Lureau

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.