This project is about a little new ecosystem for Tcl. An MIT-licensed [1] OSS collection of tools and conventions which are easy to use and easy to adapt/extend. It was born out of frustration about the lack of software modularity and long-term code re-use.
Rig and Mavrig are written in and for Tcl. They may not suit everyone, just as the Tcl language is not everyone's choice for programming. So be it. They suit me, and they help me "grow" new software. The irony is that to avoid constantly re-inventing the wheel, I'm adding Rig and Mavrig as yet another new wheel. Again, so be it. Rig goes out of its way to be as non-intrusive and accomodating for existing code as possible. Whether you use it is up to you.
What is Rig?
Rig is a Tcl script which introduces a number of conventions for application development. It can load/download other Tcl source files in a modular way, and includes a simple notification system to loosely couple an entire application. The "Rig.tcl" script can be used as main startup file for simple cases, or it can be sourced as part of larger applications for full configurability.
Rig can be used in any type of application: command-line, gui, network clients, network servers, web apps, cgi scripts, or any mix of these.
And what is Mavrig?
Mavrig is an acronym: Modular Application Views and Rigs. Mavrig is about coming up with a set of effective Rig modules. Mavrig is also the name of the website where all this and Rig itself is being developed and made available.
Modularity
The central unit of modularity is called - surprise! - a rig. Each rig consist of source files, documentation, and test files which belong together.
Here's a basic rig:
- mycode.tcl - the Tcl code for this rig
- mycode.txt - documentation for this rig
- mycode.test - unit tests for this rig
When loaded by Rig, you get a namespace called "::mycode" containing all the code and all the variables and arrays local to this code. You also get a command called "::mycode <cmd> ..." which is the public interface for this rig/module.
IOW, a rig is a source file which gets loaded as follows by Rig:
namespace eval ::mycode { namespace export -clear {[a-z]*} namespace ensemble create source /path/to/mycode.tcl }
Some points to note:
- Rig favors simple conventions along with practical/sensible defaults
- a "rig" is a module supported by "Rig" - yes, capitalization matters here
- Rig itself is a rig, it uses the ::Rig namespace and defines the ::Rig command
- Rig does not create or change global variables (there are minor exceptions)
- the name of the main script file is also the name of the rig, namespace, etc.
- rigs starting with uppercase are for public use and widespread exchange
- rig source files must be written in such a way that they can be reloaded
- rig documentation can be written as wiki markup using plain text files
- rig tests must be runnable with as few dependencies on other rigs as possible
Hello world
Rig can be used in various modes, from trivial single-file scripts to a large collection of rigs with extensive customization of the main application logic.
The simplest use, which serves absolutely no purpose other than to illustrate that Rig is non-intrusive, is as follows:
- download Rig.tcl from http://contrib.mavrig.org/0.x/Rig.tcl
- create a file called "hello.tcl" containing:
puts "Hello world" exit
- launch this application as follows:
tclsh8.5 Rig.tcl hello.tcl
- or by using tclkit or one of its alternatives:
tclkit Rig.tcl hello.tcl
Note that Rig only works with Tcl version 8.5 or later.
A slightly better example
The above example did not really need Rig. But this one does:
puts "Hello world" puts "It is now: [Rig date iso]" exit
This calls a "date" method inside Rig. In OO terms, a rig is a singleton object. If you look inside the Rig.tcl file, you will see that it contains this code:
proc date {{type ""} {secs ""}} { ... }
Explanation: Rig uses Tcl 8.5's ensemble mechanism to perform this feat. By default, only proc names starting with lowercase are exposed as public methods.
Event-driven applications
So far, the use of Rig was quite limited. Here's a Rig application using Tk:
Rig hook main.Run { package require Tk button .b -text "Hello, World!" -command { set ::Rig::exit 0 } pack .b }
This hooks into Rig's notification system to run a bit of code at a specific phase in the application and exits the application in an event-friendly manner.
To see what is going on, we need to examine Rig's default event loop:
Rig notify main.Init $argv Rig notify main.Run if {![info exists Rig::exit]} { vwait Rig::exit } Rig notify main.Done $Rig::exit exit $Rig::exit
One point to note is that Rig has a notification mechanism into which you can hook scripts. In the above case, the Tk hello world code hooks itself into a notification called "main.Run". The code will be run after initialization, just before the main event loop is started, using Tcl's vwait.
The proper way to terminate is to set ::Rig::exit, which causes vwait to return. In this example, no hooks were defined for main.Run or main.Exit, but in more elaborate cases it is easy to see how everything can be tied together cleanly.
Another self-contained example, this time a network server for telnet:
Rig hook main.Run { socket -server [namespace code Accept] 2323 } proc Accept {sock addr port} { puts $sock "Hello, world!" set ::Rig::exit 0 }
Note the use of "namespace code". This is essential in rigs, because they are always loaded into their own namespace. If the above source is in a file called "server.tcl", then Accept will actually be defined as ::server::Accept.