qapi2texi.py 6.7 KB

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