X-Git-Url: http://git.droids-corp.org/?p=diff2html.git;a=blobdiff_plain;f=diff2html.py;h=71ac19a3343b450c0d07a1a2683531caa9167129;hp=dc000eef6b1b12b8a1634864e4863adc2eed910a;hb=659c1a60bdf4fc73098bc97b48db70b68d82ad23;hpb=0ca475f9e41664da1195d4582567d52be5ab24d8 diff --git a/diff2html.py b/diff2html.py index dc000ee..71ac19a 100644 --- a/diff2html.py +++ b/diff2html.py @@ -19,7 +19,7 @@ # Transform a unified diff from stdin to a colored # side-by-side HTML page on stdout. # -# Authors: Olivier MATZ +# Authors: Olivier Matz # Alan De Smet # Sergey Satskiy # scito @@ -29,13 +29,17 @@ # # TODO: # - The sane function currently mashes non-ASCII characters to "." -# Instead be clever and convert to something like "xF0" +# 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, StringIO, codecs +import sys, re, htmlentitydefs, getopt, StringIO, codecs, datetime +try: + from simplediff import diff, string_diff +except ImportError: + sys.stderr.write("info: simplediff module not found, only linediff is available\n") + sys.stderr.write("info: it can be downloaded at https://github.com/paulgb/simplediff\n") # minimum line size, we add a zero-sized breakable space every # LINESIZE characters @@ -43,32 +47,49 @@ linesize = 20 tabsize = 8 show_CR = False encoding = "utf-8" +lang = "en" +algorithm = 0 +desc = "File comparison" +dtnow = datetime.datetime.now() +modified_date = "%s+01:00"%dtnow.isoformat() html_hdr = """ - - - - HTML Diff - - - + + + + + + HTML Diff{0} + + + + + + + + + """ html_footer = """ + """ @@ -90,7 +111,7 @@ hunk_off1, hunk_size1, hunk_off2, hunk_size2 = 0, 0, 0, 0 # Characters we're willing to word wrap on -WORDBREAK = " \t;.,/):" +WORDBREAK = " \t;.,/):-" def sane(x): r = "" @@ -103,6 +124,9 @@ def sane(x): return r def linediff(s, t): + ''' + Original line diff algorithm of diff2html. It's character based. + ''' if len(s): s = unicode(reduce(lambda x, y:x+y, [ sane(c) for c in s ])) if len(t): @@ -165,6 +189,62 @@ def linediff(s, t): return r1, r2 +def diff_changed(old, new): + ''' + Returns the differences basend on characters between two strings + wrapped with DIFFON and DIFFOFF using `diff`. + ''' + con = {'=': (lambda x: x), + '+': (lambda x: DIFFON + x + DIFFOFF), + '-': (lambda x: '')} + return "".join([(con[a])("".join(b)) for a, b in diff(old, new)]) + + +def diff_changed_ts(old, new): + ''' + Returns a tuple for a two sided comparison based on characters, see `diff_changed`. + ''' + return (diff_changed(new, old), diff_changed(old, new)) + + +def word_diff(old, new): + ''' + Returns the difference between the old and new strings based on words. Punctuation is not part of the word. + + Params: + old the old string + new the new string + + Returns: + the output of `diff` on the two strings after splitting them + on whitespace (a list of change instructions; see the docstring + of `diff`) + ''' + separator_pattern = '(\W+)'; + return diff(re.split(separator_pattern, old, flags=re.UNICODE), re.split(separator_pattern, new, flags=re.UNICODE)) + + +def diff_changed_words(old, new): + ''' + Returns the difference between two strings based on words (see `word_diff`) + wrapped with DIFFON and DIFFOFF. + + Returns: + the output of the diff expressed delimited with DIFFON and DIFFOFF. + ''' + con = {'=': (lambda x: x), + '+': (lambda x: DIFFON + x + DIFFOFF), + '-': (lambda x: '')} + return "".join([(con[a])("".join(b)) for a, b in word_diff(old, new)]) + + +def diff_changed_words_ts(old, new): + ''' + Returns a tuple for a two sided comparison based on words, see `diff_changed_words`. + ''' + return (diff_changed_words(new, old), diff_changed_words(old, new)) + + def convert(s, linesize=0, ponct=0): i = 0 t = u"" @@ -227,6 +307,9 @@ def add_line(s1, s2, output_file): global line1 global line2 + orig1 = s1 + orig2 = s2 + if s1 == None and s2 == None: type_name = "unmodified" elif s1 == None or s1 == "": @@ -237,7 +320,12 @@ def add_line(s1, s2, output_file): type_name = "unmodified" else: type_name = "changed" - s1, s2 = linediff(s1, s2) + if algorithm == 1: + s1, s2 = diff_changed_words_ts(orig1, orig2) + elif algorithm == 2: + s1, s2 = diff_changed_ts(orig1, orig2) + else: # default + s1, s2 = linediff(orig1, orig2) output_file.write(('' % type_name).encode(encoding)) if s1 != None and s1 != "": @@ -295,14 +383,15 @@ def empty_buffer(output_file): buf = [] -def parse_input(input_file, output_file, +def parse_input(input_file, output_file, input_file_name, output_file_name, exclude_headers, show_hunk_infos): global add_cpt, del_cpt global line1, line2 global hunk_off1, hunk_size1, hunk_off2, hunk_size2 if not exclude_headers: - output_file.write(html_hdr.format(encoding).encode(encoding)) + title_suffix = ' ' + input_file_name + output_file.write(html_hdr.format(title_suffix, encoding, desc, "", modified_date, lang).encode(encoding)) output_file.write(table_hdr.encode(encoding)) while True: @@ -363,7 +452,7 @@ def parse_input(input_file, output_file, empty_buffer(output_file) output_file.write(table_footer.encode(encoding)) if not exclude_headers: - output_file.write(html_footer.encode(encoding)) + output_file.write(html_footer.format("", dtnow.strftime("%d.%m.%Y")).encode(encoding)) def usage(): @@ -383,6 +472,7 @@ stdout may not work with UTF-8, instead use -o option. -l linesize set maximum line size is there is no word break (default 20) -r show \\r characters -k show hunk infos + -a algo line diff algorithm (0: linediff characters, 1: word, 2: simplediff characters) (default 0) -h show help and exit ''' @@ -390,18 +480,19 @@ def main(): global linesize, tabsize global show_CR global encoding + global algorithm - input_file = sys.stdin - output_file = sys.stdout + input_file_name = '' + output_file_name = '' exclude_headers = False show_hunk_infos = False try: - opts, args = getopt.getopt(sys.argv[1:], "he:i:o:xt:l:rk", + opts, args = getopt.getopt(sys.argv[1:], "he:i:o:xt:l:rka:", ["help", "encoding=", "input=", "output=", "exclude-html-headers", "tabsize=", - "linesize=", "show-cr", "show-hunk-infos"]) + "linesize=", "show-cr", "show-hunk-infos", "algorithm="]) except getopt.GetoptError, err: print unicode(err) # will print something like "option -a not recognized" usage() @@ -415,8 +506,10 @@ def main(): encoding = a elif o in ("-i", "--input"): input_file = codecs.open(a, "r", encoding) + input_file_name = a elif o in ("-o", "--output"): output_file = codecs.open(a, "w") + output_file_name = a elif o in ("-x", "--exclude-html-headers"): exclude_headers = True elif o in ("-t", "--tabsize"): @@ -427,16 +520,27 @@ def main(): show_CR = True elif o in ("-k", "--show-hunk-infos"): show_hunk_infos = True + elif o in ("-a", "--algorithm"): + algorithm = int(a) else: assert False, "unhandled option" - parse_input(input_file, output_file, + + # Use stdin if not input file is set + if not ('input_file' in locals()): + input_file = codecs.getreader(encoding)(sys.stdin) + + # Use stdout if not output file is set + if not ('output_file' in locals()): + output_file = codecs.getwriter(encoding)(sys.stdout) + + parse_input(input_file, output_file, input_file_name, output_file_name, exclude_headers, show_hunk_infos) def parse_from_memory(txt, exclude_headers, show_hunk_infos): " Parses diff from memory and returns a string with html " input_stream = StringIO.StringIO(txt) output_stream = StringIO.StringIO() - parse_input(input_stream, output_stream, exclude_headers, show_hunk_infos) + parse_input(input_stream, output_stream, '', '', exclude_headers, show_hunk_infos) return output_stream.getvalue()