#!/usr/bin/python # standard modules import os from xml.sax.saxutils import escape import datetime import difflib import urllib import locale # custom modules import defs from usage import usage from procs import procs HTML_DOCTYPE='\n' HTML_TAG='\n' ENCODING_TAG='\n' NO_CACHE_TAG='\n' MAIN_CSS = '\n' DIFF_CSS = '\n' SDIFF_SHORT_CSS = '\n' MAIN_SCRIPT='\n' DIFF_SCRIPT = '\n' TOOLS_SCRIPT = '\n' COMMON_SCRIPT = '\n' BIN_REP_SCRIPT = '\n' ZIP_SCRIPT = '\n' SETMAXPOS_SCRIPT = '\n' INDEX_FILE = "index.html" lc, enc = locale.getdefaultlocale() tr_classes = { "added": "added", "modified": "modified", "deleted": "deleted", "properties modified": "pmodified", "renamed": "renamed" } # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * def alert(a, b): if a!=b: print("a="+a+"b="+b) # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * def print_link(link, title): return '%s' % (urlize(link).replace('\\', '/'), title) # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * def split_author(author): if '@' in author: if '<' in author: name, address = author.rsplit('<', 1) address = address.strip('< >') elif ' ' in author: name, address = author.rsplit(' ', 1) else: name = author address = "" return name.strip(), address.strip() return author, None def parse_author(author, no_addr = False): if '@' in author: name, address = split_author(author) if no_addr: return escape(name) else: return '%(n)s'\ '%(n)s <%(a)s>' % {'n': escape(name), 'a': escape(address) } else: return author # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * def make_main_page(diffs, patches): title = "Review Pages" index = open(os.path.join(defs.target_dir, INDEX_FILE), "w") index.write(HTML_DOCTYPE) index.write(HTML_TAG) index.write("%s\n" % title) index.write(ENCODING_TAG) index.write(NO_CACHE_TAG) res_path = (usage.res+"/" if usage.res else "") index.write('\n' \ % defs.id) index.write(MAIN_CSS % res_path) # % str(uuid.uuid1()).replace('-','')) if not usage.options.no_tools: index.write(COMMON_SCRIPT % res_path) index.write(MAIN_SCRIPT % res_path) index.write(BIN_REP_SCRIPT % res_path) index.write(ZIP_SCRIPT % res_path) index.write('\n') else: index.write('\n') index.write('

%s

\n' % title) for range in usage.ranges: index.write('\n' % str(range)) #index.write('
') index.write('\n') index.write('\n') maxspan = 6 if usage.options.no_tools else 8 if not usage.options.no_tools: index.write('\n' % (maxspan-2)) index.write('') for diff in diffs: if diff.short_path != curdir: index.write('\n' % (maxspan, os.path.join(usage.options.prefix, os.path.join(usage.options.parentpath, diff.path)))) print_item(diff, index) curdir = diff.short_path index.write("
\n') author = usage.names.get(usage.options.author, usage.options.author) if author: index.write('Author: \n' '%s
\n' % parse_author(author)) if usage.options.comments: index.write('Comments: \n') index.write('%s\n' \ % escape(usage.options.comments).replace('\n', "
") \ .replace('"','"').replace("'", "'")) index.write('

\n') authors = frozenset(usage.names.get(d.old.author, d.old.author) for d in diffs if d.old and d.old.author) if len(authors): index.write('People who may be interested in review: \n') index.write('%s
\n' % ", ".join([parse_author(a) for a in authors])) if usage.options.attach: index.write('Attachments: \n') index.write('%s
\n' % \ ", ".join(map(lambda a: '%(file)s\n' % \ {'dir':urlize(defs.attach_dir), 'file':os.path.basename(a)}, \ usage.options.attach))) if patches: index.write('Patches: \n') index.write('%s
\n' % \ ", ".join(map(lambda a: '%(file)s' % \ {'dir':urlize(defs.patch_dir), 'file':os.path.basename(a)}, \ patches))) curdir = None index.write('
' '
%s
\n") if not usage.options.no_tools: name, address = split_author(author) if not address: address = "" if not name: name = "" index.write("""
\n
\n \n
\n""" \ '
' % {'au':escape(name), 'ad':escape(address) }) index.write('
\n') index.write('\n' \ % (datetime.datetime.now().ctime(), '' %(defs.full_version, defs.project_url))) if not usage.options.no_tools: index.write('') index.write('\n') # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * def urlize(path): return urllib.pathname2url(path.decode(enc).encode("utf_8")) # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * def print_item(diff, table): table.write('\n\n\n' % diff.pathname.decode(enc).encode("utf_8")) s = list(diff.status)[0] tr_class = tr_classes[s] if s in tr_classes else "none" table.write('\n' % (diff.id, tr_class)) tds = [] # 1) Status status = str(diff.status) #[0] i = diff.new if diff.new else diff.old if i.binary: status += "
binary" if i.directory: status += "
directory" tds.append(("", status)) # 2) Name tds.append(( 'class="td_filename"', diff.name.decode(enc).encode("utf_8"))) old_p = new_p = mod_p = None if "properties modified" in diff.status: if diff.old and diff.new: d, a, m = diff.old.properties.get_diff(diff.new.properties) elif diff.old: d = diff.old.properties a, m = {}, {} else: a = diff.new.properties d, m = {}, {} def join_props(i, k): return "
".join([x+": "+i[x] for x in k ]) if diff.old and d: old_p = join_props(diff.old.properties, d) if diff.new and a: new_p = join_props(diff.new.properties, a) if all((diff.old, diff.new, m)): mod_p = "
".join([x + ": " + diff.old.properties[x] + " → " + diff.new.properties[x] for x in m ]) # 3) Old revision if diff.old: oldr = diff.old.revision if diff.old.revision else "<BASE>" if diff.new and diff.old.pathname != diff.new.pathname: oldr = diff.old.name + " at " + oldr if diff.old.author: a = parse_author(usage.names.get(diff.old.author, diff.old.author), no_addr = True) rev_code = "%s by %s" % (oldr, a) else: rev_code = oldr if not (diff.old.binary or diff.old.directory or usage.options.new_only): rev_code = print_link( os.path.join(defs.old_dir, diff.short_path, diff.name+'.txt'), rev_code) if old_p: rev_code += "
"+old_p tds.append( ("", rev_code) ) elif old_p: tds.append( ("", old_p) ) else: tds.append( (None, None) ) # 4) New revision if diff.new: newr = diff.new.revision if diff.new.revision else "<WC>" if diff.new.author: a = parse_author(usage.names.get(diff.new.author, diff.new.author), no_addr = True) rev_code = "%s by %s" % (newr, a) else: rev_code = newr if not (diff.new.binary or diff.new.directory): if not (usage.options.new_only and diff.status.has_old()): rev_code = print_link( os.path.join(defs.new_dir, diff.short_path, diff.name+'.txt'), rev_code) if new_p: rev_code += "
"+new_p tds.append( ("", rev_code) ) elif new_p: tds.append( ("", new_p) ) else: tds.append( (None, None) ) # 5) Diffs if "modified" in diff.status: if diff.has_diff: diff_txt = '%s' % print_link(diff.udiff_page, "Difference") else: diff_txt = '' if mod_p: diff_txt += "
"+mod_p tds.append( ("", diff_txt) ) elif mod_p: tds.append( ("", mod_p) ) else: tds.append( (None, None) ) # 6) Comment if diff.comment: # Comment tds.append( ('class="item_comment"', escape('\n'.join(diff.comment).decode(enc).encode("utf_8")) .replace('\n', "
") \ .replace('"','"') \ .replace("'", "'")) ) else: tds.append( (None, None) ) # 7) Button if not usage.options.no_tools: tds.append(( 'align="left"', """ """ % {'item':diff.id} ) ) tds.append(( 'class="screen"', '' % (diff.id, diff.id) )) colspans = {} td_i = tds[0] for td in tds: if None in td: colspans[td_i] += 1 else: td_i = td colspans[td_i] = 1 for td in tds: if not None in td: table.write('%s\n' % (colspans[td], td[0], td[1])) table.write("\n") # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * def write_diff_header(f, diff, difftype=0): """ Writes headers for UDIFF, SDIFF short and full files """ f.write(HTML_DOCTYPE) f.write(HTML_TAG) short_path_name = os.path.normpath(os.path.join(diff.short_path, diff.name)) f.write("%s\n" \ % short_path_name.decode(enc).encode("utf_8")) f.write(ENCODING_TAG) f.write(NO_CACHE_TAG) depth = len(short_path_name.split(os.sep)) + 1 parents = '../' * depth res_path = usage.res+"/" if usage.res else parents f.write(DIFF_CSS % res_path) f.write(DIFF_SCRIPT % res_path) if not usage.options.no_tools: f.write(BIN_REP_SCRIPT % res_path) f.write(ZIP_SCRIPT % res_path) f.write(COMMON_SCRIPT % res_path) f.write(TOOLS_SCRIPT % res_path) if difftype == 1: f.write('\n') else: f.write('\n') if difftype == 3: f.write("""""") link_1 = """\n""" if difftype != 2: if not usage.options.no_tools: f.write('') else: f.write('') else: f.write('') f.write("""
""") if difftype != 1: f.write(link_1 % ( urlize(parents + diff.udiff_page), "UDIFF")) if difftype != 2: f.write(link_1 % ( urlize(parents + diff.sdifff_page), "Full")) if difftype != 3: f.write(link_1 % ( urlize(parents + diff.sdiffs_page), "Short")) if not usage.options.no_tools: f.write(""" """) f.write('\n' + (link_1 % (urlize(parents + INDEX_FILE), "Main"))) f.write("
") f.write("""
 
""" % (defs.id, diff.id, "add_udiff_comment" if difftype == 1 else "add_sdiff_comment", "add_u_static_comment" if difftype == 1 else "add_s_static_comment")) if not usage.options.no_tools: f.write("""

""") def make_diffs(diffs): for diff in diffs: diff.copy_files() if diff.status.has_new() and diff.status.has_old() and not any( \ [ not x or x.binary or x.directory for x in [diff.old, diff.new] ]): make_sdiffs(diff) # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * def make_diff(f1, f2): def format(t, l, fl, r, fr): if l: fl += ' '*(len(l)-len(fl)) if r: fr += ' '*(len(r)-len(fr)) def add_tag(f, t): flags = { ' ': 'common', '-': 'diff', '+': 'diff', '^': 'diff', '|': 'place' } if f not in flags: f = ' ' tag = 'span' return ['<%s class="%s">' % (tag, flags[f]), t, '' % tag] if t \ else ['<%s class="%s">' % (tag, flags[f], tag)] def check_deletes(f1, f2): f0 = "" if not f2: return f0 if not f1: return f1 def skip(i, s, c): while i < len(s) and s[i] in c: i += 1 return i i = j = 0 deletes = [] while i < len(f2): if f2[i] in '-+': deletes.append(j) i = skip(i, f2, f2[i]) elif f2[i] == '^': i = skip(i, f2, f2[i]) else: j += 1 i += 1 i = j = 0 while i < len(f1): if j in deletes: f0 += '|' deletes.remove(j) if f1[i] not in '-+^': j += 1 f0 += f1[i] i += 1 if deletes: # deleted from the end of the line f0 += '|' return f0 fl0 = check_deletes(fl, fr) fr = check_deletes(fr, fl) fl = fl0 def get_changes(f): ch = [] i = 0 while i < len(f): c = f[i] x = i while i < len(f) and f[i]==c: i+=1 ch.append( (c, i-x) ) return ch ch_l = get_changes(fl) if fl else [] ch_r = get_changes(fr) if fr else [] def apply_changes(s, changes): def ignored(s): for c in s: if c not in ' \t\r\n': return False return True result = [] i = 0 skip = usage.options.nospaces for change in changes: ch, l = change if ch == '|': result += add_tag(ch, '') else: sl = s[i:i+l] if usage.options.nospaces and ignored(sl): result += [escape(sl)] else: result += add_tag(ch, escape(sl)) if ch != ' ': skip = False i += l return (skip, result) lskip = rskip = not usage.options.nospaces if fl and ch_l: if len(ch_l) > 1: lskip, l = apply_changes(l, ch_l) elif usage.options.nospaces: if (not l or not l.strip()) and not r: lskip = True if fr and ch_r: if len(ch_r) > 1: rskip, r = apply_changes(r, ch_r) elif usage.options.nospaces: if (not r or not r.strip()) and not l: rskip = True if l != None: if len(ch_l) < 2: l = [ escape(l) ] if r != None: if len(ch_r) < 2: r = [ escape(r) ] if usage.options.nospaces and lskip and rskip: t = '' td_classes = {0: 'common', 1: 'unique', 2: 'diff'} # 0 - common, 1 - unique, 2 - diff state = 0 if not t else 1 if t in 'lr' else 2 if l != None: l = ['' % td_classes[state]] + l + [''] if r != None: r = ['' % td_classes[state]] + r + [''] return (t, l, r) left = right = fl = fr = flag = status = None nl = nr = 0 lines = [] # lno, lline, rno, rline t = '' for line in difflib.ndiff(f1, f2): flag = line[0] if flag in ' -+': if (left != None or right != None) \ and not (status == '-' and flag == '+'): t, l, r = format(t, left, fl, right, fr) lines.append( (t, nl, l, nr, r) ) fl = fr = '' left = right = None data = line[2:].rstrip('\r\n') if flag == ' ': left = right = data fl = fr = '' nl += 1 nr += 1 t = '' elif flag == '-': left = data nl += 1 fl = '' t = 'l' elif flag == '+': right = data nr += 1 fr = '' t = 'r' if left == None else 'm' status = flag elif flag == '?': if status == '-': fl = line[2:].rstrip("\r\n") elif status == '+': fr = line[2:].rstrip("\r\n") t = 'm' if left != None or right != None: t, l, r = format(t, left, fl, right, fr) lines.append( (t, nl, l, nr, r) ) return lines def make_sdiffs(diff): if diff.old.directory or diff.new.directory \ or diff.old.binary or diff.new.binary: return oldfilename = os.path.join(defs.target_dir, defs.old_dir, diff.short_path, diff.old.name+'.txt') newfilename = os.path.join(defs.target_dir, defs.new_dir, diff.short_path, diff.new.name+'.txt') if usage.options.noeol: oldLines = [ line.rstrip('\r\n') for line in file(oldfilename).readlines() ] newLines = [ line.rstrip('\r\n') for line in file(newfilename).readlines() ] else: oldLines = file(oldfilename).readlines() newLines = file(newfilename).readlines() lines = make_diff(oldLines, newLines) types = get_diff_sections(lines) if not types: diff.has_diff = False return 0 dir, name = os.path.split(os.path.join(defs.target_dir, diff.sdiffs_page)) procs.make_dirs(dir) sdiff_s = file(os.path.join(dir, name), "w") dir, name = os.path.split(os.path.join(defs.target_dir, diff.sdifff_page)) procs.make_dirs(dir) sdiff_f = file(os.path.join(dir, name), "w") dir, name = os.path.split(os.path.join(defs.target_dir, diff.udiff_page)) procs.make_dirs(dir) udiff = file(os.path.join(dir, name), "w") write_diff_header(sdiff_s, diff, difftype=3) write_diff_header(sdiff_f, diff, difftype=2) write_diff_header(udiff, diff, difftype=1) add_indicator(lines, sdiff_f) if usage.options.no_tools: s = '
\n\n' udiff.write('
\n') else: s = '
\n
\n' udiff.write('
\n') sdiff_s.write(s) sdiff_f.write(s) udiff.write('
--- %s %s
\n' '
+++ %s %s
\n' % (diff.old.pathname.decode(enc).encode("utf_8"), "(revision %s)" % diff.old.revision, diff.new.pathname.decode(enc).encode("utf_8"), "(revision %s)" % diff.new.revision \ if diff.new.revision else "(working copy)")) ch_count = write_lines(types, lines, sdiff_s, sdiff_f, udiff) #push_lines(sdiff_s, sdiff_f, left, right, commonLines, 2, diff) sdiff_s.write("\n
\n") sdiff_s.write(SETMAXPOS_SCRIPT % ch_count) sdiff_f.write("\n
\n") sdiff_f.write(SETMAXPOS_SCRIPT % ch_count) udiff.write("\n\n") udiff.write(SETMAXPOS_SCRIPT % ch_count) for f in sdiff_s, sdiff_f, udiff: f.write("") # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * def add_indicator(lines, f): f.write('
\n') f.write('
\n') left = [] right = [] both = [] state = 0 # 0 - empty, 1 - left, 2 - right, 3 - both i = 1 for line in lines: if line[0] == 'l': i = line[1] if state == 1: left[-1][1] = i else: left.append( [i, i] ) state = 1 elif line[0] == 'r': i = line[3] if state == 2: right[-1][1] = i else: right.append( [i, i] ) state = 2 elif line[0] == 'm': i = line[1] if state == 3: both[-1][1] = i else: both.append( [i, i] ) state = 3 else: state = 0 f.write("""\n""" % (str(left), str(right), str(both))) def get_diff_sections(lines): commonLines = defs.common_lines types = [] # 0 - to skip, 1 - common top, 2 - common bottom, 3 - common, 4 - diff i = 0 for line in lines: if line[0]: # found a diff line: # mark it as a diff line: if i < len(types): types[i] = 4 else: types.append(4) # mark previous commonLines as common if they're 'not 'to skip' if i: j = 1 while j < commonLines and i >= j and types[i-j] < 4: types[i-j] = 3 j += 1 # mark the toppest as common top if not types[i-j]: types[i-j] = 1 else: # found a common line if i and types[i-1] == 4: types[i:] = [3] * commonLines if types[-1]: types[-1] = 2 else: if i == len(types): types.append(0) i += 1 return types if 4 in types or 1 in types else None def write_lines(types, lines, sdiff_s, sdiff_f, udiff): tr_classes = { 0: 'tr_hide', 1: 'tr_top', 2: 'tr_bottom', 3: 'tr_show', 4: 'tr_diff' } ln_classes = { '': 'ln', 'm': 'ln_diff', 'r': 'ln_unique', 'l': 'ln_unique' } def wrap(s): def replace_tabs(s): return s.replace('\t', ' '*usage.options.tab_size) innerText = replace_tabs("".join(filter(lambda x: not x.startswith('<'), s))) result = [] def get_breaks(s, n): result = [] i = n while i < len(s): result.append(i) i += n return result if usage.options.column_width and len(innerText) > usage.options.column_width: breaks = get_breaks(innerText, usage.options.column_width) hellip = '' i = 0 b = 0 for ss in s: if ss.startswith('<'): result.append(ss) continue x = replace_tabs(ss) i += len(x) j = 0 while b < len(breaks) and i > breaks[b]: result.append(x[j:breaks[b]-i]) result.append(hellip) if b < len(breaks): j = breaks[b] - i b += 1 result.append(x[j:]) else: result = [ replace_tabs(ss) for ss in s ] return result def add_div(s, n, f): udiff_classes = { ' ': 'diff_common', '-': 'diff_del', '+': 'diff_add' } return '
%s%s
\n' % ( udiff_classes[f], n, f, "".join(filter(lambda x: not x.startswith('<'), s))) \ if s else \ '
%s
\n' % (udiff_classes[f], n, f) i = 0 ch = 0 for (t, nl, l, nr, r) in lines: ln_class = ln_classes[t] if l == None: td_l = '' else: td_l = '%(nl)d%(l)s' % \ {'nl': nl, 'l': "".join(wrap(l)), 'ls': ln_class} if r == None: td_r = '' else: td_r = '%(nr)d%(r)s\n' % \ {'nr': nr, 'r': "".join(wrap(r)), 'ls': ln_class} tr_class = tr_classes[types[i]] if types[i] == 1: ch += 1 tr = '%s\n#%s\n' % \ (tr_class, td_l, ln_class, ch, ch, td_r) else: tr = '%s\n%s\n' % \ (tr_class, td_l, ln_class, td_r) sdiff_f.write(tr) if types[i]: if types[i] == 1 or not i: nl1 = nr1 = 0 j = i while j < len(lines) and types[j] != 0: if lines[j][0] in 'lm': nl1 += 1 if lines[j][0] in 'rm': nr1 += 1 j += 1 s = "@@ -%d,%d +%d,%d @@" % (nl, nl1, nr, nr1) s = '
%s
\n' % (ch, ch, s) udiff.write(s) if types[i] < 4: udiff.write(add_div(l, "l"+str(nl), ' ')) else: # == 4 if l != None: udiff.write(add_div(l, "l"+str(nl), '-')) if r != None: udiff.write(add_div(r, "r"+str(nr), '+')) sdiff_s.write(tr) i += 1 return ch