return "<Comment for %s: [%d,%d:%d,%d] %s>" % (self.func, self.first_line, self.first_col, self.last_line, self.last_col, self.lines)
+## A data member comment.
+class MemberComment:
+
+ def __init__(self, text, is_transient, array_size, first_line, first_col, func):
+ self.lines = [ text ]
+ self.is_transient = is_transient
+ self.array_size = array_size
+ self.first_line = first_line
+ self.first_col = first_col
+ self.func = func
+
+ def has_comment(self, line):
+ return line == self.first_line
+
+ def __str__(self):
+
+ if self.is_transient:
+ tt = '!transient! '
+ else:
+ tt = ''
+
+ if self.array_size is not None:
+ ars = '[%s] ' % self.array_size
+ else:
+ ars = ''
+
+ return "<MemberComment for %s: [%d,%d] %s%s%s>" % (self.func, self.first_line, self.first_col, tt, ars, self.lines[0])
+
+
+## A dummy comment that removes comment lines.
+class RemoveComment(Comment):
+
+ def __init__(self, first_line, last_line):
+ self.first_line = first_line
+ self.last_line = last_line
+ self.func = '<remove>'
+
+ def __str__(self):
+ return "<RemoveComment: [%d,%d]>" % (self.first_line, self.last_line)
+
+
## Parses method comments.
#
# @param cursor Current libclang parser cursor
break
+## Parses comments to class data members.
+#
+# @param cursor Current libclang parser cursor
+# @param comments Array of comments: new ones will be appended there
+def comment_datamember(cursor, comments):
+
+ # Note: libclang 3.5 seems to have problems parsing a certain type of FIELD_DECL, so we revert
+ # to a partial manual parsing. When parsing fails, the cursor's "extent" is not set properly,
+ # returning a line range 0-0. We therefore make the not-so-absurd assumption that the datamember
+ # definition is fully on one line, and we take the line number from cursor.location.
+
+ line_num = cursor.location.line
+ raw = None
+ prev = None
+ found = False
+
+ # Huge overkill
+ with open(str(cursor.location.file)) as fp:
+ cur_line = 0
+ for raw in fp:
+ cur_line = cur_line + 1
+ if cur_line == line_num:
+ found = True
+ break
+ prev = raw
+
+ assert found, 'A line that should exist was not found in file' % cursor.location.file
+
+ recomm = r'(//(!)|///?)(\[(.*?)\])?<?\s*(.*?)\s*$'
+ recomm_doxyary = r'^\s*///\s*(.*?)\s*$'
+
+ mcomm = re.search(recomm, raw)
+ if mcomm:
+ member_name = cursor.spelling;
+ is_transient = mcomm.group(2) is not None
+ array_size = mcomm.group(4)
+ text = mcomm.group(5)
+
+ col_num = mcomm.start()+1;
+
+ if array_size is not None and prev is not None:
+ # ROOT arrays with comments already converted to Doxygen have the member description on the
+ # previous line
+ mcomm_doxyary = re.search(recomm_doxyary, prev)
+ if mcomm_doxyary:
+ text = mcomm_doxyary.group(1)
+ comments.append(RemoveComment(line_num-1, line_num-1))
+
+ logging.debug('Comment found for member %s' % Colt(member_name).magenta())
+
+ comments.append( MemberComment(
+ text,
+ is_transient,
+ array_size,
+ line_num,
+ col_num,
+ member_name ))
+
+ else:
+ assert False, 'Regular expression does not match member comment'
+
+
## Traverse the AST recursively starting from the current cursor.
#
# @param cursor A Clang parser cursor
if cursor.kind == clang.cindex.CursorKind.CXX_METHOD or cursor.kind == clang.cindex.CursorKind.CONSTRUCTOR or cursor.kind == clang.cindex.CursorKind.DESTRUCTOR:
# cursor ran into a C++ method
- logging.debug( "%5d %s%s(%s)" % (cursor.extent.start.line, indent, Colt(kind).magenta(), Colt(text).blue()) )
+ logging.debug( "%5d %s%s(%s)" % (cursor.location.line, indent, Colt(kind).magenta(), Colt(text).blue()) )
comment_method(cursor, comments)
+ elif cursor.kind == clang.cindex.CursorKind.FIELD_DECL:
+
+ # cursor ran into a data member declaration
+ logging.debug( "%5d %s%s(%s)" % (cursor.location.line, indent, Colt(kind).magenta(), Colt(text).blue()) )
+ comment_datamember(cursor, comments)
+
else:
- logging.debug( "%5d %s%s(%s)" % (cursor.extent.start.line, indent, kind, text) )
+ logging.debug( "%5d %s%s(%s)" % (cursor.location.line, indent, kind, text) )
for child_cursor in cursor.get_children():
traverse_ast(child_cursor, filename, comments, recursion+1)
def refactor_comment(comment):
recomm = r'^(/{2,}|/\*)?\s*(.*?)\s*((/{2,})?\s*|\*/)$'
+ regarbage = r'^[\s*=-_#]+$'
new_comment = []
insert_blank = False
mcomm = re.search( recomm, line_comment )
if mcomm:
new_line_comment = mcomm.group(2)
- if new_line_comment == '':
+ mgarbage = re.search( regarbage, new_line_comment )
+ if new_line_comment == '' or mgarbage is not None:
insert_blank = True
else:
if insert_blank and not wait_first_non_blank:
def rewrite_comments(fhin, fhout, comments):
line_num = 0
- cur_comment = 0
in_comment = False
skip_empty = False
+ comm = None
+ prev_comm = None
- if len(comments) > 0:
- comm = comments[0]
- else:
- comm = None
+ rindent = r'^(\s*)'
for line in fhin:
line_num = line_num + 1
- if comm and comm.has_comment( line_num ):
+ # Find current comment
+ prev_comm = comm
+ comm = None
+ for c in comments:
+ if c.has_comment(line_num):
+ comm = c
+
+ if comm:
+
+ if isinstance(comm, MemberComment):
+ non_comment = line[ 0:comm.first_col-1 ]
+
+ if comm.array_size is not None:
+
+ mindent = re.search(rindent, line)
+ if comm.is_transient:
+ tt = '!'
+ else:
+ tt = ''
+
+ # Special case: we need multiple lines not to confuse ROOT's C++ parser
+ fhout.write('%s/// %s\n%s//%s[%s]\n' % (
+ mindent.group(1),
+ comm.lines[0],
+ non_comment,
+ tt,
+ comm.array_size
+ ))
+
+ else:
+
+ if comm.is_transient:
+ tt = '!'
+ else:
+ tt = '/'
+
+ fhout.write('%s//%s< %s\n' % (
+ non_comment,
+ tt,
+ comm.lines[0]
+ ))
+
+ elif isinstance(comm, RemoveComment):
+ # Do nothing: just skip line
+ pass
- if not in_comment:
+ elif prev_comm is None:
+ # Beginning of a new comment block of type Comment
in_comment = True
- # extract the non-comment part and print it if it exists
- non_comment = line[ 0:comments[cur_comment].first_col-1 ].rstrip()
+ # Extract the non-comment part and print it if it exists
+ non_comment = line[ 0:comm.first_col-1 ].rstrip()
if non_comment != '':
fhout.write( non_comment + '\n' )
else:
+
if in_comment:
+ # We have just exited a comment block of type Comment
in_comment = False
- # dumping comments
+ # Dump revamped comment, if applicable
text_indent = ''
- for i in range(0,comments[cur_comment].indent):
+ for i in range(0,prev_comm.indent):
text_indent = text_indent + ' '
- for lc in comments[cur_comment].lines:
+ for lc in prev_comm.lines:
fhout.write( "%s/// %s\n" % (text_indent, lc) );
fhout.write('\n')
skip_empty = True
- cur_comment = cur_comment + 1
- if cur_comment < len(comments):
- comm = comments[cur_comment]
- else:
- comm = None
-
line_out = line.rstrip('\n')
if skip_empty:
skip_empty = False
comments = []
traverse_ast( translation_unit.cursor, fn, comments )
for c in comments:
- logging.debug("Comment found for %s:" % Colt(c.func).magenta())
- for l in c.lines:
+
+ logging.debug("Comment found for entity %s:" % Colt(c.func).magenta())
+
+ if isinstance(c, MemberComment):
+
+ if c.is_transient:
+ transient_text = Colt('transient ').yellow()
+ else:
+ transient_text = ''
+
+ if c.array_size is not None:
+ array_text = Colt('arraysize=%s ' % c.array_size).yellow()
+ else:
+ array_text = ''
+
logging.debug(
- Colt("[%d,%d:%d,%d] " % (c.first_line, c.first_col, c.last_line, c.last_col)).green() +
- "{%s}" % Colt(l).cyan()
- )
+ "%s %s%s{%s}" % ( \
+ Colt("[%d,%d]" % (c.first_line, c.first_col)).green(),
+ transient_text,
+ array_text,
+ Colt(c.lines[0]).cyan()
+ ))
+
+ elif isinstance(c, RemoveComment):
+
+ logging.debug( Colt('[%d,%d]' % (c.first_line, c.last_line)).green() )
+
+ else:
+ for l in c.lines:
+ logging.debug(
+ Colt("[%d,%d:%d,%d] " % (c.first_line, c.first_col, c.last_line, c.last_col)).green() +
+ "{%s}" % Colt(l).cyan()
+ )
try: