Included is a list of some of the modules that we use often within our various Tcl Scripts & Applications. Many of them are extremely small and simple (1-3 lines).
While many of the "micro packages" were created by our team, some of them were taken or adapted from our various encounters in the Tcl Wiki, other open source communities, or the excellent Tcl IRC Chatroom.
We have tried to add credits or links to reference and provide credit whenever possible. If anyone was left out and you see some of your code just let us know and I will get it added as quickly as possible!
One of Tcl's best features is how flexible the language itself is. Many of these modules take advantage of that fact to provide new features and options while writing your Applications.
You may use any of these modules however you wish.
If you have any good ones that should be added - send them over / pull requests more than welcome!
Would love to collect any other "awesome" and useful Tcl procedures that you find yourself using and others may find useful.
This repo is automatically maintained by our build system. Any time that a file is changed, the bot will commit it. While it used to auto-increment the version, filename changes does not go well with github (links change for sources, etc), it will simply add a commit message with the version of the repo when it was updated.
You can use these packages by simply downloading them and including them within your
tm module path ([::tcl::tm::path list]
). Note that some of these packages may depend
on others and/or having the directory structure intact.
Once you have done this you should be able to package require
them.
Tip: You can add to the tcl module directories list by calling
[::tcl::tm::path add $dir]
A simple "include.tcl" is included within this repo. It simply adds the directory that it is located within to the tm package path. So if you run tclsh you can do something like:
# Source the include file
source /path/to/tm/include.tcl
# Include any tcl modules
package require react
package require extend::dict
# ... your script ... #
In addition to these modules, there are a couple separate repos which can be considered apart of the "tcl-modules" repository.
- cluster - multi-protocol one-to-many IPC/IMC framework with auto-discovery and protocol-failover (a replacement for the comm package).
- task-manager - a task scheduler in pure-tcl. Think of it like an extremely powerful
[after]
which includes capabilities like[every]
,[every/while]
,[after/at]
,[after/if]
, and much more. - sagas - a (mostly-finished) concurrency library allowing the scheduling of coroutines which provide a mechanism for handling cancellation, asynchronous closures, and lifecycle management.
As well as a few other tools which you may find helpful when working with Tcl.
- kit-creator - a custom fork of rkeene's kitcreator which adds some organization and some extra extension build scripts. For example, one may add "tcl-modules" to automatically download and compile this repo (as well as task and cluster) into a custom tcl-executable.
- atom-language-dashtcl - Special syntax highlighting for tcl that is aware of the context of many built-in commands as well as various tcl-modules such as
[state]
and[graphql]
.
Below you can find a few examples of some of the modules found in this repo. Most of the packages are documented inline to the source so be sure to check those out.
A favorite among many tclers, a simple way to setup a callback command that will resolve to the current namespace. This is especially useful when scheduling callbacks from within TclOO Objects.
Simple Example
package require callback
namespace eval foo {
proc start args {
after 0 [callback complete {*}$args]
}
proc complete args {
puts "Complete! $args"
}
}
foo::start one two three
TclOO Example
package require callback
::oo::class create MyClass {
method start args {
after 0 [callback my Complete {*}$args]
}
# Works even with unexpored methods!
method Complete args {
puts "Complete! $args"
}
}
set obj [MyClass new]
$obj start one two three
An extremely simple but useful procedure that helps when you have to construct commands that may need to be evaluated both in the current context as well as in another (such as when calling uplevel or doing a coroutine injection).
Simple Example
While a silly example, it is the simplest example of how this might be useful I could think of. In general when we use this it is for building control structures and/or for coroutine injection.
package require cmdlist
proc foo { name value } {
set one foo
set two bar
set three baz
modify $name $value
}
proc modify { varname value } {
uplevel 1 [cmdlist \
{report $one $two $three} \
[list set $varname $value] \
{report $one $two $three}
]
}
proc report { args } {
puts "Value: $args"
}
foo two newvalue
# Value: foo bar baz
# Value: foo newvalue baz
cswitch is a kind of [switch]
[if]
hybrid. It's last value should be a
dict. It evaluates each expr (in order given, in the callers context) as an
expression.
By default, it will execute the first matching key's script (also in the callers
context). If the -all
flag is given [cswitch -all]
, every expression will be
evaluated and executed when true.
Finally, if the -get
flag is provided, the result of command will be a dict
where the keys are the [lindex]
of the passing expr script pair and the
value is the result of running script.
Flag Name | Description |
---|---|
-all | Run and execute for all matches rather than only the first. |
-get | Get the results as a dict where the key is the index of the check and the value is the result. |
Tip: You can use
[break]
and[continue]
to control execution. Additionally, you can return a response when breaking by using[return -code break $result]
. This will add the given result to the results (if the-get
flag is given) then stop evaluation.
Tip: Just like
[switch]
you can use-
after a command to defer to the next items body when a match occurs. When this occurs and-all
is specified, the children will not be evaluated so the given body will only be executed once.
Simple Example
package require cswitch
set foo 1
set bar 0
set result [ cswitch -all -get -- {
{ ! [info exists foo] || ! [info exists bar] } {
puts "Doesnt Exist, stop!"
break
}
# Comments are allowed, however they slow execution a bit!
{ [string is false -strict $foo] } -
0 {
puts "False! No need to continue"
return -code break foo
}
{ [string is true -strict $foo] } {
puts true!
set v h
}
{ [string is true -strict $foo] && [string is false -strict $bar] } {
puts "whoop whoop"
}
}]
# true!
# whoop whoop
puts "Result: $result" ; # Result: 3 h 4 {}
Another extremely simple one, valias is used to alias a variable to another variable so that their values will always match. Modifying one will be reflected in the other.
Simple Example
package require valias
set foo "Hello"
valias foo bar
puts $bar
# "Hello"
set bar "Hello, World!"
puts $foo
# "Hello, World!"
puts $bar
# "Hello, World!"
Taking advantage of Tcl's namespace ensemble features, extend allows us to "extend" the core Tcl Ensembles with new functionality.
- See Also: Tcl Wiki Page
[string cat] polyfill
Here is an example of extending string to add 8.6's [string cat] feature in situations that our script may be running in earlier versions.
package require extend
extend string {
if { [::catch {::string cat}] } {
proc cat args { ::join $args {} }
}
}
puts [string cat one two]
# onetwo
[run]
provides a flexible utility for running a given script within an (optionally)
scoped environment. It is run within "apply" so the return value of the script is the
value [run]
will return.
Simple Example
package require run
namespace eval foo {}
proc ::foo::start { myvar } {
set i 0
puts "::foo::start | myvar $myvar"
puts "::foo::start | i $i"
puts "--- Call next_proc ---"
next_proc i
puts "--- After next_proc ---"
puts "::foo::start | myvar $myvar"
puts "::foo::start | i $i"
}
proc ::foo::next_proc args {
set foo bar
# we can run scoped commands locally
puts "::foo::next_proc | foo $foo"
run -scoped {
# oh no!
set foo my_value
puts "::foo::next_proc run -scoped | unsetting all known vars: [info vars]"
foreach var [info vars] {
puts "::foo::next_proc run -scoped | unset $var with value [set $var]"
unset $var
} ; unset var
puts "::foo::next_proc run -scoped | vars known: [info vars]"
}
# lets run a command, scoped, in the level above us with myvar and duration.
# we may optionally specify -upvar to have the vars attached to the scope.
run -scoped -vars $args -level 1 -upvar {
# we are running a scoped script in the level above us. We have brought in
# the variables specified by $args (i) which is the only variable which we
# are modifying in this case.
incr i
# we don't have to worry about collisions with the scope
set myvar collision_occurred
set foo qux
puts "::foo::next_proc run -scoped -upvar | myvar $myvar | i $i | foo $foo"
}
puts "::foo::next_proc | known vars | [info vars] | foo $foo"
set response [ run -level 2 -vars myvar -upvar {
# 2 levels up lets change the value of myvar
set myvar changed
} ]
puts "::foo::next_proc | response $response"
}
set myvar my_value
puts ":: | myvar $myvar"
puts "--- Call ::foo::start ---"
::foo::start $myvar
puts "--- After ::foo::start ---"
puts ":: | myvar $myvar"
[pubsub]
aims to provide an extremely simple publisher/subscriber pattern for
handling the execution of one or more commands when a given message/path is
published to.
Subscribing to a Path
pubsub subscribe id ?...path? callback
# Basic
pubsub subscribe MySubscription MY_EVENT my_proc
# Multiple
pubsub subscribe MySubscription2 MY_EVENT my_proc
pubsub subscribe MySubscription3 MY_EVENT my_proc
# Nested Paths
pubsub subscribe B1Press button_one pressed my_proc
pubsub subscribe B1Release button_one release my_proc
Dispatching to a Path
pubsub dispatch data ?...path?
# Returns the total # of subscribers that were executed as a
# result of the dispatch.
set total_executed [ pubsub dispatch [dict create foo bar] MY_EVENT ]
if { ! [ pubsub dispatch [dict create foo bar] button_one pressed ] } {
puts "No Subscribers"
}
Unsubscribing by ID or Path
pubsub unsubscribe id
pubsub unsubscribe_path ?...path?
pubsub unsubscribe MySubscription
pubsub unsubscribe_path MY_EVENT
Triggering a Subscription by ID
pubsub trigger id
pubsub trigger MySubscription
Resetting / Removing all Subscriptions
pubsub reset
pubsub reset
[ensembled]
is really just a nice little convenience wrapper for defining
a namespace which will act as an ensemble and will export all procedures that
start with a lower-case [a-z] character.
Simple Example
package require ensembled
namespace eval foo { ensembled }
proc foo::call args { puts $args }
foo call one two three
# one two three