This little thing provides multi-line replacements in text files.
It's called this way:
tclsh mulster.tcl [options] fileini
where:
fileini
is a name of file containing options for replacements, e.g.
INFILE=input file name.txt OUTFILE=output file name.txt IN=BEGIN(1,1) line #1 to find line #2 to find IN=END OUT=BEGIN line #1 OUT=END
options
are:
-infile input-file-name -outfile output-file-name -mode exact | exact0 | glob | regexp -backup 0 | <dir> -keep 0 | 1 | false | true -single 0 | 1 | false | true -files 0 | 1 | false | true -charset charsetName (e.g. cp1251) -lineend lineEnding (e.g. \r\n) --
See README for details.
The mulster::Mulster
class provides two methods to be called from Tcl module.
1. The main
method performs the multi-line replacements. Declared as:
method main {fileini {mode 1} {backup BAK} {keep 0} {single 0} {charset ""} {lineend ""} {files 0}}
where:
fileini
is a file name of options
mode = 0
(exact0, EXACT0) to match exact, without leading/tailing spaces
mode = 1
(exact, EXACT) to match exact, with all their leading/tailing spaces
mode = 2
(glob, GLOB) to match glob pattern
mode = 3
(regexp, re, REGEXP, RE) to match regexp pattern
mode = regexp--
to match regexp pattern and call regsub
Note: IN= line is to be found, OUT= line is to substitute, according to the regexp syntax accepted in Tcl. Note: With regexp and regsub, only one line is of use to IN=/OUT= block.
mode = regexp-all
to match regexp pattern and call regsub -all
mode = regexp-nocase
to match regexp pattern and call regsub -nocase
mode = regexp-expanded
to match regexp pattern and call regsub -expanded
regexp mode
can be combined, e.g. "regexp-all-nocase"
backup 0
means no input files' backuping
backup dir
means backuping to dir directory
keep
if 1, keeps input files' attributes/times in output files
single
if 1, sets a standard string-for-string replacements
charset
is a charset of input files, e.g. cp1251
lineend
sets characters to end lines, e.g. \r\n (by default \n)
files
if 1, OUT= lines to be file names to take the lines from
2. The mulsterList
method performs in-memory replacements in a list. Declared as:
method mulsterList {lcont lin lout {r1 0} {r2 0} {mode 1} {single 0} {files 0}}
where:
lcont
is a list to be processed
lin
is a list of input lines (replaced)
lout
is a list of output lines (replacing)
r1
and r2
set a range of replacements
mode
is a search mode (as defined in getSearchMode method)
single
if true, means a standard 'string for string' replacements
files
if true, output lines are taken from files
The mulsterList
method returns a list processed.
Puts a synopsis of mulster utility.
proc ::mulster::synopsis {} { # Puts a synopsis of mulster utility. variable _ruff_preamble puts $_ruff_preamble }
getBoolean | Checks and gets a boolean value. |
getSearchMode | Gets a search mode as numeric from string. |
init | Initializes processing files. |
main | Processes files according to the options of 'fileini' file. |
mulsterList | Performs replacements in a list. |
Checks and gets a boolean value.
| a value to be checked |
| a default value; optional, default true |
Returns a normalized value.
method getBoolean {val {defval true}} { # Checks and gets a boolean value. # # val - a value to be checked # defval - a default value # # Returns a normalized value. if {$val ni {0 1 false true}} {set val $defval} return $val }
Gets a search mode as numeric from string.
| search mode in a string form ("exact", "RE" etc.) |
Numeric mode:
0 | to match exact, without their leading/tailing spaces (str trimmed) |
1 | to match exact, with all their leading/tailing spaces |
2 | to match glob |
3 | to match regexp for IN= lines and substitute by OUT= lines |
4 | to match regexp -all and call 'regsub -all' |
8 | to match regexp -nocase and call 'regsub -nocase' |
16 | to match regexp -expanded and call 'regsub -expanded' |
The mode can combine 4,8,16, e.g. 12 means "regexp -all -nocase".
Returns the numeric mode.
method getSearchMode {strmode} { # Gets a search mode as numeric from string. # # strmode - search mode in a string form ("exact", "RE" etc.) # # Returns the numeric mode. # # Numeric mode: # 0 - to match exact, without their leading/tailing spaces (str trimmed) # 1 - to match exact, with all their leading/tailing spaces # 2 - to match glob # 3 - to match regexp for IN= lines and substitute by OUT= lines # 4 - to match regexp -all and call 'regsub -all' # 8 - to match regexp -nocase and call 'regsub -nocase' # 16 - to match regexp -expanded and call 'regsub -expanded' # # The mode can combine 4,8,16, e.g. 12 means "regexp -all -nocase". if {[string is integer $strmode]} {return $strmode} set mode 0 switch -regexp -nocase -matchvar am $strmode { ^exact$ {set mode 1} ^glob$ {set mode 2} ^regexp$ - ^re$ {set mode 3} ^regexp\\s*(-.*)$ - ^re\\s*(-.*) { set am [split [string toupper [lindex $am 1]] " -"] if {"A" in $am || "ALL" in $am} { incr mode 4 } if {"N" in $am || "NOCASE" in $am} { incr mode 8 } if {"E" in $am || "EXPANDED" in $am} { incr mode 16 } incr mode 32 ;# to call 'regsub' anyway } } return $mode }
Initializes processing files.
| input file name |
| output file name |
| optional arguments |
method init {infile outfile args} { # Initializes processing files. # # infile - input file name # outfile - output file name # args - optional arguments array set _MMM {} my InitIO if {$infile ne ""} { set _MMM(fin) 1 set _MMM(infile) $infile } if {$outfile ne ""} { set _MMM(fout) 1 set _MMM(outfile) $outfile } set _MMM(IN_FILE) INFILE= ;# pattern for in file name set _MMM(OUT_FILE) OUTFILE= ;# pattern for out file name set _MMM(IN_BEGIN) IN=BEGIN ;# pattern for in lines start set _MMM(IN_END) IN=END ;# pattern for in lines finish set _MMM(OUT_BEGIN) OUT=BEGIN ;# pattern for out lines start set _MMM(OUT_END) OUT=END ;# pattern for out lines finish set _MMM(MODE) MODE= ;# pattern for exact/glob/regexp mode set _MMM(BACKUP) BACKUP= ;# pattern for backup dir name set _MMM(KEEP) KEEP= ;# pattern for keep mode set _MMM(SINGLE) SINGLE= ;# pattern for single mode set _MMM(CHARSET) CHARSET= ;# pattern for charset mode set _MMM(LINEEND) LINEEND= ;# pattern for lineend mode set _MMM(FILES) FILES= ;# pattern for files set _MMM(GRP1) {(.+)} ;# RE group for a file name set _MMM(GRP2) {\((.+),(.+)\)} ;# RE groups for range set _MMM(DEBUG) false ;# DEBUG mode if { [self next] != {} } { return [next {*}$args] } }
Processes files according to the options of 'fileini' file.
| options file name |
| a search mode (as defined in getSearchMode method); optional, default 1 |
| if 0, means no backuping, otherwise - backup directory; optional, default BAK |
| if 1, keeps input files' attributes/times in output files; optional, default 0 |
| if 1, standard 'one string for one string' replacements; optional, default 0 |
| a charset of input files (e.g. cp1251); optional, default "" |
| a line ending of input files (e.g. \r\n); optional, default "" |
| if 1, output lines are taken from files; optional, default 0 |
method main {fileini {mode 1} {backup BAK} {keep 0} {single 0} {charset {}} {lineend {}} {files 0}} { # Processes files according to the options of 'fileini' file. # # fileini - options file name # mode - a search mode (as defined in getSearchMode method) # backup - if 0, means no backuping, otherwise - backup directory # keep - if 1, keeps input files' attributes/times in output files # single - if 1, standard 'one string for one string' replacements # charset - a charset of input files (e.g. cp1251) # lineend - a line ending of input files (e.g. \r\n) # files - if 1, output lines are taken from files # # See also: # getSearchMode set mode [my getSearchMode $mode] set keep [my getBoolean $keep] set single [my getBoolean $single] set fileon [my getBoolean $files] if {!($_MMM(fin) + $_MMM(fout))} { my InitIO } set state NONE set _MMM(st) [set comments ""] set _MMM(nl) 0 set _MMM(fileini) $fileini set chini [my Topen $fileini] foreach _MMM(st) [split [read $chini] \n] { incr _MMM(nl) switch $state { NONE { # check for finish if {[regexp -nocase "^\\s*MODE=EXIT" $_MMM(st)]} { break # check for debug mode } elseif {[regexp -nocase "^\\s*MODE=DEBUG" $_MMM(st)]} { set _MMM(DEBUG) true # check for comments } elseif {[regexp "^\\s*#" $_MMM(st)]} { set comments "\n$_MMM(st)" } elseif {[my OptionIs $_MMM(MODE) $_MMM(GRP1)]} { # check for mode set mode [my getSearchMode $_MMM(match1)] # check for backup dir } elseif {[my OptionIs $_MMM(BACKUP) $_MMM(GRP1)]} { my FlushOut $mode $keep $charset $lineend $backup set backup $_MMM(match1) # check for keep state } elseif {[my OptionIs $_MMM(KEEP) $_MMM(GRP1)]} { my FlushOut $mode $keep $charset $lineend $backup set keep [my getBoolean $_MMM(match1)] # check for charset } elseif {[my OptionIs $_MMM(CHARSET) $_MMM(GRP1)]} { my FlushOut $mode $keep $charset $lineend $backup set charset $_MMM(match1) # check for lineend } elseif {[my OptionIs $_MMM(LINEEND) $_MMM(GRP1)]} { my FlushOut $mode $keep $charset $lineend $backup set lineend $_MMM(match1) # check for single state } elseif {[my OptionIs $_MMM(SINGLE) $_MMM(GRP1)]} { set single [my getBoolean $_MMM(match1)] # check for fileon state } elseif {[my OptionIs $_MMM(FILES) $_MMM(GRP1)]} { set fileon [my getBoolean $_MMM(match1)] # check for input file } elseif {[my OptionIs $_MMM(IN_FILE) $_MMM(GRP1)]} { my FlushOut $mode $keep $charset $lineend $backup set _MMM(fin) 1 set _MMM(infile) $_MMM(match1) # check for output file } elseif {[my OptionIs $_MMM(OUT_FILE) $_MMM(GRP1)]} { my CheckFiles 1 set _MMM(fout) 1 set _MMM(outfile) $_MMM(match1) # check for input lines beginning } elseif {[my OptionIs $_MMM(IN_BEGIN)] || [my OptionIs $_MMM(IN_BEGIN) $_MMM(GRP2)]} { my CheckFiles 1 my CheckFiles 2 lappend _MMM(inlist) [list $_MMM(match1) $_MMM(match2) $single $fileon $charset $lineend $mode $comments] set comments "" set state IN # check for output lines beginning } elseif {[my OptionIs $_MMM(OUT_BEGIN)]} { my CheckFiles 1 my CheckFiles 2 lappend _MMM(outlist) [list] set state OUT } } IN { # check for input lines ending if {[my OptionIs $_MMM(IN_END)]} { set state NONE } else { # collect the input lines set curl [lindex $_MMM(inlist) end] lappend curl $_MMM(st) set _MMM(inlist) [lreplace $_MMM(inlist) end end $curl] } } OUT { # check for output lines ending if {[my OptionIs $_MMM(OUT_END)]} { set state NONE } else { # collect the output lines set curl [lindex $_MMM(outlist) end] lappend curl $_MMM(st) set _MMM(outlist) [lreplace $_MMM(outlist) end end $curl] } } } } # flush out the collected lines if any my FlushOut $mode $keep $charset $lineend $backup close $chini }
Performs replacements in a list.
| a list to be processed |
| input lines' list |
| output lines' list |
| first index of replacements to do; optional, default 0 |
| last index of replacements to do; optional, default 0 |
| a search mode (as defined in getSearchMode method); optional, default 1 |
| if true, means a standard 'string for string' replacements; optional, default 0 |
| if true, output lines are taken from files; optional, default 0 |
Returns a list with replacements made.
method mulsterList {lcont lin lout {r1 0} {r2 0} {mode 1} {single 0} {files 0}} { # Performs replacements in a list. # # lcont - a list to be processed # lin - input lines' list # lout - output lines' list # r1 - first index of replacements to do # r2 - last index of replacements to do # mode - a search mode (as defined in getSearchMode method) # single - if true, means a standard 'string for string' replacements # files - if true, output lines are taken from files # # Returns a list with replacements made. set _MMM(repls) 0 set leni [llength $lin] if {!$leni} { return $lcont ;# nothing to replace } set lres [list] ;# resulting list set sti0 [lindex $lin 0] if {$mode==0} {set sti0 [string trim $sti0]} ;# for a quick 1st check if {$files} {set lout [my FilesContents $lout]} set leno [llength $lout] set lenc [llength $lcont] set ic [set ifnd 0] while {$ic < $lenc} { set stc [lindex $lcont $ic] if { [my CompareStr $mode $stc $sti0] } { ;# 1st line found for {set ii [set found 1]} {$ii<$leni && $found} {incr ii} { if {[set ic2 [expr {$ic + $ii}]] >= $lenc} { set found 0 ;# out of lcont's range } else { set sti [lindex $lin $ii] set sto [lindex $lcont $ic2] if {$mode==0} { ;# exact0 is 'trimmed exact' mode set found [my CompareStr $mode $sto [string trim $sti]] } else { set found [my CompareStr $mode $sto $sti] } } } if {$found} { incr ifnd ;# check a found ifnd-th bunch for the range (r1,r2) if {$ifnd>=$r1 && ($ifnd<=$r2 || !$r2)} { set rinc 1 for {set io 0} {$io<$leno} {incr io} { set stout [lindex $lout $io] if {$mode>3} { set rinc [regsub {*}[my ReOptions $mode] [lindex $lin $io] $stc $stout stout] } lappend lres $stout } incr ic $leni ;# go to a line just following the 'lin' bunch incr _MMM(repls) $rinc } else { ;# not in the range r1-r2, so skip this IN=/OUT= for {set ii 0} {$ii<$leni} {incr ii} { lappend lres [lindex $lcont $ic] incr ic } } continue } } if {$single} { ;# do standard string replacement foreach si $lin so $lout { set stc2 [string map [list $si $so] $stc] if {$stc2 ne $stc} { incr ifnd ;# check a found ifnd-th bunch for the range (r1,r2) if {$ifnd>=$r1 && ($ifnd<=$r2 || !$r2)} { set stc $stc2 incr _MMM(repls) } } } } lappend lres $stc ;# this line is not the 1st of 'lin' bunch incr ic } return $lres }