redox.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (c) 2008-2019, The Tor Project, Inc.
  4. # See LICENSE for licensing information.
  5. #
  6. # Hi!
  7. # I'm redox.py, the Tor redocumentation tool!
  8. # I am a horrible hack!
  9. # I read the output of doxygen from stderr, and add missing DOCDOC comments
  10. # to tell you where documentation should go!
  11. # To use me, edit the stuff below...
  12. # ...and run 'make doxygen 2>doxygen.stderr' ...
  13. # ...and run ./scripts/maint/redox.py < doxygen.stderr !
  14. # I'll make a bunch of new files by adding missing DOCDOC comments to your
  15. # source. Those files will have names like ./src/common/util.c.newdoc.
  16. # You will want to look over the changes by hand before checking them in.
  17. #
  18. # So, here's your workflow:
  19. #
  20. # 0. Make sure you're running a bourne shell for the redirects below.
  21. # 1. make doxygen 1>doxygen.stdout 2>doxygen.stderr.
  22. # 2. grep Warning doxygen.stderr | grep -v 'is not documented' | less
  23. # [This will tell you about all the bogus doxygen output you have]
  24. # 3. python ./scripts/maint/redox.py <doxygen.stderr
  25. # [This will make lots of .newdoc files with DOCDOC comments for
  26. # whatever was missing documentation.]
  27. # 4. Look over those .newdoc files, and see which docdoc comments you
  28. # want to merge into the main file. If it's all good, just run
  29. # "mv fname.c.newdoc fname.c". Otherwise, you'll need to merge
  30. # the parts you like by hand.
  31. # Future imports for Python 2.7, mandatory in 3.0
  32. from __future__ import division
  33. from __future__ import print_function
  34. from __future__ import unicode_literals
  35. import re
  36. import sys
  37. try:
  38. xrange # Python 2
  39. except NameError:
  40. xrange = range # Python 3
  41. # Which files should we ignore warning from? Mostly, these are external
  42. # files that we've snarfed in from somebody else, whose C we do no intend
  43. # to document for them.
  44. SKIP_FILES = [ "OpenBSD_malloc_Linux.c",
  45. "strlcat.c",
  46. "strlcpy.c",
  47. "sha256.c",
  48. "sha256.h",
  49. "aes.c",
  50. "aes.h" ]
  51. # What names of things never need javadoc
  52. SKIP_NAME_PATTERNS = [ r'^.*_c_id$',
  53. r'^.*_H_ID$' ]
  54. # Which types of things should get DOCDOC comments added if they are
  55. # missing documentation? Recognized types are in KINDS below.
  56. ADD_DOCDOCS_TO_TYPES = [ 'function', 'type', 'typedef' ]
  57. ADD_DOCDOCS_TO_TYPES += [ 'variable', ]
  58. # ====================
  59. # The rest of this should not need hacking.
  60. KINDS = [ "type", "field", "typedef", "define", "function", "variable",
  61. "enumeration" ]
  62. NODOC_LINE_RE = re.compile(r'^([^:]+):(\d+): (\w+): (.*) is not documented\.$')
  63. THING_RE = re.compile(r'^Member ([a-zA-Z0-9_]+).*\((typedef|define|function|variable|enumeration|macro definition)\) of (file|class) ')
  64. SKIP_NAMES = [re.compile(s) for s in SKIP_NAME_PATTERNS]
  65. def parsething(thing):
  66. """I figure out what 'foobar baz in quux quum is not documented' means,
  67. and return: the name of the foobar, and the kind of the foobar.
  68. """
  69. if thing.startswith("Compound "):
  70. tp, name = "type", thing.split()[1]
  71. else:
  72. m = THING_RE.match(thing)
  73. if not m:
  74. print(thing, "???? Format didn't match.")
  75. return None, None
  76. else:
  77. name, tp, parent = m.groups()
  78. if parent == 'class':
  79. if tp == 'variable' or tp == 'function':
  80. tp = 'field'
  81. return name, tp
  82. def read():
  83. """I snarf doxygen stderr from stdin, and parse all the "foo has no
  84. documentation messages. I return a map from filename to lists
  85. of tuples of (alleged line number, name of thing, kind of thing)
  86. """
  87. errs = {}
  88. for line in sys.stdin:
  89. m = NODOC_LINE_RE.match(line)
  90. if m:
  91. file, line, tp, thing = m.groups()
  92. assert tp.lower() == 'warning'
  93. name, kind = parsething(thing)
  94. errs.setdefault(file, []).append((int(line), name, kind))
  95. return errs
  96. def findline(lines, lineno, ident):
  97. """Given a list of all the lines in the file (adjusted so 1-indexing works),
  98. a line number that ident is allegedly on, and ident, I figure out
  99. the line where ident was really declared."""
  100. lno = lineno
  101. for lineno in xrange(lineno, 0, -1):
  102. try:
  103. if ident in lines[lineno]:
  104. return lineno
  105. except IndexError:
  106. continue
  107. return None
  108. FUNC_PAT = re.compile(r"^[A-Za-z0-9_]+\(")
  109. def hascomment(lines, lineno, kind):
  110. """I return true if it looks like there's already a good comment about
  111. the thing on lineno of lines of type kind. """
  112. if "*/" in lines[lineno-1]:
  113. return True
  114. if kind == 'function' and FUNC_PAT.match(lines[lineno]):
  115. if "*/" in lines[lineno-2]:
  116. return True
  117. return False
  118. def hasdocdoc(lines, lineno, kind):
  119. """I return true if it looks like there's already a docdoc comment about
  120. the thing on lineno of lines of type kind."""
  121. try:
  122. if "DOCDOC" in lines[lineno]:
  123. return True
  124. except IndexError:
  125. pass
  126. try:
  127. if "DOCDOC" in lines[lineno-1]:
  128. return True
  129. except IndexError:
  130. pass
  131. if kind == 'function' and FUNC_PAT.match(lines[lineno]):
  132. if "DOCDOC" in lines[lineno-2]:
  133. return True
  134. return False
  135. def checkf(fn, errs):
  136. """I go through the output of read() for a single file, and build a list
  137. of tuples of things that want DOCDOC comments. Each tuple has:
  138. the line number where the comment goes; the kind of thing; its name.
  139. """
  140. for skip in SKIP_FILES:
  141. if fn.endswith(skip):
  142. print("Skipping",fn)
  143. return
  144. comments = []
  145. lines = [ None ]
  146. try:
  147. lines.extend( open(fn, 'r').readlines() )
  148. except IOError:
  149. return
  150. for line, name, kind in errs:
  151. if any(pat.match(name) for pat in SKIP_NAMES):
  152. continue
  153. if kind not in ADD_DOCDOCS_TO_TYPES:
  154. continue
  155. ln = findline(lines, line, name)
  156. if ln == None:
  157. print("Couldn't find the definition of %s allegedly on %s of %s"%(
  158. name, line, fn))
  159. else:
  160. if hasdocdoc(lines, line, kind):
  161. # print "Has a DOCDOC"
  162. # print fn, line, name, kind
  163. # print "\t",lines[line-2],
  164. # print "\t",lines[line-1],
  165. # print "\t",lines[line],
  166. # print "-------"
  167. pass
  168. else:
  169. if kind == 'function' and FUNC_PAT.match(lines[ln]):
  170. ln = ln - 1
  171. comments.append((ln, kind, name))
  172. return comments
  173. def applyComments(fn, entries):
  174. """I apply lots of comments to the file in fn, making a new .newdoc file.
  175. """
  176. N = 0
  177. lines = [ None ]
  178. try:
  179. lines.extend( open(fn, 'r').readlines() )
  180. except IOError:
  181. return
  182. # Process the comments in reverse order by line number, so that
  183. # the line numbers for the ones we haven't added yet remain valid
  184. # until we add them. Standard trick.
  185. entries.sort()
  186. entries.reverse()
  187. for ln, kind, name in entries:
  188. lines.insert(ln, "/* DOCDOC %s */\n"%name)
  189. N += 1
  190. outf = open(fn+".newdoc", 'w')
  191. for line in lines[1:]:
  192. outf.write(line)
  193. outf.close()
  194. print("Added %s DOCDOCs to %s" %(N, fn))
  195. e = read()
  196. for fn, errs in e.iteritems():
  197. print(repr((fn, errs)))
  198. comments = checkf(fn, errs)
  199. if comments:
  200. applyComments(fn, comments)