/share/sphinx/ExtractRstFromSource.py

http://github.com/imageworks/OpenColorIO · Python · 324 lines · 252 code · 44 blank · 28 comment · 53 complexity · 4b13f4d0801a870f32d832622f99e2a3 MD5 · raw file

  1. #!/usr/bin/python
  2. """
  3. Small Script to extract reStructuredText from OCIO headers
  4. - http://sphinx.pocoo.org/rest.html
  5. - http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html
  6. """
  7. # TODO: extract void foo() { blah = 0 }; signatures correctly
  8. # TODO: handle typedef and enums better
  9. # TODO: handle OCIOEXPORT macro better
  10. # TODO: handle thow() funcs better
  11. RUNTEST = False
  12. import re, sys
  13. single_rst_comment = r"//(?P<single_comment>(!cpp:|!rst::).*\n)"
  14. block_rst_comment = r"/\*(?P<block_comment>(!cpp:|!rst::)([^*]*\*+)+?/)"
  15. rst_comment_regex = re.compile(r"(%s)|(%s)" % (single_rst_comment, block_rst_comment), re.MULTILINE)
  16. func_signature_regex = re.compile(r"(?P<sig_name>[^ ]*\(.*\))")
  17. rst_types = ["!rst::", "!cpp:class::", "!cpp:function::", "!cpp:member::",
  18. "!cpp:type::"]
  19. def getRstType(string):
  20. for rtype in rst_types:
  21. if string[0 : len(rtype)] == rtype:
  22. return rtype[1:]
  23. return None
  24. def getNextCodeLine(string, rst_type, from_pos):
  25. end = from_pos
  26. signature = ""
  27. if rst_type == "rst::":
  28. return signature, end
  29. if rst_type == "cpp:class::":
  30. class_open = False
  31. # first non-blank line that starts with 'class'
  32. found_signature = False
  33. # loop till the end of the class '};'
  34. ## skip other open/close '{' '}'
  35. skip_close = False
  36. x = end
  37. while x < len(string):
  38. if string[x] != '\n' and not found_signature:
  39. signature += string[x]
  40. if string[x] == '\n' and not found_signature:
  41. signature = signature.strip()
  42. if signature != '':
  43. found_signature = True
  44. signature = signature.replace("class", "")
  45. # TODO: this seem a bit dirty
  46. signature = signature.replace("OCIOEXPORT ", "")
  47. signature = signature.strip()
  48. signature = signature.split(' ', 1)[0]
  49. if string[x] == '{' and not class_open:
  50. class_open = True
  51. elif string[x] == '{' and class_open:
  52. skip_close = True
  53. elif string[x] == '}' and skip_close:
  54. skip_close = False
  55. elif string[x] == '}':
  56. end = x
  57. break
  58. x += 1
  59. return signature, end
  60. # else
  61. skip = False
  62. while string[end] != ";":
  63. if string[end] != ' ' and skip:
  64. skip = False
  65. signature += ' '
  66. if string[end] == '\n':
  67. skip = True
  68. if not skip:
  69. signature += string[end]
  70. end += 1
  71. signature += string[end]
  72. # TODO: this seem a bit dirty
  73. signature = signature.replace("OCIOEXPORT ", "")
  74. signature = signature.replace(" throw()", "")
  75. signature = signature.strip()
  76. if signature[len(signature)-1] == ';':
  77. signature = signature[:len(signature)-1]
  78. # hack hack hack
  79. if rst_type == "cpp:type::":
  80. if signature[:7] == "typedef":
  81. bits = signature.split()
  82. signature = bits[len(bits)-1]
  83. if signature[:4] == "enum":
  84. bits = signature.split()
  85. signature = bits[1]
  86. return signature, end
  87. def getNextCommentLine(string, from_pos, buffer = ""):
  88. end = from_pos
  89. tmp = ""
  90. while string[end] != "\n":
  91. tmp += string[end]
  92. end += 1
  93. tmp += string[end]
  94. if tmp.lstrip()[:2] == "//":
  95. if tmp.lstrip()[2:][0] == " ":
  96. buffer += tmp.lstrip()[3:]
  97. else:
  98. buffer += tmp.lstrip()[2:]
  99. buffer, end = getNextCommentLine(string, end+1, buffer)
  100. else:
  101. end = from_pos
  102. return buffer, end
  103. class Comment:
  104. def __init__(self, comment, start, end):
  105. self.comment = comment
  106. self.start = start
  107. self.end = end
  108. def getRstType(self):
  109. return getRstType(self.comment)
  110. def __str__(self):
  111. buffer = self.comment
  112. for rtype in rst_types:
  113. if buffer[0 : len(rtype)] == rtype:
  114. buffer = buffer[len(rtype):]
  115. buffer_lines = buffer.splitlines()
  116. buffer_lines[0] = buffer_lines[0].strip()
  117. if self.getRstType() == "rst::":
  118. buffer_lines.append('')
  119. buffer = '\n'.join(buffer_lines)
  120. return buffer
  121. if buffer_lines[0] != '':
  122. buffer_lines.insert(0, '')
  123. for x in xrange(0, len(buffer_lines)):
  124. buffer_lines[x] = " %s" % buffer_lines[x]
  125. buffer_lines.append('')
  126. buffer = '\n'.join(buffer_lines)
  127. return buffer
  128. def ExtractRst(string, fileh):
  129. items = []
  130. for item in rst_comment_regex.finditer(string):
  131. start, end = item.span()
  132. itemdict = item.groupdict()
  133. if itemdict["single_comment"] != None:
  134. ##
  135. buf = itemdict["single_comment"]
  136. comment, end = getNextCommentLine(string, end)
  137. buf += comment
  138. ##
  139. items.append(Comment(buf, start, end))
  140. elif itemdict["block_comment"] != None:
  141. ##
  142. itemdict["block_comment"] = \
  143. itemdict["block_comment"][:len(itemdict["block_comment"])-2]
  144. buf_lines = itemdict["block_comment"].splitlines()
  145. indent = 0
  146. if len(buf_lines) > 1:
  147. for char in buf_lines[1]:
  148. if char != ' ':
  149. break
  150. indent += 1
  151. # remove indent
  152. bufa = [buf_lines[0]]
  153. for x in xrange(1, len(buf_lines)):
  154. bufa.append(buf_lines[x][indent:])
  155. buf = '\n'.join(bufa) + '\n'
  156. ##
  157. items.append(Comment(buf, start, end))
  158. ##
  159. fileh.write('\n')
  160. namespaces = []
  161. for thing in items:
  162. rst_type = thing.getRstType()
  163. # .. cpp:function:: SomeClass::func2(const char * filename, std::istream& foo)
  164. # this is some of the documentation
  165. # for this function
  166. signature, end = getNextCodeLine(string, rst_type, thing.end)
  167. # if we are a class work out the begining and end so we can
  168. # give function signatures the correct namespace
  169. if rst_type == "cpp:class::":
  170. tmp = { 'name': signature, 'start': thing.end, 'end': end }
  171. namespaces.append(tmp)
  172. fileh.write(".. %s %s\n" % (rst_type, signature) )
  173. elif rst_type != "rst::":
  174. for namespace in namespaces:
  175. if end > namespace['start'] and end < namespace['end']:
  176. func = func_signature_regex.search(signature)
  177. funcpart = str(func.groupdict()["sig_name"])
  178. signature = signature.replace(funcpart, "%s::%s" % (namespace['name'], funcpart))
  179. break
  180. fileh.write(".. %s %s\n" % (rst_type, signature) )
  181. fileh.write(str(thing))
  182. fileh.write('\n')
  183. fileh.flush()
  184. if __name__ == "__main__":
  185. if not RUNTEST:
  186. if len(sys.argv) <= 2:
  187. sys.stderr.write("\nYou need to specify an input and output file\n\n")
  188. sys.exit(1)
  189. src = open(sys.argv[1]).read()
  190. output = file(sys.argv[2], 'w')
  191. ExtractRst(src, output)
  192. output.close()
  193. elif RUNTEST:
  194. testdata = """
  195. //!rst:: -------------
  196. // this comment should be ignored
  197. //!rst:: foobar
  198. // this is apart of the same
  199. // comment
  200. // this is also ignored
  201. /* this is
  202. a block comment which is
  203. ignored */
  204. //!cpp:class::
  205. // this is a comment about the class
  206. class FooBar : public std::exception
  207. {
  208. ...
  209. };
  210. /*!cpp:class::
  211. this is also a comment about this class
  212. */
  213. class FooBar2 : public std::exception
  214. {
  215. ...
  216. };
  217. /*!cpp:class::
  218. this is also a comment about this class with no new line */
  219. class FooBar3 : public std::exception
  220. {
  221. ...
  222. };
  223. //!cpp:class::
  224. class SomeClass
  225. {
  226. public:
  227. //!cpp:function::
  228. // this is some cool function for
  229. // some purpose
  230. // this line is indented
  231. static fooPtr func1();
  232. /*!cpp:function::
  233. this is a much better func for some other
  234. purpose
  235. this is also indented */
  236. static barPtr func2();
  237. /*!cpp:function:: this func wraps over two
  238. lines which needs
  239. to be caught
  240. */
  241. static weePtr func2(const char * filename,
  242. std::istream& foo);
  243. };
  244. //!cpp:function:: the class namespace should still get set correctly
  245. void foobar1();
  246. //!cpp:class:: this is some super informative
  247. // docs
  248. class SomeClass
  249. {
  250. public:
  251. //!cpp:function:: the class namespace should still get set correctly
  252. void foobar2();
  253. };
  254. //!cpp:function:: the class namespace should still get set correctly
  255. void foobar3();
  256. /*!rst:: this is a rst block
  257. **comment which needs**
  258. to be supported
  259. */
  260. """
  261. ExtractRst(testdata)