doc.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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. return '@item @code{%s%s%s}%s%s\n' % (
  108. member.name,
  109. ': ' if typ else '',
  110. typ if typ else '',
  111. ' (optional)' if member.optional else '',
  112. suffix)
  113. def texi_members(doc, what, base, variants, member_func):
  114. """Format the table of members"""
  115. items = ''
  116. for section in doc.args.values():
  117. # TODO Drop fallbacks when undocumented members are outlawed
  118. if section.text:
  119. desc = texi_format(section.text)
  120. elif (variants and variants.tag_member == section.member
  121. and not section.member.type.doc_type()):
  122. values = section.member.type.member_names()
  123. members_text = ', '.join(['@t{"%s"}' % v for v in values])
  124. desc = 'One of ' + members_text + '\n'
  125. else:
  126. desc = 'Not documented\n'
  127. items += member_func(section.member) + desc
  128. if base:
  129. items += '@item The members of @code{%s}\n' % base.doc_type()
  130. if variants:
  131. for v in variants.variants:
  132. when = ' when @code{%s} is @t{"%s"}' % (
  133. variants.tag_member.name, v.name)
  134. if v.type.is_implicit():
  135. assert not v.type.base and not v.type.variants
  136. for m in v.type.local_members:
  137. items += member_func(m, when)
  138. else:
  139. items += '@item The members of @code{%s}%s\n' % (
  140. v.type.doc_type(), when)
  141. if not items:
  142. return ''
  143. return '\n@b{%s:}\n@table @asis\n%s@end table\n' % (what, items)
  144. def texi_sections(doc):
  145. """Format additional sections following arguments"""
  146. body = ''
  147. for section in doc.sections:
  148. if section.name:
  149. # prefer @b over @strong, so txt doesn't translate it to *Foo:*
  150. body += '\n@b{%s:}\n' % section.name
  151. if section.name and section.name.startswith('Example'):
  152. body += texi_example(section.text)
  153. else:
  154. body += texi_format(section.text)
  155. return body
  156. def texi_entity(doc, what, base=None, variants=None,
  157. member_func=texi_member):
  158. return (texi_body(doc)
  159. + texi_members(doc, what, base, variants, member_func)
  160. + texi_sections(doc))
  161. class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
  162. def __init__(self):
  163. self.out = None
  164. self.cur_doc = None
  165. def visit_begin(self, schema):
  166. self.out = ''
  167. def visit_enum_type(self, name, info, values, prefix):
  168. doc = self.cur_doc
  169. self.out += 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.out += TYPE_FMT(type='Object',
  178. name=doc.symbol,
  179. body=texi_entity(doc, 'Members', base, variants))
  180. def visit_alternate_type(self, name, info, variants):
  181. doc = self.cur_doc
  182. self.out += TYPE_FMT(type='Alternate',
  183. name=doc.symbol,
  184. body=texi_entity(doc, 'Members'))
  185. def visit_command(self, name, info, arg_type, ret_type,
  186. gen, success_response, boxed):
  187. doc = self.cur_doc
  188. if boxed:
  189. body = texi_body(doc)
  190. body += ('\n@b{Arguments:} the members of @code{%s}\n'
  191. % arg_type.name)
  192. body += texi_sections(doc)
  193. else:
  194. body = texi_entity(doc, 'Arguments')
  195. self.out += MSG_FMT(type='Command',
  196. name=doc.symbol,
  197. body=body)
  198. def visit_event(self, name, info, arg_type, boxed):
  199. doc = self.cur_doc
  200. self.out += MSG_FMT(type='Event',
  201. name=doc.symbol,
  202. body=texi_entity(doc, 'Arguments'))
  203. def symbol(self, doc, entity):
  204. if self.out:
  205. self.out += '\n'
  206. self.cur_doc = doc
  207. entity.visit(self)
  208. self.cur_doc = None
  209. def freeform(self, doc):
  210. assert not doc.args
  211. if self.out:
  212. self.out += '\n'
  213. self.out += texi_body(doc) + texi_sections(doc)
  214. def texi_schema(schema):
  215. """Convert QAPI schema documentation to Texinfo"""
  216. gen = QAPISchemaGenDocVisitor()
  217. gen.visit_begin(schema)
  218. for doc in schema.docs:
  219. if doc.symbol:
  220. gen.symbol(doc, schema.lookup_entity(doc.symbol))
  221. else:
  222. gen.freeform(doc)
  223. return gen.out
  224. def gen_doc(schema, output_dir, prefix):
  225. if qapi.common.doc_required:
  226. gen = qapi.common.QAPIGenDoc()
  227. gen.add(texi_schema(schema))
  228. gen.write(output_dir, prefix + 'qapi-doc.texi')