add proposal 159, 'exit scanning', from or-dev
[tor/rransom.git] / contrib / redox.py
blobe18a92f44d642a406dc93f719c39673ecde14b7b
1 #!/usr/bin/python
3 # Copyright (c) 2008 The Tor Project, Inc.
4 # See LICENSE for licensing information.
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 ./contrib/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.
18 # So, here's your workflow:
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 ./contrib/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.
32 # Which files should we ignore warning from? Mostly, these are external
33 # files that we've snarfed in from somebody else, whose C we do no intend
34 # to document for them.
35 SKIP_FILES = [ "OpenBSD_malloc_Linux.c",
36 "eventdns.c",
37 "eventdns.h",
38 "strlcat.c",
39 "strlcpy.c",
40 "aes.c",
41 "aes.h" ]
43 # What names of things never need javadoc
44 SKIP_NAME_PATTERNS = [ r'^.*_c_id$',
45 r'^.*_H_ID$' ]
47 # Which types of things should get DOCDOC comments added if they are
48 # missing documentation? Recognized types are in KINDS below.
49 #ADD_DOCDOCS_TO_TYPES = [ 'function', 'type', 'typedef' ]
50 ADD_DOCDOCS_TO_TYPES = [ 'variable' ]
52 # ====================
53 # The rest of this should not need hacking.
55 import re
56 import sys
58 KINDS = [ "type", "field", "typedef", "define", "function", "variable" ]
60 NODOC_LINE_RE = re.compile(r'^([^:]+):(\d+): (\w+): (.*) is not documented\.$')
62 THING_RE = re.compile(r'^Member ([a-zA-Z0-9_]+).*\((typedef|define|function|variable)\) of (file|class) ')
64 SKIP_NAMES = [re.compile(s) for s in SKIP_NAME_PATTERNS]
66 def parsething(thing):
67 """I figure out what 'foobar baz in quux quum is not documented' means,
68 and return: the name of the foobar, and the kind of the foobar.
69 """
70 if thing.startswith("Compound "):
71 tp, name = "type", thing.split()[1]
72 else:
73 m = THING_RE.match(thing)
74 if not m:
75 print thing
76 return None, None
77 else:
78 name, tp, parent = m.groups()
79 if parent == 'class':
80 if tp == 'variable' or tp == 'function':
81 tp = 'field'
83 return name, tp
85 def read():
86 """I snarf doxygen stderr from stdin, and parse all the "foo has no
87 documentation messages. I return a map from filename to lists
88 of tuples of (alleged line number, name of thing, kind of thing)
89 """
90 errs = {}
91 for line in sys.stdin:
92 m = NODOC_LINE_RE.match(line)
93 if m:
94 file, line, tp, thing = m.groups()
95 assert tp == 'Warning'
96 name, kind = parsething(thing)
97 errs.setdefault(file, []).append((int(line), name, kind))
99 return errs
101 def findline(lines, lineno, ident):
102 """Given a list of all the lines in the file (adjusted so 1-indexing works),
103 a line number that ident is alledgedly on, and ident, I figure out
104 the line where ident was really declared."""
105 for lineno in xrange(lineno, 0, -1):
106 if ident in lines[lineno]:
107 return lineno
109 return None
111 FUNC_PAT = re.compile(r"^[A-Za-z0-9_]+\(")
113 def hascomment(lines, lineno, kind):
114 """I return true if it looks like there's already a good comment about
115 the thing on lineno of lines of type kind. """
116 if "*/" in lines[lineno-1]:
117 return True
118 if kind == 'function' and FUNC_PAT.match(lines[lineno]):
119 if "*/" in lines[lineno-2]:
120 return True
121 return False
123 def hasdocdoc(lines, lineno, kind):
124 """I return true if it looks like there's already a docdoc comment about
125 the thing on lineno of lines of type kind."""
126 if "DOCDOC" in lines[lineno] or "DOCDOC" in lines[lineno-1]:
127 return True
128 if kind == 'function' and FUNC_PAT.match(lines[lineno]):
129 if "DOCDOC" in lines[lineno-2]:
130 return True
131 return False
133 def checkf(fn, errs):
134 """I go through the output of read() for a single file, and build a list
135 of tuples of things that want DOCDOC comments. Each tuple has:
136 the line number where the comment goes; the kind of thing; its name.
138 for skip in SKIP_FILES:
139 if fn.endswith(skip):
140 print "Skipping",fn
141 return
143 comments = []
144 lines = [ None ]
145 try:
146 lines.extend( open(fn, 'r').readlines() )
147 except IOError:
148 return
150 for line, name, kind in errs:
151 if any(pat.match(name) for pat in SKIP_NAMES):
152 continue
154 if kind not in ADD_DOCDOCS_TO_TYPES:
155 continue
157 ln = findline(lines, line, name)
158 if ln == None:
159 print "Couldn't find the definition of %s allegedly on %s of %s"%(
160 name, line, fn)
161 else:
162 if hasdocdoc(lines, line, kind):
163 # print "Has a DOCDOC"
164 # print fn, line, name, kind
165 # print "\t",lines[line-2],
166 # print "\t",lines[line-1],
167 # print "\t",lines[line],
168 # print "-------"
169 pass
170 else:
171 if kind == 'function' and FUNC_PAT.match(lines[ln]):
172 ln = ln - 1
174 comments.append((ln, kind, name))
176 return comments
178 def applyComments(fn, entries):
179 """I apply lots of comments to the file in fn, making a new .newdoc file.
181 N = 0
183 lines = [ None ]
184 try:
185 lines.extend( open(fn, 'r').readlines() )
186 except IOError:
187 return
189 # Process the comments in reverse order by line number, so that
190 # the line numbers for the ones we haven't added yet remain valid
191 # until we add them. Standard trick.
192 entries.sort()
193 entries.reverse()
195 for ln, kind, name in entries:
197 lines.insert(ln, "/* DOCDOC %s */\n"%name)
198 N += 1
200 outf = open(fn+".newdoc", 'w')
201 for line in lines[1:]:
202 outf.write(line)
203 outf.close()
205 print "Added %s DOCDOCs to %s" %(N, fn)
207 e = read()
209 for fn, errs in e.iteritems():
210 comments = checkf(fn, errs)
211 if comments:
212 applyComments(fn, comments)