src/cm/diff.py
changeset 149 0f2c5744b39b
parent 148 03106bfa4845
child 150 db7bc2e89156
equal deleted inserted replaced
148:03106bfa4845 149:0f2c5744b39b
     1 import difflib, string
       
     2 from django.utils.translation import ugettext as _
       
     3 
       
     4 def isatag(x): return x.startswith("<") and x.endswith(">")
       
     5 
       
     6 def text_diff(a, b):
       
     7     res = []
       
     8     a, b = createlist(a), createlist(b)
       
     9     s = difflib.SequenceMatcher(isatag, a, b)
       
    10     for e in s.get_opcodes():
       
    11         if e[0] == "replace":
       
    12             res.append('<del class="diff modified">'+''.join(a[e[1]:e[2]]) + '</del><ins class="diff modified">'+''.join(b[e[3]:e[4]])+"</ins>")
       
    13         elif e[0] == "delete":
       
    14             res.append('<del class="diff">'+ ''.join(a[e[1]:e[2]]) + "</del>")
       
    15         elif e[0] == "insert":
       
    16             res.append('<ins class="diff">'+''.join(b[e[3]:e[4]]) + "</ins>")
       
    17         elif e[0] == "equal":
       
    18             res.append(''.join(b[e[3]:e[4]]))
       
    19         else: 
       
    20             raise "error parsing %s" %(e[0])
       
    21     return ''.join(res)
       
    22 
       
    23 COLORS = [
       
    24 '#FF0000',
       
    25 '#EE0000',
       
    26 '#DD0000',
       
    27 '#CC0000',
       
    28 '#BB0000',
       
    29 '#AA0000',
       
    30 '#990000',
       
    31 '#880000',
       
    32 '#770000',
       
    33 ]
       
    34 
       
    35 from colorsys import hls_to_rgb, hsv_to_rgb
       
    36 
       
    37 def generatePastelColors(n):
       
    38     s = .4
       
    39     v = .99
       
    40     return ['#'+''.join((hex(int(r*255))[2:],hex(int(g*255))[2:],hex(int(b*255))[2:])) for r,g,b in [hsv_to_rgb(h/200.,s,v) for h in range(1,100,100/n)]]
       
    41     
       
    42 
       
    43 DEFAULT_COLOR = '#D9D9D9'
       
    44 def get_colors(authors, last_white = False):
       
    45     """
       
    46     returns a dict for authors's colors (with last one white if 'last_white')
       
    47     """
       
    48     authors = set(authors)
       
    49     #new_colors = list(tuple(COLORS))
       
    50     new_colors = generatePastelColors(len(set(authors)))
       
    51     res = []
       
    52     if None in authors or '' in authors:
       
    53         res = [(_(u'unknown'),DEFAULT_COLOR) ]
       
    54     res.extend([(author,new_colors.pop()) for author in authors if author])
       
    55     #if authors[-1]:
       
    56     #    res[authors[-1]] = '#FFFFFF'
       
    57     return res
       
    58     
       
    59 
       
    60 def text_history(versions, authors):
       
    61     res = versions[0]
       
    62     colors = get_colors(authors)
       
    63     for ind in range(len(versions)-1):
       
    64         author = authors[ind]
       
    65         color = colors.get(author,DEFAULT_COLOR)
       
    66         v_2 = versions[ind + 1]
       
    67         res = text_diff_add(v_2, res, color)
       
    68     return res,colors.items()
       
    69 
       
    70 from cm.utils.html import surrond_text_node
       
    71  
       
    72 def text_diff_add(a,b, color):
       
    73     res = []
       
    74     a, b = createlist(a), createlist(b)
       
    75     s = difflib.SequenceMatcher(isatag, a, b)
       
    76     for e in s.get_opcodes():
       
    77         if e[0] == "replace":
       
    78             html_chunk = ''.join(b[e[3]:e[4]])
       
    79             new_html_chunk = surrond_text_node(html_chunk,'<span style="background: %s;">' %color,'</span>')
       
    80             res.append(new_html_chunk)
       
    81             #res.append('<font style="background: %s;" class="diff modified">' %color+''.join(b[e[3]:e[4]])+"</font>")
       
    82         elif e[0] == "delete":
       
    83             pass
       
    84         elif e[0] == "insert":
       
    85             html_chunk = ''.join(b[e[3]:e[4]])
       
    86             new_html_chunk = surrond_text_node(html_chunk,'<span style="background: %s;">' %color,'</span>') 
       
    87             res.append(new_html_chunk)
       
    88             #res.append('<font style="background: %s;" class="diff">' %color+''.join(b[e[3]:e[4]]) + "</font>")
       
    89         elif e[0] == "equal":
       
    90             res.append(''.join(b[e[3]:e[4]]))
       
    91         else: 
       
    92             raise "error parsing %s" %(e[0])
       
    93     return ''.join(res)
       
    94 
       
    95 def createlist(x, b=0):
       
    96     mode = 'char'
       
    97     cur = ''
       
    98     out = []
       
    99     for c in x:
       
   100         if mode == 'tag':
       
   101             if c == '>': 
       
   102                 if b: cur += ']'
       
   103                 else: cur += c
       
   104                 out.append(cur); cur = ''; mode = 'char'
       
   105             else: cur += c
       
   106         elif mode == 'char':
       
   107             if c == '<': 
       
   108                 out.append(cur)
       
   109                 if b: cur = '['
       
   110                 else: cur = c
       
   111                 mode = 'tag'
       
   112             elif c in string.whitespace: out.append(cur+c); cur = ''
       
   113             else: cur += c
       
   114     out.append(cur)
       
   115     return filter(lambda x: x is not '', out)