Here are seven very simple single-file applications using Rig, and two more elaborate ones.


The simplest possible app - hello1.tcl

Create the file "hello1.tcl" with these lines:

    puts "Hello World, from [namespace current]"
    exit

Launch this mini app using tclsh8.5 or tclkit85:

    tclsh8.5 Rig.tcl hello1.tcl

Output:

    Hello World, from ::hello1

This doesn't really need Rig, but as you can see "hello1" is set up using rig conventions, i.e. in its own namespace.


Hooking into notifications - hello2.tcl

Create the file "hello2.tcl" with these lines:

    Rig hook main.Run {
      puts "Hello, world!"
      set ::Rig::exit 0
    }

Launch this mini app using tclsh8.5 or tclkit85:

    tclsh8.5 Rig.tcl hello2.tcl

Output:

    notify rig.Loaded hello2
    notify main.Init {}
    notify main.Run
    Hello, world!
    notify main.Done 0

This is the normal way for rigs to hook themselves into an application. There is still not much going on, because it immediately exits, but you now get a bit of extra log output displaying the main application phases.

To disable these log messages, insert the following at the top of hello2.tcl:

    proc ::tclLog {args} {}

To redirect these log messages to Rig's default logger, use this instead:

    proc ::tclLog {msg} { Log: {$msg} }

A gui application with Tk - hello3.tcl

Create the file "hello3.tcl" with these lines:

    Rig hook main.Run {
      package require Tk
      button .b -text "Hello, World!" -command { set ::Rig::exit 0 }
      pack .b
    }

This time, you'll need to launch it using wish8.5 (or tclkit):

    wish8.5 Rig.tcl hello3.tcl

The launch command will be omitted from now on, it's always similar.

Output:

  notify rig.Loaded hello3
  notify main.Init {}
  notify main.Run

Note how the app now idles in the event loop. Click on the Tk button to stop it.


A telnet server - hello4.tcl

Create the file "hello4.tcl" with these lines:

    Rig hook main.Run {
      socket -server [namespace code Accept] 2323
    }

    proc Accept {sock addr port} {
      puts $sock "Hello, world!"
      set ::Rig::exit 0
    }

This app will wait for a telnet session on port 2323. Once connected, it'll send out a greeting and then exit. An alternative for telnet is to use Tkcon via its Console / Attach To ... / Socket menu.


A remote command session - hello5.tcl

Create the file "hello5.tcl" with these lines:

    Rig hook main.Run {
      Connect start 2424
    }

    Rig hook connect.Session OnConnect

    proc OnConnect {type} {
      switch $type {
        open  { after idle { puts "Hello, world!" } }
        close { set ::Rig::exit 0 }
      }
    }

This app also waits for a telnet session, on port 2424. But now, you can enter Tcl commands and they'll be executed. When you close the session, the app exits.

The hello5 example also does something else, though. It uses the Connect rig to implement the remote session logic. Since this is the first time it is used, Rig will auto-download jcw/Connect.tcl from the mavrig repository and place it in a new directory called "rigcache". Here is a complete log transcript:

    notify rig.Loaded hello5
    notify main.Init {}
    notify main.Run
    notify rig.Fetching jcw/Connect \
      http://contrib.mavrig.org/0.x/jcw/Connect.tcl
    notify rig.Loaded jcw/Connect
    notify connect.Session open
    notify connect.Session close
    notify main.Done 0

On subsequent runs, the Connect module will be auto-loaded locally:

    notify rig.Loaded hello5
    notify main.Init {}
    notify main.Run
    notify rig.Loaded jcw/Connect
    notify connect.Session open
    ...

Note that when used as main startup script, Rig always fetches the current list of available rigs from the contrib.mavrig.org server (this can be disabled).


A trivial webserver - hello6.tcl

Create the file "hello6.tcl" with these lines:

    Rig hook main.Run {
      Httpd start 8080 [namespace code WebRequest]
    }

    proc WebRequest {obj} {
      $obj respond "<h1>Hello, world!</h1>"

      variable counter
      if {[incr counter] >= 3} {
        set ::Rig::exit 0
      }
    }

Once launched, visit this url with your web browser:

    http://localhost:8080/

After you press refresh two more times, the server will exit due to the included counter logic.


A template-driven webserver - hello7.tcl

Create the file "hello7.tcl" with these lines:

    Rig hook main.Run {
      Render setup [Rig home]/site
      Httpd start 8181 [namespace code WebRequest]
    }

    proc WebRequest {obj} {
      Render dispatch $obj
      $obj respond [$obj NEXT]
    }

You also need to create a "site" directory, with a file called "site/index.html" containing:

    <h1>Hello, world</h1>
    <p>It is now [Rig date]!</p>

Now visit this url with your web browser:

    http://localhost:8181/index.html

This time, you'll get a dynamically generated web page.

There is no logic in the above code to gracefully shutdown the server, but this can easily be added by changing the site/index.html file to be as follows:

    <h1>Hello, world</h1>
    <p>It is now [Rig date]!</p>

    % variable counter
    % if {[incr counter] >= 3} { set ::Rig::exit 0 }

Refresh your browser a few times. On the count of three, the server will exit.

Note how the changes to site/index.html were automatically picked up. This is a convenience feature of the Render module: it detects template file changes, so you can adjust them and simply refresh pages to see their effect. There are a few edge cases where this doesn't work exactly right (having to do with the special "auto.mav" templates), but normally you can adjust the content and all changes will show up in the browser without having to restart anything.


All together now - hello[34567].tcl

For fun, and to illustrate the modularity, let's combine a bunch of these apps.

First remove hello1.tcl and hello2.tcl from your directory, since those examples exit the moment they start up. You should be left with a directory containing:

    Rig.tcl
    hello3.tcl
    hello4.tcl
    hello5.tcl
    hello6.tcl
    hello7.tcl
    rigcache/
    site/

Now launch this using wish8.5 (since hello3.tcl uses Tk) or tclkit, but in a slightly different way:

    wish8.5 Rig.tcl

You now have an application running with a Tk window showing, two telnet servers running on ports 2323 and 2424, and two httpd servers on ports 8080 and 8181.

Note: as of 2008-05-23, Httpd does not quite support multiple servers, only the last one started will work properly, i.e. hello7 on port 8181.

The example is a bit silly because the moment you connect to port 2323 this app will stop, but that's because these examples weren't designed for combined use.

But the main point is that - with a bit of care - rigs can be combined at will, including all the code you write as rigs yourself.

Bonus: picking up source file changes in a running app

As an added benefit, all *.tcl files are now auto-loaded. One very nice way to take advantage of this is to include the following hook, perhaps in hello7.tcl:

    Rig hook httpd.Accept "Rig reload ;#"

You'll need to stop and restart your app once, due to a little chicken-and-egg problem (your app cannot become autoloading before it has the logic to do so!). But from now on, at the start of each incoming HTTP request, Rig will check and reload any changed *.tcl files it finds next to it. To see it in action, try changing the WebRequest code in hello7.tcl to this:

    proc WebRequest {obj} {
      puts "new web request started: $obj"
      Render dispatch $obj
      $obj respond [$obj NEXT]
    }

Do a page refresh in the browser, and the "puts" you just added will be called.


Take control - hello.tcl

With the application starting to become more complex, it's time to take control over from the defaults built into Rig. This will allow you to adjust settings, keep the cached rigs in a different place, control whether auto-downloading should be enabled, what servers to download from, etc. And - most importantly - to make the main application event loop explicit again.

These changes turn the above hodgepodge of rigs and directories into a more manageable structure, and let you make your own decisions on how to structure your app. As would be required when using Rig with existing code, for example.

Here's one way to set up a "normal" rig app. First you need to reorganize the above files a bit and add a new top-level source file called "hello.tcl". Move the existing files around a bit, until you have the following structure:

    hello.tcl
    hello/
        Rig.tcl
        hello3.tcl
        hello4.tcl
        hello5.tcl
        hello6.tcl
        hello7.tcl
    rigcache/
    site/

It is important to keep Rig.tcl next to your main application sources, because that's what makes it easy to reload changed source files during development.

Now let's create a new main "hello.tcl" file matching this configuration:

    #!/usr/bin/env tclkit85

    # load Rig and other modules next to it
    source [file root [info script]]/Rig.tcl

    # download modules from a remote repository when needed
    Rig autoload [Rig home]/rigcache http://contrib.mavrig.org/

    # the main application logic
    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

This simple version of hello.tcl will do for now, many more application settings can be added over time as the app evolves further. Some points to note:

  • the hello.tcl script is not for application code but for global policy choices
  • I added the Unix hash-bang line to start up my favorite Tcl runtime...
  • Rig.tcl is loaded from a directory named after the root of this main script
  • in this mode Rig no longer auto-downloads, so we add a line to re-enable that
  • the last 5 lines are Rig's boilerplate for handling events and notifications
  • that's it - note that hello.tcl is not a rig and runs in the global namespace

Now we can run this script and we should end up with the same results as the "hello[34567].tcl" code before. All we did was wrestle some control from Rig.

For example, if you don't want to keep auto-downloading enabled but want to use the current sources in the rig cache, then simply change that line to:

    Rig autoload [Rig home]/rigcache

Doing so stops Rig from contacting the contrib.mavrig.org site, as will probably be a requirement for a released application. Or create some rules in Tcl which choose between these two modes automatically. It's all up to you from here on.