qapi2texi.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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. """Format the main documentation body"""
  100. return texi_format(str(doc.body)) + '\n'
  101. def texi_enum_value(value):
  102. """Format a table of members item for an enumeration value"""
  103. return "@item @code{'%s'}\n" % value.name
  104. def texi_member(member):
  105. """Format a table of members item for an object type member"""
  106. return "@item @code{'%s'}%s\n" % (
  107. member.name, ' (optional)' if member.optional else '')
  108. def texi_members(doc, member_func, show_undocumented):
  109. """Format the table of members"""
  110. items = ''
  111. for section in doc.args.itervalues():
  112. if not section.content and not show_undocumented:
  113. continue # Undocumented TODO require doc and drop
  114. desc = str(section)
  115. items += member_func(section.member) + texi_format(desc) + '\n'
  116. if not items:
  117. return ''
  118. return '@table @asis\n' + items + '@end table\n'
  119. def texi_sections(doc):
  120. """Format additional sections following arguments"""
  121. body = ''
  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. # prefer @b over @strong, so txt doesn't translate it to *Foo:*
  129. body += "\n\n@b{%s:}\n" % name
  130. body += func(doc)
  131. return body
  132. def texi_entity(doc, member_func=texi_member, show_undocumented=False):
  133. return (texi_body(doc)
  134. + texi_members(doc, member_func, show_undocumented)
  135. + texi_sections(doc))
  136. class QAPISchemaGenDocVisitor(qapi.QAPISchemaVisitor):
  137. def __init__(self):
  138. self.out = None
  139. self.cur_doc = None
  140. def visit_begin(self, schema):
  141. self.out = ''
  142. def visit_enum_type(self, name, info, values, prefix):
  143. doc = self.cur_doc
  144. if self.out:
  145. self.out += '\n'
  146. self.out += TYPE_FMT(type='Enum',
  147. name=doc.symbol,
  148. body=texi_entity(doc,
  149. member_func=texi_enum_value,
  150. show_undocumented=True))
  151. def visit_object_type(self, name, info, base, members, variants):
  152. doc = self.cur_doc
  153. if not variants:
  154. typ = 'Struct'
  155. elif variants._tag_name: # TODO unclean member access
  156. typ = 'Flat Union'
  157. else:
  158. typ = 'Simple Union'
  159. if self.out:
  160. self.out += '\n'
  161. self.out += TYPE_FMT(type=typ,
  162. name=doc.symbol,
  163. body=texi_entity(doc))
  164. def visit_alternate_type(self, name, info, variants):
  165. doc = self.cur_doc
  166. if self.out:
  167. self.out += '\n'
  168. self.out += TYPE_FMT(type='Alternate',
  169. name=doc.symbol,
  170. body=texi_entity(doc))
  171. def visit_command(self, name, info, arg_type, ret_type,
  172. gen, success_response, boxed):
  173. doc = self.cur_doc
  174. if self.out:
  175. self.out += '\n'
  176. self.out += MSG_FMT(type='Command',
  177. name=doc.symbol,
  178. body=texi_entity(doc))
  179. def visit_event(self, name, info, arg_type, boxed):
  180. doc = self.cur_doc
  181. if self.out:
  182. self.out += '\n'
  183. self.out += MSG_FMT(type='Event',
  184. name=doc.symbol,
  185. body=texi_entity(doc))
  186. def symbol(self, doc, entity):
  187. self.cur_doc = doc
  188. entity.visit(self)
  189. self.cur_doc = None
  190. def freeform(self, doc):
  191. assert not doc.args
  192. if self.out:
  193. self.out += '\n'
  194. self.out += texi_body(doc) + texi_sections(doc)
  195. def texi_schema(schema):
  196. """Convert QAPI schema documentation to Texinfo"""
  197. gen = QAPISchemaGenDocVisitor()
  198. gen.visit_begin(schema)
  199. for doc in schema.docs:
  200. if doc.symbol:
  201. gen.symbol(doc, schema.lookup_entity(doc.symbol))
  202. else:
  203. gen.freeform(doc)
  204. return gen.out
  205. def main(argv):
  206. """Takes schema argument, prints result to stdout"""
  207. if len(argv) != 2:
  208. print >>sys.stderr, "%s: need exactly 1 argument: SCHEMA" % argv[0]
  209. sys.exit(1)
  210. schema = qapi.QAPISchema(argv[1])
  211. if not qapi.doc_required:
  212. print >>sys.stderr, ("%s: need pragma 'doc-required' "
  213. "to generate documentation" % argv[0])
  214. print texi_schema(schema)
  215. if __name__ == "__main__":
  216. main(sys.argv)