Commit 593d9e4c authored by Rafael Monnerat's avatar Rafael Monnerat

Provide a feature not present into difflib, which is generate a colored diff

 from a diff file/string. This code is original form ERP5Subversion and was moved 
 to here for be used in general ERP5. 

 XXX The organisation of DiffUtils should be reviewed and reorganised if needed. 
 ie.: Move behaviour to a tool.



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@32272 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent fe519546
##############################################################################
#
# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
# Yoshinori Okuji <yo@nexedi.com>
# Christophe Dumez <christophe@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# 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 the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
"""
Provide a feature not present into difflib, which is generate a colored diff
from a diff file/string.
This code is original form ERP5Subversion and was moved to here for be used in
general ERP5.
XXX The organisation of DiffUtils should be reviewed and reorganised in a tool
if a general tool want to be provided.
"""
import os, re
from xml.sax.saxutils import escape
NBSP = '&nbsp;'
NBSP_TAB = NBSP*8
class DiffFile:
"""
# Members :
- path : path of the modified file
- children : sub codes modified
- old_revision
- new_revision
"""
def __init__(self, raw_diff):
if '@@' not in raw_diff:
self.binary = True
return
else:
self.binary = False
self.header = raw_diff.split('@@')[0][:-1]
# Getting file path in header
self.path = self.header.split('====')[0][:-1].strip()
# Getting revisions in header
for line in self.header.splitlines():
if line.startswith('--- '):
tmp = re.search('\\([^)]+\\)$', line)
self.old_revision = tmp.string[tmp.start():tmp.end()][1:-1].strip()
if line.startswith('+++ '):
tmp = re.search('\\([^)]+\\)$', line)
self.new_revision = tmp.string[tmp.start():tmp.end()][1:-1].strip()
# Splitting the body from the header
self.body = os.linesep.join(raw_diff.strip().splitlines()[4:])
# Now splitting modifications
self.children = []
first = True
tmp = []
for line in self.body.splitlines():
if line:
if line.startswith('@@') and not first:
self.children.append(CodeBlock(os.linesep.join(tmp)))
tmp = [line, ]
else:
first = False
tmp.append(line)
self.children.append(CodeBlock(os.linesep.join(tmp)))
def toHTML(self):
""" return HTML diff
"""
# Adding header of the table
if self.binary:
return '<b>Folder or binary file or just no changes!</b><br/><br/><br/>'
html_list = []
html_list.append('''
<table style="text-align: left; width: 100%%; border: 0;" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td style="background-color: grey; text-align: center; font-weight: bold;">%s</td>
<td style="background-color: black; width: 2px;"></td>
<td style="background-color: grey; text-align: center; font-weight: bold;">%s</td>
</tr>''' % (self.old_revision, self.new_revision))
header_color = 'grey'
child_html_text = '''<tr><td style="background-color: %(headcolor)s">
&nbsp;</td><td style="background-color: black; width: 2px;"></td>
<td style="background-color: %(headcolor)s">&nbsp;</td></tr><tr>
<td style="background-color: rgb(68, 132, 255);font-weight: bold;">Line %(oldline)s</td>
<td style="background-color: black; width: 2px;"></td>
<td style="background-color: rgb(68, 132, 255);font-weight: bold;">Line %(newline)s</td>
</tr>'''
for child in self.children:
# Adding line number of the modification
html_list.append( child_html_text % {'headcolor':header_color, 'oldline':child.old_line, 'newline':child.new_line} )
header_color = 'white'
# Adding diff of the modification
old_code_list = child.getOldCodeList()
new_code_list = child.getNewCodeList()
i = 0
for old_line_tuple in old_code_list:
new_line_tuple = new_code_list[i]
new_line = new_line_tuple[0] or ' '
old_line = old_line_tuple[0] or ' '
i += 1
html_list.append( '''<tr>
<td style="background-color: %s">%s</td>
<td style="background-color: black; width: 2px;"></td>
<td style="background-color: %s">%s</td>
</tr>'''%(old_line_tuple[1],
escape(old_line).replace(' ', NBSP).replace('\t', NBSP_TAB),
new_line_tuple[1],
escape(new_line).replace(' ', NBSP).replace('\t', NBSP_TAB))
)
html_list.append('''</tbody></table><br/>''')
return '\n'.join(html_list)
class CodeBlock:
"""
A code block contains several SubCodeBlocks
Members :
- old_line : line in old code (before modif)
- new line : line in new code (after modif)
Methods :
- getOldCodeList() : return code before modif
- getNewCodeList() : return code after modif
Note: the code returned is a list of tuples (code line, background color)
"""
def __init__(self, raw_diff):
# Splitting body and header
self.body = os.linesep.join(raw_diff.splitlines()[1:])
self.header = raw_diff.splitlines()[0]
# Getting modifications lines
tmp = re.search('^@@ -\d+', self.header)
self.old_line = tmp.string[tmp.start():tmp.end()][4:]
tmp = re.search('\+\d+', self.header)
self.new_line = tmp.string[tmp.start():tmp.end()][1:]
# Splitting modifications in SubCodeBlocks
in_modif = False
self.children = []
tmp = []
for line in self.body.splitlines():
if line:
if (line.startswith('+') or line.startswith('-')):
if in_modif:
tmp.append(line)
else:
self.children.append(SubCodeBlock(os.linesep.join(tmp)))
tmp = [line, ]
in_modif = True
else:
if in_modif:
self.children.append(SubCodeBlock(os.linesep.join(tmp)))
tmp = [line, ]
in_modif = False
else:
tmp.append(line)
self.children.append(SubCodeBlock(os.linesep.join(tmp)))
def getOldCodeList(self):
""" Return code before modification
"""
tmp = []
for child in self.children:
tmp.extend(child.getOldCodeList())
return tmp
def getNewCodeList(self):
""" Return code after modification
"""
tmp = []
for child in self.children:
tmp.extend(child.getNewCodeList())
return tmp
class SubCodeBlock:
""" a SubCodeBlock contain 0 or 1 modification (not more)
"""
def __init__(self, code):
self.body = code
self.modification = self._getModif()
self.old_code_length = self._getOldCodeLength()
self.new_code_length = self._getNewCodeLength()
# Choosing background color
if self.modification == 'none':
self.color = 'white'
elif self.modification == 'change':
self.color = 'rgb(253, 228, 6);'#light orange
elif self.modification == 'deletion':
self.color = 'rgb(253, 117, 74);'#light red
else: # addition
self.color = 'rgb(83, 253, 74);'#light green
def _getModif(self):
""" Return type of modification :
addition, deletion, none
"""
nb_plus = 0
nb_minus = 0
for line in self.body.splitlines():
if line.startswith("-"):
nb_minus -= 1
elif line.startswith("+"):
nb_plus += 1
if (nb_plus == 0 and nb_minus == 0):
return 'none'
if (nb_minus == 0):
return 'addition'
if (nb_plus == 0):
return 'deletion'
return 'change'
def _getOldCodeLength(self):
""" Private function to return old code length
"""
nb_lines = 0
for line in self.body.splitlines():
if not line.startswith("+"):
nb_lines += 1
return nb_lines
def _getNewCodeLength(self):
""" Private function to return new code length
"""
nb_lines = 0
for line in self.body.splitlines():
if not line.startswith("-"):
nb_lines += 1
return nb_lines
def getOldCodeList(self):
""" Return code before modification
"""
if self.modification == 'none':
old_code = [(x, 'white') for x in self.body.splitlines()]
elif self.modification == 'change':
old_code = [self._getOldCodeList(x) for x in self.body.splitlines() \
if self._getOldCodeList(x)[0]]
# we want old_code_list and new_code_list to have the same length
if(self.old_code_length < self.new_code_length):
filling = [(None, self.color)] * (self.new_code_length - \
self.old_code_length)
old_code.extend(filling)
else: # deletion or addition
old_code = [self._getOldCodeList(x) for x in self.body.splitlines()]
return old_code
def _getOldCodeList(self, line):
""" Private function to return code before modification
"""
if line.startswith('+'):
return (None, self.color)
if line.startswith('-'):
return (' ' + line[1:], self.color)
return (line, self.color)
def getNewCodeList(self):
""" Return code after modification
"""
if self.modification == 'none':
new_code = [(x, 'white') for x in self.body.splitlines()]
elif self.modification == 'change':
new_code = [self._getNewCodeList(x) for x in self.body.splitlines() \
if self._getNewCodeList(x)[0]]
# we want old_code_list and new_code_list to have the same length
if(self.new_code_length < self.old_code_length):
filling = [(None, self.color)] * (self.old_code_length - \
self.new_code_length)
new_code.extend(filling)
else: # deletion or addition
new_code = [self._getNewCodeList(x) for x in self.body.splitlines()]
return new_code
def _getNewCodeList(self, line):
""" Private function to return code after modification
"""
if line.startswith('-'):
return (None, self.color)
if line.startswith('+'):
return (' ' + line[1:], self.color)
return (line, self.color)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment