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