[Erp5-report] r32272 rafael - /erp5/trunk/products/ERP5Type/DiffUtils.py
nobody at svn.erp5.org
nobody at svn.erp5.org
Thu Feb 4 20:26:19 CET 2010
Author: rafael
Date: Thu Feb 4 20:26:18 2010
New Revision: 32272
URL: http://svn.erp5.org?rev=32272&view=rev
Log:
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.
Added:
erp5/trunk/products/ERP5Type/DiffUtils.py
Added: erp5/trunk/products/ERP5Type/DiffUtils.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/DiffUtils.py?rev=32272&view=auto
==============================================================================
--- erp5/trunk/products/ERP5Type/DiffUtils.py (added)
+++ erp5/trunk/products/ERP5Type/DiffUtils.py [utf8] Thu Feb 4 20:26:18 2010
@@ -1,0 +1,302 @@
+##############################################################################
+#
+# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
+# Yoshinori Okuji <yo at nexedi.com>
+# Christophe Dumez <christophe at 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_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">
+ </td><td style="background-color: black; width: 2px;"></td>
+ <td style="background-color: %(headcolor)s"> </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)
More information about the Erp5-report
mailing list