2
0

qapi2texi.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. #!/usr/bin/env python
  2. # QAPI texi generator
  3. #
  4. # This work is licensed under the terms of the GNU LGPL, version 2+.
  5. # See the COPYING file in the top-level directory.
  6. """This script produces the documentation of a qapi schema in texinfo format"""
  7. import re
  8. import sys
  9. import qapi
  10. MSG_FMT = """
  11. @deftypefn {type} {{}} {name}
  12. {body}
  13. @end deftypefn
  14. """.format
  15. TYPE_FMT = """
  16. @deftp {{{type}}} {name}
  17. {body}
  18. @end deftp
  19. """.format
  20. EXAMPLE_FMT = """@example
  21. {code}
  22. @end example
  23. """.format
  24. def subst_strong(doc):
  25. """Replaces *foo* by @strong{foo}"""
  26. return re.sub(r'\*([^*\n]+)\*', r'@emph{\1}', doc)
  27. def subst_emph(doc):
  28. """Replaces _foo_ by @emph{foo}"""
  29. return re.sub(r'\b_([^_\n]+)_\b', r' @emph{\1} ', doc)
  30. def subst_vars(doc):
  31. """Replaces @var by @code{var}"""
  32. return re.sub(r'@([\w-]+)', r'@code{\1}', doc)
  33. def subst_braces(doc):
  34. """Replaces {} with @{ @}"""
  35. return doc.replace("{", "@{").replace("}", "@}")
  36. def texi_example(doc):
  37. """Format @example"""
  38. # TODO: Neglects to escape @ characters.
  39. # We should probably escape them in subst_braces(), and rename the
  40. # function to subst_special() or subs_texi_special(). If we do that, we
  41. # need to delay it until after subst_vars() in texi_format().
  42. doc = subst_braces(doc).strip('\n')
  43. return EXAMPLE_FMT(code=doc)
  44. def texi_format(doc):
  45. """
  46. Format documentation
  47. Lines starting with:
  48. - |: generates an @example
  49. - =: generates @section
  50. - ==: generates @subsection
  51. - 1. or 1): generates an @enumerate @item
  52. - */-: generates an @itemize list
  53. """
  54. lines = []
  55. doc = subst_braces(doc)
  56. doc = subst_vars(doc)
  57. doc = subst_emph(doc)
  58. doc = subst_strong(doc)
  59. inlist = ""
  60. lastempty = False
  61. for line in doc.split('\n'):
  62. empty = line == ""
  63. # FIXME: Doing this in a single if / elif chain is
  64. # problematic. For instance, a line without markup terminates
  65. # a list if it follows a blank line (reaches the final elif),
  66. # but a line with some *other* markup, such as a = title
  67. # doesn't.
  68. #
  69. # Make sure to update section "Documentation markup" in
  70. # docs/qapi-code-gen.txt when fixing this.
  71. if line.startswith("| "):
  72. line = EXAMPLE_FMT(code=line[2:])
  73. elif line.startswith("= "):
  74. line = "@section " + line[2:]
  75. elif line.startswith("== "):
  76. line = "@subsection " + line[3:]
  77. elif re.match(r'^([0-9]*\.) ', line):
  78. if not inlist:
  79. lines.append("@enumerate")
  80. inlist = "enumerate"
  81. line = line[line.find(" ")+1:]
  82. lines.append("@item")
  83. elif re.match(r'^[*-] ', line):
  84. if not inlist:
  85. lines.append("@itemize %s" % {'*': "@bullet",
  86. '-': "@minus"}[line[0]])
  87. inlist = "itemize"
  88. lines.append("@item")
  89. line = line[2:]
  90. elif lastempty and inlist:
  91. lines.append("@end %s\n" % inlist)
  92. inlist = ""
  93. lastempty = empty
  94. lines.append(line)
  95. if inlist:
  96. lines.append("@end %s\n" % inlist)
  97. return "\n".join(lines)
  98. def texi_body(doc):
  99. """
  100. Format the body of a symbol documentation:
  101. - main body
  102. - table of arguments
  103. - followed by "Returns/Notes/Since/Example" sections
  104. """
  105. body = texi_format(str(doc.body)) + "\n"
  106. if doc.args:
  107. body += "@table @asis\n"
  108. for arg, section in doc.args.iteritems():
  109. desc = str(section)
  110. opt = ''
  111. if section.optional:
  112. desc = re.sub(r'^ *#optional *\n?|\n? *#optional *$|#optional',
  113. '', desc)
  114. opt = ' (optional)'
  115. body += "@item @code{'%s'}%s\n%s\n" % (arg, opt,
  116. texi_format(desc))
  117. body += "@end table\n"
  118. for section in doc.sections:
  119. name, doc = (section.name, str(section))
  120. func = texi_format
  121. if name.startswith("Example"):
  122. func = texi_example
  123. if name:
  124. # prefer @b over @strong, so txt doesn't translate it to *Foo:*
  125. body += "\n\n@b{%s:}\n" % name
  126. body += func(doc)
  127. return body
  128. def texi_alternate(expr, doc):
  129. """Format an alternate to texi"""
  130. body = texi_body(doc)
  131. return TYPE_FMT(type="Alternate",
  132. name=doc.symbol,
  133. body=body)
  134. def texi_union(expr, doc):
  135. """Format a union to texi"""
  136. discriminator = expr.get("discriminator")
  137. if discriminator:
  138. union = "Flat Union"
  139. else:
  140. union = "Simple Union"
  141. body = texi_body(doc)
  142. return TYPE_FMT(type=union,
  143. name=doc.symbol,
  144. body=body)
  145. def texi_enum(expr, doc):
  146. """Format an enum to texi"""
  147. for i in expr['data']:
  148. if i not in doc.args:
  149. doc.args[i] = qapi.QAPIDoc.ArgSection(i)
  150. body = texi_body(doc)
  151. return TYPE_FMT(type="Enum",
  152. name=doc.symbol,
  153. body=body)
  154. def texi_struct(expr, doc):
  155. """Format a struct to texi"""
  156. body = texi_body(doc)
  157. return TYPE_FMT(type="Struct",
  158. name=doc.symbol,
  159. body=body)
  160. def texi_command(expr, doc):
  161. """Format a command to texi"""
  162. body = texi_body(doc)
  163. return MSG_FMT(type="Command",
  164. name=doc.symbol,
  165. body=body)
  166. def texi_event(expr, doc):
  167. """Format an event to texi"""
  168. body = texi_body(doc)
  169. return MSG_FMT(type="Event",
  170. name=doc.symbol,
  171. body=body)
  172. def texi_expr(expr, doc):
  173. """Format an expr to texi"""
  174. (kind, _) = expr.items()[0]
  175. fmt = {"command": texi_command,
  176. "struct": texi_struct,
  177. "enum": texi_enum,
  178. "union": texi_union,
  179. "alternate": texi_alternate,
  180. "event": texi_event}[kind]
  181. return fmt(expr, doc)
  182. def texi(docs):
  183. """Convert QAPI schema expressions to texi documentation"""
  184. res = []
  185. for doc in docs:
  186. expr = doc.expr
  187. if not expr:
  188. res.append(texi_body(doc))
  189. continue
  190. try:
  191. doc = texi_expr(expr, doc)
  192. res.append(doc)
  193. except:
  194. print >>sys.stderr, "error at @%s" % doc.info
  195. raise
  196. return '\n'.join(res)
  197. def main(argv):
  198. """Takes schema argument, prints result to stdout"""
  199. if len(argv) != 2:
  200. print >>sys.stderr, "%s: need exactly 1 argument: SCHEMA" % argv[0]
  201. sys.exit(1)
  202. schema = qapi.QAPISchema(argv[1])
  203. if not qapi.doc_required:
  204. print >>sys.stderr, ("%s: need pragma 'doc-required' "
  205. "to generate documentation" % argv[0])
  206. print texi(schema.docs)
  207. if __name__ == "__main__":
  208. main(sys.argv)