| # util.tcl -- | |
| # | |
| # This file implements package ::Utility, which ... | |
| # | |
| # Copyright (c) 1997-8 Jeffrey Hobbs | |
| # | |
| # See the file "license.terms" for information on usage and | |
| # redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. | |
| # | |
| ## The provide goes first to prevent the recursive provide/require | |
| ## loop for subpackages | |
| package provide ::Utility 1.0 | |
| ## This assumes that all util-*.tcl files are in the same directory | |
| if {[lsearch -exact $auto_path [file dirname [info script]]]==-1} { | |
| lappend auto_path [file dirname [info script]] | |
| } | |
| namespace eval ::Utility {; | |
| ## Protos | |
| namespace export -clear * | |
| proc get_opts args {} | |
| proc get_opts2 args {} | |
| proc lremove args {} | |
| proc lrandomize args {} | |
| proc lunique args {} | |
| proc luniqueo args {} | |
| proc line_append args {} | |
| proc highlight args {} | |
| proc echo args {} | |
| proc alias args {} | |
| proc which args {} | |
| proc ls args {} | |
| proc dir args {} | |
| proc fit_format args {} | |
| proc validate args {} | |
| proc allow_null_elements args {} | |
| proc deny_null_elements args {} | |
| }; # end of ::Utility namespace prototype headers | |
| package require ::Utility::number | |
| package require ::Utility::string | |
| package require ::Utility::dump | |
| package require ::Utility::expand | |
| package require ::Utility::tk | |
| namespace eval ::Utility {; | |
| foreach namesp [namespace children [namespace current]] { | |
| namespace import -force ${namesp}::* | |
| } | |
| # psource -- | |
| # | |
| # ADD COMMENTS HERE | |
| # | |
| # Arguments: | |
| # args comments | |
| # Results: | |
| # Returns ... | |
| # | |
| ;proc psource {file namesp {import *}} { | |
| uplevel \#0 [subst { | |
| source $file | |
| namespace import -force ${namesp}::$import | |
| } | |
| ] | |
| } | |
| # get_opts -- | |
| # | |
| # Processes -* named options, with or w/o possible associated value | |
| # and returns remaining args | |
| # | |
| # Arguments: | |
| # var variable into which option values should be stored | |
| # arglist argument list to parse | |
| # optlist list of valid options with default value | |
| # typelist optional list of option types that can be used to | |
| # validate incoming options | |
| # nocomplain whether to complain about unknown -switches (0 - default) | |
| # or not (1) | |
| # Results: | |
| # Returns unprocessed arguments. | |
| # | |
| ;proc get_opts {var arglist optlist {typelist {}} {nocomplain 0}} { | |
| upvar 1 $var data | |
| if {![llength $optlist] || ![llength $arglist]} { return $arglist } | |
| array set opts $optlist | |
| array set types $typelist | |
| set i 0 | |
| while {[llength $arglist]} { | |
| set key [lindex $arglist $i] | |
| if {[string match -- $key]} { | |
| set arglist [lreplace $arglist $i $i] | |
| break | |
| } elseif {![string match -* $key]} { | |
| break | |
| } elseif {[string match {} [set akey [array names opts $key]]]} { | |
| set akey [array names opts ${key}*] | |
| } | |
| switch [llength $akey] { | |
| 0 { ## oops, no keys matched | |
| if {$nocomplain} { | |
| incr i | |
| } else { | |
| return -code error "unknown switch '$key', must be:\ | |
| [join [array names opts] {, }]" | |
| } | |
| } | |
| 1 { ## Perfect, found just the right key | |
| if {$opts($akey)} { | |
| set val [lrange $arglist [expr {$i+1}] \ | |
| [expr {$i+$opts($akey)}]] | |
| set arglist [lreplace $arglist $i [expr {$i+$opts($akey)}]] | |
| if {[info exists types($akey)] && \ | |
| ([string compare none $types($akey)] && \ | |
| ![validate $types($akey) $val])} { | |
| return -code error "the value for \"$akey\" is not in\ | |
| proper $types($akey) format" | |
| } | |
| set data($akey) $val | |
| } else { | |
| set arglist [lreplace $arglist $i [expr {$i+$opts($akey)}]] | |
| set data($akey) 1 | |
| } | |
| } | |
| default { ## Oops, matches too many possible keys | |
| return -code error "ambiguous option \"$key\",\ | |
| must be one of: [join $akey {, }]" | |
| } | |
| } | |
| } | |
| return $arglist | |
| } | |
| # get_opts2 -- | |
| # | |
| # Process options into an array. -- short-circuits the processing | |
| # | |
| # Arguments: | |
| # var variable into which option values should be stored | |
| # arglist argument list to parse | |
| # optlist list of valid options with default value | |
| # typelist optional list of option types that can be used to | |
| # validate incoming options | |
| # Results: | |
| # Returns unprocessed arguments. | |
| # | |
| ;proc get_opts2 {var arglist optlist {typelist {}}} { | |
| upvar 1 $var data | |
| if {![llength $optlist] || ![llength $arglist]} { return $arglist } | |
| array set data $optlist | |
| array set types $typelist | |
| foreach {key val} $arglist { | |
| if {[string match -- $key]} { | |
| set arglist [lreplace $arglist 0 0] | |
| break | |
| } | |
| if {[string match {} [set akey [array names data $key]]]} { | |
| set akey [array names data ${key}*] | |
| } | |
| switch [llength $akey] { | |
| 0 { ## oops, no keys matched | |
| return -code error "unknown switch '$key', must be:\ | |
| [join [array names data] {, }]" | |
| } | |
| 1 { ## Perfect, found just the right key | |
| if {[info exists types($akey)] && \ | |
| ![validate $types($akey) $val]} { | |
| return -code error "the value for \"$akey\" is not in\ | |
| proper $types($akey) format" | |
| } | |
| set data($akey) $val | |
| } | |
| default { ## Oops, matches too many possible keys | |
| return -code error "ambiguous option \"$key\",\ | |
| must be one of: [join $akey {, }]" | |
| } | |
| } | |
| set arglist [lreplace $arglist 0 1] | |
| } | |
| return $arglist | |
| } | |
| # lremove -- | |
| # remove items from a list | |
| # Arguments: | |
| # ?-all? remove all instances of said item | |
| # list list to remove items from | |
| # args items to remove | |
| # Returns: | |
| # The list with items removed | |
| # | |
| ;proc lremove {args} { | |
| set all 0 | |
| if {[string match \-a* [lindex $args 0]]} { | |
| set all 1 | |
| set args [lreplace $args 0 0] | |
| } | |
| set l [lindex $args 0] | |
| foreach i [join [lreplace $args 0 0]] { | |
| if {[set ix [lsearch -exact $l $i]] == -1} continue | |
| set l [lreplace $l $ix $ix] | |
| if {$all} { | |
| while {[set ix [lsearch -exact $l $i]] != -1} { | |
| set l [lreplace $l $ix $ix] | |
| } | |
| } | |
| } | |
| return $l | |
| } | |
| # lrandomize -- | |
| # randomizes a list | |
| # Arguments: | |
| # ls list to randomize | |
| # Returns: | |
| # returns list in with randomized items | |
| # | |
| ;proc lrandomize ls { | |
| set res {} | |
| while {[string compare $ls {}]} { | |
| set i [randrng [llength $ls]] | |
| lappend res [lindex $ls $i] | |
| set ls [lreplace $ls $i $i] | |
| } | |
| return $res | |
| } | |
| # lunique -- | |
| # order independent list unique proc, not most efficient. | |
| # Arguments: | |
| # ls list of items to make unique | |
| # Returns: | |
| # list of only unique items, order not defined | |
| # | |
| ;proc lunique ls { | |
| foreach l $ls {set ($l) x} | |
| return [array names {}] | |
| } | |
| # lunique -- | |
| # order independent list unique proc. most efficient, but requires | |
| # __LIST never be an element of the input list | |
| # Arguments: | |
| # __LIST list of items to make unique | |
| # Returns: | |
| # list of only unique items, order not defined | |
| # | |
| ;proc lunique __LIST { | |
| if {[llength $__LIST]} { | |
| foreach $__LIST $__LIST break | |
| unset __LIST | |
| return [info locals] | |
| } | |
| } | |
| # luniqueo -- | |
| # order dependent list unique proc | |
| # Arguments: | |
| # ls list of items to make unique | |
| # Returns: | |
| # list of only unique items in same order as input | |
| # | |
| ;proc luniqueo ls { | |
| set rs {} | |
| foreach l $ls { | |
| if {[info exist ($l)]} { continue } | |
| lappend rs $l | |
| set ($l) 0 | |
| } | |
| return $rs | |
| } | |
| # flist -- | |
| # | |
| # list open files and sockets | |
| # | |
| # Arguments: | |
| # pattern restrictive regexp pattern for numbers | |
| # manum max socket/file number to search until | |
| # Results: | |
| # Returns ... | |
| # | |
| ;proc flist {{pattern .*} {maxnum 1025}} { | |
| set result {} | |
| for {set i 1} {$i <= $maxnum} {incr i} { | |
| if {![regexp $pattern $i]} { continue } | |
| if {![catch {fconfigure file$i} conf]} { | |
| lappend result [list file$i $conf] | |
| } | |
| if {![catch {fconfigure sock$i} conf]} { | |
| array set c {-peername {} -sockname {}} | |
| array set c $conf | |
| lappend result [list sock$i $c(-peername) $c(-sockname)] | |
| } | |
| } | |
| return $result | |
| } | |
| # highlight -- | |
| # | |
| # searches in text widget for $str and highlights it | |
| # If $str is empty, it just deletes any highlighting | |
| # This really belongs in ::Utility::tk | |
| # | |
| # Arguments: | |
| # w text widget | |
| # str string to search for | |
| # -nocase specifies to be case insensitive | |
| # -regexp specifies that $str is a pattern | |
| # -tag tagId name of tag in text widget | |
| # -color color color of tag in text widget | |
| # Results: | |
| # Returns ... | |
| # | |
| ;proc highlight {w str args} { | |
| $w tag remove __highlight 1.0 end | |
| array set opts { | |
| -nocase 0 | |
| -regexp 0 | |
| -tag __highlight | |
| -color yellow | |
| } | |
| set args [get_opts opts $args {-nocase 0 -regexp 0 -tag 1 -color 1}] | |
| if {[string match {} $str]} return | |
| set pass {} | |
| if {$opts(-nocase)} { append pass "-nocase " } | |
| if {$opts(-regexp)} { append pass "-regexp " } | |
| $w tag configure $opts(-tag) -background $opts(-color) | |
| $w mark set $opts(-tag) 1.0 | |
| while {[string compare {} [set ix [eval $w search $pass -count numc -- \ | |
| [list $str] $opts(-tag) end]]]} { | |
| $w tag add $opts(-tag) $ix ${ix}+${numc}c | |
| $w mark set $opts(-tag) ${ix}+1c | |
| } | |
| catch {$w see $opts(-tag).first} | |
| return [expr {[llength [$w tag ranges $opts(-tag)]]/2}] | |
| } | |
| # best_match -- | |
| # finds the best unique match in a list of names | |
| # The extra $e in this argument allows us to limit the innermost loop a | |
| # little further. | |
| # Arguments: | |
| # l list to find best unique match in | |
| # e currently best known unique match | |
| # Returns: | |
| # longest unique match in the list | |
| # | |
| ;proc best_match {l {e {}}} { | |
| set ec [lindex $l 0] | |
| if {[llength $l]>1} { | |
| set e [string length $e]; incr e -1 | |
| set ei [string length $ec]; incr ei -1 | |
| foreach l $l { | |
| while {$ei>=$e && [string first $ec $l]} { | |
| set ec [string range $ec 0 [incr ei -1]] | |
| } | |
| } | |
| } | |
| return $ec | |
| } | |
| # getrandfile -- | |
| # | |
| # returns a random line from a file | |
| # | |
| # Arguments: | |
| # file filename to get line from | |
| # Results: | |
| # Returns a line as a string | |
| # | |
| ;proc getrandfile {file} { | |
| set fid [open $file] | |
| set data [split [read $fid] \n] | |
| close $fid | |
| return [lindex $data [randrng [llength $data]]] | |
| } | |
| # randrng -- | |
| # gets random number within input range | |
| # Arguments: | |
| # rng range to limit output to | |
| # Returns: | |
| # returns random number within range 0..$rng | |
| ;proc randrng {rng} { | |
| return [expr {int($rng * rand())}] | |
| } | |
| # grep -- | |
| # cheap grep routine | |
| # Arguments: | |
| # exp regular expression to look for | |
| # args files to search in | |
| # Returns: | |
| # list of lines that in files that matched $exp | |
| # | |
| ;proc grep {exp args} { | |
| if 0 { | |
| ## To be implemented | |
| -count -nocase -number -names -reverse -exact | |
| } | |
| if {[string match {} $args]} return | |
| set output {} | |
| foreach file [eval glob $args] { | |
| set fid [open $file] | |
| foreach line [split [read $fid] \n] { | |
| if {[regexp $exp $line]} { lappend output $line } | |
| } | |
| close $fid | |
| } | |
| return $output | |
| } | |
| # line_append -- | |
| # appends a string to the end of every line of data from a file | |
| # Arguments: | |
| # file file to get data from | |
| # stuff stuff to append to each line | |
| # Returns: | |
| # file data with stuff appended to each line | |
| # | |
| ;proc line_append {file stuff} { | |
| set fid [open $file] | |
| set data [read $fid] | |
| catch {close $fid} | |
| return [join [split $data \n] $stuff\n] | |
| } | |
| # alias -- | |
| # akin to the csh alias command | |
| # Arguments: | |
| # newcmd (optional) command to bind alias to | |
| # args command and args being aliased | |
| # Returns: | |
| # If called with no args, then it dumps out all current aliases | |
| # If called with one arg, returns the alias of that arg (or {} if none) | |
| # | |
| ;proc alias {{newcmd {}} args} { | |
| if {[string match {} $newcmd]} { | |
| set res {} | |
| foreach a [interp aliases] { | |
| lappend res [list $a -> [interp alias {} $a]] | |
| } | |
| return [join $res \n] | |
| } elseif {[string match {} $args]} { | |
| interp alias {} $newcmd | |
| } else { | |
| eval interp alias [list {} $newcmd {}] $args | |
| } | |
| } | |
| # echo -- | |
| # Relaxes the one string restriction of 'puts' | |
| # Arguments: | |
| # args any number of strings to output to stdout | |
| # Returns: | |
| # Outputs all input to stdout | |
| # | |
| ;proc echo args { puts [concat $args] } | |
| # which -- | |
| # tells you where a command is found | |
| # Arguments: | |
| # cmd command name | |
| # Returns: | |
| # where command is found (internal / external / unknown) | |
| # | |
| ;proc which cmd { | |
| ## FIX - make namespace friendly | |
| set lcmd [list $cmd] | |
| if { | |
| [string compare {} [uplevel info commands $lcmd]] || | |
| ([uplevel auto_load $lcmd] && | |
| [string compare {} [uplevel info commands $lcmd]]) | |
| } { | |
| set ocmd [uplevel namespace origin $lcmd] | |
| # First check to see if it is an alias | |
| # This requires two checks because interp aliases doesn't | |
| # canonically return fully (un)qualified names | |
| set aliases [interp aliases] | |
| if {[lsearch -exact $aliases $ocmd] > -1} { | |
| set result "$cmd: aliased to \"[alias $ocmd]\"" | |
| } elseif {[lsearch -exact $aliases $cmd] > -1} { | |
| set result "$cmd: aliased to \"[alias $cmd]\"" | |
| } elseif {[string compare {} [uplevel info procs $lcmd]] || \ | |
| ([string match ?*::* $ocmd] && \ | |
| [string compare {} [namespace eval \ | |
| [namespace qualifiers $ocmd] \ | |
| [list info procs [namespace tail $ocmd]]]])} { | |
| # Here we checked if the proc that has been imported before | |
| # deciding it is a regular command | |
| set result "$cmd: procedure $ocmd" | |
| } else { | |
| set result "$cmd: command" | |
| } | |
| global auto_index | |
| if {[info exists auto_index($cmd)]} { | |
| # This tells you where the command MIGHT have come from - | |
| # not true if the command was redefined interactively or | |
| # existed before it had to be auto_loaded. This is just | |
| # provided as a hint at where it MAY have come from | |
| append result " ($auto_index($cmd))" | |
| } | |
| return $result | |
| } elseif {[string compare {} [auto_execok $cmd]]} { | |
| return [auto_execok $cmd] | |
| } else { | |
| return -code error "$cmd: command not found" | |
| } | |
| } | |
| # ls -- | |
| # mini-ls equivalent (directory lister) | |
| # Arguments: | |
| # ?-all? list hidden files as well (Unix dot files) | |
| # ?-long? list in full format "permissions size date filename" | |
| # ?-full? displays / after directories and link paths for links | |
| # args names/glob patterns of directories to list | |
| # Returns: | |
| # a directory listing | |
| # | |
| interp alias {} ::Utility::dir {} namespace inscope ::Utility ls | |
| ;proc ls {args} { | |
| array set s { | |
| -all 0 -full 0 -long 0 | |
| 0 --- 1 --x 2 -w- 3 -wx 4 r-- 5 r-x 6 rw- 7 rwx | |
| } | |
| set args [get_opts s $args [array get s -*]] | |
| set sep [string trim [file join . .] .] | |
| if {[string match {} $args]} { set args . } | |
| foreach arg $args { | |
| if {[file isdir $arg]} { | |
| set arg [string trimr $arg $sep]$sep | |
| if {$s(-all)} { | |
| lappend out [list $arg [lsort [glob -nocomplain -- $arg.* $arg*]]] | |
| } else { | |
| lappend out [list $arg [lsort [glob -nocomplain -- $arg*]]] | |
| } | |
| } else { | |
| lappend out [list [file dirname $arg]$sep \ | |
| [lsort [glob -nocomplain -- $arg]]] | |
| } | |
| } | |
| if {$s(-long)} { | |
| global tcl_platform | |
| set old [clock scan {1 year ago}] | |
| switch -exact -- $tcl_platform(os) { | |
| windows { set fmt "%-5s %8d %s %s\n" } | |
| default { set fmt "%s %-8s %-8s %8d %s %s\n" } | |
| } | |
| foreach o $out { | |
| set d [lindex $o 0] | |
| if {[llength $out]>1} { append res $d:\n } | |
| foreach f [lindex $o 1] { | |
| file lstat $f st | |
| array set st [file attrib $f] | |
| set f [file tail $f] | |
| if {$s(-full)} { | |
| switch -glob $st(type) { | |
| dir* { append f $sep } | |
| link { append f " -> [file readlink $d$sep$f]" } | |
| fifo { append f | } | |
| default { if {[file exec $d$sep$f]} { append f * } } | |
| } | |
| } | |
| switch -exact -- $st(type) { | |
| file { set mode - } | |
| fifo { set mode p } | |
| default { set mode [string index $st(type) 0] } | |
| } | |
| set cfmt [expr {$st(mtime)>$old?{%b %d %H:%M}:{%b %d %Y}}] | |
| switch -exact -- $tcl_platform(os) { | |
| windows { | |
| # RHSA | |
| append mode $st(-readonly) $st(-hidden) \ | |
| $st(-system) $st(-archive) | |
| append res [format $fmt $mode $st(size) \ | |
| [clock format $st(mtime) -format $cfmt] $f] | |
| } | |
| macintosh { | |
| append mode $st(-readonly) $st(-hidden) | |
| append res [format $fmt $mode $st(-creator) \ | |
| $st(-type) $st(size) \ | |
| [clock format $st(mtime) -format $cfmt] $f] | |
| } | |
| default { ## Unix is our default platform type | |
| foreach j [split [format %o \ | |
| [expr {$st(mode)&0777}]] {}] { | |
| append mode $s($j) | |
| } | |
| append res [format $fmt $mode $st(-owner) $st(-group) \ | |
| $st(size) \ | |
| [clock format $st(mtime) -format $cfmt] $f] | |
| } | |
| } | |
| } | |
| append res \n | |
| } | |
| } else { | |
| foreach o $out { | |
| set d [lindex $o 0] | |
| if {[llength $out]>1} { append res $d:\n } | |
| set i 0 | |
| foreach f [lindex $o 1] { | |
| if {[string len [file tail $f]] > $i} { | |
| set i [string len [file tail $f]] | |
| } | |
| } | |
| set i [expr {$i+2+$s(-full)}] | |
| ## Assume we have at least 70 char cols | |
| set j [expr {70/$i}] | |
| set k 0 | |
| foreach f [lindex $o 1] { | |
| set f [file tail $f] | |
| if {$s(-full)} { | |
| switch -glob [file type $d$sep$f] { | |
| d* { append f $sep } | |
| l* { append f @ } | |
| default { if {[file exec $d$sep$f]} { append f * } } | |
| } | |
| } | |
| append res [format "%-${i}s" $f] | |
| if {[incr k]%$j == 0} {set res [string trimr $res]\n} | |
| } | |
| append res \n\n | |
| } | |
| } | |
| return [string trimr $res] | |
| } | |
| # fit_format -- | |
| # This procedure attempts to format a value into a particular format string. | |
| # | |
| # Arguments: | |
| # format - The format to fit | |
| # val - The value to be validated | |
| # | |
| # Returns: 0 or 1 (whether it fits the format or not) | |
| # | |
| # Switches: | |
| # -fill ?var? - Default values will be placed to fill format to spec | |
| # and the resulting value will be placed in variable 'var'. | |
| # It will equal {} if the match invalid | |
| # (doesn't work all that great currently) | |
| # -best ?var? - 'Fixes' value to fit format, placing best correct value | |
| # in variable 'var'. If current value is ok, the 'var' | |
| # will equal it, otherwise it removes chars from the end | |
| # until it fits the format, then adds any fixed format | |
| # chars to value. Can be slow (recursive tkFormat op). | |
| # -strict - Value must be an exact match for format (format && length) | |
| # -- - End of switches | |
| ;proc fit_format {args} { | |
| set fill {}; set strict 0; set best {}; set result 1; | |
| set name [lindex [info level 0] 0] | |
| while {[string match {-*} [lindex $args 0]]} { | |
| switch -- [string index [lindex $args 0] 1] { | |
| b { | |
| set best [lindex $args 1] | |
| set args [lreplace $args 0 1] | |
| } | |
| f { | |
| set fill [lindex $args 1] | |
| set args [lreplace $args 0 1] | |
| } | |
| s { | |
| set strict 1 | |
| set args [lreplace $args 0 0] | |
| } | |
| - { | |
| set args [lreplace $args 0 0] | |
| break | |
| } | |
| default { | |
| return -code error "bad $name option \"[lindex $args 0]\",\ | |
| must be: -best, -fill, -strict, or --" | |
| } | |
| } | |
| } | |
| if {[llength $args] != 2} { | |
| return -code error "wrong \# args: should be \"$name ?-best varname?\ | |
| ?-fill varname? ?-strict? ?--? format value\"" | |
| } | |
| set format [lindex $args 0] | |
| set val [lindex $args 1] | |
| set flen [string length $format] | |
| set slen [string length $val] | |
| if {$slen > $flen} {set result 0} | |
| if {$strict} { if {$slen != $flen} {set result 0} } | |
| if {$result} { | |
| set regform {} | |
| foreach c [split $format {}] { | |
| set special 0 | |
| if {[string match {[0AaWzZ]} $c]} { | |
| set special 1 | |
| switch $c { | |
| 0 {set fmt {[0-9]}} | |
| A {set fmt {[A-Z]}} | |
| a {set fmt {[a-z]}} | |
| W {set fmt "\[ \t\r\n\]"} | |
| z {set fmt {[A-Za-z]}} | |
| Z {set fmt {[A-Za-z0-9]}} | |
| } | |
| } else { | |
| set fmt $c | |
| } | |
| } | |
| echo $regform $format $val | |
| set result [string match $regform $val] | |
| } | |
| if [string compare $fill {}] { | |
| upvar $fill fvar | |
| if {$result} { | |
| set fvar $val[string range $format $i end] | |
| } else { | |
| set fvar {} | |
| } | |
| } | |
| if [string compare $best {}] { | |
| upvar $best bvar | |
| set bvar $val | |
| set len [string length $bvar] | |
| if {!$result} { | |
| incr len -2 | |
| set bvar [string range $bvar 0 $len] | |
| # Remove characters until it's in valid format | |
| while {$len > 0 && ![tkFormat $format $bvar]} { | |
| set bvar [string range $bvar 0 [incr len -1]] | |
| } | |
| # Add back characters that are fixed | |
| while {($len<$flen) && ![string match \ | |
| {[0AaWzZ]} [string index $format [incr len]]]} { | |
| append bvar [string index $format $len] | |
| } | |
| } else { | |
| # If it's already valid, at least we can add fixed characters | |
| while {($len<$flen) && ![string match \ | |
| {[0AaWzZ]} [string index $format $len]]} { | |
| append bvar [string index $format $len] | |
| incr len | |
| } | |
| } | |
| } | |
| return $result | |
| } | |
| # validate -- | |
| # This procedure validates particular types of numbers/formats | |
| # | |
| # Arguments: | |
| # type - The type of validation (alphabetic, alphanumeric, date, | |
| # hex, integer, numeric, real). Date is always strict. | |
| # val - The value to be validated | |
| # | |
| # Returns: 0 or 1 (whether or not it resembles the type) | |
| # | |
| # Switches: | |
| # -incomplete enable less precise (strict) pattern matching on number | |
| # useful for when the number might be half-entered | |
| # | |
| # Example use: validate real 55e-5 | |
| # validate -incomplete integer -505 | |
| # | |
| ;proc validate {args} { | |
| if {[string match [lindex $args 0]* "-incomplete"]} { | |
| set strict 0 | |
| set opt * | |
| set args [lreplace $args 0 0] | |
| } else { | |
| set strict 1 | |
| set opt + | |
| } | |
| if {[llength $args] != 2} { | |
| return -code error "wrong \# args: should be\ | |
| \"[lindex [info level 0] 0] ?-incomplete? type value\"" | |
| } else { | |
| set type [lindex $args 0] | |
| set val [lindex $args 1] | |
| } | |
| ## This is a big switch for speed reasons | |
| switch -glob -- $type { | |
| alphab* { # alphabetic | |
| return [regexp -nocase "^\[a-z\]$opt\$" $val] | |
| } | |
| alphan* { # alphanumeric | |
| return [regexp -nocase "^\[a-z0-9\]$opt\$" $val] | |
| } | |
| b* { # boolean - would be nice if it were more than 0/1 | |
| return [regexp "^\[01\]$opt\$" $val] | |
| } | |
| d* { # date - always strict | |
| return [expr {![catch {clock scan $val}]}] | |
| } | |
| h* { # hexadecimal | |
| return [regexp -nocase "^(0x)?\[0-9a-f\]$opt\$" $val] | |
| } | |
| i* { # integer | |
| return [regexp "^\[-+\]?\[0-9\]$opt\$" $val] | |
| } | |
| n* { # numeric | |
| return [regexp "^\[0-9\]$opt\$" $val] | |
| } | |
| rea* { # real | |
| return [regexp -nocase [expr {$strict | |
| ?{^[-+]?([0-9]+\.?[0-9]*|[0-9]*\.?[0-9]+)(e[-+]?[0-9]+)?$} | |
| :{^[-+]?[0-9]*\.?[0-9]*([0-9]\.?e[-+]?[0-9]*)?$}}] $val] | |
| } | |
| reg* { # regexp | |
| return [expr {![catch {regexp $val {}}]}] | |
| } | |
| val* { # value | |
| return [expr {![catch {expr {1*$val}}]}] | |
| } | |
| l* { # list | |
| return [expr {![catch {llength $val}]}] | |
| } | |
| w* { # widget | |
| return [winfo exists $val] | |
| } | |
| default { | |
| return -code error "bad [lindex [info level 0] 0] type \"$type\":\ | |
| \nmust be [join [lsort {alphabetic alphanumeric date \ | |
| hexadecimal integer numeric real value \ | |
| list boolean}] {, }]" | |
| } | |
| } | |
| return | |
| } | |
| # allow_null_elements -- | |
| # | |
| # Sets up a read trace on an array to allow reading any value | |
| # and ensure that some default exists | |
| # | |
| # Arguments: | |
| # args comments | |
| # Results: | |
| # Returns ... | |
| # | |
| ;proc allow_null_elements {array {default {}}} { | |
| uplevel 1 [list trace variable $array r [list \ | |
| [namespace code ensure_default] $default]] | |
| } | |
| ;proc ensure_default {val array idx op} { | |
| upvar $array var | |
| if {[array exists var]} { | |
| if {![info exists var($idx)]} { | |
| set var($idx) $val | |
| } | |
| } elseif {![info exists var]} { | |
| set var $val | |
| } | |
| } | |
| # deny_null_elements -- | |
| # | |
| # ADD COMMENTS HERE | |
| # | |
| # Arguments: | |
| # args comments | |
| # Results: | |
| # Returns ... | |
| # | |
| ;proc deny_null_elements {array {default {}}} { | |
| ## FIX: should use vinfo and remove any *ensure_default* read traces | |
| uplevel 1 [list trace vdelete $array r [list \ | |
| [namespace code ensure_default] $default]] | |
| } | |
| }; # end namespace ::Utility |