]> git.uio.no Git - u/mrichter/AliRoot.git/blob - doxygen/thtml2doxy_clang.py
doxy: refactor single/multiline comments
[u/mrichter/AliRoot.git] / doxygen / thtml2doxy_clang.py
1 #!/usr/bin/env python
2
3 ## @package thtml2doxy_clang
4 #  Translates THtml C++ comments to Doxygen using libclang as parser.
5 #
6 #  This code relies on Python bindings for libclang: libclang's interface is pretty unstable, and
7 #  its Python bindings are unstable as well.
8 #
9 #  AST (Abstract Source Tree) traversal is performed entirely using libclang used as a C++ parser,
10 #  instead of attempting to write a parser ourselves.
11 #
12 #  This code (expecially AST traversal) was inspired by:
13 #
14 #   - [Implementing a code generator with libclang](http://szelei.me/code-generator/)
15 #     (this refers to API calls used here)
16 #   - [Parsing C++ in Python with Clang](http://eli.thegreenplace.net/2011/07/03/parsing-c-in-python-with-clang)
17 #     (outdated, API calls described there do not work anymore, but useful to understand some basic
18 #     concepts)
19 #
20 #  Usage:
21 #
22 #    `thtml2doxy_clang file1 [file2 [file3...]]`
23 #
24 #  @author Dario Berzano <dario.berzano@cern.ch>
25 #  @date 2014-12-05
26
27
28 import sys
29 import os
30 import re
31 import clang.cindex
32
33
34 ## Brain-dead color output for terminal.
35 class Colt(str):
36
37   def red(self):
38     return self.color('\033[31m')
39
40   def green(self):
41     return self.color('\033[32m')
42
43   def yellow(self):
44     return self.color('\033[33m')
45
46   def blue(self):
47     return self.color('\033[34m')
48
49   def magenta(self):
50     return self.color('\033[35m')
51
52   def cyan(self):
53     return self.color('\033[36m')
54
55   def color(self, c):
56     return c + self + '\033[m'
57
58
59 ## Traverse the AST recursively starting from the current cursor.
60 #
61 #  @param cursor    A Clang parser cursor
62 #  @param recursion Current recursion depth
63 def traverse_ast(cursor, recursion=0):
64
65   text = cursor.spelling or cursor.displayname
66   kind = str(cursor.kind)[str(cursor.kind).index('.')+1:]
67
68   indent = ''
69   for i in range(0, recursion):
70     indent = indent + '  '
71
72   if cursor.kind == clang.cindex.CursorKind.CXX_METHOD:
73
74     # cursor ran into a C++ method
75     print "%s%s(%s)" % (indent, Colt(kind).magenta(), Colt(text).blue())
76
77     # we are looking for the following structure: method -> compound statement -> comment, i.e. we
78     # need to extract the first comment in the compound statement composing the method
79
80     in_compound_stmt = False
81     expect_comment = False
82     last_comment_line = -1
83
84     for token in cursor.get_tokens():
85
86       if token.cursor.kind == clang.cindex.CursorKind.COMPOUND_STMT:
87         if not in_compound_stmt:
88           in_compound_stmt = True
89           expect_comment = True
90           last_comment_line = -1
91       else:
92         if in_compound_stmt:
93           in_compound_stmt = False
94           break
95
96       # tkind = str(token.kind)[str(token.kind).index('.')+1:]
97       # ckind = str(token.cursor.kind)[str(token.cursor.kind).index('.')+1:]
98
99       if in_compound_stmt:
100
101         if expect_comment:
102
103           extent = token.extent
104           line_start = extent.start.line
105           line_end = extent.end.line
106
107           if token.kind == clang.cindex.TokenKind.PUNCTUATION and token.spelling == '{':
108             pass
109
110           elif token.kind == clang.cindex.TokenKind.COMMENT and (last_comment_line == -1 or line_start == last_comment_line+1):
111             #print Colt("%s  %s:%s = %s" % (indent, ckind, tkind, token.spelling)).green()
112             last_comment_line = line_end
113             new_comment = refactor_comment(token.spelling)
114
115             for comment_line in new_comment:
116               print Colt("%s  [%d-%d]" % (indent, line_start, line_end)).green(),
117               print Colt(comment_line).cyan()
118
119           else:
120             expect_comment = False
121
122       # else:
123       #   print Colt("%s  %s:%s = %s" % (indent, ckind, tkind, token.spelling)).yellow()
124
125
126   else:
127
128     print "%s%s(%s)" % (indent, kind, text)
129
130   for child_cursor in cursor.get_children():
131     traverse_ast(child_cursor, recursion+1)
132
133 ## Remove garbage from comments and convert special tags from THtml to Doxygen.
134 #
135 #  @param comment The original comment
136 def refactor_comment(comment):
137
138   resingle = r'^/{2,}\s*(.*?)\s*(/{2,})?\s*$'
139   remulti_first = r'^/\*\s*(.*?)\s*\*?\s*$'
140   remulti_last = r'^\s*(.*?)\s*\*/$'
141
142   new_comment = comment.split('\n')
143
144   if len(new_comment) == 1:
145     msingle = re.search(resingle, comment)
146     if msingle:
147       new_comment[0] = msingle.group(1)
148
149   else:
150
151     for i in range(0, len(new_comment)):
152       if i == 0:
153         mmulti = re.search(remulti_first, new_comment[i])
154         if mmulti:
155           new_comment[i] = mmulti.group(1)
156       elif i == len(new_comment)-1:
157         mmulti = re.search(remulti_last, new_comment[i])
158         if mmulti:
159           new_comment[i] = mmulti.group(1)
160       else:
161         new_comment[i] = new_comment[i].strip()
162
163   return new_comment
164
165
166 ## The main function.
167 #
168 #  **Note:** this program only has this function.
169 def main(argv):
170
171   # Attempt to load libclang from a list of known locations
172   libclang_locations = [
173     '/usr/lib/llvm-3.5/lib/libclang.so.1',
174     '/usr/lib/libclang.so',
175     '/Library/Developer/CommandLineTools/usr/lib/libclang.dylib'
176   ]
177   libclang_found = False
178
179   for lib in libclang_locations:
180     if os.path.isfile(lib):
181       clang.cindex.Config.set_library_file(lib)
182       libclang_found = True
183       break
184
185   if not libclang_found:
186     print Colt('[Error] Cannot find libclang, aborting').red()
187     return 1
188
189   # Loop over all files
190   for fn in argv[1:]:
191
192     index = clang.cindex.Index.create()
193     translation_unit = index.parse(fn, args=['-x', 'c++'])
194     traverse_ast( translation_unit.cursor )
195
196   return 0
197
198
199 if __name__ == '__main__':
200   sys.exit( main( sys.argv ) )