Version 6 of Tee

Updated 2022-05-25 10:04:30 by sbron

sbron: In unix/linux there is a standard command named tee , that duplicates its input to both standard output and a file. Inspired on that concept, I came up with some code to add similar functionality to Tcl.

The module adds a tee command to Tcl. It has three subcommands:

tee replace chan file
Copy all data sent to chan to file. chan must have been opened for writing. If file exists, it is truncated. This is the default, so it can also be invoked as: tee chan file.
tee append chan file
Copy all data sent to chan to file. chan must have been opened for writing. If file exists, the data is appended.
tee channel chan fd
Copy all data sent to chan to an already open fd. Both chan and fd must have been opened for writing.

The commands return a handle to the secondary output file to allow the caller to configure things like encoding, buffering, and baud rate. It is even possible to stack another transchan on top, like timestamp.

To stop copying the data to file and return chan to normal, use: chan pop chan. This closes the secondary output file. (Also in the case of tee channel chan fd!)

Usage example:

package require tee
package require timestamp
set tee [tee stdout /tmp/sessionlog.txt]
try {
    # Make the output line buffered, so time stamps will be accurate
    fconfigure $tee -buffering line
    # Add time stamps only to the copy going to the log file
    timestamp channel $tee
    puts "Hello, World!"
} finally {
    # Return stdout to normal, even in case of errors
    chan pop stdout
}

Implementation

Put the following code in a file called tee-1.0.tm and place it somewhere in the tcl::tm path:

namespace eval tee {
    variable methods {initialize finalize write}
    namespace ensemble create -subcommands {replace append channel} \
      -unknown [namespace current]::default
    namespace ensemble create -command transchan -parameters fd \
      -subcommands $methods
}

proc tee::default {command subcommand args} {
    return [list $command replace $subcommand]
}

proc tee::channel {chan fd} {
    chan push $chan [list [namespace which transchan] $fd]
    return $fd
}

proc tee::replace {chan file} {
    return [channel $chan [open $file w]]
}

proc tee::append {chan file} {
    return [channel $chan [open $file a]]
}

proc tee::initialize {fd handle mode} {
    variable methods
    return $methods
}

proc tee::finalize {fd handle} {
    close $fd
}

proc tee::write {fd handle buffer} {
    puts -nonewline $fd $buffer
    return $buffer
}