ulis, 2003-11-03. If you need to write a Tcl package.
- This is a robust and versatil package proto that you can adapt to fit your needs.
- Care was taken to hide the internal procs when an error occurs.
- Extending this package is as easy as filling a table with procs name and descriptions syntax.
- You can define subcommands as easily you defined commands.
The package edit
(file: apackage.tcl)
if {[info exists ::apackage::version]} { return }
namespace eval ::apackage \
{
namespace export apackage
variable {}
variable version 0.9
package provide APackage $version
package require Tcl 8.4
interp alias {} ::apackage::apackage {} ::apackage::w:dispatch main
proc w:dispatch {desc operation args} \
{
variable {}
if {[incr (:level)] == 1} { set (:errInfo) "" }
set rc [catch \
{
foreach {pattern item} $(:$desc:cmd) \
{
if {[string match $pattern $operation]} \
{
set oper [lindex $item 0]
set lvl [lindex $item 1]
set msg [lindex $item 2]
set conds [lrange $item 3 end]
break
}
}
if {![info exists oper]} \
{
error "bad operation \"$operation\": should be $(:$desc:msg)"
}
set map [eval $(:$desc:map)]
foreach cond $conds \
{
set cond [string map $map $cond]
if $cond \
{ w:error "wrong # args: should be [string map $map $msg]" }
}
if {[llength $args] == 0} { uplevel $lvl [namespace code $oper] } \
else { uplevel $lvl [namespace code $oper] $args }
} msg]
set code [expr {$rc ? "error" : "ok"}]
if {$(:errInfo) == ""} { set (:errInfo) $::errorInfo }
return -code $code -errorinfo $(:errInfo) $msg
}
set (:level) 0
set (:errInfo) ""
proc w:error {{msg ""}} \
{
variable {}
if {$msg != ""} { set (:errInfo) $msg }
set (:level) 0
uplevel 1 [list error $msg]
}
set (:main:msg) {operation1, operation2, operation3 or sub1}
set (:main:map) {list %len% [llength $args] %action% [lindex $args 0]}
set (:main:cmd) \
{
operation1 \
{
w:operation1
1
{"apackage operation1 arg1 $args"}
{%len% < 1}
}
operation2 \
{
w:operation2
1
{"apackage operation2 ?$key $value?..."}
{%len% % 2 != 0}
}
operation3 \
{
w:operation3
{"apackage operation3 ?$arg?..."}
{0}
}
sub1 \
{
w:sub1
0
{"apackage sub1 action1|action2 ?$arg?..."}
{"%action%" != "action1" && "%action%" != "action2"}
}
version \
{
w:version
0
{"apackage version"}
{%len% != 0}
}
}
interp alias {} ::apackage::w:sub1 {} ::apackage::w:dispatch sub1
interp alias {} ::apackage::w:version {} set ::apackage::version
set (:sub1:msg) {action1 or action2}
set (:sub1:map) {list %len% [llength $args]}
set (:sub1:cmd) \
{
action1 \
{
w:sub1:action1
1
{"sub1 action1 arg1 $args"}
{%len% < 1}
}
action2 \
{
w:sub1:action2
1
{"sub1 action2 ?$key $value?..."}
{%len% % 2 != 0}
}
}
proc w:operation1 {arg1 args} \
{
if {![string is integer -strict $arg1]} \
{ w:error "expected integer, got \"$arg1\"" }
if {$arg1 == 0} { eval w:sub1 action2 $args }
}
proc w:operation2 {args} \
{
foreach {key value} $args \
{
switch -glob -- $key \
{
one -
two -
thr* -
fou* -
fiv* -
six { # ok }
default \
{ w:error "unknown key \"$key\"" }
}
}
}
proc w:sub1:action1 {arg1 args} \
{
if {![string is integer -strict $arg1]} \
{ w:error "expected integer, got \"$arg1\"" }
}
proc w:sub1:action2 {args} \
{
foreach {key value} $args \
{
switch -glob -- $key \
{
-one -
-two -
-thr* -
-fou* -
-fiv* -
-six { # ok }
default \
{ w:error "unknown key \"$key\"" }
}
}
}
}
The corresponding reference edit
(file: pkgIndex.tcl)
package ifneeded APackage 0.9 [list source [file join $dir apackage.tcl]]
(file: demo.tcl)
lappend auto_path [pwd]
package require APackage 0.9
namespace import ::apackage::apackage
proc demo {} \
{
result { apackage version }
result { apackage operation1 }
result { apackage operation1 "" }
result { apackage operation1 2 }
result { apackage operation1 0 one }
result { apackage operation1 0 one 1 }
result { apackage operation1 0 -one 1 }
result { apackage sub1 action1 arg }
result { apackage sub1 action2 -one 1 }
puts "\na full error trace:"
apackage operation1 0 one 1
}
proc result {script} \
{
set rc [catch { uplevel 1 $script } res]
if {$rc} { puts "{$script} -->$res" } \
elseif {$res != ""} { puts "{$script} : $res" } \
else { puts "{$script}" }
}
demo
The result
{ apackage version } : 0.9
{ apackage operation1 } -->wrong # args: should be "apackage operation1 arg1 $args"
{ apackage operation1 "" } -->expected integer, got ""
{ apackage operation1 2 }
{ apackage operation1 0 one } -->wrong # args: should be "sub1 action2 ?$key $value?..."
{ apackage operation1 0 one 1 } -->unknown key "one"
{ apackage operation1 0 -one 1 }
{ apackage sub1 action1 arg } -->expected integer, got "arg"
{ apackage sub1 action2 -one 1 }
a full error trace:
unknown key "one"
invoked from within
"apackage operation1 0 one 1"
(procedure "demo" line 12)
invoked from within
"demo"
(file "D:\mb\Src\Package\demo.tcl" line 44)