[Qemu-devel] [PATCH for-2.9 16/47] qapi2texi: Convert to QAPISchemaVisitor

Markus Armbruster posted 47 patches 8 years, 7 months ago
There is a newer version of this series
[Qemu-devel] [PATCH for-2.9 16/47] qapi2texi: Convert to QAPISchemaVisitor
Posted by Markus Armbruster 8 years, 7 months ago
qapi2texi works with schema expression trees.  Such a tight coupling
to schema language syntax is not a good idea.  Convert it to the visitor
interface the other generators use.

No change to generated documentation.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
---
 scripts/qapi2texi.py | 228 ++++++++++++++++++++++++++-------------------------
 1 file changed, 118 insertions(+), 110 deletions(-)

diff --git a/scripts/qapi2texi.py b/scripts/qapi2texi.py
index 299dcf9..6d4e757 100755
--- a/scripts/qapi2texi.py
+++ b/scripts/qapi2texi.py
@@ -123,31 +123,39 @@ def texi_format(doc):
     return "\n".join(lines)
 
 
-def texi_body(doc, only_documented=False):
-    """
-    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"
-
-    args = ''
-    for name, section in doc.args.iteritems():
-        if not section.content and not only_documented:
-            continue        # Undocumented TODO require doc and drop
-        desc = str(section)
-        opt = ''
-        if section.optional:
-            desc = re.sub(r'^ *#optional *\n?|\n? *#optional *$|#optional',
-                          '', desc)
-            opt = ' (optional)'
-        args += "@item @code{'%s'}%s\n%s\n" % (name, opt, texi_format(desc))
-    if args:
-        body += "@table @asis\n"
-        body += args
-        body += "@end table\n"
+def texi_body(doc):
+    """Format the main documentation body"""
+    return texi_format(str(doc.body)) + '\n'
 
+
+def texi_enum_value(value):
+    """Format a table of members item for an enumeration value"""
+    return "@item @code{'%s'}\n" % value.name
+
+
+def texi_member(member):
+    """Format a table of members item for an object type member"""
+    return "@item @code{'%s'}%s\n" % (
+        member.name, ' (optional)' if member.optional else '')
+
+
+def texi_members(doc, member_func, show_undocumented):
+    """Format the table of members"""
+    items = ''
+    for section in doc.args.itervalues():
+        if not section.content and not show_undocumented:
+            continue          # Undocumented TODO require doc and drop
+        desc = re.sub(r'^ *#optional *\n?|\n? *#optional *$|#optional',
+                      '', str(section))
+        items += member_func(section.member) + texi_format(desc) + '\n'
+    if not items:
+        return ''
+    return '@table @asis\n' + items + '@end table\n'
+
+
+def texi_sections(doc):
+    """Format additional sections following arguments"""
+    body = ''
     for section in doc.sections:
         name, doc = (section.name, str(section))
         func = texi_format
@@ -159,94 +167,94 @@ def texi_body(doc, only_documented=False):
             body += "\n\n@b{%s:}\n" % name
 
         body += func(doc)
-
     return body
 
 
-def texi_alternate(expr, doc):
-    """Format an alternate to texi"""
-    body = texi_body(doc)
-    return TYPE_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 TYPE_FMT(type=union,
-                    name=doc.symbol,
-                    body=body)
-
-
-def texi_enum(expr, doc):
-    """Format an enum to texi"""
-    body = texi_body(doc, True)
-    return TYPE_FMT(type="Enum",
-                    name=doc.symbol,
-                    body=body)
-
-
-def texi_struct(expr, doc):
-    """Format a struct to texi"""
-    body = texi_body(doc)
-    return TYPE_FMT(type="Struct",
-                    name=doc.symbol,
-                    body=body)
-
-
-def texi_command(expr, doc):
-    """Format a command to texi"""
-    body = texi_body(doc)
-    return MSG_FMT(type="Command",
-                   name=doc.symbol,
-                   body=body)
-
-
-def texi_event(expr, doc):
-    """Format an event to texi"""
-    body = texi_body(doc)
-    return MSG_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 texi_entity(doc, member_func=texi_member, show_undocumented=False):
+    return (texi_body(doc)
+            + texi_members(doc, member_func, show_undocumented)
+            + texi_sections(doc))
+
+
+class QAPISchemaGenDocVisitor(qapi.QAPISchemaVisitor):
+    def __init__(self):
+        self.out = None
+        self.cur_doc = None
+
+    def visit_begin(self, schema):
+        self.out = ''
+
+    def visit_enum_type(self, name, info, values, prefix):
+        doc = self.cur_doc
+        if self.out:
+            self.out += '\n'
+        self.out += TYPE_FMT(type='Enum',
+                             name=doc.symbol,
+                             body=texi_entity(doc,
+                                              member_func=texi_enum_value,
+                                              show_undocumented=True))
+
+    def visit_object_type(self, name, info, base, members, variants):
+        doc = self.cur_doc
+        if not variants:
+            typ = 'Struct'
+        elif variants._tag_name:        # TODO unclean member access
+            typ = 'Flat Union'
+        else:
+            typ = 'Simple Union'
+        if self.out:
+            self.out += '\n'
+        self.out += TYPE_FMT(type=typ,
+                             name=doc.symbol,
+                             body=texi_entity(doc))
+
+    def visit_alternate_type(self, name, info, variants):
+        doc = self.cur_doc
+        if self.out:
+            self.out += '\n'
+        self.out += TYPE_FMT(type='Alternate',
+                             name=doc.symbol,
+                             body=texi_entity(doc))
+
+    def visit_command(self, name, info, arg_type, ret_type,
+                      gen, success_response, boxed):
+        doc = self.cur_doc
+        if self.out:
+            self.out += '\n'
+        self.out += MSG_FMT(type='Command',
+                            name=doc.symbol,
+                            body=texi_entity(doc))
+
+    def visit_event(self, name, info, arg_type, boxed):
+        doc = self.cur_doc
+        if self.out:
+            self.out += '\n'
+        self.out += MSG_FMT(type='Event',
+                            name=doc.symbol,
+                            body=texi_entity(doc))
+
+    def symbol(self, doc, entity):
+        self.cur_doc = doc
+        entity.visit(self)
+        self.cur_doc = None
+
+    def freeform(self, doc):
+        assert not doc.args
+        if self.out:
+            self.out += '\n'
+        self.out += texi_body(doc) + texi_sections(doc)
+
+
+def texi_schema(schema):
+    """Convert QAPI schema documentation to Texinfo"""
+    gen = QAPISchemaGenDocVisitor()
+    gen.visit_begin(schema)
+    for doc in schema.docs:
+        if doc.symbol:
+            gen.symbol(doc, schema.lookup_entity(doc.symbol))
+        else:
+            gen.freeform(doc)
+    return gen.out
 
 
 def main(argv):
@@ -259,7 +267,7 @@ def main(argv):
     if not qapi.doc_required:
         print >>sys.stderr, ("%s: need pragma 'doc-required' "
                              "to generate documentation" % argv[0])
-    print texi(schema.docs)
+    print texi_schema(schema)
 
 
 if __name__ == "__main__":
-- 
2.7.4


Re: [Qemu-devel] [PATCH for-2.9 16/47] qapi2texi: Convert to QAPISchemaVisitor
Posted by Eric Blake 8 years, 7 months ago
On 03/13/2017 01:18 AM, Markus Armbruster wrote:
> qapi2texi works with schema expression trees.  Such a tight coupling
> to schema language syntax is not a good idea.  Convert it to the visitor
> interface the other generators use.
> 
> No change to generated documentation.
> 
> Signed-off-by: Markus Armbruster <armbru@redhat.com>
> ---
>  scripts/qapi2texi.py | 228 ++++++++++++++++++++++++++-------------------------
>  1 file changed, 118 insertions(+), 110 deletions(-)
> 

> +    def visit_object_type(self, name, info, base, members, variants):
> +        doc = self.cur_doc
> +        if not variants:
> +            typ = 'Struct'
> +        elif variants._tag_name:        # TODO unclean member access
> +            typ = 'Flat Union'
> +        else:
> +            typ = 'Simple Union'

Do we even want to document this distinction to the end user?
Introspection managed to hide the difference by introducing the
appropriate generated wrapper types that demonstrate the additional {}
nesting in a way compatible with rewriting simple unions into flat
unions.  If we don't explain the difference here, we can get rid of the
unclean member access, but then again risk documentation that is not
clear whether {} nesting is needed.

I guess there's also the fact that for this patch, you intentionally
tried to make no difference to the generated docs (good); so any tweaks
to union output should be later patches anyways.

Reviewed-by: Eric Blake <eblake@redhat.com>

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org