::trimmerTop

Trimming a Tcl source file off comments and whitespaces.


Licensetrimmer, Top

MIT.


Downloadtrimmer, Top

trimmer.zip


Usagetrimmer, Top


tclsh trim.tcl [glob_ptn] [-i idir|ifile] [-o odir] [-r] [-f] [-n] [--] [app args]

where:

glob_ptnsets a glob pattern instead of *.tcl that is default
-i idira directory of files to process (by default ./)
-i ifilea file listing .tcl files (#-comments disregarded)
-o odira directory of resulting files (by default ../release)
appan application to be run after trimming
argsoptional arguments of app

The -i (or --input) can be multiple, -o (or --output) can not.

The command switches mean:

  • If -r (or --recursive) is set, the input directories are processed recursively. By default, they are processed non-recursively.
  • If -f (or --force) is set, the existing output file(s) will be rewritten. By default, the trim.tcl doesn't rewrite the existing file(s).
  • If -n (or --no) is set, no real changes made, supposed changes shown only.

The trim.tcl by no means changes the input file(s).

Example:


tclsh trim.tcl -i ./lib -o ./bin tclsh -f ./bin/main.tcl arg1 "arg 2"

Limitationstrimmer, Top

The trim.tcl sets the following limitations for the code processed:

1. In general, multi-line strings should be double-quoted (not braced), because the braced strings would be trimmed. But there are two important exceptions: when set and variable commands use a braced string, it is not trimmed, e.g.


set str1 " Correct" ;# equals to set str1 "\n Correct" set str1 { Correct} ;# equals to set str1 "\n Correct" variable str2 " Correct" ;# equals to variable str2 "\n Correct" variable str2 { Correct} ;# equals to variable str2 "\n Correct" puts " Correct" ;# equals to puts "\n Correct" puts { NOT CORRECT} ;# equals to puts "NOT CORRECT"

2. Comments after "{" should begin with ";#", e.g.


while {true} { ;# infinite cycle }

3. List or switch commands can contain comments which are not considered to be meaningful items, e.g.


switch $option { # it's a comment (and the error in standard Tcl switch) -opt1 { puts "-opt1 processed" } # ... }

The 1st limitation is rarely encountered and easily overcome with \n escape sequences.

The last two limitations are actually the advantages of the utility.

The 2nd requires a bit more discipline of coders.

The 3rd eliminates Tcl comment freaks, incl. unmatched braces.

The trim.tcl and trim_test.tcl set examples of this in action:


tclsh trim.tcl -f -o trimmed tclsh trim_test.tcl -f -o trimmed tclsh trimmed/trim.tcl -f -o trimmed tclsh trimmed/trim_test.tcl -f -o trimmed

Commandstrimmer, Top




countCh [::trimmer]trimmer, Top

Counts a character in a string.

countCh str ch
Parameters
stra string
cha character
Return value

Returns a number of non-escaped occurences of character ch in string str.

See also

wiki.tcl-lang.org


proc ::trimmer::countCh {str ch} { # Counts a character in a string. # str - a string # ch - a character # # Returns a number of non-escaped occurences of character *ch* in # string *str*. # # See also: # [wiki.tcl-lang.org](https://wiki.tcl-lang.org/page/Reformatting+Tcl+code+indentation) set icnt 0 while {[set idx [string first $ch $str]] >= 0} { set backslashes 0 set nidx $idx while {[string equal [string index $str [incr nidx -1]] \\]} { incr backslashes } if {$backslashes % 2 == 0} { incr icnt } set str [string range $str [incr idx] end] } return $icnt }




trimFile [::trimmer]trimmer, Top

Trims an input file and writes a result to an output file.

trimFile finp fout ?args?
Parameters
finpinput file name
foutoutput file name
argsadditional parameters (so far not used)

proc ::trimmer::trimFile {finp fout args} { # Trims an input file and writes a result to an output file. # finp - input file name # fout - output file name # args - additional parameters (so far not used) if { [catch {set chani [open "$finp" "r"]} e] } { batchio::onError $e 0 return } if { [catch {set chano [open "$fout" "w"]} e] } { close $chani batchio::onError $e 0 return } chan configure $chani -encoding utf-8 chan configure $chano -encoding utf-8 set brace [set braceST [set ccmnt -2]] set quoted [set cbrc 0] while {[gets $chani line] >= 0} { if {!$quoted} { ;# it's not a multi-line quoted string if {$braceST<1} { ;# check a braced string at some important commands foreach {cmd a1} {set "\\S" variable "\\S"} { if {[set braceST [regexp "^\\s*$cmd\\s+$a1+\\s+\{" $line]]} { set line "\n[string trimleft $line]" set cbrc 0 break } } } if {$braceST>-2} { incr cbrc [ expr {[countCh $line "\{"] - [countCh $line "\}"]} ] } if {$braceST>0} { ;# check matching left/right braces if {$cbrc<=0} { set brace [set braceST -1] } else { puts $chano $line continue } } if {[regexp "^\\s*;*#" $line] || $ccmnt} { set cc [ expr {[string index $line end] eq "\\"} ] if {($ccmnt || $cc) && $brace!=-2} { if {$cc} {set line "\n$line"} ;# newline for comments inside braces puts $chano $line ;# permit 1st comments & their continuations } elseif {[regexp "^\\s*;#" $line]} { puts $chano ";" ;# this semicolon may be meaningful } set ccmnt $cc ;# comments continued continue } set line [string trimleft $line "\t\ "] if {$line eq ""} continue foreach s [regexp -all -inline -indices ";#" $line] { if {$cbrc<=0 && [countCh [set _ [string range $line 0 [lindex $s 0]-1]] "\""] % 2 == 0} { ;# example of continued command set ccmnt [ expr {[string index $line end] eq "\\"} ] set line [ string trimright $_ ] break ;# the ending ";#" is a comment } } if {$ccmnt} { set brace [set braceST -1] puts $chano "\n$line" ;# the ending ";#" is a comment continued \ (example of continued comment) \ (end of example) continue } } set line [string trimright $line] set prevbrace $brace set brace [ expr {$line eq "\}" ? 1 : 0} ] if {$prevbrace in {1 0} && !$brace} { puts $chano "" } if {[countCh $line "\""]%2} { set quoted [ expr {!$quoted} ] } if {[set _ [string index $line end]] eq "\{"} { set brace 2 ;# line ending with \{ will join with next line } elseif {$_ eq "\\"} { set brace 2 ;# the ending "\" should be removed set line [string range $line 0 end-1] if {!$quoted} { set line "[string trimright $line] " } } puts -nonewline $chano $line } puts $chano "\n#by trimmer" close $chani close $chano }



::trimmer::batchioTop

Processing a batch of input files to make appropriate output files.


Licensebatchio, Top

MIT.


Usagebatchio, Top


tclsh script.tcl [-i idir|ifile] [-o odir] [-r] [-f] [-n] [--] [app args]

where

script.tcl is a Tcl script sourcing batchio.tcl to call its 'main' proc

arguments are:

-i idira directory of files to process (by default ./)
-i ifilea file listing files to process (#-comments disregarded)
-o odira directory of resulting files (by default ../release)
appan application to be run after trimming
argsoptional arguments of app

The -i (or --input) can be multiple, -o (or --output) can not.

The command switches mean:

  • If -r (or --recursive) is set, the input directories are processed recursively. By default, they are processed non-recursively.
  • If -f (or --force) is set, the existing output file(s) will be rewritten. By default, the existing file(s) aren't rewritten.
  • If -n (or --no) is set, no real changes made, supposed changes shown only.

Example:


tclsh trim.tcl -i ./lib -o ./bin tclsh -f ./bin/main.tcl arg1 "arg 2"

Commandsbatchio, Top




doFile [::trimmer::batchio]batchio, Top

Prepares and does a call of procedure to process two files.

doFile procN noact pdirN root finp odir rm ?args?
Parameters
procNname of procedure to process two files
noactif true, no output file
pdirNvariable's name of previous input directory
rootroot of input directory
finpinput file
odiroutput directory
rmflag 'rewrite the existing output file or not'
argsother arguments
Description

If noact is true, no outputs are actually made (to show only).

The pdirN is used to show an input directory when changed. It needs only to be initialized with "".

The root is a root name of input directory that is used to make the output path from the odir and a relative (to root) part of finp.

If root is empty, an input file is set instead of a directory, so the output path has no relative part.


proc ::trimmer::batchio::doFile {procN noact pdirN root finp odir rm args} { # Prepares and does a call of procedure to process two files. # procN - name of procedure to process two files # noact - if true, no output file # pdirN - variable's name of previous input directory # root - root of input directory # finp - input file # odir - output directory # rm - flag 'rewrite the existing output file or not' # args - other arguments # # If *noact* is true, no outputs are actually made (to show only). # # The *pdirN* is used to show an input directory when changed. # It needs only to be initialized with "". # # The *root* is a root name of input directory that is used to make # the output path from the *odir* and a relative (to *root*) part # of *finp*. # # If *root* is empty, an input file is set instead of a directory, # so the output path has no relative part. upvar $pdirN pdir if {$root ne ""} { # input subdir set idir [file dirname [file normalize $finp]] set idir [string range $idir [string length $root]+1 end] # outdir + input subdir + input filename = output file set fout [file join $odir $idir [file tail $finp]] } else { set fout [file join $odir [file tail $finp]] } set dinp [file dirname $finp] set dout [file normalize $odir] if {$pdir ne $dinp} { set pdir $dinp puts " Input directory : $dinp" } set fdisp "...[string range $fout [string length $dout] end]" append fdisp [string repeat " " 80] puts -nonewline " Output file : [string range $fdisp 0 38]" if { "[file normalize $finp]" eq "[file normalize $fout]" } { onError "- the same." 0 return } if { !$rm && [file exists $fout] } { onError "- already exists." 0 return } puts "" if {$noact} return catch {file mkdir [file dirname $fout]} $procN $finp $fout {*}$args }




getFilesList [::trimmer::batchio]batchio, Top

Gets a list of input files.

getFilesList ilistN idir globPatt recursive
Parameters
ilistNa name of variable to contain the resulting list
idira name of input directory / file
globPattglob pattern for files to be processed
recursivea boolean to scan directories recursively
Description

If idir is a directory, it is scanned for files.

If idir is a file, it should contain a list of directories (to scan for files to be included in the list) and/or files (to be included in the list directly).

Return value

Returns a list of roots, i.e. root directory names for scanned directories or "" for files included directly.

See also

recurseProc


proc ::trimmer::batchio::getFilesList {ilistN idir globPatt recursive} { # Gets a list of input files. # ilistN - a name of variable to contain the resulting list # idir - a name of input directory / file # globPatt - glob pattern for files to be processed # recursive - a boolean to scan directories recursively # # If *idir* is a directory, it is scanned for files. # # If *idir* is a file, it should contain a list of directories (to scan # for files to be included in the list) and/or files (to be included in # the list directly). # # Returns a list of *roots*, i.e. root directory names for scanned # directories or "" for files included directly. # # See also: recurseProc if {![file exist $idir]} { onError "Input directory/file \"$idir\" does not exist." } set root {} if {[file isfile $idir]} { # it's a file containing a list of files and/or directories set ch [open $idir] foreach fin [split [read $ch] \n] { set fin [string trim $fin] if {$fin ne "" && [file exists $fin]} { if {[file isfile $fin]} { ;# a file lappend $ilistN $fin lappend root "" } else { ;# a directory set [namespace current]::iltmp {} if {$recursive} { recurseProc "lappend [namespace current]::iltmp" $fin $globPatt } else { set [namespace current]::iltmp [glob -nocomplain [file join $fin $globPatt]] } set rdir [file normalize $fin] foreach f [set [namespace current]::iltmp] { lappend $ilistN $f lappend root $rdir } unset [namespace current]::iltmp } } } } else { if {$recursive} { recurseProc "lappend $ilistN" $idir $globPatt } else { lappend $ilistN {*}[glob -nocomplain [file join $idir $globPatt]] } lappend root [file normalize $idir] } return $root }




main [::trimmer::batchio]batchio, Top

Main procedure of batchio.

main procN globPatt options ?args?
Parameters
procNname of procedure to process two files
globPattglob pattern for files to be processed
optionslist of {opt optshort}, additional options
argsarguments passed to script.tcl, containing switches and values:
--to end the switches and begin a command to run
-f(or --force) a flag to rewrite existing output files
-i(or --input) input directory or file of processed list
-n(or --no) a flag to show output files only, without making
-o(or --output) output directory
-r(or --recursive) a flag to scan directories recursively
restresting arguments are a command to run and its arguments

proc ::trimmer::batchio::main {procN globPatt options args} { # Main procedure of batchio. # procN - name of procedure to process two files # globPatt - glob pattern for files to be processed # options - list of {opt optshort}, additional options # args - arguments passed to script.tcl, containing switches and values: # -i - (or --input) input directory or file of processed list # -o - (or --output) output directory # -r - (or --recursive) a flag to scan directories recursively # -f - (or --force) a flag to rewrite existing output files # -n - (or --no) a flag to show output files only, without making # -- - to end the switches and begin a command to run # rest - resting arguments are a command to run and its arguments underLine # when run without arguments, put the synopsis if {![set ac [llength $args]]} {synopsis; underLine } # get arguments and check if the arguments are correct set mydir [file dirname [info script]] set err [set remove [set recursive [set noact false]]] lassign "" iopt odir mfile margs addargs for {set i 0} {$i<$ac} {incr i} { lassign [lrange $args $i end] opt val if {$opt eq "--"} { set mfile $val incr i } elseif {$mfile ne ""} { ;# after an application, its arguments go lappend margs $opt } else { ;# here the batchio options go switch $opt { --input - -i { lappend iopt $val incr i } --output - -o { if {$odir ne ""} { onError "--output argument duplicated" } set odir $val incr i } --recursive - -r { if {$recursive} { onError "--recursive argument duplicated" } set recursive true } --force - -f { if {$remove} { onError "--force argument duplicated" } set remove true } --no - -n { if {$noact} { onError "--no argument duplicated" } set noact true } default { set isapp true set i2 0 foreach {o osh} $options { if {$opt in "$o $osh"} { set isapp false lset options $i2 {} lappend addargs $val incr i break } incr i2 } if {$isapp} { set mfile $opt } } } } } if {$iopt eq ""} { set iopt ./ } if {$odir eq ""} { set odir ../release } set odir [file normalize $odir] if {[file isfile $odir]} { onError "\"$odir\" is not a directory." } # get the input files list from the list of input directories and/or files set ilistALL [set rootlistALL {}] set tmpl [namespace current]::ilistTmp foreach idir $iopt { set $tmpl {} set rootlist [getFilesList $tmpl $idir $globPatt $recursive] lappend ilistALL {*}[set $tmpl] lappend rootlistALL {*}$rootlist unset $tmpl } # process all files puts " OUTPUT DIRECTORY: $odir" underLine set pdir "" lassign $rootlistALL root foreach fin $ilistALL rt $rootlistALL { if {$rt ne ""} { set root $rt } set fin [file normalize $fin] if {[file isfile $fin]} { doFile $procN $noact pdir $root $fin $odir $remove {*}$addargs } } underLine # run a command if set set dispargs "" foreach a $margs { if {[llength $a]>1} {set a "\"$a\""} append dispargs "$a " } if {$noact} { puts " Supposed run: \"$mfile\" $dispargs" } elseif {$mfile ne ""} { set err "" if {[set comm [auto_execok $mfile]] eq ""} { set err " Not found: $mfile" } if {$err ne "" || [catch {exec {*}$comm {*}$margs } err]} { onError "Run:\n \"$mfile\" $dispargs\n$err" 0 } } }




onError [::trimmer::batchio]batchio, Top

Shows an error and optionally performs exit.

onError ?err? ?doexit?
Parameters
erroptional error message (empty by default, to put the synopsis); optional, default ""
doexitoptional boolean flag to perform exit (true by default); optional, default true

proc ::trimmer::batchio::onError {{err {}} {doexit true}} { # Shows an error and optionally performs *exit*. # err - optional error message (empty by default, to put the synopsis) # doexit - optional boolean flag to perform *exit* (true by default) set err [string trim $err] if {$doexit} { synopsis if {$err != ""} { underLine; puts " $err" } underLine exit } puts " $err" }




recurseProc [::trimmer::batchio]batchio, Top

Applies a command to files of a directory, recursively.

recurseProc procf dirname pattns
Parameters
procfa command to apply
dirnamea directory to scan
pattnsglob patterns of files (devided by " " or ",")
See also

getFilesList


proc ::trimmer::batchio::recurseProc {procf dirname pattns} { # Applies a command to files of a directory, recursively. # procf - a command to apply # dirname - a directory to scan # pattns - glob patterns of files (devided by " " or ",") # # See also: getFilesList foreach dir [glob -nocomplain [file join $dirname *]] { if {[file isdirectory $dir]} { recurseProc $procf $dir $pattns } } # no dirs anymore foreach filetempl [split $pattns ", "] { if {![catch { set files [glob -nocomplain [file join $dirname $filetempl]]}]} { foreach f $files { {*}$procf [file normalize $f] } } } }




synopsis [::trimmer::batchio]batchio, Top

Shows a syntax and a usage of namespace.

synopsis
Description

If _ruff_preamble is defined in the parent namespace, puts it. Otherwise puts its own _ruff_preamble.


proc ::trimmer::batchio::synopsis {} { # Shows a syntax and a usage of namespace. # # If *_ruff_preamble* is defined in the parent namespace, puts it. # Otherwise puts its own *_ruff_preamble*. variable SynShown if {$SynShown} exit {set SynShown true} set preamble [namespace parent]::_ruff_preamble if {![info exist $preamble]} { set preamble [namespace current]::_ruff_preamble } puts [string map [list \\\{ \{] [set $preamble]]\n }




underLine [::trimmer::batchio]batchio, Top

Puts out an underlining string.

underLine

proc ::trimmer::batchio::underLine {} { # Puts out an underlining string. puts " [string repeat - 75]" }

Document generated by Ruff!