qapi: add qapi2texi script
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 and limitations: - 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. - a few minor issues, all marked TODO/FIXME in the code Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Message-Id: <20170113144135.5150-16-marcandre.lureau@redhat.com> Reviewed-by: Markus Armbruster <armbru@redhat.com> [test-qapi.py tweaked to avoid trailing empty lines in .out] Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
parent
231aaf3a82
commit
3313b6124b
|
@ -44,40 +44,154 @@ Input must be ASCII (although QMP supports full Unicode strings, the
|
||||||
QAPI parser does not). At present, there is no place where a QAPI
|
QAPI parser does not). At present, there is no place where a QAPI
|
||||||
schema requires the use of JSON numbers or null.
|
schema requires the use of JSON numbers or null.
|
||||||
|
|
||||||
Comments are allowed; anything between an unquoted # and the following
|
|
||||||
newline is ignored. Although there is not yet a documentation
|
|
||||||
generator, a form of stylized comments has developed for consistently
|
|
||||||
documenting details about an expression and when it was added to the
|
|
||||||
schema. The documentation is delimited between two lines of ##, then
|
|
||||||
the first line names the expression, an optional overview is provided,
|
|
||||||
then individual documentation about each member of 'data' is provided,
|
|
||||||
and finally, a 'Since: x.y.z' tag lists the release that introduced
|
|
||||||
the expression. Optional members are tagged with the phrase
|
|
||||||
'#optional', often with their default value; and extensions added
|
|
||||||
after the expression was first released are also given a '(since
|
|
||||||
x.y.z)' comment. For example:
|
|
||||||
|
|
||||||
##
|
=== Comments ===
|
||||||
# @BlockStats:
|
|
||||||
#
|
Comments are allowed; anything between an unquoted # and the following
|
||||||
# Statistics of a virtual block device or a block backing device.
|
newline is ignored.
|
||||||
#
|
|
||||||
# @device: #optional If the stats are for a virtual block device, the name
|
A multi-line comment that starts and ends with a '##' line is a
|
||||||
# corresponding to the virtual block device.
|
documentation comment. These are parsed by the documentation
|
||||||
#
|
generator, which recognizes certain markup detailed below.
|
||||||
# @stats: A @BlockDeviceStats for the device.
|
|
||||||
#
|
|
||||||
# @parent: #optional This describes the file block device if it has one.
|
==== Documentation markup ====
|
||||||
#
|
|
||||||
# @backing: #optional This describes the backing block device if it has one.
|
Comment text starting with '=' is a section title:
|
||||||
# (Since 2.0)
|
|
||||||
#
|
# = Section title
|
||||||
# Since: 0.14.0
|
|
||||||
##
|
Double the '=' for a subsection title:
|
||||||
{ 'struct': 'BlockStats',
|
|
||||||
'data': {'*device': 'str', 'stats': 'BlockDeviceStats',
|
# == Subection title
|
||||||
'*parent': 'BlockStats',
|
|
||||||
'*backing': 'BlockStats'} }
|
'|' 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.
|
||||||
|
|
||||||
|
Lists can't be nested. Blank lines are currently not supported within
|
||||||
|
lists.
|
||||||
|
|
||||||
|
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 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.
|
||||||
|
|
||||||
|
FIXME: the parser accepts these things in almost any order.
|
||||||
|
|
||||||
|
Optional arguments / 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:".
|
||||||
|
The section ends with the start of a new section.
|
||||||
|
|
||||||
|
A 'Since: x.y.z' tagged section 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
|
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
|
that will use those types. Forward references are allowed: the parser
|
||||||
|
|
251
scripts/qapi.py
251
scripts/qapi.py
|
@ -125,6 +125,122 @@ class QAPISemError(QAPIError):
|
||||||
info['parent'], msg)
|
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:' 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=""):
|
||||||
|
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):
|
class QAPISchemaParser(object):
|
||||||
|
|
||||||
def __init__(self, fp, previously_included=[], incl_info=None):
|
def __init__(self, fp, previously_included=[], incl_info=None):
|
||||||
|
@ -140,11 +256,17 @@ class QAPISchemaParser(object):
|
||||||
self.line = 1
|
self.line = 1
|
||||||
self.line_pos = 0
|
self.line_pos = 0
|
||||||
self.exprs = []
|
self.exprs = []
|
||||||
|
self.docs = []
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
while self.tok is not None:
|
while self.tok is not None:
|
||||||
info = {'file': fname, 'line': self.line,
|
info = {'file': fname, 'line': self.line,
|
||||||
'parent': self.incl_info}
|
'parent': self.incl_info}
|
||||||
|
if self.tok == '#':
|
||||||
|
doc = self.get_doc(info)
|
||||||
|
self.docs.append(doc)
|
||||||
|
continue
|
||||||
|
|
||||||
expr = self.get_expr(False)
|
expr = self.get_expr(False)
|
||||||
if isinstance(expr, dict) and "include" in expr:
|
if isinstance(expr, dict) and "include" in expr:
|
||||||
if len(expr) != 1:
|
if len(expr) != 1:
|
||||||
|
@ -162,6 +284,7 @@ class QAPISchemaParser(object):
|
||||||
raise QAPISemError(info, "Inclusion loop for %s"
|
raise QAPISemError(info, "Inclusion loop for %s"
|
||||||
% include)
|
% include)
|
||||||
inf = inf['parent']
|
inf = inf['parent']
|
||||||
|
|
||||||
# skip multiple include of the same file
|
# skip multiple include of the same file
|
||||||
if incl_abs_fname in previously_included:
|
if incl_abs_fname in previously_included:
|
||||||
continue
|
continue
|
||||||
|
@ -172,12 +295,19 @@ class QAPISchemaParser(object):
|
||||||
exprs_include = QAPISchemaParser(fobj, previously_included,
|
exprs_include = QAPISchemaParser(fobj, previously_included,
|
||||||
info)
|
info)
|
||||||
self.exprs.extend(exprs_include.exprs)
|
self.exprs.extend(exprs_include.exprs)
|
||||||
|
self.docs.extend(exprs_include.docs)
|
||||||
else:
|
else:
|
||||||
expr_elem = {'expr': expr,
|
expr_elem = {'expr': expr,
|
||||||
'info': info}
|
'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)
|
self.exprs.append(expr_elem)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self, skip_comment=True):
|
||||||
while True:
|
while True:
|
||||||
self.tok = self.src[self.cursor]
|
self.tok = self.src[self.cursor]
|
||||||
self.pos = self.cursor
|
self.pos = self.cursor
|
||||||
|
@ -185,7 +315,13 @@ class QAPISchemaParser(object):
|
||||||
self.val = None
|
self.val = None
|
||||||
|
|
||||||
if self.tok == '#':
|
if self.tok == '#':
|
||||||
|
if self.src[self.cursor] == '#':
|
||||||
|
# Start of doc comment
|
||||||
|
skip_comment = False
|
||||||
self.cursor = self.src.find('\n', self.cursor)
|
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 "{}:,[]":
|
elif self.tok in "{}:,[]":
|
||||||
return
|
return
|
||||||
elif self.tok == "'":
|
elif self.tok == "'":
|
||||||
|
@ -319,6 +455,28 @@ class QAPISchemaParser(object):
|
||||||
raise QAPIParseError(self, 'Expected "{", "[" or string')
|
raise QAPIParseError(self, 'Expected "{", "[" or string')
|
||||||
return expr
|
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
|
# Semantic analysis of schema expressions
|
||||||
# TODO fold into QAPISchema
|
# TODO fold into QAPISchema
|
||||||
|
@ -703,6 +861,11 @@ def check_exprs(exprs):
|
||||||
for expr_elem in exprs:
|
for expr_elem in exprs:
|
||||||
expr = expr_elem['expr']
|
expr = expr_elem['expr']
|
||||||
info = expr_elem['info']
|
info = expr_elem['info']
|
||||||
|
|
||||||
|
if 'doc' not in expr_elem:
|
||||||
|
raise QAPISemError(info,
|
||||||
|
"Expression missing documentation comment")
|
||||||
|
|
||||||
if 'enum' in expr:
|
if 'enum' in expr:
|
||||||
check_keys(expr_elem, 'enum', ['data'], ['prefix'])
|
check_keys(expr_elem, 'enum', ['data'], ['prefix'])
|
||||||
add_enum(expr['enum'], info, expr['data'])
|
add_enum(expr['enum'], info, expr['data'])
|
||||||
|
@ -761,6 +924,88 @@ def check_exprs(exprs):
|
||||||
return 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, 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:
|
||||||
|
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
|
||||||
|
# TODO either fix the schema and make this an error,
|
||||||
|
# or drop #optional entirely
|
||||||
|
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
|
# Schema compiler frontend
|
||||||
#
|
#
|
||||||
|
@ -1229,7 +1474,9 @@ class QAPISchemaEvent(QAPISchemaEntity):
|
||||||
class QAPISchema(object):
|
class QAPISchema(object):
|
||||||
def __init__(self, fname):
|
def __init__(self, fname):
|
||||||
try:
|
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._entity_dict = {}
|
||||||
self._predefining = True
|
self._predefining = True
|
||||||
self._def_predefineds()
|
self._def_predefineds()
|
||||||
|
|
271
scripts/qapi2texi.py
Executable file
271
scripts/qapi2texi.py
Executable file
|
@ -0,0 +1,271 @@
|
||||||
|
#!/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.
|
||||||
|
#
|
||||||
|
# Make sure to update section "Documentation markup" in
|
||||||
|
# docs/qapi-code-gen.txt when fixing this.
|
||||||
|
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(r'^([0-9]*\.) ', line):
|
||||||
|
if not inlist:
|
||||||
|
lines.append("@enumerate")
|
||||||
|
inlist = "enumerate"
|
||||||
|
line = line[line.find(" ")+1:]
|
||||||
|
lines.append("@item")
|
||||||
|
elif re.match(r'^[*-] ', 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:
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
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)
|
|
@ -352,6 +352,24 @@ qapi-schema += base-cycle-direct.json
|
||||||
qapi-schema += base-cycle-indirect.json
|
qapi-schema += base-cycle-indirect.json
|
||||||
qapi-schema += command-int.json
|
qapi-schema += command-int.json
|
||||||
qapi-schema += comments.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-data.json
|
||||||
qapi-schema += double-type.json
|
qapi-schema += double-type.json
|
||||||
qapi-schema += duplicate-key.json
|
qapi-schema += duplicate-key.json
|
||||||
|
@ -445,6 +463,8 @@ qapi-schema += union-optional-branch.json
|
||||||
qapi-schema += union-unknown.json
|
qapi-schema += union-unknown.json
|
||||||
qapi-schema += unknown-escape.json
|
qapi-schema += unknown-escape.json
|
||||||
qapi-schema += unknown-expr-key.json
|
qapi-schema += unknown-expr-key.json
|
||||||
|
|
||||||
|
|
||||||
check-qapi-schema-y := $(addprefix tests/qapi-schema/, $(qapi-schema))
|
check-qapi-schema-y := $(addprefix tests/qapi-schema/, $(qapi-schema))
|
||||||
|
|
||||||
GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h \
|
GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h \
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
# we do not allow the 'any' type as an alternate branch
|
# we do not allow the 'any' type as an alternate branch
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Alt:
|
||||||
|
##
|
||||||
{ 'alternate': 'Alt',
|
{ 'alternate': 'Alt',
|
||||||
'data': { 'one': 'any',
|
'data': { 'one': 'any',
|
||||||
'two': 'int' } }
|
'two': 'int' } }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
# we do not allow array branches in alternates
|
# we do not allow array branches in alternates
|
||||||
|
|
||||||
|
##
|
||||||
|
# @One:
|
||||||
|
##
|
||||||
# TODO: should we support this?
|
# TODO: should we support this?
|
||||||
{ 'struct': 'One',
|
{ 'struct': 'One',
|
||||||
'data': { 'name': 'str' } }
|
'data': { 'name': 'str' } }
|
||||||
|
##
|
||||||
|
# @Alt:
|
||||||
|
##
|
||||||
{ 'alternate': 'Alt',
|
{ 'alternate': 'Alt',
|
||||||
'data': { 'one': 'One',
|
'data': { 'one': 'One',
|
||||||
'two': [ 'int' ] } }
|
'two': [ 'int' ] } }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
# we reject alternate with base type
|
# we reject alternate with base type
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Base:
|
||||||
|
##
|
||||||
{ 'struct': 'Base',
|
{ 'struct': 'Base',
|
||||||
'data': { 'string': 'str' } }
|
'data': { 'string': 'str' } }
|
||||||
|
##
|
||||||
|
# @Alt:
|
||||||
|
##
|
||||||
{ 'alternate': 'Alt',
|
{ 'alternate': 'Alt',
|
||||||
'base': 'Base',
|
'base': 'Base',
|
||||||
'data': { 'number': 'int' } }
|
'data': { 'number': 'int' } }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -4,5 +4,9 @@
|
||||||
# TODO: In the future, if alternates are simplified to not generate
|
# TODO: In the future, if alternates are simplified to not generate
|
||||||
# the implicit Alt1Kind enum, we would still have a collision with the
|
# the implicit Alt1Kind enum, we would still have a collision with the
|
||||||
# resulting C union trying to have two members named 'a_b'.
|
# resulting C union trying to have two members named 'a_b'.
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Alt1:
|
||||||
|
##
|
||||||
{ 'alternate': 'Alt1',
|
{ 'alternate': 'Alt1',
|
||||||
'data': { 'a-b': 'str', 'a_b': 'int' } }
|
'data': { 'a-b': 'str', 'a_b': 'int' } }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
# we reject alternates with multiple object branches
|
# we reject alternates with multiple object branches
|
||||||
|
|
||||||
|
##
|
||||||
|
# @One:
|
||||||
|
##
|
||||||
{ 'struct': 'One',
|
{ 'struct': 'One',
|
||||||
'data': { 'name': 'str' } }
|
'data': { 'name': 'str' } }
|
||||||
|
##
|
||||||
|
# @Two:
|
||||||
|
##
|
||||||
{ 'struct': 'Two',
|
{ 'struct': 'Two',
|
||||||
'data': { 'value': 'int' } }
|
'data': { 'value': 'int' } }
|
||||||
|
##
|
||||||
|
# @Alt:
|
||||||
|
##
|
||||||
{ 'alternate': 'Alt',
|
{ 'alternate': 'Alt',
|
||||||
'data': { 'one': 'One',
|
'data': { 'one': 'One',
|
||||||
'two': 'Two' } }
|
'two': 'Two' } }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
# we reject alternates with multiple string-like branches
|
# we reject alternates with multiple string-like branches
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Enum:
|
||||||
|
##
|
||||||
{ 'enum': 'Enum',
|
{ 'enum': 'Enum',
|
||||||
'data': [ 'hello', 'world' ] }
|
'data': [ 'hello', 'world' ] }
|
||||||
|
##
|
||||||
|
# @Alt:
|
||||||
|
##
|
||||||
{ 'alternate': 'Alt',
|
{ 'alternate': 'Alt',
|
||||||
'data': { 'one': 'str',
|
'data': { 'one': 'str',
|
||||||
'two': 'Enum' } }
|
'two': 'Enum' } }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# alternates must list at least two types to be useful
|
# alternates must list at least two types to be useful
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Alt:
|
||||||
|
##
|
||||||
{ 'alternate': 'Alt', 'data': { 'i': 'int' } }
|
{ 'alternate': 'Alt', 'data': { 'i': 'int' } }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
# we reject a nested alternate branch
|
# we reject a nested alternate branch
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Alt1:
|
||||||
|
##
|
||||||
{ 'alternate': 'Alt1',
|
{ 'alternate': 'Alt1',
|
||||||
'data': { 'name': 'str', 'value': 'int' } }
|
'data': { 'name': 'str', 'value': 'int' } }
|
||||||
|
##
|
||||||
|
# @Alt2:
|
||||||
|
##
|
||||||
{ 'alternate': 'Alt2',
|
{ 'alternate': 'Alt2',
|
||||||
'data': { 'nested': 'Alt1', 'b': 'bool' } }
|
'data': { 'nested': 'Alt1', 'b': 'bool' } }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
# we reject an alternate with unknown type in branch
|
# we reject an alternate with unknown type in branch
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Alt:
|
||||||
|
##
|
||||||
{ 'alternate': 'Alt',
|
{ 'alternate': 'Alt',
|
||||||
'data': { 'unknown': 'MissingType', 'i': 'int' } }
|
'data': { 'unknown': 'MissingType', 'i': 'int' } }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
# we do not allow alternate arguments
|
# we do not allow alternate arguments
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Alt:
|
||||||
|
##
|
||||||
{ 'alternate': 'Alt', 'data': { 'case1': 'int', 'case2': 'str' } }
|
{ 'alternate': 'Alt', 'data': { 'case1': 'int', 'case2': 'str' } }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @oops:
|
||||||
|
##
|
||||||
{ 'command': 'oops', 'data': 'Alt' }
|
{ 'command': 'oops', 'data': 'Alt' }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we do not allow an 'any' argument
|
# we do not allow an 'any' argument
|
||||||
|
|
||||||
|
##
|
||||||
|
# @oops:
|
||||||
|
##
|
||||||
{ 'command': 'oops', 'data': 'any' }
|
{ 'command': 'oops', 'data': 'any' }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we reject an array for data if it does not contain a known type
|
# we reject an array for data if it does not contain a known type
|
||||||
|
|
||||||
|
##
|
||||||
|
# @oops:
|
||||||
|
##
|
||||||
{ 'command': 'oops', 'data': { 'empty': [ ] } }
|
{ 'command': 'oops', 'data': { 'empty': [ ] } }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we reject an array for data if it does not contain a known type
|
# we reject an array for data if it does not contain a known type
|
||||||
|
|
||||||
|
##
|
||||||
|
# @oops:
|
||||||
|
##
|
||||||
{ 'command': 'oops', 'data': { 'array': [ 'NoSuchType' ] } }
|
{ 'command': 'oops', 'data': { 'array': [ 'NoSuchType' ] } }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# 'boxed' should only appear with value true
|
# 'boxed' should only appear with value true
|
||||||
|
|
||||||
|
##
|
||||||
|
# @foo:
|
||||||
|
##
|
||||||
{ 'command': 'foo', 'boxed': false }
|
{ 'command': 'foo', 'boxed': false }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# 'boxed' can only be used with named types
|
# 'boxed' can only be used with named types
|
||||||
|
|
||||||
|
##
|
||||||
|
# @foo:
|
||||||
|
##
|
||||||
{ 'command': 'foo', 'boxed': true, 'data': { 'string': 'str' } }
|
{ 'command': 'foo', 'boxed': true, 'data': { 'string': 'str' } }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
# 'boxed' requires a non-empty type
|
# 'boxed' requires a non-empty type
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Empty:
|
||||||
|
##
|
||||||
{ 'struct': 'Empty', 'data': {} }
|
{ 'struct': 'Empty', 'data': {} }
|
||||||
|
|
||||||
|
##
|
||||||
|
# @foo:
|
||||||
|
##
|
||||||
{ 'command': 'foo', 'boxed': true, 'data': 'Empty' }
|
{ 'command': 'foo', 'boxed': true, 'data': 'Empty' }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# 'boxed' requires a complex (not built-in) type
|
# 'boxed' requires a complex (not built-in) type
|
||||||
|
|
||||||
|
##
|
||||||
|
# @foo:
|
||||||
|
##
|
||||||
{ 'command': 'foo', 'boxed': true, 'data': 'str' }
|
{ 'command': 'foo', 'boxed': true, 'data': 'str' }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we reject commands where data is not an array or complex type
|
# we reject commands where data is not an array or complex type
|
||||||
|
|
||||||
|
##
|
||||||
|
# @oops:
|
||||||
|
##
|
||||||
{ 'command': 'oops', 'data': 'int' }
|
{ 'command': 'oops', 'data': 'int' }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
|
##
|
||||||
|
# @foo:
|
||||||
|
##
|
||||||
{ 'command': 'foo',
|
{ 'command': 'foo',
|
||||||
'data': false }
|
'data': false }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we reject data if it does not contain a valid array type
|
# we reject data if it does not contain a valid array type
|
||||||
|
|
||||||
|
##
|
||||||
|
# @oops:
|
||||||
|
##
|
||||||
{ 'command': 'oops', 'data': { 'member': [ { 'nested': 'str' } ] } }
|
{ 'command': 'oops', 'data': { 'member': [ { 'nested': 'str' } ] } }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# Member names should be 'lower-case' unless the struct/command is whitelisted
|
# 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' } }
|
{ 'command': 'no-way-this-will-get-whitelisted', 'data': { 'Arg': 'int' } }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we reject data if it does not contain a known type
|
# we reject data if it does not contain a known type
|
||||||
|
|
||||||
|
##
|
||||||
|
# @oops:
|
||||||
|
##
|
||||||
{ 'command': 'oops', 'data': { 'member': 'NoSuchType' } }
|
{ 'command': 'oops', 'data': { 'member': 'NoSuchType' } }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
# C member name collision
|
# C member name collision
|
||||||
# Reject members that clash when mapped to C names (we would have two 'a_b'
|
# Reject members that clash when mapped to C names (we would have two 'a_b'
|
||||||
# members).
|
# members).
|
||||||
|
|
||||||
|
##
|
||||||
|
# @oops:
|
||||||
|
##
|
||||||
{ 'command': 'oops', 'data': { 'a-b': 'str', 'a_b': 'str' } }
|
{ 'command': 'oops', 'data': { 'a-b': 'str', 'a_b': 'str' } }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
# use of union arguments requires 'boxed':true
|
# use of union arguments requires 'boxed':true
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Uni:
|
||||||
|
##
|
||||||
{ 'union': 'Uni', 'data': { 'case1': 'int', 'case2': 'str' } }
|
{ 'union': 'Uni', 'data': { 'case1': 'int', 'case2': 'str' } }
|
||||||
|
##
|
||||||
|
# oops:
|
||||||
|
##
|
||||||
{ 'command': 'oops', 'data': 'Uni' }
|
{ 'command': 'oops', 'data': 'Uni' }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we reject data if it does not contain a known type
|
# we reject data if it does not contain a known type
|
||||||
|
|
||||||
|
##
|
||||||
|
# @oops:
|
||||||
|
##
|
||||||
{ 'command': 'oops', 'data': 'NoSuchType' }
|
{ 'command': 'oops', 'data': 'NoSuchType' }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
# we reject a base that is not a struct
|
# we reject a base that is not a struct
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Union:
|
||||||
|
##
|
||||||
{ 'union': 'Union', 'data': { 'a': 'int', 'b': 'str' } }
|
{ 'union': 'Union', 'data': { 'a': 'int', 'b': 'str' } }
|
||||||
|
##
|
||||||
|
# @MyType:
|
||||||
|
##
|
||||||
{ 'struct': 'MyType', 'base': 'Union', 'data': { 'c': 'int' } }
|
{ 'struct': 'MyType', 'base': 'Union', 'data': { 'c': 'int' } }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we ensure 'data' is a dictionary for all but enums
|
# we ensure 'data' is a dictionary for all but enums
|
||||||
|
|
||||||
|
##
|
||||||
|
# @oops:
|
||||||
|
##
|
||||||
{ 'command': 'oops', 'data': [ ] }
|
{ 'command': 'oops', 'data': [ ] }
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we reject creating a type name with bad name
|
# we reject creating a type name with bad name
|
||||||
|
|
||||||
|
##
|
||||||
|
# @*oops:
|
||||||
|
##
|
||||||
{ 'struct': '*oops', 'data': { 'i': 'int' } }
|
{ 'struct': '*oops', 'data': { 'i': 'int' } }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we reject an expression with a metatype that is not a string
|
# we reject an expression with a metatype that is not a string
|
||||||
|
|
||||||
|
##
|
||||||
|
# @true:
|
||||||
|
##
|
||||||
{ 'struct': true, 'data': { } }
|
{ 'struct': true, 'data': { } }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we reject an expression with a metatype that is not a string
|
# we reject an expression with a metatype that is not a string
|
||||||
|
|
||||||
|
##
|
||||||
|
# @foo:
|
||||||
|
##
|
||||||
{ 'command': { } }
|
{ 'command': { } }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we reject a loop in base classes
|
# we reject a loop in base classes
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Loopy:
|
||||||
|
##
|
||||||
{ 'struct': 'Loopy', 'base': 'Loopy', 'data': {} }
|
{ 'struct': 'Loopy', 'base': 'Loopy', 'data': {} }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
# we reject a loop in base classes
|
# we reject a loop in base classes
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Base1:
|
||||||
|
##
|
||||||
{ 'struct': 'Base1', 'base': 'Base2', 'data': {} }
|
{ 'struct': 'Base1', 'base': 'Base2', 'data': {} }
|
||||||
|
##
|
||||||
|
# @Base2:
|
||||||
|
##
|
||||||
{ 'struct': 'Base2', 'base': 'Base1', 'data': {} }
|
{ 'struct': 'Base2', 'base': 'Base1', 'data': {} }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# we reject collisions between commands and types
|
# we reject collisions between commands and types
|
||||||
|
|
||||||
|
##
|
||||||
|
# @int:
|
||||||
|
##
|
||||||
{ 'command': 'int', 'data': { 'character': 'str' } }
|
{ 'command': 'int', 'data': { 'character': 'str' } }
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
# Unindented comment
|
# Unindented comment
|
||||||
|
|
||||||
|
##
|
||||||
|
# @Status:
|
||||||
|
##
|
||||||
{ 'enum': 'Status', # Comment to the right of code
|
{ 'enum': 'Status', # Comment to the right of code
|
||||||
# Indented comment
|
# Indented comment
|
||||||
'data': [ 'good', 'bad', 'ugly' ] }
|
'data': [ 'good', 'bad', 'ugly' ] }
|
||||||
|
|
|
@ -2,3 +2,4 @@ enum QType ['none', 'qnull', 'qint', 'qstring', 'qdict', 'qlist', 'qfloat', 'qbo
|
||||||
prefix QTYPE
|
prefix QTYPE
|
||||||
enum Status ['good', 'bad', 'ugly']
|
enum Status ['good', 'bad', 'ugly']
|
||||||
object q_empty
|
object q_empty
|
||||||
|
doc symbol=Status expr=('enum', 'Status')
|
||||||
|
|
1
tests/qapi-schema/doc-bad-args.err
Normal file
1
tests/qapi-schema/doc-bad-args.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/doc-bad-args.json:3: The following documented members are not in the declaration: b
|
1
tests/qapi-schema/doc-bad-args.exit
Normal file
1
tests/qapi-schema/doc-bad-args.exit
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1
|
8
tests/qapi-schema/doc-bad-args.json
Normal file
8
tests/qapi-schema/doc-bad-args.json
Normal file
|
@ -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'} }
|
0
tests/qapi-schema/doc-bad-args.out
Normal file
0
tests/qapi-schema/doc-bad-args.out
Normal file
1
tests/qapi-schema/doc-bad-symbol.err
Normal file
1
tests/qapi-schema/doc-bad-symbol.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/doc-bad-symbol.json:3: Definition of 'foo' follows documentation for 'food'
|
1
tests/qapi-schema/doc-bad-symbol.exit
Normal file
1
tests/qapi-schema/doc-bad-symbol.exit
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1
|
6
tests/qapi-schema/doc-bad-symbol.json
Normal file
6
tests/qapi-schema/doc-bad-symbol.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Documentation symbol mismatch with expression
|
||||||
|
|
||||||
|
##
|
||||||
|
# @food:
|
||||||
|
##
|
||||||
|
{ 'command': 'foo', 'data': {'a': 'int'} }
|
0
tests/qapi-schema/doc-bad-symbol.out
Normal file
0
tests/qapi-schema/doc-bad-symbol.out
Normal file
1
tests/qapi-schema/doc-duplicated-arg.err
Normal file
1
tests/qapi-schema/doc-duplicated-arg.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/doc-duplicated-arg.json:6:1: 'a' parameter name duplicated
|
1
tests/qapi-schema/doc-duplicated-arg.exit
Normal file
1
tests/qapi-schema/doc-duplicated-arg.exit
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1
|
7
tests/qapi-schema/doc-duplicated-arg.json
Normal file
7
tests/qapi-schema/doc-duplicated-arg.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Do not allow duplicated argument
|
||||||
|
|
||||||
|
##
|
||||||
|
# @foo:
|
||||||
|
# @a:
|
||||||
|
# @a:
|
||||||
|
##
|
0
tests/qapi-schema/doc-duplicated-arg.out
Normal file
0
tests/qapi-schema/doc-duplicated-arg.out
Normal file
1
tests/qapi-schema/doc-duplicated-return.err
Normal file
1
tests/qapi-schema/doc-duplicated-return.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/doc-duplicated-return.json:7:1: Duplicated 'Returns' section
|
1
tests/qapi-schema/doc-duplicated-return.exit
Normal file
1
tests/qapi-schema/doc-duplicated-return.exit
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1
|
8
tests/qapi-schema/doc-duplicated-return.json
Normal file
8
tests/qapi-schema/doc-duplicated-return.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Do not allow duplicated Returns section
|
||||||
|
|
||||||
|
##
|
||||||
|
# @foo:
|
||||||
|
#
|
||||||
|
# Returns: 0
|
||||||
|
# Returns: 1
|
||||||
|
##
|
0
tests/qapi-schema/doc-duplicated-return.out
Normal file
0
tests/qapi-schema/doc-duplicated-return.out
Normal file
1
tests/qapi-schema/doc-duplicated-since.err
Normal file
1
tests/qapi-schema/doc-duplicated-since.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/doc-duplicated-since.json:7:1: Duplicated 'Since' section
|
1
tests/qapi-schema/doc-duplicated-since.exit
Normal file
1
tests/qapi-schema/doc-duplicated-since.exit
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1
|
8
tests/qapi-schema/doc-duplicated-since.json
Normal file
8
tests/qapi-schema/doc-duplicated-since.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Do not allow duplicated Since section
|
||||||
|
|
||||||
|
##
|
||||||
|
# @foo:
|
||||||
|
#
|
||||||
|
# Since: 0
|
||||||
|
# Since: 1
|
||||||
|
##
|
0
tests/qapi-schema/doc-duplicated-since.out
Normal file
0
tests/qapi-schema/doc-duplicated-since.out
Normal file
1
tests/qapi-schema/doc-empty-arg.err
Normal file
1
tests/qapi-schema/doc-empty-arg.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/doc-empty-arg.json:5:1: Invalid parameter name
|
1
tests/qapi-schema/doc-empty-arg.exit
Normal file
1
tests/qapi-schema/doc-empty-arg.exit
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1
|
6
tests/qapi-schema/doc-empty-arg.json
Normal file
6
tests/qapi-schema/doc-empty-arg.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# An invalid empty argument name
|
||||||
|
|
||||||
|
##
|
||||||
|
# @foo:
|
||||||
|
# @:
|
||||||
|
##
|
0
tests/qapi-schema/doc-empty-arg.out
Normal file
0
tests/qapi-schema/doc-empty-arg.out
Normal file
1
tests/qapi-schema/doc-empty-section.err
Normal file
1
tests/qapi-schema/doc-empty-section.err
Normal file
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/doc-empty-section.json:3: Empty doc section 'Note'
|
1
tests/qapi-schema/doc-empty-section.exit
Normal file
1
tests/qapi-schema/doc-empty-section.exit
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1
|
8
tests/qapi-schema/doc-empty-section.json
Normal file
8
tests/qapi-schema/doc-empty-section.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Tagged-section must not be empty
|
||||||
|
|
||||||
|
##
|
||||||
|
# @foo:
|
||||||
|
#
|
||||||
|
# Note:
|
||||||
|
##
|
||||||
|
{ 'command': 'foo', 'data': {'a': 'int'} }
|
0
tests/qapi-schema/doc-empty-section.out
Normal file
0
tests/qapi-schema/doc-empty-section.out
Normal file
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue