qapi2texi.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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 "#optional" in desc:
  112. desc = desc.replace("#optional", "")
  113. opt = ' (optional)'
  114. body += "@item @code{'%s'}%s\n%s\n" % (arg, opt,
  115. texi_format(desc))
  116. body += "@end table\n"
  117. for section in doc.sections:
  118. name, doc = (section.name, str(section))
  119. func = texi_format
  120. if name.startswith("Example"):
  121. func = texi_example
  122. if name:
  123. # prefer @b over @strong, so txt doesn't translate it to *Foo:*
  124. body += "\n\n@b{%s:}\n" % name
  125. body += func(doc)
  126. return body
  127. def texi_alternate(expr, doc):
  128. """Format an alternate to texi"""
  129. body = texi_body(doc)
  130. return TYPE_FMT(type="Alternate",
  131. name=doc.symbol,
  132. body=body)
  133. def texi_union(expr, doc):
  134. """Format a union to texi"""
  135. discriminator = expr.get("discriminator")
  136. if discriminator:
  137. union = "Flat Union"
  138. else:
  139. union = "Simple Union"
  140. body = texi_body(doc)
  141. return TYPE_FMT(type=union,
  142. name=doc.symbol,
  143. body=body)
  144. def texi_enum(expr, doc):
  145. """Format an enum to texi"""
  146. for i in expr['data']:
  147. if i not in doc.args:
  148. doc.args[i] = ''
  149. body = texi_body(doc)
  150. return TYPE_FMT(type="Enum",
  151. name=doc.symbol,
  152. body=body)
  153. def texi_struct(expr, doc):
  154. """Format a struct to texi"""
  155. body = texi_body(doc)
  156. return TYPE_FMT(type="Struct",
  157. name=doc.symbol,
  158. body=body)
  159. def texi_command(expr, doc):
  160. """Format a command to texi"""
  161. body = texi_body(doc)
  162. return MSG_FMT(type="Command",
  163. name=doc.symbol,
  164. body=body)
  165. def texi_event(expr, doc):
  166. """Format an event to texi"""
  167. body = texi_body(doc)
  168. return MSG_FMT(type="Event",
  169. name=doc.symbol,
  170. body=body)
  171. def texi_expr(expr, doc):
  172. """Format an expr to texi"""
  173. (kind, _) = expr.items()[0]
  174. fmt = {"command": texi_command,
  175. "struct": texi_struct,
  176. "enum": texi_enum,
  177. "union": texi_union,
  178. "alternate": texi_alternate,
  179. "event": texi_event}[kind]
  180. return fmt(expr, doc)
  181. def texi(docs):
  182. """Convert QAPI schema expressions to texi documentation"""
  183. res = []
  184. for doc in docs:
  185. expr = doc.expr
  186. if not expr:
  187. res.append(texi_body(doc))
  188. continue
  189. try:
  190. doc = texi_expr(expr, doc)
  191. res.append(doc)
  192. except:
  193. print >>sys.stderr, "error at @%s" % doc.info
  194. raise
  195. return '\n'.join(res)
  196. def main(argv):
  197. """Takes schema argument, prints result to stdout"""
  198. if len(argv) != 2:
  199. print >>sys.stderr, "%s: need exactly 1 argument: SCHEMA" % argv[0]
  200. sys.exit(1)
  201. schema = qapi.QAPISchema(argv[1])
  202. if not qapi.doc_required:
  203. print >>sys.stderr, ("%s: need pragma 'doc-required' "
  204. "to generate documentation" % argv[0])
  205. print texi(schema.docs)
  206. if __name__ == "__main__":
  207. main(sys.argv)