Back to index

plone3  3.1.7
differ.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """ Diff utilities for comparing configurations.
00014 
00015 $Id: differ.py 76500 2007-06-08 15:01:44Z jens $
00016 """
00017 
00018 from difflib import unified_diff
00019 import re
00020 
00021 from Globals import InitializeClass
00022 from AccessControl import ClassSecurityInfo
00023 
00024 from interfaces import SKIPPED_FILES
00025 
00026 BLANKS_REGEX = re.compile( r'^\s*$' )
00027 
00028 def unidiff( a
00029            , b
00030            , filename_a='original'
00031            , timestamp_a=None
00032            , filename_b='modified'
00033            , timestamp_b=None
00034            , ignore_blanks=False
00035            ):
00036     r"""Compare two sequences of lines; generate the resulting delta.
00037 
00038     Each sequence must contain individual single-line strings
00039     ending with newlines. Such sequences can be obtained from the
00040     `readlines()` method of file-like objects.  The delta
00041     generated also consists of newline-terminated strings, ready
00042     to be printed as-is via the writeline() method of a file-like
00043     object.
00044 
00045     Note that the last line of a file may *not* have a newline;
00046     this is reported in the same way that GNU diff reports this.
00047     *This method only supports UNIX line ending conventions.*
00048 
00049         filename_a and filename_b are used to generate the header,
00050         allowing other tools to determine what 'files' were used
00051         to generate this output.
00052 
00053         timestamp_a and timestamp_b, when supplied, are expected
00054         to be last-modified timestamps to be inserted in the
00055         header, as floating point values since the epoch.
00056 
00057     Example:
00058 
00059     >>> print ''.join(UniDiffer().compare(
00060     ...     'one\ntwo\nthree\n'.splitlines(1),
00061     ...     'ore\ntree\nemu\n'.splitlines(1))),
00062     +++ original
00063     --- modified
00064     @@ -1,3 +1,3 @@
00065     -one
00066     +ore
00067     -two
00068     -three
00069     +tree
00070     +emu
00071     """
00072     if isinstance( a, basestring ):
00073         a = a.splitlines()
00074 
00075     if isinstance( b, basestring ):
00076         b = b.splitlines()
00077 
00078     if ignore_blanks:
00079         a = [ x for x in a if not BLANKS_REGEX.match( x ) ]
00080         b = [ x for x in b if not BLANKS_REGEX.match( x ) ]
00081 
00082     return unified_diff( a
00083                        , b
00084                        , filename_a
00085                        , filename_b
00086                        , timestamp_a
00087                        , timestamp_b
00088                        , lineterm=""
00089                        )
00090 
00091 class ConfigDiff:
00092 
00093     security = ClassSecurityInfo()
00094 
00095     def __init__( self
00096                 , lhs
00097                 , rhs
00098                 , missing_as_empty=False
00099                 , ignore_blanks=False
00100                 , skip=SKIPPED_FILES
00101                 ):
00102         self._lhs = lhs
00103         self._rhs = rhs
00104         self._missing_as_empty = missing_as_empty
00105         self._ignore_blanks=ignore_blanks
00106         self._skip = skip
00107 
00108     security.declarePrivate( 'compareDirectories' )
00109     def compareDirectories( self, subdir=None ):
00110 
00111         lhs_files = self._lhs.listDirectory( subdir, self._skip )
00112         if lhs_files is None:
00113             lhs_files = []
00114 
00115         rhs_files = self._rhs.listDirectory( subdir, self._skip )
00116         if rhs_files is None:
00117             rhs_files = []
00118 
00119         added = [ f for f in rhs_files if f not in lhs_files ]
00120         removed = [ f for f in lhs_files if f not in rhs_files ]
00121         all_files = lhs_files + added
00122         all_files.sort()
00123 
00124         result = []
00125 
00126         for filename in all_files:
00127 
00128             if subdir is None:
00129                 pathname = filename
00130             else:
00131                 pathname = '%s/%s' % ( subdir, filename )
00132 
00133             if filename not in added:
00134                 isDirectory = self._lhs.isDirectory( pathname )
00135             else:
00136                 isDirectory = self._rhs.isDirectory( pathname )
00137 
00138             if not self._missing_as_empty and filename in removed:
00139 
00140                 if isDirectory:
00141                     result.append( '** Directory %s removed\n' % pathname )
00142                     result.extend( self.compareDirectories( pathname ) )
00143                 else:
00144                     result.append( '** File %s removed\n' % pathname )
00145 
00146             elif not self._missing_as_empty and filename in added:
00147 
00148                 if isDirectory:
00149                     result.append( '** Directory %s added\n' % pathname )
00150                     result.extend( self.compareDirectories( pathname ) )
00151                 else:
00152                     result.append( '** File %s added\n' % pathname )
00153 
00154             elif isDirectory:
00155 
00156                 result.extend( self.compareDirectories( pathname ) )
00157 
00158                 if ( filename not in added + removed and
00159                     not self._rhs.isDirectory( pathname ) ):
00160 
00161                     result.append( '** Directory %s replaced with a file of '
00162                                    'the same name\n' % pathname )
00163 
00164                     if self._missing_as_empty:
00165                         result.extend( self.compareFiles( filename, subdir ) )
00166             else:
00167                 if ( filename not in added + removed and
00168                      self._rhs.isDirectory( pathname ) ):
00169 
00170                     result.append( '** File %s replaced with a directory of '
00171                                    'the same name\n' % pathname )
00172 
00173                     if self._missing_as_empty:
00174                         result.extend( self.compareFiles( filename, subdir ) )
00175 
00176                     result.extend( self.compareDirectories( pathname ) )
00177                 else:
00178                     result.extend( self.compareFiles( filename, subdir ) )
00179 
00180         return result
00181 
00182     security.declarePrivate( 'compareFiles' )
00183     def compareFiles( self, filename, subdir=None ):
00184 
00185         if subdir is None:
00186             path = filename
00187         else:
00188             path = '%s/%s' % ( subdir, filename )
00189 
00190         lhs_file = self._lhs.readDataFile( filename, subdir )
00191         lhs_time = self._lhs.getLastModified( path )
00192 
00193         if lhs_file is None:
00194             assert self._missing_as_empty
00195             lhs_file = ''
00196             lhs_time = 0
00197 
00198         rhs_file = self._rhs.readDataFile( filename, subdir )
00199         rhs_time = self._rhs.getLastModified( path )
00200 
00201         if rhs_file is None:
00202             assert self._missing_as_empty
00203             rhs_file = ''
00204             rhs_time = 0
00205 
00206         if lhs_file == rhs_file:
00207             diff_lines = []
00208         else:
00209             diff_lines = unidiff( lhs_file
00210                                 , rhs_file
00211                                 , filename_a=path
00212                                 , timestamp_a=lhs_time
00213                                 , filename_b=path
00214                                 , timestamp_b=rhs_time
00215                                 , ignore_blanks=self._ignore_blanks
00216                                 )
00217             diff_lines = list( diff_lines ) # generator
00218 
00219         if len( diff_lines ) == 0: # No *real* difference found
00220             return []
00221 
00222         diff_lines.insert( 0, 'Index: %s' % path )
00223         diff_lines.insert( 1, '=' * 67 )
00224 
00225         return diff_lines
00226 
00227     security.declarePrivate( 'compare' )
00228     def compare( self ):
00229         return '\n'.join( [str(line) for line in self.compareDirectories()] )
00230 
00231 InitializeClass( ConfigDiff )