[Keith Vetter] 2003-11-07 : There's been a recent hue and cry for "a good diffs module" (see [There is a huge need for a diffs module!]). Here's something I've been using for half a year or so. You enter a wiki page number and it displays the revision history for that page. You then select any two entries, click a button and it will fetch those two revisions and run [tkdiff] on them (or if you prefer you can use ExamDiff [http://www.prestosoft.com/ps.asp?page=edp_examdiff]). This works reasonably well but with two flaws: first, the information in the wiki history seems to be a day behind the wiki itself, and second, wiki pages often contain very long lines that don't have good diff behavior. [WikiDiff] handles this second problem in a very clever way by doing a word level diff. [KPV] It seems these two flaws have been addressed. The first via an undocumented interface that lets you access the latest version; the second by the new version of [tkdiff] (4.0b1) that does inline comparisons. [KPV] 2004-01-23: Added Recents window, a Tkhtml widget showing Wiki page 4--Recent changes. Clicking on any link will put its Wiki page number into the Wiki Page entry. Double clicking any link will fetch that page's history. ---- ''Keith, FYI, on Mac OS X the above needs font "courier 10" to be readable (with X11). Also note that this works fine with the tkdiff starkit on [sdarchive]. Great utility! -[jcw]'' ---- [KPV] 2007-06-12: I updated the code to use the new wiki history format. [LV] Hi! I was wondering - I am seeing the error: diff failed: Warning: missing newline at end of file /tmp/wiki.10335.27 Warning: missing newline at end of file /tmp/wiki.10335.28 when I select page 10335 and ask for a difference in the last two versions... [KPV] This utility uses [tkdiff] for its diff, and that in turn uses diff, the many versions of which behave differently on missing newline handlng. I'm using cygwin diff v2.8.7 on windows without a problem. You could see if your diff takes a command line argument to ignore irrelevant whitespace. Another alternative is to use a different visual diff program, such as Examdiff. ---- [LV] 2007 June 13 - anyone know what version of [tkhtml] this code is expecting? When I try to run it, I get the error: unknown option "-background" while executing "html .r.h -yscrollcommand {.r.v set} -background white -height 500" (procedure "Recents" line 10) invoked from within "Recents" (file "/projects/xopsrc/bin/wikidiff.tcl" line 330) [LV] looks like it is using Tkhtml 2. Anyone know how to update this so that it works for either version 2 or 3? [KPV] Ugh. I might just throw away Tkhtml and use the rss feed directly. ---- ##+########################################################################## # # Wiki History Diff.tcl -- diffs back versions of the tcl Wiki pages # by Keith Vetter # # Revisions: # KPV Mar 11, 2003 - initial revision # KPV Nov 07, 2003 - some touch up work # KPV Nov 10, 2003 - added JCW's undocumented way of fetching latest version # KPV Jan 23, 2004 - added Recents window showing Wiki page 4 # KPV Jun 12, 2007 - updated to use new history format # package require Tk package require http set S(tkhtml) [expr {! [catch {package require Tkhtml 2}]}] set S(diffprog) tkdiff ;# Examdiff.exe works well too set S(title) "Wiki History Diff" set S(bg) "#9977cc" set S(pnum) "xx" ;# Page we want to compare set S(pnum2) "" ;# Entry widget version of pnum set S(busy) 0 set S(font) {courier 8} if {$tcl_platform(machine) == "Power Macintosh"} { set S(font) {courier 10} } # Stupid temp directory set S(tmp) [pwd] if {[file isdirectory "c:/temp"]} { set S(tmp) "c:/temp"} if {[file isdirectory "/tmp"]} { set S(tmp) "/tmp"} catch {set S(tmp) $env(TRASH_FOLDER)} ;# Macintosh(?) catch {set S(tmp) $env(TMPDIR)} catch {set S(tmp) $env(TMP)} catch {set S(tmp) $env(TEMP)} proc INFO {msg} {puts [set ::S(msg) $msg]} proc DoDisplay {} { global S wm title . "Wiki History Diff" wm protocol . WM_DELETE_WINDOW {Cleanup 1} wm geom . +10+10 frame .bottom pack .bottom -side bottom -fill x button .recent -text "Show Recent Changes" -command Recents if {! $S(tkhtml)} { .recent config -state disabled} label .msg -textvariable S(msg) button .about -text About -command About pack .recent -in .bottom -side left -padx .1i pack .about -in .bottom -side right -padx .1i pack .msg -in .bottom -side bottom -fill x option add *Label.background $S(bg) option add *Label.foreground white option add *Button.background $S(bg) option add *Button.activeBackground $S(bg) option add *Button.foreground white option add *Button.activeForeground white frame .top -bg $S(bg) -relief groove -bd 2 frame .topl -bg $S(bg) button .topl.wiki -text "Wiki Page " -command DoWikiPage -relief flat entry .topl.ewiki -textvariable S(pnum2) -relief sunken -width 6 bind .topl.ewiki [list .topl.wiki invoke] label .top.title -textvariable S(title) -fg magenta trace variable S(pnum2) w tracer set font [font actual [.top.title cget -font]] .top.title config -font "$font -weight bold -size 18" frame .topr -bg $S(bg) label .topr.l0 -text "Version " label .topr.e0 -textvar S(left) -width 5 -relief sunken label .topr.v -text " vs. " label .topr.l1 -text "Version " label .topr.e1 -textvar S(right) -width 5 -relief sunken label .topr.= -text " => " button .topr.diff -text "Run $S(diffprog)" -command RunTKDiff -bd 5 pack .top -side top -fill x pack .topr -in .top -side right pack .topl -in .top -side left pack .top.title -side top -expand 1 eval pack [winfo child .topl] -side left eval pack [winfo child .topr] -side left pack config .topl.wiki -padx {.1i .05i} pack config .topr.diff -padx {0 .1i} pack [frame .mid -bd 4 -relief ridge] -side top -fill both -expand 1 foreach w {0 1} side {left right} { listbox .l$w -exportselection 0 -width 63 -activestyle none -bd 0 \ -font {courier 8} -yscrollcommand [list .sb$w set] -height 20 bind .l$w <> [list ListboxSelect %W $side] bind .l$w <1> [list focus %W] scrollbar .sb$w -orient v -command [list .l$w yview] } grid .l0 .sb0 .l1 .sb1 -in .mid -sticky news grid rowconfigure .mid 0 -weight 1 grid columnconfigure .mid {0 2} -weight 1 option clear bind all {console show} bind all exit MouseWheelBind focus .topl.ewiki } proc ListboxSelect {W side} { global S set row [$W curselection] set data [$W get $row] set S($side) [lindex [split [string trimleft $data] " "] 0] } proc tracer {var1 var2 op} { global S if {$S(pnum2) == $S(pnum)} { .topl.ewiki config -bg [lindex [.topl.ewiki config -bg] 3] .topl.wiki config -relief flat } else { .topl.ewiki config -bg red .topl.wiki config -relief raised } } proc GetVersionInfo {} { global S .l0 delete 0 end .l1 delete 0 end set S(left) [set S(right) ""] update set url "http://wiki.tcl.tk/_history/$S(pnum)" set token [::http::geturl $url] set data [::http::data $token] ; list ::http::cleanup $token set S(title) "No Wiki History" regexp -line {Change history of.*?>(.*?)} $data => S(title) set S(title) "\"$S(title)\"" foreach line [split $data "\n"] { if {! [string match "} $line {} line set matches [regexp -all -inline {(.*?)} $line] foreach {. vnum . vwhen . vwho} $matches break set txt [format " %3s %-20s %s" $vnum $vwho $vwhen] .l0 insert end $txt .l1 insert end $txt } .l0 selection clear 0 end .l0 selection set 1 .l0 selection anchor 1 event generate .l0 <> .l1 selection clear 0 end .l1 selection set 0 .l1 selection anchor 0 event generate .l1 <> } proc GetRevision {pnum ver} { set fname [file join $::S(tmp) "wiki.$pnum.$ver"] set fout [open $fname "w"] # http://wiki.tcl.tk/_revision/606.txt?V=156 set url "http://wiki.tcl.tk/_revision/${pnum}.txt?V=$ver" set token [::http::geturl $url -channel $fout] puts $fout "" ; # added by lv to handle missing newline close $fout ::http::cleanup $token return $fname } proc RunTKDiff {} { global S Cleanup if {$S(left) eq "" || $S(left) eq ""} return set f1 [GetRevision $S(pnum) $S(left)] set f2 [GetRevision $S(pnum) $S(right)] exec $S(diffprog) $f1 $f2 & set ::TMPFILES($f1) 1 set ::TMPFILES($f2) 1 after 2000 ;# Pause to let tkdiff start } proc DoWikiPage {} { global S set S(busy) 1 while {1} { after 10 Cleanup .topl.ewiki icursor end if {! [string is integer -strict $S(pnum2)]} break if {$S(pnum) == $S(pnum2)} break set S(pnum) $S(pnum2) GetVersionInfo set S(pnum2) $S(pnum2) } set S(busy) 0 } proc Cleanup {{exit 0}} { global TMPFILES foreach fname [array names TMPFILES] { set n [catch {file delete $fname}] if {! $n} { unset TMPFILES($fname) } } if {$exit} exit } proc About {} { set msg "WikiDiff\nby Keith Vetter\nMarch 2003\n\n" append msg "Compares back revisions of a\nTcl'ers Wiki " append msg "page using tkdiff." tk_messageBox -title "About" -message $msg } proc Recents {} { if {! $::S(tkhtml)} return destroy .r toplevel .r wm title .r "WIKI Recent Changes" wm transient .r . wm geom .r +10+470 scrollbar .r.v -o v -command {.r.h yv} html .r.h -yscrollcommand {.r.v set} -background white -height 500 bind .r.h.x <1> [list Click 1 %x %y] bind .r.h.x [list Click 2 %x %y] bind .r.h.x [list 3Click %x %y] #bind .r.h [bind Text ] pack .r.v -side right -fill y pack .r.h -side left -fill both -expand 1 FillRecents } proc FillRecents {} { .r.h clear .r.h parse "

Downloading Recent Changes..." #update idletask update set url "http://mini.net/tcl/4.html" set url "http://wiki.tcl.tk/4" set n [catch { set token [::http::geturl $url] }] if {$n} { ;# Error .r.h clear .r.h parse "

Error loading Recent Changes" return } set data [::http::data $token] ; list ::http::cleanup $token .r.h clear .r.h config -base $url .r.h parse $data set ::data $data focus .r.h } proc Click {cnt x y} { if {$::S(busy)} return set href [.r.h href $x $y] set ::href $href if {$href == ""} return if {$href == "{http://mini.net/tcl/4!}"} { if {$cnt == 2} FillRecents return } set ::href $href set n [regexp {/(\d+)\}?$} $href => pnum] if {! $n} return set ::S(pnum2) $pnum if {$cnt == 2} { .topl.wiki invoke } } proc 3Click {x y} { set href [.r.h href $x $y] if {$href == ""} return set n [catch {eval exec [auto_execok start] $href &} err] } proc MouseWheelBind {} { # Taken from tip #171 # Remove existing class MouseWheel bindings set mw_classes [list Text Listbox Table TreeCtrl] foreach class $mw_classes { bind $class {} } if {[tk windowingsystem] eq "x11"} { foreach class $mw_classes { bind $class <4> {} bind $class <5> {} } } proc ::tk::MouseWheel {wFired D X Y {dir y}} { # do not double-fire in case the class already has a binding if {[bind [winfo class $wFired] ] ne ""} { return } # obtain the window the mouse is over set w [winfo containing $X $Y] # if we are outside the app, try and scroll the focus widget if {![winfo exists $w]} { catch {set w [focus]} } if {[winfo exists $w]} { # scrollbars have different call conventions if {[winfo class $w] eq "Scrollbar"} { catch {tk::ScrollByUnits $w \ [string index [$w cget -orient] 0] \ [expr {-($D/30)}]} } else { catch {$w ${dir}view scroll [expr {- ($D / 120) * 4}] units} } } } bind all [list ::tk::MouseWheel %W %D %X %Y] bind all [list ::tk::MouseWheel %W %D %X %Y x] if {[tk windowingsystem] eq "x11"} { # Support for mousewheels on Linux/Unix commonly comes through # mapping the wheel to the extended buttons. bind all <4> [list ::tk::MouseWheel %W 120 %X %Y] bind all <5> [list ::tk::MouseWheel %W -120 %X %Y] } } DoDisplay set S(pnum2) [lindex $argv 0] .l0 insert end "Enter the number of a Wiki page to run diff on" Recents return ---- [Category Wikit] | [Category Application]