doc.py 8.4 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. from __future__ import print_function
  8. import re
  9. import qapi.common
  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'@strong{\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. ret = ''
  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/devel/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. ret += '@enumerate\n'
  80. inlist = 'enumerate'
  81. ret += '@item\n'
  82. line = line[line.find(' ')+1:]
  83. elif re.match(r'^[*-] ', line):
  84. if not inlist:
  85. ret += '@itemize %s\n' % {'*': '@bullet',
  86. '-': '@minus'}[line[0]]
  87. inlist = 'itemize'
  88. ret += '@item\n'
  89. line = line[2:]
  90. elif lastempty and inlist:
  91. ret += '@end %s\n\n' % inlist
  92. inlist = ''
  93. lastempty = empty
  94. ret += line + '\n'
  95. if inlist:
  96. ret += '@end %s\n\n' % inlist
  97. return ret
  98. def texi_body(doc):
  99. """Format the main documentation body"""
  100. return texi_format(doc.body.text)
  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, suffix=''):
  105. """Format a table of members item for an object type member"""
  106. typ = member.type.doc_type()
  107. membertype = ': ' + typ if typ else ''
  108. return '@item @code{%s%s}%s%s\n' % (
  109. member.name, membertype,
  110. ' (optional)' if member.optional else '',
  111. suffix)
  112. def texi_members(doc, what, base, variants, member_func):
  113. """Format the table of members"""
  114. items = ''
  115. for section in doc.args.values():
  116. # TODO Drop fallbacks when undocumented members are outlawed
  117. if section.text:
  118. desc = texi_format(section.text)
  119. elif (variants and variants.tag_member == section.member
  120. and not section.member.type.doc_type()):
  121. values = section.member.type.member_names()
  122. members_text = ', '.join(['@t{"%s"}' % v for v in values])
  123. desc = 'One of ' + members_text + '\n'
  124. else:
  125. desc = 'Not documented\n'
  126. items += member_func(section.member) + desc
  127. if base:
  128. items += '@item The members of @code{%s}\n' % base.doc_type()
  129. if variants:
  130. for v in variants.variants:
  131. when = ' when @code{%s} is @t{"%s"}' % (
  132. variants.tag_member.name, v.name)
  133. if v.type.is_implicit():
  134. assert not v.type.base and not v.type.variants
  135. for m in v.type.local_members:
  136. items += member_func(m, when)
  137. else:
  138. items += '@item The members of @code{%s}%s\n' % (
  139. v.type.doc_type(), when)
  140. if not items:
  141. return ''
  142. return '\n@b{%s:}\n@table @asis\n%s@end table\n' % (what, items)
  143. def texi_sections(doc):
  144. """Format additional sections following arguments"""
  145. body = ''
  146. for section in doc.sections:
  147. if section.name:
  148. # prefer @b over @strong, so txt doesn't translate it to *Foo:*
  149. body += '\n@b{%s:}\n' % section.name
  150. if section.name and section.name.startswith('Example'):
  151. body += texi_example(section.text)
  152. else:
  153. body += texi_format(section.text)
  154. return body
  155. def texi_entity(doc, what, base=None, variants=None,
  156. member_func=texi_member):
  157. return (texi_body(doc)
  158. + texi_members(doc, what, base, variants, member_func)
  159. + texi_sections(doc))
  160. class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
  161. def __init__(self, prefix):
  162. self._prefix = prefix
  163. self._gen = qapi.common.QAPIGenDoc()
  164. self.cur_doc = None
  165. def write(self, output_dir):
  166. self._gen.write(output_dir, self._prefix + 'qapi-doc.texi')
  167. def visit_enum_type(self, name, info, values, prefix):
  168. doc = self.cur_doc
  169. self._gen.add(TYPE_FMT(type='Enum',
  170. name=doc.symbol,
  171. body=texi_entity(doc, 'Values',
  172. member_func=texi_enum_value)))
  173. def visit_object_type(self, name, info, base, members, variants):
  174. doc = self.cur_doc
  175. if base and base.is_implicit():
  176. base = None
  177. self._gen.add(TYPE_FMT(type='Object',
  178. name=doc.symbol,
  179. body=texi_entity(doc, 'Members',
  180. base, variants)))
  181. def visit_alternate_type(self, name, info, variants):
  182. doc = self.cur_doc
  183. self._gen.add(TYPE_FMT(type='Alternate',
  184. name=doc.symbol,
  185. body=texi_entity(doc, 'Members')))
  186. def visit_command(self, name, info, arg_type, ret_type,
  187. gen, success_response, boxed, allow_oob):
  188. doc = self.cur_doc
  189. if boxed:
  190. body = texi_body(doc)
  191. body += ('\n@b{Arguments:} the members of @code{%s}\n'
  192. % arg_type.name)
  193. body += texi_sections(doc)
  194. else:
  195. body = texi_entity(doc, 'Arguments')
  196. self._gen.add(MSG_FMT(type='Command',
  197. name=doc.symbol,
  198. body=body))
  199. def visit_event(self, name, info, arg_type, boxed):
  200. doc = self.cur_doc
  201. self._gen.add(MSG_FMT(type='Event',
  202. name=doc.symbol,
  203. body=texi_entity(doc, 'Arguments')))
  204. def symbol(self, doc, entity):
  205. if self._gen._body:
  206. self._gen.add('\n')
  207. self.cur_doc = doc
  208. entity.visit(self)
  209. self.cur_doc = None
  210. def freeform(self, doc):
  211. assert not doc.args
  212. if self._gen._body:
  213. self._gen.add('\n')
  214. self._gen.add(texi_body(doc) + texi_sections(doc))
  215. def gen_doc(schema, output_dir, prefix):
  216. if not qapi.common.doc_required:
  217. return
  218. vis = QAPISchemaGenDocVisitor(prefix)
  219. vis.visit_begin(schema)
  220. for doc in schema.docs:
  221. if doc.symbol:
  222. vis.symbol(doc, schema.lookup_entity(doc.symbol))
  223. else:
  224. vis.freeform(doc)
  225. vis.write(output_dir)