from __future__ import generators import os import re import time _date_pat = re.compile('^(\d\d\d\d)-(\d\d)-(\d\d)\s+(.*)\s*$') def _parse_header(line): if line[0].isspace(): return None match = _date_pat.match(line) if match: when = (int(match.group(1)), int(match.group(2)), int(match.group(3)), 0,0,0, 0,0,0) who = match.group(4) return (when, who) parts = line.split(None, 5) if len(parts) == 6: try: timeval = ' '.join(parts[:5]) when = time.strptime(timeval, '%a %b %d %H:%M:%S %Y') who = parts[5] return (when, who) except ValueError: pass return None def parse(fp): '''parse a changelog in the standard format Yields tuples of the form (when, who, changes).''' when = None who = None changes = [] for line in fp: header = _parse_header(line) if header: if when: yield (when, who, changes) when, who = header changes = [] else: changes.append(line) if when: yield (when, who, changes) def _find_changelogs(checkoutdir, base): if checkoutdir[-1] != '/': checkoutdir = checkoutdir + '/' def walker(changelogs, dirname, fnames): if 'ChangeLog' in fnames: changelogs.append(os.path.join(dirname, 'ChangeLog')) elif 'ChangeLog,v' in fnames: changelogs.append(os.path.join(dirname, 'ChangeLog,v')) changelogs = [] os.path.walk(os.path.join(checkoutdir, base), walker, changelogs) for i in range(len(changelogs)): changelogs[i] = changelogs[i][len(checkoutdir):] return changelogs def aggregate(checkoutdir, base): filenames = _find_changelogs(checkoutdir, base) logs = [] for i in range(len(filenames)): filename = os.path.join(checkoutdir, filenames[i]) if filename.endswith(',v'): fp = os.popen('co -p %s 2>/dev/null' % filename, 'r') else: fp = file(filename, 'r') parser = parse(fp) try: item = parser.next() logs.append((filenames[i], parser, item)) except StopIteration: pass def compare_tuples(t1, t2): '''compare two (filename, parser, item) tuples by date''' item1 = t1[2] item2 = t2[2] return cmp(item1[0], item2[0]) while logs: logs.sort(compare_tuples) # sort logs by time of last read entry (filename, parser, item) = logs[-1] del logs[-1] yield (filename,) + item # now read another entry from that log try: item = parser.next() logs.append((filename, parser, item)) except StopIteration: pass def _escape(s): s = s.replace('&', '&') s = s.replace('<', '<') s = s.replace('>', '>') return s def _break_into_paragraphs(lines): paragraphs = [[]] for line in lines: line = _escape(line.strip()) if not line: if paragraphs[-1]: paragraphs.append([]) continue if line[0] == '*': # bullets start new paragraphs if paragraphs[-1]: paragraphs.append([]) paragraphs[-1].append(line) if not paragraphs[-1]: del paragraphs[-1] return paragraphs def _splitword(s): parts = s.split(None, 1) parts.append('') return parts[0], parts[1] email_pat = re.compile(r'(<(.*@[-a-zA-Z0-9.]+)>)') bug_pat = re.compile(r'((?:bug\s+#?|#)(\d\d\d\d*))', re.I) def _linkify(s, bugsroot=None): s = email_pat.sub(r'\1', s) if bugsroot: s = bug_pat.sub(r'\1' % bugsroot, s) return s function_pat = re.compile(r'^(\s*\()([^\)]+)(\):.*)$') def changes_to_html(changes, webcvsroot=None, bugsroot=None): ret = [ '
\n') for line in para: ret.append(_linkify(line, bugsroot) + '\n') ret.append('
\n') ret.append('