X-Git-Url: http://git.droids-corp.org/?p=diff2html.git;a=blobdiff_plain;f=diff2html.py;h=36d191bc84de961fd18c586af2f4b7cd2372e155;hp=321632ec274234f1c4f3bdadc914ac8ace1be79a;hb=68c39b65bc6c15f2df60564f0599bdfdb24d91c0;hpb=86b0fd9f28780eb0d2e52cc5097a7768bb1561d2 diff --git a/diff2html.py b/diff2html.py index 321632e..36d191b 100644 --- a/diff2html.py +++ b/diff2html.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/python # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,13 +18,32 @@ # Transform a unified diff from stdin to a colored # side-by-side HTML page on stdout. # -# Author: Olivier MATZ +# Authors: Olivier MATZ +# Alan De Smet # # Inspired by diff2html.rb from Dave Burt # (mainly for html theme) +# +# TODO: +# - The sane function currently mashes non-ASCII characters to "." +# Instead be clever and convert to something like "xF0" +# (the hex value), and mark with a . Even more clever: +# Detect if the character is "printable" for whatever definition, +# and display those directly. + +import sys, re, htmlentitydefs, getopt + +# minimum line size, we add a zero-sized breakable space every +# LINESIZE characters +linesize = 20 +tabsize = 8 +inputfile = sys.stdin +outputfile = sys.stdout +exclude_headers = False +show_CR = False +show_hunk_infos = False -import sys, re, htmlentitydefs html_hdr=""" @@ -34,37 +53,43 @@ html_hdr=""" table { border:0px; border-collapse:collapse; width: 100%; font-size:0.75em; font-family: Lucida Console, monospace } td.line { color:#8080a0 } th { background: black; color: white } - tr.unmodified td { background: #D0D0E0 } - tr.hunk td { background: #A0A0A0 } - tr.added td { background: #CCFFCC } - tr.deleted td { background: #FFCCCC } - tr.changed td { background: #FFFFA0 } - span.changed2 { background: #E0C880 } - span.ponct { color: #B08080 } - tr.misc td {} - tr.separator td {} + tr.diffunmodified td { background: #D0D0E0 } + tr.diffhunk td { background: #A0A0A0 } + tr.diffadded td { background: #CCFFCC } + tr.diffdeleted td { background: #FFCCCC } + tr.diffchanged td { background: #FFFFA0 } + span.diffchanged2 { background: #E0C880 } + span.diffponct { color: #B08080 } + tr.diffmisc td {} + tr.diffseparator td {} - """ html_footer=""" -
+ +""" + +table_hdr=""" + +""" + +table_footer=""" +
""" DIFFON="\x01" DIFFOFF="\x02" -buffer=[] +buf=[] add_cpt, del_cpt = 0,0 line1, line2 = 0,0 hunk_off1, hunk_size1, hunk_off2, hunk_size2 = 0,0,0,0 -# minimum line size, we add a zero-sized breakable space every -# LINESIZE characters -LINESIZE=20 -TAB=8 + +# Characters we're willing to word wrap on +WORDBREAK=" \t;.,/):" def sane(x): r="" @@ -147,7 +172,7 @@ def convert(s, linesize=0, ponct=0): for c in s: # used by diffs if c==DIFFON: - t += '' + t += '' elif c==DIFFOFF: t += "" @@ -158,41 +183,46 @@ def convert(s, linesize=0, ponct=0): # special highlighted chars elif c=="\t" and ponct==1: - n = TAB-(i%TAB) + n = tabsize-(i%tabsize) if n==0: - n=TAB - t += ('»
'+' '*(n-1)) - i += n + n=tabsize + t += ('»'+' '*(n-1)) + elif c==" " and ponct==1: + t += '·' elif c=="\n" and ponct==1: - t += '\' + if show_CR: + t += '\' else: t += c i += 1 + + if linesize and (WORDBREAK.count(c)==1): + t += '​' + i=0 if linesize and i>linesize: i=0 t += "​" - - if ponct==1: - t = t.replace(' ', '·') - t = t.replace("spanclass", "span class") return t def add_comment(s): - sys.stdout.write('%s\n'%convert(s)) + outputfile.write('%s\n'%convert(s)) def add_filename(f1, f2): - sys.stdout.write("%s"%convert(f1, linesize=LINESIZE)) - sys.stdout.write("%s\n"%convert(f2, linesize=LINESIZE)) + outputfile.write("%s"%convert(f1, linesize=linesize)) + outputfile.write("%s\n"%convert(f2, linesize=linesize)) def add_hunk(): - global hunk_off1 - global hunk_size1 - global hunk_off2 - global hunk_size2 - sys.stdout.write('Offset %d, %d lines modified'%(hunk_off1, hunk_size1)) - sys.stdout.write('Offset %d, %d lines modified\n'%(hunk_off2, hunk_size2)) + global hunk_off1, hunk_size1, hunk_off2, hunk_size2 + global show_hunk_infos + if show_hunk_infos: + outputfile.write('Offset %d, %d lines modified'%(hunk_off1, hunk_size1)) + outputfile.write('Offset %d, %d lines modified\n'%(hunk_off2, hunk_size2)) + else: + # ⋮ - vertical ellipsis + outputfile.write('⋮⋮'); + def add_line(s1, s2): global line1 @@ -200,9 +230,9 @@ def add_line(s1, s2): if s1==None and s2==None: type="unmodified" - elif s1==None: + elif s1==None or s1=="": type="added" - elif s2==None: + elif s2==None or s1=="": type="deleted" elif s1==s2: type="unmodified" @@ -210,26 +240,26 @@ def add_line(s1, s2): type="changed" s1,s2 = linediff(s1, s2) - sys.stdout.write(''%type) + outputfile.write(''%type) if s1!=None and s1!="": - sys.stdout.write('%d '%line1) - sys.stdout.write('') - sys.stdout.write(convert(s1, linesize=LINESIZE, ponct=1)) - sys.stdout.write('') + outputfile.write('%d '%line1) + outputfile.write('') + outputfile.write(convert(s1, linesize=linesize, ponct=1)) + outputfile.write('') else: s1="" - sys.stdout.write(' ') + outputfile.write(' ') if s2!=None and s2!="": - sys.stdout.write('%d '%line2) - sys.stdout.write('') - sys.stdout.write(convert(s2, linesize=LINESIZE, ponct=1)) - sys.stdout.write('') + outputfile.write('%d '%line2) + outputfile.write('') + outputfile.write(convert(s2, linesize=linesize, ponct=1)) + outputfile.write('') else: s2="" - sys.stdout.write('') + outputfile.write('') - sys.stdout.write('\n') + outputfile.write('\n') if s1!="": line1 += 1 @@ -238,17 +268,17 @@ def add_line(s1, s2): def empty_buffer(): - global buffer + global buf global add_cpt global del_cpt if del_cpt == 0 or add_cpt == 0: - for l in buffer: + for l in buf: add_line(l[0], l[1]) elif del_cpt != 0 and add_cpt != 0: l0, l1 = [], [] - for l in buffer: + for l in buf: if l[0] != None: l0.append(l[0]) if l[1] != None: @@ -263,65 +293,132 @@ def empty_buffer(): add_line(s0, s1) add_cpt, del_cpt = 0,0 - buffer = [] + buf = [] +def parse_input(): + global buf, add_cpt, del_cpt + global line1, line2 + global hunk_off1, hunk_size1, hunk_off2, hunk_size2 -sys.stdout.write(html_hdr) + if not exclude_headers: + outputfile.write(html_hdr) + outputfile.write(table_hdr) + + while True: + l=inputfile.readline() + if l=="": + break -while True: - l=sys.stdin.readline() - if l=="": - break - - m=re.match('^--- ([^\s]*)', l) - if m: - empty_buffer() - file1=m.groups()[0] - l=sys.stdin.readline() - m=re.match('^\+\+\+ ([^\s]*)', l) + m=re.match('^--- ([^\s]*)', l) + if m: + empty_buffer() + file1=m.groups()[0] + l=inputfile.readline() + m=re.match('^\+\+\+ ([^\s]*)', l) + if m: + file2=m.groups()[0] + add_filename(file1, file2) + hunk_off1, hunk_size1, hunk_off2, hunk_size2 = 0,0,0,0 + continue + + m=re.match("@@ -(\d+),?(\d*) \+(\d+),?(\d*)", l) if m: - file2=m.groups()[0] - add_filename(file1, file2) - hunk_off1, hunk_size1, hunk_off2, hunk_size2 = 0,0,0,0 - continue + empty_buffer() + hunk_data = map(lambda x:x=="" and 1 or int(x), m.groups()) + hunk_off1, hunk_size1, hunk_off2, hunk_size2 = hunk_data + line1, line2 = hunk_off1, hunk_off2 + add_hunk() + continue + + if hunk_size1 == 0 and hunk_size2 == 0: + empty_buffer() + add_comment(l) + continue + + if re.match("^\+", l): + add_cpt += 1 + hunk_size2 -= 1 + buf.append((None, l[1:])) + continue + + if re.match("^\-", l): + del_cpt += 1 + hunk_size1 -= 1 + buf.append((l[1:], None)) + continue + + if re.match("^\ ", l) and hunk_size1 and hunk_size2: + empty_buffer() + hunk_size1 -= 1 + hunk_size2 -= 1 + buf.append((l[1:], l[1:])) + continue - m=re.match("@@ -(\d+),?(\d*) \+(\d+),?(\d*)", l) - if m: - empty_buffer() - hunk_data = map(lambda x:x=="" and 1 or int(x), m.groups()) - hunk_off1, hunk_size1, hunk_off2, hunk_size2 = hunk_data - line1, line2 = hunk_off1, hunk_off2 - add_hunk() - continue - - if hunk_size1 == 0 and hunk_size2 == 0: empty_buffer() add_comment(l) - continue - - if re.match("^\+", l): - add_cpt += 1 - hunk_size2 -= 1 - buffer.append((None, l[1:])) - continue - - if re.match("^\-", l): - del_cpt += 1 - hunk_size1 -= 1 - buffer.append((l[1:], None)) - continue - - if re.match("^\ ", l) and hunk_size1 and hunk_size2: - empty_buffer() - hunk_size1 -= 1 - hunk_size2 -= 1 - buffer.append((l[1:], l[1:])) - continue empty_buffer() - add_comment(l) - + outputfile.write(table_footer) + if not exclude_headers: + outputfile.write(html_footer) + + +def usage(): + print ''' +diff2html.py [-i file] [-o file] [-x] +diff2html.py -h + +Transform a unified diff from stdin to a colored side-by-side HTML +page on stdout. + + -i file set input file, else use stdin + -o file set output file, else use stdout + -x exclude html header and footer + -t tabsize set tab size (default 8) + -l linesize set maximum line size is there is no word break (default 20) + -r show \\r characters + -k show hunk infos + -h show help and exit +''' + +def main(): + global linesize, tabsize + global inputfile, outputfile + global exclude_headers, show_CR, show_hunk_infos + + try: + opts, args = getopt.getopt(sys.argv[1:], "hi:o:xt:l:rk", + ["help", "input=", "output=", + "exclude-html-headers", "tabsize=", + "linesize=", "show-cr", "show-hunk-infos"]) + except getopt.GetoptError, err: + print str(err) # will print something like "option -a not recognized" + usage() + sys.exit(2) + output = None + verbose = False + for o, a in opts: + if o in ("-h", "--help"): + usage() + sys.exit() + elif o in ("-i", "--input"): + inputfile = open(a, "r") + elif o in ("-o", "--output"): + outputfile = open(a, "w") + elif o in ("-x", "--exclude-html-headers"): + exclude_headers = True + elif o in ("-t", "--tabsize"): + tabsize = int(a) + elif o in ("-l", "--linesize"): + linesize = int(a) + elif o in ("-r", "--show-cr"): + show_CR = True + elif o in ("-k", "--show-hunk-infos"): + show_hunk_infos = True + else: + assert False, "unhandled option" + parse_input() -empty_buffer() -sys.stdout.write(html_footer) +if __name__ == "__main__": + main()