?? contribulyze.py
字號:
out.write('</tr>\n') out.write('</table>\n\n') out.write('</div>\n\n') sorted_logs = unique_logs.keys() sorted_logs.sort() for log in sorted_logs: out.write('<hr />\n') out.write('<div class="h3" id="%s" title="%s">\n' % (log.revision, log.revision)) out.write('<pre>\n') if revision_url_pattern: revision_url = revision_url_pattern % log.revision[1:] revision = '<a href="%s">%s</a>' \ % (escape_html(revision_url), log.revision) else: revision = log.revision out.write('<b>%s | %s | %s</b>\n\n' % (revision, escape_html(log.committer), escape_html(log.date))) out.write(spam_guard_in_html_block(escape_html(log.message))) out.write('</pre>\n') out.write('</div>\n\n') out.write('<hr />\n') out.write(html_footer()) out.close()class Field: """One field in one log message.""" def __init__(self, name, alias = None): # The name of this field (e.g., "Patch", "Review", etc). self.name = name # An alias for the name of this field (e.g., "Reviewed"). self.alias = alias # A list of contributor objects, in the order in which they were # encountered in the field. self.contributors = [ ] # Any parenthesized asides immediately following the field. The # parentheses and trailing newline are left on. In theory, this # supports concatenation of consecutive asides. In practice, the # parser only detects the first one anyway, because additional # ones are very uncommon and furthermore by that point one should # probably be looking at the full log message. self.addendum = '' def add_contributor(self, contributor): self.contributors.append(contributor) def add_endum(self, addendum): self.addendum += addendum def __str__(self): s = 'FIELD: %s (%d contributors)\n' % (self.name, len(self.contributors)) for contributor in self.contributors: s += str(contributor) + '\n' s += self.addendum return sclass LogMessage: # Maps revision strings (e.g., "r12345") onto LogMessage instances, # holding all the LogMessage instances ever created. all_logs = { } def __init__(self, revision, committer, date): """Instantiate a log message. All arguments are strings, including REVISION, which should retain its leading 'r'.""" self.revision = revision self.committer = committer self.date = date self.message = '' # Map field names (e.g., "Patch", "Review", "Suggested") onto # Field objects. self.fields = { } if LogMessage.all_logs.has_key(revision): complain("Revision '%s' seen more than once" % revision, True) LogMessage.all_logs[revision] = self def add_field(self, field): self.fields[field.name] = field def accum(self, line): """Accumulate one more line of raw message.""" self.message += line def __cmp__(self, other): """Compare two log messages by revision number, for sort(). Return -1, 0 or 1 depending on whether a > b, a == b, or a < b. Note that this is reversed from normal sorting behavior, but it's what we want for reverse chronological ordering of revisions.""" a = int(self.revision[1:]) b = int(other.revision[1:]) if a > b: return -1 if a < b: return 1 else: return 0 def __hash__(self): """I don't really understand why defining __cmp__() but not __hash__() renders an object type unfit to be a dictionary key, especially in light of the recommendation that if a class defines mutable objects and implements __cmp__() or __eq__(), then it should not implement __hash__(). See these for details: http://mail.python.org/pipermail/python-dev/2004-February/042580.html http://mail.python.org/pipermail/python-bugs-list/2003-December/021314.html In the meantime, I think it's safe to use the revision as a hash value.""" return int(self.revision[1:]) def __str__(self): s = '=' * 15 header = ' LOG: %s | %s ' % (self.revision, self.committer) s += header s += '=' * 15 s += '\n' for field_name in self.fields.keys(): s += str(self.fields[field_name]) + '\n' s += '-' * 15 s += '-' * len(header) s += '-' * 15 s += '\n' return s### Code to parse the logs. ##log_separator = '-' * 72 + '\n'log_header_re = re.compile\ ('^(r[0-9]+) \| ([^|]+) \| ([^|]+) \| ([0-9]+)[^0-9]')field_re = re.compile('^(Patch|Review(ed)?|Suggested|Found) by:\s*(.*)')field_aliases = { 'Reviewed' : 'Review' }parenthetical_aside_re = re.compile('^\(.*\)\s*$')def graze(input): just_saw_separator = False while True: line = input.readline() if line == '': break if line == log_separator: if just_saw_separator: sys.stderr.write('Two separators in a row.\n') sys.exit(1) else: just_saw_separator = True num_lines = None continue else: if just_saw_separator: m = log_header_re.match(line) if not m: sys.stderr.write('Could not match log message header.\n') sys.stderr.write('Line was:\n') sys.stderr.write("'%s'\n" % line) sys.exit(1) else: log = LogMessage(m.group(1), m.group(2), m.group(3)) num_lines = int(m.group(4)) just_saw_separator = False line = input.readline() # Handle 'svn log -v' by waiting for the blank line. while line != '\n': line = input.readline() # Parse the log message. field = None while num_lines > 0: line = input.readline() log.accum(line) m = field_re.match(line) if m: # We're on the first line of a field. Parse the field. while m: if not field: ident = m.group(1) if field_aliases.has_key(ident): field = Field(field_aliases[ident], ident) else: field = Field(ident) # Each line begins either with "WORD by:", or with whitespace. in_field_re = re.compile('^(' + (field.alias or field.name) + ' by:\s+|\s+)(\S.*)+') m = in_field_re.match(line) user, real, email = Contributor.parse(m.group(2)) if user == 'me': user = log.committer c = Contributor.get(user, real, email) c.add_activity(field.name, log) field.add_contributor(c) line = input.readline() log.accum(line) num_lines -= 1 m = in_field_re.match(line) if not m: m = field_re.match(line) if not m: aside_match = parenthetical_aside_re.match(line) if aside_match: field.add_endum(line) log.add_field(field) field = None num_lines -= 1 continueindex_introduction = '''<p>The following list of contributors and their contributions is meantto help us keep track of whom to consider for commit access. The listwas generated from "svn log" output by <ahref="http://svn.collab.net/repos/svn/trunk/tools/dev/contribulyze.py">contribulyze.py</a>, which looks for log messages that use the <ahref="http://subversion.tigris.org/hacking.html#crediting">specialcontribution format</a>.</p><p><i>Please do not use this list as a generic guide to who hascontributed what to Subversion!</i> It omits existing full committers,for example, because they are irrelevant to our search for newcommitters. Also, it merely counts changes, it does not evaluatethem. To truly understand what someone has contributed, you have toread their changes in detail. This page can only assist humanjudgement, not substitute for it.</p>'''def drop(revision_url_pattern): # Output the data. # # The data structures are all linked up nicely to one another. You # can get all the LogMessages, and each LogMessage contains all the # Contributors involved with that commit; likewise, each Contributor # points back to all the LogMessages it contributed to. # # However, the HTML output is pretty simple right now. It's not take # full advantage of all that cross-linking. For each contributor, we # just create a file listing all the revisions contributed to; and we # build a master index of all contributors, each name being a link to # that contributor's individual file. Much more is possible... but # let's just get this up and running first. for key in LogMessage.all_logs.keys(): # You could print out all log messages this way, if you wanted to. pass # print LogMessage.all_logs[key] detail_subdir = "detail" if not os.path.exists(detail_subdir): os.mkdir(detail_subdir) index = open('index.html', 'w') index.write(html_header('Contributors')) index.write(index_introduction) index.write('<ol>\n') # The same contributor appears under multiple keys, so uniquify. seen_contributors = { } # Sorting alphabetically is acceptable, but even better would be to # sort by number of contributions, so the most active people appear at # the top -- that way we know whom to look at first for commit access # proposals. sorted_contributors = Contributor.all_contributors.values() sorted_contributors.sort() for c in sorted_contributors: if not seen_contributors.has_key(c): if c.score() > 0: if c.is_full_committer: # Don't even bother to print out full committers. They are # a distraction from the purposes for which we're here. continue else: committerness = '' if c.is_committer: committerness = ' (partial committer)' urlpath = "%s/%s.html" % (detail_subdir, c.canonical_name()) fname = os.path.join(detail_subdir, "%s.html" % c.canonical_name()) index.write('<li><p><a href="%s">%s</a> [%s]%s</p></li>\n' % (url_encode(urlpath), c.big_name(html=True), c.score_str(), committerness)) c.html_out(revision_url_pattern, fname) seen_contributors[c] = True index.write('</ol>\n') index.write(html_footer()) index.close()def process_committers(committers): """Read from open file handle COMMITTERS, which should be in the same format as the Subversion 'COMMITTERS' file. Create Contributor objects based on the contents.""" line = committers.readline() while line != 'Blanket commit access:\n': line = committers.readline() in_full_committers = True matcher = re.compile('(\S+)\s+([^\(\)]+)\s+(\([^()]+\)){0,1}') line = committers.readline() while line: # Every @-sign we see after this point indicates a committer line. if line == 'Commit access for specific areas:\n': in_full_committers = False elif line.find('@') >= 0: line = line.strip() m = matcher.match(line) user = m.group(1) real_and_email = m.group(2).strip() ignored, real, email = Contributor.parse(real_and_email) c = Contributor.get(user, real, email) c.is_committer = True c.is_full_committer = in_full_committers line = committers.readline()def usage(): print 'USAGE: %s [-C COMMITTERS_FILE] < SVN_LOG_OR_LOG-V_OUTPUT' \ % os.path.basename(sys.argv[0]) print '' print 'Create HTML files in the current directory, rooted at index.html,' print 'in which you can browse to see who contributed what.' print '' print 'The log input should use the contribution-tracking format defined' print 'in http://subversion.tigris.org/hacking.html#crediting.' print '' print 'Options:' print '' print ' -h, -H, -?, --help Print this usage message and exit' print ' -C FILE Use FILE as the COMMITTERS file' print ' -U URL Use URL as a Python interpolation pattern to' print ' generate URLs to link revisions to some kind' print ' of web-based viewer (e.g. ViewCVS). The' print ' interpolation pattern should contain exactly' print ' one format specifier, \'%s\', which will be' print ' replaced with the revision number.' print ''def main(): try: opts, args = getopt.getopt(sys.argv[1:], 'C:U:hH?', [ 'help' ]) except getopt.GetoptError, e: complain(str(e) + '\n\n') usage() sys.exit(1) # Parse options. revision_url_pattern = None for opt, value in opts: if opt in ('--help', '-h', '-H', '-?'): usage() sys.exit(0) elif opt == '-C': process_committers(open(value)) elif opt == '-U': revision_url_pattern = value # Gather the data. graze(sys.stdin) # Output the data. drop(revision_url_pattern)if __name__ == '__main__': main()
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -