A simple package example

Richard Suchenwirth 2002-04-15 - In my simple ways of Tcl programming, I have never before created a package. However, as I will have to teach Tcl programming, I finally could not avoid this point - and as usual with Tcl, it was little hassle but fun ;-) . The following script creates the trivial but educational package futil, which in a namespace of the same name implements two procs for reading and writing complete text files, and the little introspection helper function, futil::?. The command to register the package (package provide) is executed only after all went well - this way, buggy source code, which raises an error during package require, will not be registered. (Other bugs you'd have to spot and fix for yourself...)

Common Tcl distribution practice has the good habit of profound testing, typically in a separate test directory. On the other hand, including a self-test in the same file with the code makes editing easier, so after the package provide comes a section only executed if this file is sourced as a top- level script, which exercises the commands defined in futil. Whether the string read should be equal to the string written is debatable - the present implementation appends \n to the string if it doesn't end in one, because some tools complain or misbehave if they don't see a final newline.

If the tests do not run into an error either, even the required construction of a package index is fired - assuming the simplified case that the directory contains only one package. Otherwise, you'd better remove these lines, and take care of index creation yourself.

A script that uses this package will only have to contain the two lines

 lappend ::auto_path <directory of this file>
 package require futil

You can even omit the first line, if you install (copy) the directory with the source and pkgIndex.tcl below ${tcl_install_directory}/lib.

 namespace eval futil {
     set version 0.1
 }

 proc futil::read {filename} {
    set fp [open $filename]
    set string [::read $fp] ;# prevent name conflict with itself
    close $fp
    return $string
 }
 proc futil::write {filename string} {
    set fp [open $filename w]
    if {[string index $string end]!="\n"} {append string \n}
    puts -nonewline $fp $string
    close $fp
 }
 proc futil::? {} {lsort [info procs ::futil::*]}
 # If execution comes this far, we have succeeded ;-)
 package provide futil $futil::version

 #--------------------------- Self-test code
 if {[info ex argv0] && [file tail [info script]] == [file tail $argv0]} {
    puts "package futil contains [futil::?]"
    set teststring {
        This is a teststring
        in several lines...}
    puts teststring:'$teststring'
    futil::write test.tmp $teststring
    set string2 [futil::read test.tmp]
    puts string2:'$string2'
    puts "strings are [expr {$teststring==$string2? {}:{not}}] equal"
    file delete test.tmp ;# don't leave traces of testing

    # Simple index generator, if the directory contains only this package
    pkg_mkIndex -verbose [file dirn [info scr]] [file tail [info scr]]
 }