Another diff in tcl

09.01.2012 HE: I needed a diff procedure in pure Tcl without holding the contents of the file and the result in the memory.

# Syntax:
#        filediff <file1> <file2> <cmdEqual> <cmdAdd> <cmdDel>
# Description:
#        Compare two files line by line directly from the local filesystem and provide the result with the stated procs.
# with:
#         <file1>    = One of the files to compare.
#        <file2>    = The other file to compare.
#        <cmdEqual> = Procedure to call if two lines are equal. Default is 'cmdEqual'.
#        <cmdAdd>   = Procedure to call if an addtional row is found in <file2>. Default is 'cmdAdd'.
#        <cmdDel>   = Procedure to call if an row is found which not exist in <file2>. Default is 'cmdDel'.
#
# Create your own procedures to output the result if you need anything different than simply write to stdout with puts

proc filediff {file1 file2 {cmdEqual {cmdEqual}} {cmdAdd {cmdAdd}} {cmdDel {cmdDel}}} {
        set sourcefid1 [open $file1 r]
        set sourcefid2 [open $file2 r]

        set found 1
        while {![eof $sourcefid1] && ![eof $sourcefid2]} {
                set lastmark [tell $sourcefid2] ;# Position in <file2> before reading the next line
                gets $sourcefid1 line1
                gets $sourcefid2 line2

                if {$line1 eq $line2} {
                        $cmdEqual $line1
                        continue
                }

                # Lines with only whitespace are also equal
                if {[regexp -- {^\s*$} $line1] && [regexp -- {^\s*$} $line2]} {
                        $cmdEqual {}
                        continue
                }

                # From here both lines are unequal

                set state 0
                while {[regexp -- {^\s*$} $line1]} {
                        # If unequal then directly state empty lines in <file1> as deleted.
                        $cmdDel $line1
                        if {![eof $sourcefid1]} {
                                gets $sourcefid1 line1
                                if {$line1 eq $line2} {
                                        $cmdEqual $line1
                                        set state 1
                                        break
                                }
                        } else {
                                break
                        }
                }
                if {$state} {
                        continue
                }
                
                # Remember position in <file2> and look forward
                set mark2  [tell $sourcefid2]
                set mark2a $lastmark
                set found 0
                while {![eof $sourcefid2]} {
                        gets $sourcefid2 line2
                        if {$line1 ne $line2} {
                                set mark2a $mark2
                                set mark2 [tell $sourcefid2]
                        } else {
                                # Found a matching line. Everything up to the line before are new lines
                                seek $sourcefid2 $lastmark
                                while {[tell $sourcefid2] <= $mark2a} {
                                        gets $sourcefid2 line2
                                        $cmdAdd $line2
                                }
                                gets $sourcefid2 line2
                                $cmdEqual $line2
                                set found 1
                                break
                        }
                }
                if {!$found} {
                        # No matching line found in <file2>. Line must be deleted
                        $cmdDel $line1
                        seek $sourcefid2 $lastmark
                }
        }
        # Output the rest of <file1> as deleted
        while {![eof $sourcefid1]} {
                gets $sourcefid1 line1
                $cmdDel $line1
        }

        # Output the rest of <file2> as added
        while {![eof $sourcefid2]} {
                gets $sourcefid2 line2
                $cmdAdd $line2
        }
        close $sourcefid2
        close $sourcefid1
        return
}

proc cmdEqual {txt} {
        global n
        puts "=$txt";update
}

proc cmdAdd {txt} {
        global n
        puts "+$txt";update
}

proc cmdDel {txt} {
        global n
        puts "-$txt";update
}

See also: