fredderic's gems

Here's a couple handy little gems I like to have available. The one-letter ones are not always the most efficient way of doing the task, but when the line's getting a little convoluted, these things can make it a little more readable. At least, I think so...

interp alias {} nocase {} string equal -nocase

interp alias {} L {} list

proc K {args} {lindex $args 0}

proc U {varName} {
        uplevel 1 [subst -noc {lindex [list [set $varName] [unset $varName]] 0}]
}

proc S {varName {value {}} } {
        uplevel 1 \
        [subst -noc {lindex [list [set $varName] [set $varName [list $value]]] 0}]
}

proc G {varName {default ""} } {
        upvar 1 $varName var
        if { [info exists var] } {return $var}
        return $default
}

proc error-args {mesg} {
        return -level 2 -code error "wrong # args: should be \"$mesg\""
}

proc list2 {args} {
        set outl [list]
        foreach item $args {lappend outl $item $item}
        return $outl
}

And what I consider to be an irritatingly trivial omission from the expr command:

proc ::tcl::mathfunc::sgn {v} {expr {$v > 0 ? 1 : $v < 0 ? -1 : 0}}

Also there's my version of the lshift command.


AMG: Here's rmax's clever version of sgn:

proc ::tcl::mathfunc::sgn {v} {expr {($v > 0) - ($v < 0)}}

Here's a proc I use for adding new commands to an existing ensemble. It's probably not the best version, but it's handy, and it works where I've needed it.

proc proc-ensemble {args} {
        if { ( [llength $args] < 4 || [llength $args] % 3 != 1 ) &&
                        [llength $args] != 2 } {
                error-args "proc-ensemble command ?name args body?..."
        }
        set args [lassign $args command]
        if { [set exists [namespace ensemble exists $command]] } {
                set spc [namespace ensemble configure $command -namespace]
                set map [namespace ensemble configure $command -map]
                # convert "-subcommands" into "-map"
                set cmds [namespace ensemble configure $command -subcommands]
                if { [llength $cmds] } {
                        foreach cmd [namespace ensemble configure $command -subcommands] {
                                dict set map $cmd ${spc}::$cmd
                        }
                        namespace ensemble configure $command -subcommands {}
                }
        } else {
                set spc $command
                set map {}
        }
        # validate list-of-procs form
        if { [llength $args] == 1 } {
                set args [lindex $args 0]
                if { [llength $args] % 3 != 0 } {
                        error-args "proc-ensemble command ?name args body?..."
                }
        }
        # build the new ensemble commands
        foreach {name args body} $args {
                set lname [string tolower [namespace tail $name]]
                if { $body ne "" } {
                        namespace eval $spc [list proc $name $args $body]
                        dict set map $lname ${spc}::$name
                } elseif { [dict exists $map $lname] } {
                        dict unset map $lname
                }
        }
        # update the ensemble command map
        if { $exists } {
                namespace ensemble configure $command -map $map
        } else {
                namespace ensemble create -command $command -map $map
        }
}

And now, a proc that does a similar job to regsub, except that the text to be substituted is the value produced by evaluating the supplied script.

proc regcmd {args} {
        if { [llength $args] < 3 } {
                error-args "regcmd ?switches? exp string script"
        }

        set parts [list]; set opts [list]; set start 0; set all 0
        for {set n 0} {$n < [llength $args]-3} {incr n} {
                set arg [lindex $args $n]
                switch -exact -- $arg {
                   -expanded - -line - -linestop - -lineanchor - -nocase
                        {if {$arg ni $opts} {lappend opts $arg}}
                   -indices - -inline {if {$arg ni $parts} {lappend parts $arg}}
                   -start {set start [lindex $args [incr n]]}
                -all {set all 1}
                default {return -level 1 -code error "bad switch \"$arg\": must be -indices, -inline, -expanded, -line, -linestop, -lineanchor, -nocase, or -start"}
                }
        }
        if { ! [llength $parts] } {set parts "-inline"}
        lassign [lrange $args end-2 end] exp strn script
        lappend opts -start $start $exp
        
        set out ""
        while { [llength [set ranges [regexp -inline -indices {*}$opts $strn]]] } {
                set list [list]
                foreach range $ranges {
                        set item [list]
                        foreach part $parts {
                                switch -exact -- $part {
                                "-inline" {lappend item [string range $strn {*}$range]}
                                "-indices" {lappend item {*}$range}
                                }
                        }
                        lappend list $item
                }
                lassign [lindex $ranges 0] start stop
                append out [string range $strn 0 $start-1][uplevel 1 $script {*}$list]
                set strn [string range $strn $stop+1 end]
                if { ! $all } break
        }

        return $out$strn
}