Search code examples
try-catchtclcatch-block

Capture stdout within a TCL catch command


In my main tcl script, I am calling a tcl proc wrapped in a catch command. This proc in-turn calls 10 more procs.

When there is an error in execution in any of those 10 procs, TCL still continues execution of my main script as expected and I am just able to view the error message which I captured. This error message may/may-not be conclusive enough to determine which of the 10 procs errored out during execution.

Is there a way to still keep capturing all the stdout until the point of error? I know it can be done by writing all messages (puts statements) in those 10 procs to another log file. But I'm interested in knowing if there is any other way.


Solution

  • The catch command doesn't intercept I/O at all. To intercept output, the simplest and most method is to put a channel transform on that channel with chan push.

    oo::class create Capture {
        variable contents encoding
        # Implement the channel interception protocol
        method initialize {handle mode} {
            set contents {}
            return {initialize finalize write}
        }   
        method finalize handle {
            # We do nothing here
        }
        method write {handle buffer} {
            append contents $buffer
            return $buffer
        }
    
        # Methods for ordinary people!
        method capture {channel body} {
            set encoding [chan configure $channel -encoding]
            chan push $channel [self]
            try {
                uplevel 1 $body
            } finally {
                chan pop $channel
            }
        }
        method contents {} {
            # Careful; need the encoding as channels work with binary data
            return [encoding convertfrom $encoding $contents]
        }
    }
    

    How to use this class:

    set capt [Capture new]
    $capt capture stdout {
        puts "Hello world!"
    }
    puts "Captured [string length [$capt contents]] characters"
    puts [lmap c [split [$capt contents] ""] {scan $c "%c"}]
    

    Output (I assume you recognise ASCII codes; the 13 10 at the end is a carriage-return/new-line sequence):

    Hello world!
    Captured 14 characters
    72 101 108 108 111 32 119 111 114 108 100 33 13 10