command dispatcher

Simple mechanism to dispatch procs corresponding to given commands and subcommands. Commands and subcommands can be abbreviated. Command 'help errorlevel' is mapped to a procedure '.help.errorlevel', e.g. 'he err' does the job, too.

# Example

proc .help {} {
}

proc .help.errorlevel {} {
}

proc dispatch {allowed cmdwords} {
     if {[llength $cmdwords] == 0} {
        puts stderr "command missing!"
        puts stderr "Possible commands are:"
        lmap l $allowed {puts stderr [lindex $l 0]}
        exit 250
     }
     foreach word $cmdwords {
          set cmdword [lsearch -glob -nocase -index 0 -inline $allowed $word*]
          if {[llength $cmdword]} {
             append p .  [lindex $cmdword 0]; # expanded command name
             set allowed [lindex $cmdword 1]; # subcommands
          } else {
             puts stderr "Unknown (sub-)command: $word"
             puts stderr "Possible (sub-)commands are:"
             lmap l $allowed {puts stderr [lindex $l 0]}
             exit 250
          }
     }
     if {[catch {tailcall $p} rc]} {
        puts stderr "cmd '$cmdwords' noch implemented yet!"
        exit 250
     }
}

# Give the list of allowed commands and subcommands, and the current commandwords. The format of the list is:
#  each element of the list is one command
#  each element can in turn be a list; element #1 of the list is the command itself, element #2 is a list of possible subcommands
#  and so on
dispatch {
   {help {errorlevel inifile examples}}
   list
} $argv

All that's to do to implement a new command is to add it to the list given to dispatch and implement the corresponding proc. I found that simpler than namespace ensemble which confuses me still.


Later I found out that tailcall should not be catched... Also, I want to be able to store some kind of description alongside commands (didn't make use of them, though). So the above snippet turnded into the following dictionary based variant. In addition, each tailcall gets its subcommands, if any, as an arg so it's possible to show some help about the available subcommands from within the handler.

proc dispatch {allowed cmdwords} {
     foreach word $cmdwords {
        # set key [lindex [dict keys $allowed $word*] 0]; # unfortunally we have no -nocase here, so we use lsearch on the dict
          set key [lsearch -glob -nocase -inline [lmap {k v} $allowed {set k}] $word*]
          if {[string length $key]} {
             set val [dict get $allowed $key]
             append p . $key
             set allowed [dict get [dict merge {subcommands {}} $val] subcommands]
           } else {
             puts "unknown (sub)command: $word"; return
          }
     }
     tailcall $p $allowed;
}

# Dictionary mit erlaubten Kommandos, Hilfen und Subkommandos
#  - jedes Kommando ist ein Dictioary Eintrag:
#    Schlüsselname := Kommandoname
#    Schlüsselwert := ein weiteres Dictionary, kann leer sein oder folgende Einträge enthalten:
#       info "hilfetext"
#       subcommands "wieder ein dictionary wie hier beschrieben"
#
dispatch {
   cmd1 {info "Erläuterung zum cmd1"}
   cmd2 {info "Erläuterung zum cmd2" subcommands {
           subcmd21 {info "Erläuterung zum subcmd21"}
           subcmd22 {info "Erläuterung zum subcmd22"}
        }
   }
   cmd4 {}
   cmd5 {
      info "Hilfe zum cmd5" subcommands {
           subCmd51 {
                info "Hilfe zum SubCmd51" subcommands {
                     SubSubCmd511 {}
                }
           }
           subCmd52 {
                info "Hilfe zum SubCmd52" subcommands {
                     SubSubCmd521 {info "Hilfe zum SubSubCmd521" subcommands {
                                        SubSubSubCmd5211 {info "Hilfe zum SubSubSubCmd5211"}
                                  }
                     }
                }
           }
      }
   }
} $argv