mirror of https://github.com/xemu-project/xemu.git
QAPI patches for 2019-10-29
-----BEGIN PGP SIGNATURE----- iQJGBAABCAAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAl233vgSHGFybWJydUBy ZWRoYXQuY29tAAoJEDhwtADrkYZTV4gP/02KEDNINA7n4Rm/56PpCDJiPB0xaro8 zxd/GB6FoB84xHzDFLyzKCi1mgVgAnFYI2VJr0M7ILFfow+pJY4PfqkM4472HTUw devJgBlzBFTbuI6jnATKMfKnEo5baGItWJfuTpJE2d/EJo0A92JOtuFBnYqRiaLJ 3UcQatTmwMTJRdzZewpQ6fdq387VAM9D+XVvCROq4kjjOUoSSRz9RwHJSTxEYISc jYDKJEUeVHMRfpht9x43PnI5f0TEkkGS+qKDKZSLauOepkgsvrYp/1byYfWRukiL Ioe38w7DTv66Sd4bp/+5iPXR2Z8TWX5IBaMwvEOiJlAGGQAQVhoUEMX3pftoCjZz ReMUrf7fYMUfJ93El7eDb/8cVipIq7oLtRruMzqENsUYAQTUjRmx7VL01Vukrlcu 4xRWQ0Sk7zlQxOp6Zb8KS+qe0DKaVFJn9XR4vIrJ0VChqr2UTWUMSxuCUcebynaJ 0JJ1ZNP92QaJM45g5U+zeNbWEYEm0LtsvUVy/vjaEi7hR0AkA4cckfTxAiRP/6kV mhENFKh1yhrJu/XWeBmMia9s2jRMmbZtI1ABiMR4K15MSCmX607pfC6ty9AZ1G+w 2dFoBjwzqIYGmX82TXHeCr1ImrhGKl30SI+ccpNwqufAev7HcvOIxno4h2VWuBV0 ZJ/F4oqoIJWo =c3NL -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2019-10-29' into staging QAPI patches for 2019-10-29 # gpg: Signature made Tue 29 Oct 2019 06:40:56 GMT # gpg: using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653 # gpg: issuer "armbru@redhat.com" # gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full] # gpg: aka "Markus Armbruster <armbru@pond.sub.org>" [full] # Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653 * remotes/armbru/tags/pull-qapi-2019-10-29: qapi: Check feature documentation against the schema qapi: Polish reporting of bogus member documentation qapi: Lift features into QAPISchemaEntity qapi: Fold normalize_enum() into check_enum() qapi: Fold normalize_features() into check_features() qapi: Fold normalize_if() into check_if() qapi: Eliminate .check_doc() overrides qapi: Simplify ._make_implicit_object_type() qapi: Fix doc comment checking for commands and events qapi: Clean up doc comment checking for implicit union base qapi: Fix enum doc comment checking qapi: Split .connect_doc(), .check_doc() off .check() qapi: De-duplicate entity documentation generation code qapi: Implement boxed event argument documentation qemu-doc: Belatedly document QMP command deprecation tests/qapi-schema: Fix feature documentation testing tests/qapi-schema: Cover alternate documentation comments tests/qapi-schema: Demonstrate command and event doc comment bugs tests/qapi-schema: Demonstrate feature and enum doc comment bugs Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
16884391c7
|
@ -723,8 +723,6 @@
|
|||
# Trigger generation of broadcast RARP frames to update network switches.
|
||||
# This can be useful when network bonds fail-over the active slave.
|
||||
#
|
||||
# @params: AnnounceParameters giving timing and repetition count of announce
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# -> { "execute": "announce-self",
|
||||
|
|
|
@ -149,6 +149,18 @@ QEMU 4.1 has three options, please migrate to one of these three:
|
|||
|
||||
@section QEMU Machine Protocol (QMP) commands
|
||||
|
||||
@subsection change (since 2.5.0)
|
||||
|
||||
Use ``blockdev-change-medium'' or ``change-vnc-password'' instead.
|
||||
|
||||
@subsection migrate_set_downtime and migrate_set_speed (since 2.8.0)
|
||||
|
||||
Use ``migrate-set-parameters'' instead.
|
||||
|
||||
@subsection migrate-set-cache-size and query-migrate-cache-size (since 2.11.0)
|
||||
|
||||
Use ``migrate-set-parameters'' and ``query-migrate-parameters'' instead.
|
||||
|
||||
@subsection query-block result field dirty-bitmaps[i].status (since 4.0)
|
||||
|
||||
The ``status'' field of the ``BlockDirtyInfo'' structure, returned by
|
||||
|
|
|
@ -12,7 +12,7 @@ from qapi.gen import QAPIGenDoc, QAPISchemaVisitor
|
|||
MSG_FMT = """
|
||||
@deftypefn {type} {{}} {name}
|
||||
|
||||
{body}
|
||||
{body}{members}{features}{sections}
|
||||
@end deftypefn
|
||||
|
||||
""".format
|
||||
|
@ -20,7 +20,7 @@ MSG_FMT = """
|
|||
TYPE_FMT = """
|
||||
@deftp {{{type}}} {name}
|
||||
|
||||
{body}
|
||||
{body}{members}{features}{sections}
|
||||
@end deftp
|
||||
|
||||
""".format
|
||||
|
@ -149,7 +149,8 @@ def texi_member(member, desc, suffix):
|
|||
suffix, desc, texi_if(member.ifcond, prefix='@*'))
|
||||
|
||||
|
||||
def texi_members(doc, what, base, variants, member_func):
|
||||
def texi_members(doc, what, base=None, variants=None,
|
||||
member_func=texi_member):
|
||||
"""Format the table of members"""
|
||||
items = ''
|
||||
for section in doc.args.values():
|
||||
|
@ -182,6 +183,14 @@ def texi_members(doc, what, base, variants, member_func):
|
|||
return '\n@b{%s:}\n@table @asis\n%s@end table\n' % (what, items)
|
||||
|
||||
|
||||
def texi_arguments(doc, boxed_arg_type):
|
||||
if boxed_arg_type:
|
||||
assert not doc.args
|
||||
return ('\n@b{Arguments:} the members of @code{%s}\n'
|
||||
% boxed_arg_type.name)
|
||||
return texi_members(doc, 'Arguments')
|
||||
|
||||
|
||||
def texi_features(doc):
|
||||
"""Format the table of features"""
|
||||
items = ''
|
||||
|
@ -208,12 +217,22 @@ def texi_sections(doc, ifcond):
|
|||
return body
|
||||
|
||||
|
||||
def texi_entity(doc, what, ifcond, base=None, variants=None,
|
||||
member_func=texi_member):
|
||||
return (texi_body(doc)
|
||||
+ texi_members(doc, what, base, variants, member_func)
|
||||
+ texi_features(doc)
|
||||
+ texi_sections(doc, ifcond))
|
||||
def texi_type(typ, doc, ifcond, members):
|
||||
return TYPE_FMT(type=typ,
|
||||
name=doc.symbol,
|
||||
body=texi_body(doc),
|
||||
members=members,
|
||||
features=texi_features(doc),
|
||||
sections=texi_sections(doc, ifcond))
|
||||
|
||||
|
||||
def texi_msg(typ, doc, ifcond, members):
|
||||
return MSG_FMT(type=typ,
|
||||
name=doc.symbol,
|
||||
body=texi_body(doc),
|
||||
members=members,
|
||||
features=texi_features(doc),
|
||||
sections=texi_sections(doc, ifcond))
|
||||
|
||||
|
||||
class QAPISchemaGenDocVisitor(QAPISchemaVisitor):
|
||||
|
@ -227,48 +246,36 @@ class QAPISchemaGenDocVisitor(QAPISchemaVisitor):
|
|||
|
||||
def visit_enum_type(self, name, info, ifcond, members, prefix):
|
||||
doc = self.cur_doc
|
||||
self._gen.add(TYPE_FMT(type='Enum',
|
||||
name=doc.symbol,
|
||||
body=texi_entity(doc, 'Values', ifcond,
|
||||
member_func=texi_enum_value)))
|
||||
self._gen.add(texi_type('Enum', doc, ifcond,
|
||||
texi_members(doc, 'Values',
|
||||
member_func=texi_enum_value)))
|
||||
|
||||
def visit_object_type(self, name, info, ifcond, base, members, variants,
|
||||
features):
|
||||
doc = self.cur_doc
|
||||
if base and base.is_implicit():
|
||||
base = None
|
||||
self._gen.add(TYPE_FMT(type='Object',
|
||||
name=doc.symbol,
|
||||
body=texi_entity(doc, 'Members', ifcond,
|
||||
base, variants)))
|
||||
self._gen.add(texi_type('Object', doc, ifcond,
|
||||
texi_members(doc, 'Members', base, variants)))
|
||||
|
||||
def visit_alternate_type(self, name, info, ifcond, variants):
|
||||
doc = self.cur_doc
|
||||
self._gen.add(TYPE_FMT(type='Alternate',
|
||||
name=doc.symbol,
|
||||
body=texi_entity(doc, 'Members', ifcond)))
|
||||
self._gen.add(texi_type('Alternate', doc, ifcond,
|
||||
texi_members(doc, 'Members')))
|
||||
|
||||
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
|
||||
success_response, boxed, allow_oob, allow_preconfig,
|
||||
features):
|
||||
doc = self.cur_doc
|
||||
if boxed:
|
||||
body = texi_body(doc)
|
||||
body += ('\n@b{Arguments:} the members of @code{%s}\n'
|
||||
% arg_type.name)
|
||||
body += texi_features(doc)
|
||||
body += texi_sections(doc, ifcond)
|
||||
else:
|
||||
body = texi_entity(doc, 'Arguments', ifcond)
|
||||
self._gen.add(MSG_FMT(type='Command',
|
||||
name=doc.symbol,
|
||||
body=body))
|
||||
self._gen.add(texi_msg('Command', doc, ifcond,
|
||||
texi_arguments(doc,
|
||||
arg_type if boxed else None)))
|
||||
|
||||
def visit_event(self, name, info, ifcond, arg_type, boxed):
|
||||
doc = self.cur_doc
|
||||
self._gen.add(MSG_FMT(type='Event',
|
||||
name=doc.symbol,
|
||||
body=texi_entity(doc, 'Arguments', ifcond)))
|
||||
self._gen.add(texi_msg('Event', doc, ifcond,
|
||||
texi_arguments(doc,
|
||||
arg_type if boxed else None)))
|
||||
|
||||
def symbol(self, doc, entity):
|
||||
if self._gen._body:
|
||||
|
|
|
@ -95,12 +95,6 @@ def check_flags(expr, info):
|
|||
info, "flag '%s' may only use true value" % key)
|
||||
|
||||
|
||||
def normalize_if(expr):
|
||||
ifcond = expr.get('if')
|
||||
if isinstance(ifcond, str):
|
||||
expr['if'] = [ifcond]
|
||||
|
||||
|
||||
def check_if(expr, info, source):
|
||||
|
||||
def check_if_str(ifcond, info):
|
||||
|
@ -126,6 +120,7 @@ def check_if(expr, info, source):
|
|||
check_if_str(elt, info)
|
||||
else:
|
||||
check_if_str(ifcond, info)
|
||||
expr['if'] = [ifcond]
|
||||
|
||||
|
||||
def normalize_members(members):
|
||||
|
@ -175,21 +170,16 @@ def check_type(value, info, source,
|
|||
raise QAPISemError(info, "%s uses reserved name" % key_source)
|
||||
check_keys(arg, info, key_source, ['type'], ['if'])
|
||||
check_if(arg, info, key_source)
|
||||
normalize_if(arg)
|
||||
check_type(arg['type'], info, key_source, allow_array=True)
|
||||
|
||||
|
||||
def normalize_features(features):
|
||||
if isinstance(features, list):
|
||||
features[:] = [f if isinstance(f, dict) else {'name': f}
|
||||
for f in features]
|
||||
|
||||
|
||||
def check_features(features, info):
|
||||
if features is None:
|
||||
return
|
||||
if not isinstance(features, list):
|
||||
raise QAPISemError(info, "'features' must be an array")
|
||||
features[:] = [f if isinstance(f, dict) else {'name': f}
|
||||
for f in features]
|
||||
for f in features:
|
||||
source = "'features' member"
|
||||
assert isinstance(f, dict)
|
||||
|
@ -198,13 +188,6 @@ def check_features(features, info):
|
|||
source = "%s '%s'" % (source, f['name'])
|
||||
check_name_str(f['name'], info, source)
|
||||
check_if(f, info, source)
|
||||
normalize_if(f)
|
||||
|
||||
|
||||
def normalize_enum(expr):
|
||||
if isinstance(expr['data'], list):
|
||||
expr['data'] = [m if isinstance(m, dict) else {'name': m}
|
||||
for m in expr['data']]
|
||||
|
||||
|
||||
def check_enum(expr, info):
|
||||
|
@ -219,6 +202,8 @@ def check_enum(expr, info):
|
|||
|
||||
permit_upper = name in info.pragma.name_case_whitelist
|
||||
|
||||
members[:] = [m if isinstance(m, dict) else {'name': m}
|
||||
for m in members]
|
||||
for member in members:
|
||||
source = "'data' member"
|
||||
check_keys(member, info, source, ['name'], ['if'])
|
||||
|
@ -227,7 +212,6 @@ def check_enum(expr, info):
|
|||
check_name_str(member['name'], info, source,
|
||||
enum_member=True, permit_upper=permit_upper)
|
||||
check_if(member, info, source)
|
||||
normalize_if(member)
|
||||
|
||||
|
||||
def check_struct(expr, info):
|
||||
|
@ -259,7 +243,6 @@ def check_union(expr, info):
|
|||
check_name_str(key, info, source)
|
||||
check_keys(value, info, source, ['type'], ['if'])
|
||||
check_if(value, info, source)
|
||||
normalize_if(value)
|
||||
check_type(value['type'], info, source, allow_array=not base)
|
||||
|
||||
|
||||
|
@ -273,7 +256,6 @@ def check_alternate(expr, info):
|
|||
check_name_str(key, info, source)
|
||||
check_keys(value, info, source, ['type'], ['if'])
|
||||
check_if(value, info, source)
|
||||
normalize_if(value)
|
||||
check_type(value['type'], info, source)
|
||||
|
||||
|
||||
|
@ -339,7 +321,6 @@ def check_exprs(exprs):
|
|||
if meta == 'enum':
|
||||
check_keys(expr, info, meta,
|
||||
['enum', 'data'], ['if', 'prefix'])
|
||||
normalize_enum(expr)
|
||||
check_enum(expr, info)
|
||||
elif meta == 'union':
|
||||
check_keys(expr, info, meta,
|
||||
|
@ -357,7 +338,6 @@ def check_exprs(exprs):
|
|||
check_keys(expr, info, meta,
|
||||
['struct', 'data'], ['base', 'if', 'features'])
|
||||
normalize_members(expr['data'])
|
||||
normalize_features(expr.get('features'))
|
||||
check_struct(expr, info)
|
||||
elif meta == 'command':
|
||||
check_keys(expr, info, meta,
|
||||
|
@ -366,7 +346,6 @@ def check_exprs(exprs):
|
|||
'gen', 'success-response', 'allow-oob',
|
||||
'allow-preconfig'])
|
||||
normalize_members(expr.get('data'))
|
||||
normalize_features(expr.get('features'))
|
||||
check_command(expr, info)
|
||||
elif meta == 'event':
|
||||
check_keys(expr, info, meta,
|
||||
|
@ -376,7 +355,6 @@ def check_exprs(exprs):
|
|||
else:
|
||||
assert False, 'unexpected meta type'
|
||||
|
||||
normalize_if(expr)
|
||||
check_if(expr, info, meta)
|
||||
check_flags(expr, info)
|
||||
|
||||
|
|
|
@ -555,16 +555,31 @@ class QAPIDoc(object):
|
|||
self.args[member.name] = QAPIDoc.ArgSection(member.name)
|
||||
self.args[member.name].connect(member)
|
||||
|
||||
def connect_feature(self, feature):
|
||||
if feature.name not in self.features:
|
||||
raise QAPISemError(feature.info,
|
||||
"feature '%s' lacks documentation"
|
||||
% feature.name)
|
||||
self.features[feature.name] = QAPIDoc.ArgSection(feature.name)
|
||||
self.features[feature.name].connect(feature)
|
||||
|
||||
def check_expr(self, expr):
|
||||
if self.has_section('Returns') and 'command' not in expr:
|
||||
raise QAPISemError(self.info,
|
||||
"'Returns:' is only valid for commands")
|
||||
|
||||
def check(self):
|
||||
bogus = [name for name, section in self.args.items()
|
||||
if not section.member]
|
||||
if bogus:
|
||||
raise QAPISemError(
|
||||
self.info,
|
||||
"the following documented members are not in "
|
||||
"the declaration: %s" % ", ".join(bogus))
|
||||
|
||||
def check_args_section(args, info, what):
|
||||
bogus = [name for name, section in args.items()
|
||||
if not section.member]
|
||||
if bogus:
|
||||
raise QAPISemError(
|
||||
self.info,
|
||||
"documented member%s '%s' %s not exist"
|
||||
% ("s" if len(bogus) > 1 else "",
|
||||
"', '".join(bogus),
|
||||
"do" if len(bogus) > 1 else "does"))
|
||||
|
||||
check_args_section(self.args, self.info, 'members')
|
||||
check_args_section(self.features, self.info, 'features')
|
||||
|
|
|
@ -27,8 +27,11 @@ from qapi.parser import QAPISchemaParser
|
|||
class QAPISchemaEntity(object):
|
||||
meta = None
|
||||
|
||||
def __init__(self, name, info, doc, ifcond=None):
|
||||
def __init__(self, name, info, doc, ifcond=None, features=None):
|
||||
assert name is None or isinstance(name, str)
|
||||
for f in features or []:
|
||||
assert isinstance(f, QAPISchemaFeature)
|
||||
f.set_defined_in(name)
|
||||
self.name = name
|
||||
self._module = None
|
||||
# For explicitly defined entities, info points to the (explicit)
|
||||
|
@ -39,6 +42,7 @@ class QAPISchemaEntity(object):
|
|||
self.info = info
|
||||
self.doc = doc
|
||||
self._ifcond = ifcond or []
|
||||
self.features = features or []
|
||||
self._checked = False
|
||||
|
||||
def c_name(self):
|
||||
|
@ -49,8 +53,21 @@ class QAPISchemaEntity(object):
|
|||
if self.info:
|
||||
self._module = os.path.relpath(self.info.fname,
|
||||
os.path.dirname(schema.fname))
|
||||
seen = {}
|
||||
for f in self.features:
|
||||
f.check_clash(self.info, seen)
|
||||
if self.doc:
|
||||
self.doc.connect_feature(f)
|
||||
|
||||
self._checked = True
|
||||
|
||||
def connect_doc(self, doc=None):
|
||||
pass
|
||||
|
||||
def check_doc(self):
|
||||
if self.doc:
|
||||
self.doc.check()
|
||||
|
||||
@property
|
||||
def ifcond(self):
|
||||
assert self._checked
|
||||
|
@ -217,8 +234,12 @@ class QAPISchemaEnumType(QAPISchemaType):
|
|||
seen = {}
|
||||
for m in self.members:
|
||||
m.check_clash(self.info, seen)
|
||||
if self.doc:
|
||||
self.doc.connect_member(m)
|
||||
|
||||
def connect_doc(self, doc=None):
|
||||
doc = doc or self.doc
|
||||
if doc:
|
||||
for m in self.members:
|
||||
doc.connect_member(m)
|
||||
|
||||
def is_implicit(self):
|
||||
# See QAPISchema._make_implicit_enum_type() and ._def_predefineds()
|
||||
|
@ -296,7 +317,7 @@ class QAPISchemaObjectType(QAPISchemaType):
|
|||
# struct has local_members, optional base, and no variants
|
||||
# flat union has base, variants, and no local_members
|
||||
# simple union has local_members, variants, and no base
|
||||
QAPISchemaType.__init__(self, name, info, doc, ifcond)
|
||||
QAPISchemaType.__init__(self, name, info, doc, ifcond, features)
|
||||
self.meta = 'union' if variants else 'struct'
|
||||
assert base is None or isinstance(base, str)
|
||||
for m in local_members:
|
||||
|
@ -305,15 +326,11 @@ class QAPISchemaObjectType(QAPISchemaType):
|
|||
if variants is not None:
|
||||
assert isinstance(variants, QAPISchemaObjectTypeVariants)
|
||||
variants.set_defined_in(name)
|
||||
for f in features:
|
||||
assert isinstance(f, QAPISchemaFeature)
|
||||
f.set_defined_in(name)
|
||||
self._base_name = base
|
||||
self.base = None
|
||||
self.local_members = local_members
|
||||
self.variants = variants
|
||||
self.members = None
|
||||
self.features = features
|
||||
|
||||
def check(self, schema):
|
||||
# This calls another type T's .check() exactly when the C
|
||||
|
@ -345,22 +362,12 @@ class QAPISchemaObjectType(QAPISchemaType):
|
|||
for m in self.local_members:
|
||||
m.check(schema)
|
||||
m.check_clash(self.info, seen)
|
||||
if self.doc:
|
||||
self.doc.connect_member(m)
|
||||
members = seen.values()
|
||||
|
||||
if self.variants:
|
||||
self.variants.check(schema, seen)
|
||||
self.variants.check_clash(self.info, seen)
|
||||
|
||||
# Features are in a name space separate from members
|
||||
seen = {}
|
||||
for f in self.features:
|
||||
f.check_clash(self.info, seen)
|
||||
|
||||
if self.doc:
|
||||
self.doc.check()
|
||||
|
||||
self.members = members # mark completed
|
||||
|
||||
# Check that the members of this type do not cause duplicate JSON members,
|
||||
|
@ -372,6 +379,14 @@ class QAPISchemaObjectType(QAPISchemaType):
|
|||
for m in self.members:
|
||||
m.check_clash(info, seen)
|
||||
|
||||
def connect_doc(self, doc=None):
|
||||
doc = doc or self.doc
|
||||
if doc:
|
||||
if self.base and self.base.is_implicit():
|
||||
self.base.connect_doc(doc)
|
||||
for m in self.local_members:
|
||||
doc.connect_member(m)
|
||||
|
||||
@property
|
||||
def ifcond(self):
|
||||
assert self._checked
|
||||
|
@ -639,10 +654,12 @@ class QAPISchemaAlternateType(QAPISchemaType):
|
|||
"%s can't be distinguished from '%s'"
|
||||
% (v.describe(self.info), types_seen[qt]))
|
||||
types_seen[qt] = v.name
|
||||
if self.doc:
|
||||
self.doc.connect_member(v)
|
||||
if self.doc:
|
||||
self.doc.check()
|
||||
|
||||
def connect_doc(self, doc=None):
|
||||
doc = doc or self.doc
|
||||
if doc:
|
||||
for v in self.variants.variants:
|
||||
doc.connect_member(v)
|
||||
|
||||
def c_type(self):
|
||||
return c_name(self.name) + pointer_suffix
|
||||
|
@ -662,12 +679,9 @@ class QAPISchemaCommand(QAPISchemaEntity):
|
|||
def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
|
||||
gen, success_response, boxed, allow_oob, allow_preconfig,
|
||||
features):
|
||||
QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
|
||||
QAPISchemaEntity.__init__(self, name, info, doc, ifcond, features)
|
||||
assert not arg_type or isinstance(arg_type, str)
|
||||
assert not ret_type or isinstance(ret_type, str)
|
||||
for f in features:
|
||||
assert isinstance(f, QAPISchemaFeature)
|
||||
f.set_defined_in(name)
|
||||
self._arg_type_name = arg_type
|
||||
self.arg_type = None
|
||||
self._ret_type_name = ret_type
|
||||
|
@ -677,7 +691,6 @@ class QAPISchemaCommand(QAPISchemaEntity):
|
|||
self.boxed = boxed
|
||||
self.allow_oob = allow_oob
|
||||
self.allow_preconfig = allow_preconfig
|
||||
self.features = features
|
||||
|
||||
def check(self, schema):
|
||||
QAPISchemaEntity.check(self, schema)
|
||||
|
@ -707,10 +720,11 @@ class QAPISchemaCommand(QAPISchemaEntity):
|
|||
"command's 'returns' cannot take %s"
|
||||
% self.ret_type.describe())
|
||||
|
||||
# Features are in a name space separate from members
|
||||
seen = {}
|
||||
for f in self.features:
|
||||
f.check_clash(self.info, seen)
|
||||
def connect_doc(self, doc=None):
|
||||
doc = doc or self.doc
|
||||
if doc:
|
||||
if self.arg_type and self.arg_type.is_implicit():
|
||||
self.arg_type.connect_doc(doc)
|
||||
|
||||
def visit(self, visitor):
|
||||
QAPISchemaEntity.visit(self, visitor)
|
||||
|
@ -748,6 +762,12 @@ class QAPISchemaEvent(QAPISchemaEntity):
|
|||
"event's 'data' can take %s only with 'boxed': true"
|
||||
% self.arg_type.describe())
|
||||
|
||||
def connect_doc(self, doc=None):
|
||||
doc = doc or self.doc
|
||||
if doc:
|
||||
if self.arg_type and self.arg_type.is_implicit():
|
||||
self.arg_type.connect_doc(doc)
|
||||
|
||||
def visit(self, visitor):
|
||||
QAPISchemaEntity.visit(self, visitor)
|
||||
visitor.visit_event(self.name, self.info, self.ifcond,
|
||||
|
@ -873,8 +893,7 @@ class QAPISchema(object):
|
|||
self._def_entity(QAPISchemaArrayType(name, info, element_type))
|
||||
return name
|
||||
|
||||
def _make_implicit_object_type(self, name, info, doc, ifcond,
|
||||
role, members):
|
||||
def _make_implicit_object_type(self, name, info, ifcond, role, members):
|
||||
if not members:
|
||||
return None
|
||||
# See also QAPISchemaObjectTypeMember.describe()
|
||||
|
@ -892,7 +911,7 @@ class QAPISchema(object):
|
|||
# TODO kill simple unions or implement the disjunction
|
||||
assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access
|
||||
else:
|
||||
self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond,
|
||||
self._def_entity(QAPISchemaObjectType(name, info, None, ifcond,
|
||||
None, members, None, []))
|
||||
return name
|
||||
|
||||
|
@ -939,7 +958,7 @@ class QAPISchema(object):
|
|||
assert len(typ) == 1
|
||||
typ = self._make_array_type(typ[0], info)
|
||||
typ = self._make_implicit_object_type(
|
||||
typ, info, None, self.lookup_type(typ),
|
||||
typ, info, self.lookup_type(typ),
|
||||
'wrapper', [self._make_member('data', typ, None, info)])
|
||||
return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
|
||||
|
||||
|
@ -952,7 +971,7 @@ class QAPISchema(object):
|
|||
tag_member = None
|
||||
if isinstance(base, dict):
|
||||
base = self._make_implicit_object_type(
|
||||
name, info, doc, ifcond,
|
||||
name, info, ifcond,
|
||||
'base', self._make_members(base, info))
|
||||
if tag_name:
|
||||
variants = [self._make_variant(key, value['type'],
|
||||
|
@ -999,7 +1018,7 @@ class QAPISchema(object):
|
|||
features = expr.get('features', [])
|
||||
if isinstance(data, OrderedDict):
|
||||
data = self._make_implicit_object_type(
|
||||
name, info, doc, ifcond, 'arg', self._make_members(data, info))
|
||||
name, info, ifcond, 'arg', self._make_members(data, info))
|
||||
if isinstance(rets, list):
|
||||
assert len(rets) == 1
|
||||
rets = self._make_array_type(rets[0], info)
|
||||
|
@ -1015,7 +1034,7 @@ class QAPISchema(object):
|
|||
ifcond = expr.get('if')
|
||||
if isinstance(data, OrderedDict):
|
||||
data = self._make_implicit_object_type(
|
||||
name, info, doc, ifcond, 'arg', self._make_members(data, info))
|
||||
name, info, ifcond, 'arg', self._make_members(data, info))
|
||||
self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
|
||||
|
||||
def _def_exprs(self, exprs):
|
||||
|
@ -1043,6 +1062,8 @@ class QAPISchema(object):
|
|||
def check(self):
|
||||
for ent in self._entity_list:
|
||||
ent.check(self)
|
||||
ent.connect_doc()
|
||||
ent.check_doc()
|
||||
|
||||
def visit(self, visitor):
|
||||
visitor.visit_begin(self)
|
||||
|
|
|
@ -341,7 +341,11 @@ qapi-schema += base-cycle-indirect.json
|
|||
qapi-schema += command-int.json
|
||||
qapi-schema += comments.json
|
||||
qapi-schema += doc-bad-alternate-member.json
|
||||
qapi-schema += doc-bad-boxed-command-arg.json
|
||||
qapi-schema += doc-bad-command-arg.json
|
||||
qapi-schema += doc-bad-enum-member.json
|
||||
qapi-schema += doc-bad-event-arg.json
|
||||
qapi-schema += doc-bad-feature.json
|
||||
qapi-schema += doc-bad-section.json
|
||||
qapi-schema += doc-bad-symbol.json
|
||||
qapi-schema += doc-bad-union-member.json
|
||||
|
@ -365,6 +369,7 @@ qapi-schema += doc-missing-expr.json
|
|||
qapi-schema += doc-missing-space.json
|
||||
qapi-schema += doc-missing.json
|
||||
qapi-schema += doc-no-symbol.json
|
||||
qapi-schema += doc-undoc-feature.json
|
||||
qapi-schema += double-type.json
|
||||
qapi-schema += duplicate-key.json
|
||||
qapi-schema += empty.json
|
||||
|
|
|
@ -1 +1 @@
|
|||
doc-bad-alternate-member.json:3: the following documented members are not in the declaration: aa, bb
|
||||
doc-bad-alternate-member.json:3: documented members 'aa', 'bb' do not exist
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
doc-bad-boxed-command-arg.json:9: documented member 'a' does not exist
|
|
@ -0,0 +1,14 @@
|
|||
# Boxed arguments are not to be documented with the command
|
||||
|
||||
##
|
||||
# @Args:
|
||||
# @a: an argument
|
||||
##
|
||||
{ 'struct': 'Args', 'data': { 'a': 'int' } }
|
||||
|
||||
##
|
||||
# @cmd-boxed:
|
||||
# @a: bogus
|
||||
##
|
||||
{ 'command': 'cmd-boxed', 'boxed': true,
|
||||
'data': 'Args' }
|
|
@ -1 +1 @@
|
|||
doc-bad-command-arg.json:3: the following documented members are not in the declaration: b
|
||||
doc-bad-command-arg.json:3: documented member 'b' does not exist
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
doc-bad-enum-member.json:3: documented member 'a' does not exist
|
|
@ -0,0 +1,8 @@
|
|||
# Members listed in the doc comment must exist in the actual schema
|
||||
|
||||
##
|
||||
# @Foo:
|
||||
# @a: a
|
||||
# @b: b
|
||||
##
|
||||
{ 'enum': 'Foo', 'data': [ 'b' ] }
|
|
@ -0,0 +1 @@
|
|||
doc-bad-event-arg.json:3: documented member 'a' does not exist
|
|
@ -0,0 +1,7 @@
|
|||
# Arguments listed in the doc comment must exist in the actual schema
|
||||
|
||||
##
|
||||
# @FOO:
|
||||
# @a: a
|
||||
##
|
||||
{ 'event': 'FOO' }
|
|
@ -0,0 +1 @@
|
|||
doc-bad-feature.json:3: documented member 'a' does not exist
|
|
@ -0,0 +1,9 @@
|
|||
# Features listed in the doc comment must exist in the actual schema
|
||||
|
||||
##
|
||||
# @foo:
|
||||
#
|
||||
# Features:
|
||||
# @a: a
|
||||
##
|
||||
{ 'command': 'foo' }
|
|
@ -1 +1 @@
|
|||
doc-bad-union-member.json:3: the following documented members are not in the declaration: a, b
|
||||
doc-bad-union-member.json:3: documented members 'a', 'b' do not exist
|
||||
|
|
|
@ -98,6 +98,14 @@
|
|||
{ 'union': 'SugaredUnion',
|
||||
'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2', 'if': 'IFTWO' } } }
|
||||
|
||||
##
|
||||
# @Alternate:
|
||||
# @i: an integer
|
||||
# @b is undocumented
|
||||
##
|
||||
{ 'alternate': 'Alternate',
|
||||
'data': { 'i': 'int', 'b': 'bool' } }
|
||||
|
||||
##
|
||||
# == Another subsection
|
||||
##
|
||||
|
@ -149,3 +157,9 @@
|
|||
{ 'command': 'cmd-boxed', 'boxed': true,
|
||||
'data': 'Object',
|
||||
'features': [ 'cmd-feat1', 'cmd-feat2' ] }
|
||||
|
||||
##
|
||||
# @EVT-BOXED:
|
||||
##
|
||||
{ 'event': 'EVT-BOXED', 'boxed': true,
|
||||
'data': 'Object' }
|
||||
|
|
|
@ -42,6 +42,10 @@ object SugaredUnion
|
|||
case one: q_obj_Variant1-wrapper
|
||||
case two: q_obj_Variant2-wrapper
|
||||
if ['IFTWO']
|
||||
alternate Alternate
|
||||
tag type
|
||||
case i: int
|
||||
case b: bool
|
||||
object q_obj_cmd-arg
|
||||
member arg1: int optional=False
|
||||
member arg2: str optional=True
|
||||
|
@ -54,6 +58,8 @@ command cmd-boxed Object -> None
|
|||
gen=True success_response=True boxed=True oob=False preconfig=False
|
||||
feature cmd-feat1
|
||||
feature cmd-feat2
|
||||
event EVT-BOXED Object
|
||||
boxed=True
|
||||
doc freeform
|
||||
body=
|
||||
= Section
|
||||
|
@ -120,6 +126,8 @@ A paragraph
|
|||
Another paragraph (but no @var: line)
|
||||
arg=var1
|
||||
|
||||
feature=variant1-feat
|
||||
a feature
|
||||
doc symbol=Variant2
|
||||
body=
|
||||
|
||||
|
@ -131,6 +139,14 @@ doc symbol=SugaredUnion
|
|||
|
||||
arg=type
|
||||
|
||||
doc symbol=Alternate
|
||||
body=
|
||||
|
||||
arg=i
|
||||
an integer
|
||||
@b is undocumented
|
||||
arg=b
|
||||
|
||||
doc freeform
|
||||
body=
|
||||
== Another subsection
|
||||
|
@ -144,6 +160,10 @@ the second
|
|||
argument
|
||||
arg=arg3
|
||||
|
||||
feature=cmd-feat1
|
||||
a feature
|
||||
feature=cmd-feat2
|
||||
another feature
|
||||
section=Note
|
||||
@arg3 is undocumented
|
||||
section=Returns
|
||||
|
@ -166,7 +186,14 @@ Duis aute irure dolor
|
|||
doc symbol=cmd-boxed
|
||||
body=
|
||||
If you're bored enough to read this, go see a video of boxed cats
|
||||
feature=cmd-feat1
|
||||
a feature
|
||||
feature=cmd-feat2
|
||||
another feature
|
||||
section=Example
|
||||
-> in
|
||||
|
||||
<- out
|
||||
doc symbol=EVT-BOXED
|
||||
body=
|
||||
|
||||
|
|
|
@ -170,6 +170,23 @@ One of @t{"one"}, @t{"two"}
|
|||
@end deftp
|
||||
|
||||
|
||||
|
||||
@deftp {Alternate} Alternate
|
||||
|
||||
|
||||
|
||||
@b{Members:}
|
||||
@table @asis
|
||||
@item @code{i: int}
|
||||
an integer
|
||||
@code{b} is undocumented
|
||||
@item @code{b: boolean}
|
||||
Not documented
|
||||
@end table
|
||||
|
||||
@end deftp
|
||||
|
||||
|
||||
@subsection Another subsection
|
||||
|
||||
|
||||
|
@ -258,3 +275,13 @@ another feature
|
|||
|
||||
@end deftypefn
|
||||
|
||||
|
||||
|
||||
@deftypefn Event {} EVT-BOXED
|
||||
|
||||
|
||||
|
||||
@b{Arguments:} the members of @code{Object}
|
||||
|
||||
@end deftypefn
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
doc-undoc-feature.json: In command 'foo':
|
||||
doc-undoc-feature.json:9: feature 'undoc' lacks documentation
|
|
@ -0,0 +1,9 @@
|
|||
# Doc comment must cover all features
|
||||
|
||||
##
|
||||
# @foo:
|
||||
#
|
||||
# Features:
|
||||
# @doc: documented feature
|
||||
##
|
||||
{ 'command': 'foo', 'features': ['undoc', 'doc'] }
|
|
@ -117,6 +117,8 @@ def test_frontend(fname):
|
|||
print(' body=\n%s' % doc.body.text)
|
||||
for arg, section in doc.args.items():
|
||||
print(' arg=%s\n%s' % (arg, section.text))
|
||||
for feat, section in doc.features.items():
|
||||
print(' feature=%s\n%s' % (feat, section.text))
|
||||
for section in doc.sections:
|
||||
print(' section=%s\n%s' % (section.name, section.text))
|
||||
|
||||
|
|
Loading…
Reference in New Issue