Tcl provides a package system that allows Tclers to share code. This package system supports two different formats that serve different use-cases:
-
The "package" or "8.5" format is more verbose, but allows package authors more flexibility through the
pkgIndex.tcl
file. This is designed for complex, multi-file programs. -
The "module" or
tm
format is simpler, reducing all package setup to the module file name, e.g.foo-1.0.0.tm
. This is designed for simple, single-file programs.
Both formats can be used with the same syntax, e.g. package require foo 1.0.0
.
We are going to steal an example from the Tcl 8.5 tutorial, and create a package that provides a stack data structure.
First we need to write the code we want to share, tutstack.tcl
:
# Register the package
package provide tutstack 1.0
package require Tcl 8.5
# Create the namespace
# Note: Tcl namespaces are traversed using `::`.
namespace eval ::tutstack {
# Export commands
namespace export create destroy push pop peek empty
# Set up state
variable stack
variable id 0
}
# Create a new stack
proc ::tutstack::create {} {
variable stack
variable id
set token "stack[incr id]"
set stack($token) [list]
return $token
}
# Destroy a stack
proc ::tutstack::destroy {token} {
variable stack
unset stack($token)
}
# Push an element onto a stack
proc ::tutstack::push {token elem} {
variable stack
lappend stack($token) $elem
}
# Check if stack is empty
proc ::tutstack::empty {token} {
variable stack
set num [llength $stack($token)]
return [expr {$num == 0}]
}
# See what is on top of the stack without removing it
proc ::tutstack::peek {token} {
variable stack
if {[empty $token]} {
error "stack empty"
}
return [lindex $stack($token) end]
}
# Remove an element from the top of the stack
proc ::tutstack::pop {token} {
variable stack
set ret [peek $token]
set stack($token) [lrange $stack($token) 0 end-1]
return $ret
}
Then we need to create the pkgIndex.tcl
file:
echo 'pkg_mkIndex .' | tclsh
Which should create the following:
# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script. It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands. When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.
package ifneeded tutstack 1.0 [list source [file join $dir tutstack.tcl]]
We can see here that this file is telling Tcl where to find our package, but if we need to do more than that we can write whatever we want in here.
Tcl scans for packages in various directories, you can see which using:
echo 'puts $auto_path | tclsh'
Paths can be added to the list using the $TCLLIBPATH
environment variable.
The Tcl manual
has more information about the various package
commands.
To writ the above as a module we create tutstack-1.0.0.tm
:
# Create the namespace
namespace eval ::tutstack {
# Export commands
namespace export create destroy push pop peek empty
# Set up state
variable stack
variable id 0
}
# Create a new stack
proc ::tutstack::create {} {
variable stack
variable id
set token "stack[incr id]"
set stack($token) [list]
return $token
}
# Destroy a stack
proc ::tutstack::destroy {token} {
variable stack
unset stack($token)
}
# Push an element onto a stack
proc ::tutstack::push {token elem} {
variable stack
lappend stack($token) $elem
}
# Check if stack is empty
proc ::tutstack::empty {token} {
variable stack
set num [llength $stack($token)]
return [expr {$num == 0}]
}
# See what is on top of the stack without removing it
proc ::tutstack::peek {token} {
variable stack
if {[empty $token]} {
error "stack empty"
}
return [lindex $stack($token) end]
}
# Remove an element from the top of the stack
proc ::tutstack::pop {token} {
variable stack
set ret [peek $token]
set stack($token) [lrange $stack($token) 0 end-1]
return $ret
}
There are no package commands in this file, everything is setup in the filename.
There is also no need to generate a pkgIndex.tcl
. There is however one
detail: Tcl looks for modules using a different list of paths, which we can
list out using:
$ echo 'puts [::tcl::tm::list]' | tclsh
We can add to these paths using environment variables. Since modules cannot
be scripted to check what version of tclsh
is calling them (unlike 8.5-style
packages with their pkgIndex.tcl
file), so there are different module paths
for different versions of Tcl. So for Tcl 8.6 we would add paths to
$TCL8_6_TM_PATH
and for 8.5 $TCL8_5_TM_PATH
. Module paths can also
be set in a file using the ::tcl::tm::path
commands. The
Tcl manual has more information
on the module system and tm
commands.