multiop.tcl

Go to the documentation of this file.
00001 /*  ### ### ### ######### ######### #########*/
00002 /** 
00003  * (c) 2007 Andreas Kupries.
00004  */
00005 
00006 /*  DSL allowing the easy specification of multi-file copy and/or move*/
00007 /*  and/or deletion operations. Alternate names would be scatter/gather*/
00008 /*  processor, or maybe even assembler.*/
00009 
00010 /*  Examples:*/
00011 /*  (1) copy*/
00012 /*      into [installdir_of tls]*/
00013 /*      from c:/TDK/PrivateOpenSSL/bin*/
00014 /*      the  *.dll*/
00015 /* */
00016 /*  (2) move*/
00017 /*      from /sources*/
00018 /*      into /scratch*/
00019 /*      the  **/
00020 /*      but not *.html*/
00021 /*   (Alternatively: except for *.html)*/
00022 /* */
00023 /*  (3) into /scratch*/
00024 /*      from /sources*/
00025 /*      move*/
00026 /*      as   pkgIndex.tcl*/
00027 /*      the  index*/
00028 /* */
00029 /*  (4) in /scratch*/
00030 /*      remove*/
00031 /*      the *.txt*/
00032 
00033 /*  The language is derived from the parts of TclApp's option language*/
00034 /*  dealing with files and their locations, yet not identical. In parts*/
00035 /*  simplified, in parts more capable, keyword names were changed*/
00036 /*  throughout.*/
00037 
00038 /*  Language commands*/
00039 
00040 /*  From the examples*/
00041 /* */
00042 /*  into        DIR           : Specify destination directory.*/
00043 /*  in          DIR           : See 'into'.*/
00044 /*  from        DIR           : Specify source directory.*/
00045 /*  the         PATTERN (...) : Specify files to operate on.*/
00046 /*  but not     PATTERN       : Specify exceptions to 'the'.*/
00047 /*  but exclude PATTERN       : Specify exceptions to 'the'.*/
00048 /*  except for  PATTERN       : See 'but not'.*/
00049 /*  as          NAME          : New name for file.*/
00050 /*  move                      : Move files.*/
00051 /*  copy                      : Copy files.*/
00052 /*  remove                    : Delete files.*/
00053 /* */
00054 /*  Furthermore*/
00055 /* */
00056 /*  reset     : Force to defaults.*/
00057 /*  cd    DIR : Change destination to subdirectory.*/
00058 /*  up        : Change destination to parent directory.*/
00059 /*  (         : Save a copy of the current state.*/
00060 /*  )         : Restore last saved state and make it current.*/
00061 
00062 /*  The main active element is the command 'the'. In other words, this*/
00063 /*  command not only specifies the files to operate on, but also*/
00064 /*  executes the operation as defined in the current state. All other*/
00065 /*  commands modify the state to set the operation up, and nothing*/
00066 /*  else. To allow for a more natural syntax the active command also*/
00067 /*  looks ahead for the commands 'as', 'but', and 'except', and executes*/
00068 /*  them, like qualifiers, so that they take effect as if they had been*/
00069 /*  written before. The command 'but' and 'except use identical*/
00070 /*  constructions to handle their qualifiers, i.e. 'not' and 'for'.*/
00071 
00072 /*  Note that the fact that most commands just modify the state allows*/
00073 /*  us to use more off forms as specifications instead of just natural*/
00074 /*  language sentences For example the example 2 can re-arranged into:*/
00075 /* */
00076 /*  (5) from /sources*/
00077 /*      into /scratch*/
00078 /*      but not *.html*/
00079 /*      move*/
00080 /*      the  **/
00081 /* */
00082 /*  and the result is still a valid specification.*/
00083 
00084 /*  Further note that the information collected by 'but', 'except', and*/
00085 /*  'as' is automatically reset after the associated 'the' was*/
00086 /*  executed. However no other state is reset in that manner, allowing*/
00087 /*  the user to avoid repetitions of unchanging information. Lets us for*/
00088 /*  example merge the examples 2 and 3. The trivial merge is:*/
00089 
00090 /*  (6) move*/
00091 /*      into /scratch*/
00092 /*      from /sources*/
00093 /*      the  **/
00094 /*      but not *.html not index*/
00095 /*      move*/
00096 /*      into /scratch*/
00097 /*      from /sources*/
00098 /*      the  index*/
00099 /*      as   pkgIndex.tcl*/
00100 /* */
00101 /*  With less repetitions*/
00102 /* */
00103 /*  (7) move*/
00104 /*      into /scratch*/
00105 /*      from /sources*/
00106 /*      the  **/
00107 /*      but not *.html not index*/
00108 /*      the  index*/
00109 /*      as   pkgIndex.tcl*/
00110 
00111 /*  I have not yet managed to find a suitable syntax to specify when to*/
00112 /*  add a new extension to the moved/copied files, or have to strip all*/
00113 /*  extensions, a specific extension, or even replace extensions.*/
00114 
00115 /*  Other possibilities to muse about: Load the patterns for 'not'/'for'*/
00116 /*  from a file ... Actually, load the whole exceptions from a file,*/
00117 /*  with its contents a proper interpretable word list. Which makes it*/
00118 /*  general processing of include files.*/
00119 
00120 /*  ### ### ### ######### ######### #########*/
00121 /*  Requisites*/
00122 
00123 /*  This processor uses the 'wip' word list interpreter as its*/
00124 /*  foundation.*/
00125 
00126 package require fileutil      ; /*  File testing*/
00127 package require snit          ; /*  OO support*/
00128 package require struct::stack ; /*  Context stack*/
00129 package require wip           ; /*  DSL execution core*/
00130 
00131 /*  ### ### ### ######### ######### #########*/
00132 /*  API & Implementation*/
00133 
00134 snit::type ::fileutil::multi::op {
00135     /*  ### ### ### ######### ######### #########*/
00136     /*  API*/
00137 
00138     constructor {args} {} ; /*  create processor*/
00139 
00140     /*  ### ### ### ######### ######### #########*/
00141     /*  API - Implementation.*/
00142 
00143     constructor {args} {
00144     install stack using struct::stack::stack ${selfns}::stack
00145     $self wip_up = 
00146 
00147     /*  Mapping dsl commands to methods.*/
00148     defdva \
00149         re  Reset =     (    Push   )       Pop \
00150         into   Into     in   Into   from    From    \
00151         cd     ChDir    up   ChUp   as      As  \
00152         move   Move     copy Copy   remove  Remove  \
00153         but    But      not  Exclude    the     The \
00154         except Except   for  Exclude    exclude Exclude \
00155         to     Into         ->   Save       the- TheSet =   \
00156         recursive Recursive recursively Recursive           \
00157         for-win     ForWindows   for-unix   ForUnix         \
00158         for-windows ForWindows   expand     Expand          \
00159         invoke Invoke strict Strict !strict NotStrict \
00160         files  Files  links  Links  all Everything    \
00161         dirs   Directories directories Directories    \
00162         state? QueryState from? QueryFrom into? QueryInto \
00163         excluded? QueryExcluded as? QueryAs type? QueryType \
00164         recursive? QueryRecursive operation? QueryOperation \
00165         strict? QueryStrict !recursive NotRecursive
00166 
00167     $self Re
00168     runl =  $args
00169     return
00170     }
00171 
00172     destructor {
00173     $wip destroy
00174     return
00175     }
00176 
00177     ret  do (type args) {
00178     return [runl $args]
00179     }
00180 
00181     /*  ### ### ### ######### ######### #########*/
00182     /*  DSL Implementation*/
00183     wip::dsl
00184 
00185     /*  General reset of processor state*/
00186     ret  Reset () {
00187     $stack clear
00188     set base     ""
00189     set alias    ""
00190     set op       ""
00191     set recursive 0 
00192     set src      ""
00193     set excl     ""
00194     set types    {}
00195     set strict   0
00196     return
00197     }
00198 
00199     /*  Stack manipulation*/
00200     ret  Push () {
00201     $stack push [list $base $alias $op $opcmd $recursive $src $excl $types $strict]
00202     return
00203     }
00204 
00205     ret  Pop () {
00206     if {![$stack size]} {
00207         return -code error {Stack underflow}
00208     }
00209     foreach {base alias op opcmd recursive src excl types strict} [$stack pop] break
00210     return
00211     }
00212 
00213     /*  Destination directory*/
00214     ret  Into (type dir) {
00215     if {$dir eq ""} {set dir [pwd]}
00216     if {$strict && ![fileutil::test $dir edr msg {Destination directory}]} {
00217         return -code error $msg
00218     }
00219     set base $dir
00220     return
00221     }
00222 
00223     ret  ChDir (type dir) { $self Into [file join    $base $dir] ; return }
00224     ret  ChUp  ()    { $self Into [file dirname $base]      ; return }
00225 
00226     /*  Detail*/
00227     ret  As (type fname) {
00228     set alias [ForceRelative $fname]
00229     return
00230     }
00231 
00232     /*  Operations*/
00233     ret  Move   () { set op move   ; return }
00234     ret  Copy   () { set op copy   ; return }
00235     ret  Remove () { set op remove ; return }
00236     ret  Expand () { set op expand ; return }
00237 
00238     ret  Invoke (type cmdprefix) {
00239     set op    invoke
00240     set opcmd $cmdprefix
00241     return
00242     }
00243 
00244     /*  Operation qualifier*/
00245     ret  Recursive    () { set recursive 1 ; return }
00246     ret  NotRecursive () { set recursive 0 ; return }
00247 
00248     /*  Source directory*/
00249     ret  From (type dir) {
00250     if {$dir eq ""} {set dir [pwd]}
00251     if {![fileutil::test $dir edr msg {Source directory}]} {
00252         return -code error $msg
00253     }
00254     set src $dir
00255     return
00256     }
00257 
00258     /*  Exceptions*/
00259     ret  But    () { run_next_while {not exclude} ; return }
00260     ret  Except () { run_next_while {for}         ; return }
00261 
00262     ret  Exclude (type pattern) {
00263     lappend excl $pattern
00264     return
00265     }
00266 
00267     /*  Define the files to operate on, and perform the operation.*/
00268     ret  The (type pattern) {
00269     run_next_while {as but except exclude from into in to files dirs directories links all}
00270 
00271     switch -exact -- $op {
00272         invoke {Invoke [Resolve [Remember [Exclude [Expand $src  $pattern]]]]}
00273         move   {Move   [Resolve [Remember [Exclude [Expand $src  $pattern]]]]}
00274         copy   {Copy   [Resolve [Remember [Exclude [Expand $src  $pattern]]]]}
00275         remove {Remove          [Remember [Exclude [Expand $base $pattern]]] }
00276         expand {                 Remember [Exclude [Expand $base $pattern]]  }
00277     }
00278 
00279     # Reset the per-pattern flags of the resolution context back
00280     # to their defaults, for the next pattern.
00281 
00282     set alias    {}
00283     set excl     {}
00284     set recursive 0
00285     return
00286     }
00287 
00288     /*  Like 'The' above, except that the fileset is taken from the*/
00289     /*  specified variable. Semi-complementary to 'Save' below.*/
00290     /*  Exclusion data and recursion info do not apply for this, this is*/
00291     /*  already implicitly covered by the set, when it was generated.*/
00292 
00293     ret  TheSet (type varname) {
00294     # See 'Save' for the levels we jump here.
00295     upvar 5 $varname var
00296 
00297     run_next_while {as from into in to}
00298 
00299     switch -exact -- $op {
00300         invoke {Invoke [Resolve $var]}
00301         move   {Move   [Resolve $var]}
00302         copy   {Copy   [Resolve $var]}
00303         remove {Remove          $var }
00304         expand {
00305         return -code error "Expansion does not make sense\
00306                                     when we already have a set of files."
00307         }
00308     }
00309 
00310     # Reset the per-pattern flags of the resolution context back
00311     # to their defaults, for the next pattern.
00312 
00313     set alias    {}
00314     return
00315     }
00316 
00317     /*  Save the last expansion result to a variable for use by future commands.*/
00318 
00319     ret  Save (type varname) {
00320     # Levels to jump. Brittle.
00321     # 5: Caller
00322     # 4:   object do ...
00323     # 3:     runl
00324     # 2:       wip::runl
00325     # 1:         run_next
00326     # 0: Here
00327     upvar 5 $varname v
00328     set v $lastexpansion
00329     return
00330     }
00331 
00332     /*  Platform conditionals ...*/
00333 
00334     ret  ForUnix () {
00335     global tcl_platform
00336     if {$tcl_platform(platform) eq "unix"} return
00337     # Kill the remaining code. This effectively aborts processing.
00338     replacel {}
00339     return
00340     }
00341 
00342     ret  ForWindows () {
00343     global tcl_platform
00344     if {$tcl_platform(platform) eq "windows"} return
00345     # Kill the remaining code. This effectively aborts processing.
00346     replacel {}
00347     return
00348     }
00349 
00350     /*  Strictness*/
00351 
00352     ret  Strict () {
00353     set strict 1
00354     return
00355     }
00356 
00357     ret  NotStrict () {
00358     set strict 0
00359     return
00360     }
00361 
00362     /*  Type qualifiers*/
00363 
00364     ret  Files () {
00365     set types files
00366     return
00367     }
00368 
00369     ret  Links () {
00370     set types links
00371     return
00372     }
00373 
00374     ret  Directories () {
00375     set types dirs
00376     return
00377     }
00378 
00379     ret  Everything () {
00380     set types {}
00381     return
00382     }
00383 
00384     /*  State interogation*/
00385 
00386     ret  QueryState () {
00387     return [list \
00388             from      $src \
00389             into      $base \
00390             as        $alias \
00391             op        $op \
00392             excluded  $excl \
00393             recursive $recursive \
00394             type      $types \
00395             strict    $strict \
00396            ]
00397     }
00398     ret  QueryExcluded () {
00399     return $excl
00400     }
00401     ret  QueryFrom () {
00402     return $src
00403     }
00404     ret  QueryInto () {
00405     return $base
00406     }
00407     ret  QueryAs () {
00408     return $alias
00409     }
00410     ret  QueryOperation () {
00411     return $op
00412     }
00413     ret  QueryRecursive () {
00414     return $recursive
00415     }
00416     ret  QueryType () {
00417     return $types
00418     }
00419     ret  QueryStrict () {
00420     return $strict
00421     }
00422 
00423     /*  ### ### ### ######### ######### #########*/
00424     /*  DSL State*/
00425 
00426     component stack       ; /*  State stack     - ( )*/
00427     variable  base     "" ; /*  Destination dir - into, in, cd, up*/
00428     variable  alias    "" ; /*  Detail          - as*/
00429     variable  op       "" ; /*  Operation       - move, copy, remove, expand, invoke*/
00430     variable  opcmd    "" ; /*  Command prefix for invoke.*/
00431     variable  recursive 0 ; /*  Op. qualifier: recursive expansion?*/
00432     variable  src      "" ; /*  Source dir      - from*/
00433     variable  excl     "" ; /*  Excluded files  - but not|exclude, except for*/
00434     /*  incl                ; # Included files  - the (immediate use)*/
00435     variable types     {} ; /*  Limit glob/find to specific types (f, l, d).*/
00436     variable strict    0  ; /*  Strictness of into/Expand*/
00437 
00438     variable lastexpansion "" ; /*  Area for last expansion result, for 'Save' to take from.*/
00439 
00440     /*  ### ### ### ######### ######### #########*/
00441     /*  Internal -- Path manipulation helpers.*/
00442 
00443     ret  ForceRelative (type path) {
00444     set pathtype [file pathtype $path]
00445     switch -exact -- $pathtype {
00446         relative {
00447         return $path
00448         }
00449         absolute {
00450         # Chop off the first element in the path, which is the
00451         # root, either '/' or 'x:/'. If this was the only
00452         # element assume an empty path.
00453 
00454         set path [lrange [file split $path] 1 end]
00455         if {![llength $path]} {return {}}
00456         return [eval [linsert $path 0 file join]]
00457         }
00458         volumerelative {
00459         return -code error {Unable to handle volumerelative path, yet}
00460         }
00461     }
00462 
00463     return -code error \
00464         "file pathtype returned unknown type \"$pathtype\""
00465     }
00466 
00467     ret  ForceAbsolute (type path) {
00468     return [file join [pwd] $path]
00469     }
00470 
00471     /*  ### ### ### ######### ######### #########*/
00472     /*  Internal - Operation execution helpers*/
00473 
00474     ret  Invoke (type files) {
00475     upvar 1 base base src src opcmd opcmd
00476     uplevel #0 [linsert $opcmd end $src $base $files]
00477     return
00478     }
00479 
00480     ret  Move (type files) {
00481     upvar 1 base base src src
00482 
00483     foreach {s d} $files {
00484         set s [file join $src  $s]
00485         set d [file join $base $d]
00486 
00487         file mkdir [file dirname $d]
00488         file rename -force $s $d
00489     }
00490     return
00491     }
00492 
00493     ret  Copy (type files) {
00494     upvar 1 base base src src
00495 
00496     foreach {s d} $files {
00497         set s [file join $src  $s]
00498         set d [file join $base $d]
00499 
00500         file mkdir [file dirname $d]
00501         file copy -force $s $d
00502     }
00503     return
00504     }
00505 
00506     ret  Remove (type files) {
00507     upvar 1 base base
00508 
00509     foreach f $files {
00510         file delete -force [file join $base $f]
00511     }
00512     return
00513     }
00514 
00515     /*  ### ### ### ######### ######### #########*/
00516     /*  Internal -- Resolution helper commands*/
00517 
00518     typevariable tmap -array {
00519     files {f TFile}
00520     links {l TLink}
00521     dirs  {d TDir}
00522     {}    {{} {}}
00523     }
00524 
00525     ret  Expand (type dir , type pattern) {
00526     upvar 1 recursive recursive strict strict types types tmap tmap
00527     # FUTURE: struct::list filter ...
00528 
00529     set files {}
00530     if {$recursive} {
00531         # Recursion through the entire directory hierarchy, save
00532         # all matching paths.
00533 
00534         set filter [lindex $tmap($types) 1]
00535         if {$filter ne ""} {
00536         set filter [myproc $filter]
00537         }
00538 
00539         foreach f [fileutil::find $dir $filter] {
00540         if {![string match $pattern [file tail $f]]} continue
00541         lappend files [fileutil::stripPath $dir $f]
00542         }
00543     } else {
00544         # No recursion, just scan the whole directory for matching paths.
00545         # check for specific types integrated.
00546 
00547         set filter [lindex $tmap($types) 0]
00548         if {$filter ne ""} {
00549         foreach f [glob -nocomplain -directory $dir -types $filter -- $pattern] {
00550             lappend files [fileutil::stripPath $dir $f]
00551         }
00552         } else {
00553         foreach f [glob -nocomplain -directory $dir -- $pattern] {
00554             lappend files [fileutil::stripPath $dir $f]
00555         }
00556         }
00557     }
00558 
00559     if {[llength $files]} {return $files}
00560     if {!$strict}         {return {}}
00561 
00562     return -code error \
00563         "No files matching pattern \"$pattern\" in directory \"$dir\""
00564     }
00565 
00566     ret  TFile (type f) {file isfile $f}
00567     ret  TDir  (type f) {file isdirectory $f}
00568     ret  TLink (type f) {expr {[file type $f] eq "link"}}
00569 
00570     ret  Exclude (type files) {
00571     upvar 1 excl excl
00572 
00573     # FUTURE: struct::list filter ...
00574     set res {}
00575     foreach f $files {
00576         if {[IsExcluded $f $excl]} continue
00577         lappend res $f
00578     }
00579     return $res
00580     }
00581 
00582     ret  IsExcluded (type f , type patterns) {
00583     foreach p $patterns {
00584         if {[string match $p $f]} {return 1}
00585     }
00586     return 0
00587     }
00588 
00589     ret  Resolve (type files) {
00590     upvar 1 alias alias
00591     set res {}
00592     foreach f $files {
00593 
00594         # Remember alias for processing and auto-invalidate to
00595         # prevent contamination of the next file.
00596 
00597         set thealias $alias
00598         set alias    ""
00599 
00600         if {$thealias eq ""} {
00601         set d $f
00602         } else {
00603         set d [file dirname $f]
00604         if {$d eq "."} {
00605             set d $thealias
00606         } else {
00607             set d [file join $d $thealias]
00608         }
00609         }
00610 
00611         lappend res $f $d
00612     }
00613     return $res
00614     }
00615 
00616     ret  Remember (type files) {
00617     upvar 1 lastexpansion lastexpansion
00618     set lastexpansion $files
00619     return $files
00620     }
00621 
00622     /** 
00623      * ### ### ### ######### ######### #########
00624  */
00625 }
00626 
00627 /*  ### ### ### ######### ######### #########*/
00628 /*  Ready*/
00629 
00630 package provide fileutil::multi::op 0.5
00631 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1