summaryrefslogblamecommitdiffstats
path: root/scripts/qapi2texi.py
blob: 83ded95c2dba29497743bfbd6e22e83d8065c40b (plain) (tree)














































































































































































































































































                                                                               
#!/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)