tfu

2007-Dec-02 Here is a tool to synchronize two filesystem directories, which the name of means 'Tcl File Updater'. -- Sarnold

Usage:

 tfu ?--debug boolean? ?--repare boolean? /src/dir/ /target/dir/

Output:

 U /updated/file
 C /created/directory/
 R /restored/file
 A /file/added/to/target/dir

Notes

I use this script daily for the sake of not to use any tool like CVS for small development. I think it is close to rsync, with the restriction that it will not operate with distant machines like rsync does.


The script

 #!/usr/bin/env tclsh
 
 set cwd [pwd]
 array set opts [list debug off repare off]
 
 proc testopt {key val} {
         global opts
         foreach {opt oname} {-d debug -r repare} {
                 if {$key eq $opt || $key eq "--$oname"} {
                         set opts($oname) $val
                         return true
                 }
         }
         return false
 }
 
 proc usage {} {
         puts {usage: tfu [options] sourcedir/ targetdir/
 Options are:
         -d        --debug        boolean debug mode (does not alter the filesystem)
         -h  --help        prints this message}
         exit 0
 }
 
 if {[llength $argv]<2} {
         usage
 }
 
 foreach {source target} [lrange $argv end-1 end] break
 foreach {key val} [lrange $argv 0 end-2] {
         if {[testopt $key $val]} continue
         error "unknown option $key"
 }
 
 proc Repare {dir with} {
         cd $with
         if {![file exists $dir]} {
                 puts "C $dir"
                 Mkdir $dir
         }
         set dirs [list]
         foreach f [glob -nocomplain *] {
                 if {[file isdirectory $f]} {
                         lappend dirs $f
                 } elseif {[file isfile $f] && ![file exists $dir/$f]} {
                         puts "R $dir/$f"
                         Copy $f $dir/$f
                 }
         }
         foreach d $dirs {
                 Repare $dir/$d $with/$d
         }
 }
 
 proc Update {dir with} {
         cd $with
         if {![file exists $dir]} {
                 puts "C $dir"
                 Mkdir $dir
         }
         set dirs [list]
         foreach f [glob -nocomplain *] {
                 if {[file isdirectory $f]} {
                         lappend dirs $f
                 } elseif {[file isfile $f]} {
                         if {![file exists $dir/$f]} {
                                 puts "A $dir/$f"
                                 Copy $f $dir/$f
                         } elseif {[file mtime $dir/$f] < [file mtime $f]} {
                                 puts "U $dir/$f"
                                 Copy-force $f $dir/$f
                         }
                 }
         }
         foreach d $dirs {
                 Update $dir/$d $with/$d
         }
 }
 proc CreateShadowProcs {} {
         global opts
         if {!$opts(debug)} {
                 interp alias {} Cd {} cd
                 interp alias {} Copy {} file copy
                 interp alias {} Copy-force {} file copy -force
                 interp alias {} Mkdir {} file mkdir
         } else {
                 proc Cd {dir} {catch {cd $dir}}
                 proc Copy {a b} {catch {file copy $a $b}}
                 proc Copy-force {a b} {catch {file copy -force $a $b}}
                 proc Mkdir {d} {catch {file mkdir $d}}
         }
 }
 proc main {args} {
         global source target opts
         set source [checkDir $source]
         set target [checkDir $target]
         CreateShadowProcs
         if {$opts(repare)} {
                 Repare $source $target
         }
         Update $target $source
 }
 proc checkDir {name} {
         if {![file isdirectory $name]} {error "not a valid directory: $name"}
         file normalize $name
 }
 catch {tcldebug::debug}
 main