doxygen2pydoc.py.in 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. #!@PYTHON@
  2. # Copyright (C) 2011 Internet Systems Consortium.
  3. #
  4. # Permission to use, copy, modify, and distribute this software for any
  5. # purpose with or without fee is hereby granted, provided that the above
  6. # copyright notice and this permission notice appear in all copies.
  7. #
  8. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  9. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  10. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  11. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  12. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  13. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  14. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  15. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. r"""
  17. A helper to semi-auto generate Python docstring text from C++ Doxygen
  18. documentation.
  19. This script converts an XML-format doxygen documentation for C++ library
  20. into a template Python docstring for the corresponding Python version
  21. of the library. While it's not perfect and you'll still need to edit the
  22. output by hand, but past experiments showed the script produces a pretty
  23. good template. It will help provide more compatible documentation for
  24. both C++ and Python versions of library from a unified source (C++ Doxygen
  25. documentation) with minimizing error-prone and boring manual conversion.
  26. HOW TO USE IT
  27. 1. Generate XML output by doxygen. Use bind10/doc/Doxyfile-xml:
  28. % cd bind10/doc
  29. % doxygen Doxyfile-xml
  30. (XML files will be generated under bind10/doc/html/xml)
  31. 2. Identify the xml file of the conversion target (C++ class, function, etc)
  32. This is a bit tricky. You'll probably need to do manual search.
  33. For example, to identify the xml file for a C++ class
  34. isc::datasrc::memory::ZoneWriter, you might do:
  35. % cd bind10/doc/html/xml
  36. % grep ZoneWriter *.xml | grep 'kind="class"'
  37. index.xml: <compound refid="d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter" kind="class"><name>isc::datasrc::memory::ZoneWriter</name>
  38. In this case the file under the d4/d3c directory (with .xml suffix) would
  39. be the file you're looking for.
  40. 3. Run this script for the xml file:
  41. % python3 doxygen2pydoc.py <top_srcdir>/doc/html/xml/d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter.xml > output.cc
  42. The template content is dumped to standard out (redirected to file
  43. "output.cc" in this example).
  44. Sometimes the script produces additional output to standard error,
  45. like this:
  46. Replaced camelCased terms:
  47. resetMemorySegment => reset_memory_segment
  48. getConfiguration => get_configuration
  49. In BIND 10 naming convention for methods is different for C++ and
  50. Python. This script uses some heuristic guess to convert the
  51. C++-style method names to likely Python-style ones, and the converted
  52. method names are used in the dumped template. In many cases the guessed
  53. names are correct, but you should check this list and make adjustments
  54. by hand if necessary.
  55. If there's no standard error output, this type of conversion didn't
  56. happen.
  57. 4. Edit and copy the template
  58. The dumped template has the following organization:
  59. namespace {
  60. #ifdef COPY_THIS_TO_MAIN_CC
  61. { "cleanup", ZoneWriter_cleanup, METH_NOARGS,
  62. ZoneWriter_cleanup_doc },
  63. { "install", ZoneWriter_install, METH_NOARGS,
  64. ZoneWriter_install_doc },
  65. { "load", ZoneWriter_load, METH_VARARGS,
  66. ZoneWriter_load_doc },
  67. #endif // COPY_THIS_TO_MAIN_CC
  68. const char* const ZoneWriter_doc = "\
  69. ...
  70. ";
  71. const char* const ZoneWriter_install_doc = "\
  72. ...
  73. ";
  74. ...
  75. }
  76. The ifdef-ed block is a template for class methods information
  77. to be added to the corresponding PyMethodDef structure array
  78. (your wrapper C++ source would have something like ZoneWriter_methods
  79. of this type). These lines should be copied there. As long as
  80. the method names and corresponding wrapper function (such as
  81. ZoneWriter_cleanup) are correct you shouldn't have to edit this part
  82. (and they would be normally correct, unless the guessed method name
  83. conversion was needed).
  84. The rest of the content is a sequence of constant C-string variables.
  85. Usually the first variable corresponds to the class description, and
  86. the rest are method descriptions (note that ZoneWriter_install_doc
  87. is referenced from the ifdef-ed block). The content of this part
  88. would generally make sense, but you'll often need to make some
  89. adjsutments by hand. A common examples of such adjustment is to
  90. replace "NULL" with "None". Also, it's not uncommon that some part
  91. of the description simply doesn't apply to the Python version or
  92. that Python specific notes are needed. So go through the description
  93. carefully and make necessary changes. A common practice is to add
  94. comments for a summary of adjustments like this:
  95. // Modifications:
  96. // NULL->None
  97. // - removed notes about derived classes (which doesn't apply for python)
  98. const char* const ZoneWriter_doc = "\
  99. ...
  100. ";
  101. This note will help next time you need to auto-generate and edit the
  102. template (probably because the original C++ document is updated).
  103. You can simply copy this part to the main C++ wrapper file, but since
  104. it's relatively large a common practice is to maintain it in a separate
  105. file that is exclusively included from the main file: if the name of
  106. the main file is zonewriter_python.cc, the pydoc strings would be copied
  107. in zonewriter_python_inc.cc, and the main file would have this line:
  108. #include "zonewriter_inc.cc"
  109. (In case you are C++ language police: it's okay to use the unnamed
  110. name space for a file to be included because it's essentially a part
  111. of the single .cc file, not expected to be included by others).
  112. In either case, the ifdef-ed part should be removed.
  113. ADVANCED FEATURES
  114. You can use a special "xmlonly" doxygen command in C++ doxygent document
  115. in order to include Python code excerpt (while hiding it from the doxygen
  116. output for the C++ version). This command will be converted to
  117. a special XML tag in the XML output.
  118. The block enclosed by \xmlonly and \endxmlonly should contain
  119. a verbatim XML tag named "pythonlisting", in which the python code should
  120. be placed.
  121. /// \code
  122. /// Name name("example.com");
  123. /// std::cout << name.toText() << std::endl;
  124. /// \endcode
  125. ///
  126. /// \xmlonly <pythonlisting>
  127. /// name = Name("example.com")
  128. /// print(name.to_text())
  129. /// </pythonlisting> \endxmlonly
  130. Note that there must be a blank line between \endcode and \xmlonly.
  131. doxygen2pydoc assume the pythonlisting tag is in a separate <para> node.
  132. This blank ensures doxygen will produce the XML file that meets the
  133. assumption.
  134. INTERNAL MEMO (incomplete, and not very unredable yet)
  135. This simplified utility assumes the following structure:
  136. ...
  137. <compounddef ...>
  138. <compoundname>isc::dns::TSIGError</compoundname>
  139. <sectiondef kind="user-defined">
  140. constructor, destructor
  141. </sectiondef>
  142. <sectiondef kind="public-type">
  143. ..
  144. </sectiondef>
  145. <sectiondef kind="public-func">
  146. <memberdef kind="function"...>
  147. <type>return type (if any)</type>
  148. <argsstring>(...) [const]</argsstring>
  149. <name>method name</name>
  150. <briefdescription>method's brief description</briefdescription>
  151. <detaileddescription>
  152. <para>...</para>...
  153. <para>
  154. <parameterlist kind="exception">
  155. <parameteritem>
  156. <parameternamelist>
  157. <parametername>Exception name</parametername>
  158. </parameternamelist>
  159. <parameterdescription>
  160. <para>exception desc</para>
  161. </parameterdescription>
  162. </parameteritem>
  163. </parameterlist>
  164. <parameterlist kind="param">
  165. <parameteritem>
  166. <parameternamelist>
  167. <parametername>param name</parametername>
  168. </parameternamelist>
  169. <parameterdescription>
  170. <para>param desc</para>
  171. </parameterdescription>
  172. </parameteritem>
  173. ...
  174. </parameterlist>
  175. <simplesect kind="return">Return value</simplesect>
  176. </para>
  177. </detaileddescription>
  178. </memberdef>
  179. </sectiondef>
  180. <sectiondef kind="public-static-attrib|user-defined">
  181. <memberdef kind="variable"...>
  182. <name>class-specific-constant</name>
  183. <initializer>value</initializer>
  184. <brief|detaileddescription>paragraph(s)</brief|detaileddescription>
  185. </sectiondef>
  186. <briefdescription>
  187. class's brief description
  188. </briefdescription>
  189. <detaileddescription>
  190. class's detailed description
  191. </detaileddescription>
  192. </compounddef>
  193. """
  194. import re, string, sys, textwrap
  195. from xml.dom.minidom import parse
  196. from textwrap import fill, dedent, TextWrapper
  197. camel_replacements = {}
  198. member_functions = []
  199. constructors = []
  200. class_variables = []
  201. RE_CAMELTERM = re.compile('([\s\.]|^)[a-z]+[A-Z]\S*')
  202. RE_SIMPLECAMEL = re.compile("([a-z])([A-Z])")
  203. RE_CAMELAFTERUPPER = re.compile("([A-Z])([A-Z])([a-z])")
  204. class Paragraph:
  205. TEXT = 0
  206. ITEMIZEDLIST = 1
  207. CPPLISTING = 2
  208. PYLISTING = 3
  209. VERBATIM = 4
  210. def __init__(self, xml_node):
  211. if len(xml_node.getElementsByTagName("pythonlisting")) > 0:
  212. self.type = self.PYLISTING
  213. self.text = re.sub("///", "", get_text(xml_node))
  214. elif len(xml_node.getElementsByTagName("verbatim")) > 0:
  215. self.type = self.VERBATIM
  216. self.text = get_text(xml_node)
  217. elif len(xml_node.getElementsByTagName("programlisting")) > 0:
  218. # We ignore node containing a "programlisting" tag.
  219. # They are C++ example code, and we are not interested in them
  220. # in pydoc.
  221. self.type = self.CPPLISTING
  222. elif len(xml_node.getElementsByTagName("itemizedlist")) > 0:
  223. self.type = self.ITEMIZEDLIST
  224. self.items = []
  225. for item in xml_node.getElementsByTagName("listitem"):
  226. self.items.append(get_text(item))
  227. else:
  228. self.type = self.TEXT
  229. # A single textual paragraph could have multiple simple sections
  230. # if it contains notes.
  231. self.texts = []
  232. subnodes = []
  233. for child in xml_node.childNodes:
  234. if child.nodeType == child.ELEMENT_NODE and \
  235. child.nodeName == 'simplesect' and \
  236. child.getAttribute('kind') == 'note':
  237. if len(subnodes) > 0:
  238. self.texts.append(get_text_fromnodelist(subnodes))
  239. subnodes = []
  240. subtext = 'Note: '
  241. for t in child.childNodes:
  242. subtext += get_text(t)
  243. self.texts.append(subtext)
  244. else:
  245. subnodes.append(child)
  246. if len(subnodes) > 0:
  247. self.texts.append(get_text_fromnodelist(subnodes))
  248. def dump(self, f, wrapper):
  249. if self.type == self.CPPLISTING:
  250. return
  251. elif self.type == self.ITEMIZEDLIST:
  252. for item in self.items:
  253. item_wrapper = TextWrapper(\
  254. initial_indent=wrapper.initial_indent + "- ",
  255. subsequent_indent=wrapper.subsequent_indent + " ")
  256. dump_filled_text(f, item_wrapper, item)
  257. f.write("\\n\\\n")
  258. elif self.type == self.TEXT:
  259. for text in self.texts:
  260. if text != self.texts[0]:
  261. f.write("\\n\\\n")
  262. dump_filled_text(f, wrapper, text)
  263. f.write("\\n\\\n")
  264. else:
  265. dump_filled_text(f, None, self.text)
  266. f.write("\\n\\\n")
  267. f.write("\\n\\\n")
  268. class NamedItem:
  269. def __init__(self, name, desc):
  270. self.name = name
  271. self.desc = desc
  272. def dump(self, f, wrapper):
  273. # we use deeper indent inside the item list.
  274. new_initial_indent = wrapper.initial_indent + " " * 2
  275. new_subsequent_indent = wrapper.subsequent_indent + " " * (2 + 11)
  276. local_wrapper = TextWrapper(initial_indent=new_initial_indent,
  277. subsequent_indent=new_subsequent_indent)
  278. # concatenate name and description with a fixed width (up to 10 chars)
  279. # for the name, and wrap the entire text, then dump it to file.
  280. dump_filled_text(f, local_wrapper, "%-10s %s" % (self.name, self.desc))
  281. f.write("\\n\\\n")
  282. class FunctionDefinition:
  283. # function types
  284. CONSTRUCTOR = 0
  285. COPY_CONSTRUCTOR = 1
  286. DESTRUCTOR = 2
  287. ASSIGNMENT_OP = 3
  288. OTHER = 4
  289. def __init__(self):
  290. self.type = self.OTHER
  291. self.name = None
  292. self.pyname = None
  293. self.args = ""
  294. self.ret_type = None
  295. self.brief_desc = None
  296. self.detailed_desc = []
  297. self.exceptions = []
  298. self.parameters = []
  299. self.returns = None
  300. self.have_param = False
  301. def dump_doc(self, f, wrapper=TextWrapper()):
  302. f.write(self.pyname + "(" + self.args + ")")
  303. if self.ret_type is not None:
  304. f.write(" -> " + self.ret_type)
  305. f.write("\\n\\\n\\n\\\n")
  306. if self.brief_desc is not None:
  307. dump_filled_text(f, wrapper, self.brief_desc)
  308. f.write("\\n\\\n\\n\\\n")
  309. for para in self.detailed_desc:
  310. para.dump(f, wrapper)
  311. if len(self.exceptions) > 0:
  312. f.write(wrapper.fill("Exceptions:") + "\\n\\\n")
  313. for ex_desc in self.exceptions:
  314. ex_desc.dump(f, wrapper)
  315. f.write("\\n\\\n")
  316. if len(self.parameters) > 0:
  317. f.write(wrapper.fill("Parameters:") + "\\n\\\n")
  318. for param_desc in self.parameters:
  319. param_desc.dump(f, wrapper)
  320. f.write("\\n\\\n")
  321. if self.returns is not None:
  322. dump_filled_text(f, wrapper, "Return Value(s): " + self.returns)
  323. f.write("\\n\\\n")
  324. def dump_pymethod_def(self, f, class_name):
  325. f.write(' { "' + self.pyname + '", ')
  326. f.write(class_name + '_' + self.name + ', ')
  327. if len(self.parameters) == 0:
  328. f.write('METH_NOARGS,\n')
  329. else:
  330. f.write('METH_VARARGS,\n')
  331. f.write(' ' + class_name + '_' + self.name + '_doc },\n')
  332. class VariableDefinition:
  333. def __init__(self, nodelist):
  334. self.value = None
  335. self.brief_desc = None
  336. self.detailed_desc = []
  337. for node in nodelist:
  338. if node.nodeName == "name":
  339. self.name = get_text(node)
  340. elif node.nodeName == "initializer":
  341. self.value = get_text(node)
  342. elif node.nodeName == "briefdescription":
  343. self.brief_desc = get_text(node)
  344. elif node.nodeName == "detaileddescription":
  345. for para in node.childNodes:
  346. if para.nodeName != "para":
  347. # ignore surrounding empty nodes
  348. continue
  349. self.detailed_desc.append(Paragraph(para))
  350. def dump_doc(self, f, wrapper=TextWrapper()):
  351. name_value = self.name
  352. if self.value is not None:
  353. name_value += ' = ' + self.value
  354. dump_filled_text(f, wrapper, name_value)
  355. f.write('\\n\\\n')
  356. desc_initial_indent = wrapper.initial_indent + " "
  357. desc_subsequent_indent = wrapper.subsequent_indent + " "
  358. desc_wrapper = TextWrapper(initial_indent=desc_initial_indent,
  359. subsequent_indent=desc_subsequent_indent)
  360. if self.brief_desc is not None:
  361. dump_filled_text(f, desc_wrapper, self.brief_desc)
  362. f.write("\\n\\\n\\n\\\n")
  363. for para in self.detailed_desc:
  364. para.dump(f, desc_wrapper)
  365. def dump_filled_text(f, wrapper, text):
  366. """Fill given text using wrapper, and dump it to the given file
  367. appending an escaped CR at each end of line.
  368. """
  369. filled_text = wrapper.fill(text) if wrapper is not None else text
  370. f.write("".join(re.sub("\n", r"\\n\\\n", filled_text)))
  371. def camel_to_lowerscores(matchobj):
  372. oldtext = matchobj.group(0)
  373. newtext = re.sub(RE_SIMPLECAMEL, r"\1_\2", oldtext)
  374. newtext = re.sub(RE_CAMELAFTERUPPER, r"\1_\2\3", newtext)
  375. newtext = newtext.lower()
  376. camel_replacements[oldtext] = newtext
  377. return newtext.lower()
  378. def cpp_to_python(text):
  379. text = text.replace("::", ".")
  380. text = text.replace('"', '\\"')
  381. # convert camelCase to "_"-concatenated format
  382. # (e.g. getLength -> get_length)
  383. return re.sub(RE_CAMELTERM, camel_to_lowerscores, text)
  384. def convert_type_name(type_name):
  385. """Convert C++ type name to python type name using common conventions"""
  386. # strip off leading 'const' and trailing '&/*'
  387. type_name = re.sub("^const\S*", "", type_name)
  388. type_name = re.sub("\S*[&\*]$", "", type_name)
  389. # We often typedef smart pointers as [Const]TypePtr. Convert them to
  390. # just "Type"
  391. type_name = re.sub("^Const", "", type_name)
  392. type_name = re.sub("Ptr$", "", type_name)
  393. if type_name == "std::string":
  394. return "string"
  395. if re.search(r"(int\d+_t|size_t)", type_name):
  396. return "integer"
  397. return type_name
  398. def get_text(root, do_convert=True):
  399. """Recursively extract bare text inside the specified node (root),
  400. concatenate all extracted text and return the result.
  401. """
  402. nodelist = root.childNodes
  403. rc = []
  404. for node in nodelist:
  405. if node.nodeType == node.TEXT_NODE:
  406. if do_convert:
  407. rc.append(cpp_to_python(node.data))
  408. else:
  409. rc.append(node.data)
  410. elif node.nodeType == node.ELEMENT_NODE:
  411. rc.append(get_text(node))
  412. # return the result, removing any leading newlines (that often happens for
  413. # brief descriptions, which will cause lines not well aligned)
  414. return re.sub("^(\n*)", "", ''.join(rc))
  415. def get_text_fromnodelist(nodelist, do_convert=True):
  416. """Recursively extract bare text inside the specified node (root),
  417. concatenate all extracted text and return the result.
  418. """
  419. rc = []
  420. for node in nodelist:
  421. if node.nodeType == node.TEXT_NODE:
  422. if do_convert:
  423. rc.append(cpp_to_python(node.data))
  424. else:
  425. rc.append(node.data)
  426. elif node.nodeType == node.ELEMENT_NODE:
  427. rc.append(get_text(node))
  428. # return the result, removing any leading newlines (that often happens for
  429. # brief descriptions, which will cause lines not well aligned)
  430. return re.sub("^(\n*)", "", ''.join(rc))
  431. def parse_parameters(nodelist):
  432. rc = []
  433. for node in nodelist:
  434. if node.nodeName != "parameteritem":
  435. continue
  436. # for simplicity, we assume one parametername and one
  437. # parameterdescription for each parameter.
  438. name = get_text(node.getElementsByTagName("parametername")[0])
  439. desc = get_text(node.getElementsByTagName("parameterdescription")[0])
  440. rc.append(NamedItem(name, desc))
  441. return rc
  442. def parse_function_description(func_def, nodelist):
  443. for node in nodelist:
  444. # nodelist contains beginning and ending empty text nodes.
  445. # ignore them (otherwise they cause disruption below).
  446. if node.nodeName != "para":
  447. continue
  448. if node.getElementsByTagName("parameterlist"):
  449. # within this node there may be exception list, parameter list,
  450. # and description for return value. parse and store them
  451. # seprately.
  452. for paramlist in node.getElementsByTagName("parameterlist"):
  453. if paramlist.getAttribute("kind") == "exception":
  454. func_def.exceptions = \
  455. parse_parameters(paramlist.childNodes)
  456. elif paramlist.getAttribute("kind") == "param":
  457. func_def.parameters = \
  458. parse_parameters(paramlist.childNodes)
  459. if node.getElementsByTagName("simplesect"):
  460. simplesect = node.getElementsByTagName("simplesect")[0]
  461. if simplesect.getAttribute("kind") == "return":
  462. func_def.returns = get_text(simplesect)
  463. else:
  464. # for normal text, python listing and itemized list, append them
  465. # to the list of paragraphs
  466. func_def.detailed_desc.append(Paragraph(node))
  467. def parse_function(func_def, class_name, nodelist):
  468. for node in nodelist:
  469. if node.nodeName == "name":
  470. func_def.name = get_text(node, False)
  471. func_def.pyname = cpp_to_python(func_def.name)
  472. elif node.nodeName == "argsstring":
  473. # extract parameter names only, assuming they immediately follow
  474. # their type name + space, and are immeidatelly followed by
  475. # either "," or ")". If it's a pointer or reference, */& is
  476. # prepended to the parameter name without a space:
  477. # e.g. (int var1, char *var2, Foo &var3)
  478. args = get_text(node, False)
  479. # extract parameter names, possibly with */&
  480. func_def.args = ', '.join(re.findall(r"\s(\S+)[,)]", args))
  481. # then remove any */& symbols
  482. func_def.args = re.sub("[\*&]", "", func_def.args)
  483. elif node.nodeName == "type" and node.hasChildNodes():
  484. func_def.ret_type = convert_type_name(get_text(node, False))
  485. elif node.nodeName == "param":
  486. func_def.have_param = True
  487. elif node.nodeName == "briefdescription":
  488. func_def.brief_desc = get_text(node)
  489. elif node.nodeName == "detaileddescription":
  490. parse_function_description(func_def, node.childNodes)
  491. # identify the type of function using the name and arg
  492. if func_def.name == class_name and \
  493. re.search("^\(const " + class_name + " &[^,]*$", args):
  494. # This function is ClassName(const ClassName& param), which is
  495. # the copy constructor.
  496. func_def.type = func_def.COPY_CONSTRUCTOR
  497. elif func_def.name == class_name:
  498. # if it's not the copy ctor but the function name == class name,
  499. # it's a constructor.
  500. func_def.type = func_def.CONSTRUCTOR
  501. elif func_def.name == "~" + class_name:
  502. func_def.type = func_def.DESTRUCTOR
  503. elif func_def.name == "operator=":
  504. func_def.type = func_def.ASSIGNMENT_OP
  505. # register the definition to the approriate list
  506. if func_def.type == func_def.CONSTRUCTOR:
  507. constructors.append(func_def)
  508. elif func_def.type == func_def.OTHER:
  509. member_functions.append(func_def)
  510. def parse_functions(class_name, nodelist):
  511. for node in nodelist:
  512. if node.nodeName == "memberdef" and \
  513. node.getAttribute("kind") == "function":
  514. func_def = FunctionDefinition()
  515. parse_function(func_def, class_name, node.childNodes)
  516. def parse_class_variables(class_name, nodelist):
  517. for node in nodelist:
  518. if node.nodeName == "memberdef" and \
  519. node.getAttribute("kind") == "variable":
  520. class_variables.append(VariableDefinition(node.childNodes))
  521. def dump(f, class_name, class_brief_doc, class_detailed_doc):
  522. f.write("namespace {\n")
  523. f.write('#ifdef COPY_THIS_TO_MAIN_CC\n')
  524. for func in member_functions:
  525. func.dump_pymethod_def(f, class_name)
  526. f.write('#endif // COPY_THIS_TO_MAIN_CC\n\n')
  527. f.write("const char* const " + class_name + '_doc = "\\\n')
  528. if class_brief_doc is not None:
  529. f.write("".join(re.sub("\n", r"\\n\\\n", fill(class_brief_doc))))
  530. f.write("\\n\\\n")
  531. f.write("\\n\\\n")
  532. if len(class_detailed_doc) > 0:
  533. for para in class_detailed_doc:
  534. para.dump(f, wrapper=TextWrapper())
  535. # dump constructors
  536. for func in constructors:
  537. indent = " " * 4
  538. func.dump_doc(f, wrapper=TextWrapper(initial_indent=indent,
  539. subsequent_indent=indent))
  540. # dump class variables
  541. if len(class_variables) > 0:
  542. f.write("Class constant data:\\n\\\n")
  543. for var in class_variables:
  544. var.dump_doc(f)
  545. f.write("\";\n")
  546. for func in member_functions:
  547. f.write("\n")
  548. f.write("const char* const " + class_name + "_" + func.name + \
  549. "_doc = \"\\\n");
  550. func.dump_doc(f)
  551. f.write("\";\n")
  552. f.write("} // unnamed namespace") # close namespace
  553. if __name__ == '__main__':
  554. dom = parse(sys.argv[1])
  555. class_elements = dom.getElementsByTagName("compounddef")[0].childNodes
  556. class_brief_doc = None
  557. class_detailed_doc = []
  558. for node in class_elements:
  559. if node.nodeName == "compoundname":
  560. # class name is the last portion of the period-separated fully
  561. # qualified class name. (this should exist)
  562. class_name = re.split("\.", get_text(node))[-1]
  563. if node.nodeName == "briefdescription":
  564. # we assume a brief description consists at most one para
  565. class_brief_doc = get_text(node)
  566. elif node.nodeName == "detaileddescription":
  567. # a detaild description consists of one or more paragraphs
  568. for para in node.childNodes:
  569. if para.nodeName != "para": # ignore surrounding empty nodes
  570. continue
  571. class_detailed_doc.append(Paragraph(para))
  572. elif node.nodeName == "sectiondef" and \
  573. node.getAttribute("kind") == "public-func":
  574. parse_functions(class_name, node.childNodes)
  575. elif node.nodeName == "sectiondef" and \
  576. node.getAttribute("kind") == "public-static-attrib":
  577. parse_class_variables(class_name, node.childNodes)
  578. elif node.nodeName == "sectiondef" and \
  579. node.getAttribute("kind") == "user-defined":
  580. # there are two possiblities: functions and variables
  581. for child in node.childNodes:
  582. if child.nodeName != "memberdef":
  583. continue
  584. if child.getAttribute("kind") == "function":
  585. parse_function(FunctionDefinition(), class_name,
  586. child.childNodes)
  587. elif child.getAttribute("kind") == "variable":
  588. class_variables.append(VariableDefinition(child.childNodes))
  589. dump(sys.stdout, class_name, class_brief_doc, class_detailed_doc)
  590. if len(camel_replacements) > 0:
  591. sys.stderr.write("Replaced camelCased terms:\n")
  592. for oldterm in camel_replacements.keys():
  593. sys.stderr.write("%s => %s\n" % (oldterm,
  594. camel_replacements[oldterm]))