Posted to tcl by aspect at Mon Jul 06 08:03:34 GMT 2015view pretty

#!/usr/bin/tclsh

# neat little options/arguments parser I apparently wrote.
# commented sections are questionable support for validation of arguments (not opts - conflicts with multi-value form).

namespace eval options {

    proc options {args} {
        # parse optspec
        foreach optspec $args {
            set name [lindex $optspec 0]
            switch [llength $optspec] {
                1 {
                    dict set opts $name type 0 ;# flag
                    uplevel 1 [list set [string range $name 1 end] 0]
                    #dict set opts $name value 0
                } 
                2 {
                    dict set opts $name type 1 ;# arbitrary value
                    dict set opts $name default [lindex $optspec 1]
                    uplevel 1 [list set [string range $name 1 end] [lindex $optspec 1]]
                    #dict set opts $name value [lindex $optspec 1]
                }
                default {
                    dict set opts $name type 2 ;# choice
                    dict set opts $name default [lindex $optspec 1]
                    dict set opts $name values [lrange $optspec 1 end]
                    uplevel 1 [list set [string range $name 1 end] [lindex $optspec 1]]
                }
            }
        }
        # get caller's args
        upvar 1 args argv
        for {set i 0} {$i<[llength $argv]} {} {
            set arg [lindex $argv $i]
            if {![string match -* $arg]} {
                break
            }
            incr i
            if {$arg eq "--"} {
                break
            }
            set candidates [dict filter $opts key $arg*]
            switch [dict size $candidates] {
                0 {
                    return -code error -level 2 "Unknown option $arg: must be one of [dict keys $opts]"
                }
                1 {
                    dict for {name spec} $candidates {break}
                    set name [string range $name 1 end]
                    dict with spec {} ;# look out
                    if {$type==0} {
                        uplevel 1 [list set $name 1]
                        #dict set opts $name value 1
                    } else {
                        if {[llength $argv]<($i+1)} {
                            return -code error -level 2 "Option $name requires a value"
                        }
                        set val [lindex $argv $i]
                        if {$type==2} {
                            set is [lsearch -all -glob $values $val*]
                            switch [llength $is] {
                                1 {
                                    set val [lindex $values $is]
                                }
                                0 {
                                    return -code error -level 2 "Bad $name \"$val\": must be one of $values"
                                }
                                default {
                                    return -code error -level 2 "Ambiguous $name \"$val\": could be any of [lmap i $is {lindex $values $i}]"
                                }
                            }
                        }
                        uplevel 1 [list set $name $val]
                        incr i
                    }
                }
                default {
                    return -code error -level 2 "Ambiguous option $arg: maybe one of [dict keys $candidates]"
                }
            }
        }
        set argv [lrange $argv $i end]
    }

    proc formatArgspec {argspec} {
        join [lmap arg $argspec {
            if {[llength $arg]>1} {
                K "?[lindex $arg 0]?"
            } elseif {$arg eq "args"} {
                K "?args ...?"
            } else {
                K $arg
            }
        }] " "
    }

    proc arguments {argspec} {
        upvar 1 args argv
        for {set i 0} {$i<[llength $argv]} {incr i} {
            if {$i >= [llength $argspec]} {
                return -code error -level 2 "wrong # args: should be \"[lindex [info level -1] 0] [formatArgspec $argspec]\""
            }
            set name [lindex $argspec $i 0]
            if {$name eq "args"} {
                uplevel 1 [list set args [lrange $argv $i end]]
                return
            }
            set value [lindex $argv $i]
#            set test [lindex $argspec $i 2]
#            if {$test != ""} {
#                set valid [uplevel 1 $test $value]
#                if {!$value} {
#                    return -code error -level 2 "Invalid $name \"$value\", must be $test"
#                }
#            }
            uplevel 1 [list set $name $value]
        }
        # defaults:
        for {} {$i < [llength $argspec]} {incr i} {
            set as [lindex $argspec $i]
            if {[llength $as]==1} {
                if {$as ne "args"} {
                    return -code error -level 2 "wrong # args: should be \"[lindex [info level -1] 0] [formatArgspec $argspec]\""
                }
                upvar 1 args args
                set args [lrange $argv $i end]
                return
            }
            lassign $as name value
#            set test [lindex $argspec $i 2]
#            if {$test != ""} {
#                set valid [uplevel 1 $test $value]
#                if {!$value} {
#                    return -code error -level 2 "Invalid $name \"$value\", must be $test"
#                }
#            }
            uplevel 1 [list set $name $value]
        }
    }

    namespace export options arguments
}

namespace import options::*
#
#proc test {args} {
#    options {-flag} {-flip {}} {-value 100} {-colour red green blue black}
#    arguments {rabbit {poo yes} args}
#    foreach name [info locals] {
#        puts "$name = [set $name]"
#    }
#    puts {}
#}
#
#test hehe
#test -fla hehe
#test -fli lalala hehe
#test -val 230 hehe
#test hjg hgj hj ghjgjh
#test -col gr hg h
#test