import sys import py from docutils import core, nodes def _start_line(node, subtree): starts = [n.line for n in subtree if n.line is not None] if not starts: return None return min(starts)-1 # zero rebase def _end_line(node, subtree): exclude = dict.fromkeys(subtree) starts = [n.line for n in node.traverse(ascend=1) if n.line is not None and n not in exclude] if not starts: return None return min(starts)-1 # zero rebase def start_end(node): subtree = node.traverse() start = _start_line(node, subtree) if start is None: return None, None end = _end_line(node, subtree) if end is not None: end = max(start, end-1) return start, end class BlameInfo(object): def __init__(self, path): docpath = py.path.local(path) self.dir_rev = py.path.svnwc(docpath.dirpath()).info().rev docsvn = py.path.svnwc(docpath) data = docsvn.blame() min_rev = sys.maxint #self.debug = data[:] for i in range(len(data)): rev, _, line = data[i] if not line.isspace(): data[i] = rev min_rev = min(rev, min_rev) else: data[i] = None self.revdata = data max_rev = max(data) min_rev = min(min_rev, max_rev) self.max_rev = max_rev self.rev_interval = (min_rev, max_rev) def medianrev(self, start, end): revrng = [x for x in self.revdata[start:(end and end+1)] if x is not None] n = len(revrng) if not n: return None revrng.sort() if n%2 == 0: return revrng[n//2-1] else: return revrng[n//2] def getwriter(): from docutils.writers.html4css1 import HTMLTranslator, Writer class DocAgeTranslator(HTMLTranslator): _blame = None def __init__(self, document): HTMLTranslator.__init__(self, document) self._blame = BlameInfo(document.settings._source) def medianrev(self, start, end): return self._blame.medianrev(start, end) def getcolorcomp(self, rev, rev_interval): min_rev, max_rev = rev_interval if min_rev == max_rev: return 0xff agef = (rev-min_rev)/(float(max_rev)-min_rev) return int(127+128*agef) def getcolor(self, m): return "#ffff%02x" % self.getcolorcomp(m, self._blame.rev_interval) def gettitlecolor(self): comp = "%02x" % self.getcolorcomp(self._blame.max_rev, (0, self._blame.dir_rev)) return '#' + comp * 3 def starttag(self, node, tagname, suffix='\n', empty=0, **attributes): color = None if 'CLASS' in attributes and attributes['CLASS'] == 'title': color = self.gettitlecolor() rev = self._blame.max_rev elif tagname in ('p', 'li', 'pre', 'td', 'dd'): start, end = start_end(node) if node.tagname == 'entry': # xxx hack start, end = start-1, end if start is not None: m = self.medianrev(start, end) if m is not None: color = self.getcolor(m) rev = m else: print "OOPS", start, end if color is not None: if 'title' not in attributes: attributes['title'] = str(rev) attributes['style'] = 'background-color: %s' % color tag = HTMLTranslator.starttag(self, node, tagname, suffix, empty, **attributes) return tag writer = Writer() writer.translator_class = DocAgeTranslator return writer # monkey patch docutils _publish_programmatically = core.publish_programmatically def publish_programmatically(source_class, source, source_path, destination_class, destination, destination_path, reader, reader_name, parser, parser_name, writer, writer_name, settings, settings_spec, settings_overrides, config_section, enable_exit_status): if writer is None and writer_name == 'html': print "Substituting writer..." writer_name = None writer = getwriter() return _publish_programmatically(source_class, source, source_path, destination_class, destination, destination_path, reader, reader_name, parser, parser_name, writer, writer_name, settings, settings_spec, settings_overrides, config_section, enable_exit_status) core.publish_programmatically = publish_programmatically