Version 1 of cmdStream

Updated 2011-11-14 07:34:19 by AMG


makes use of a slightly-modified version of cmdSplit

walk through a text stream, extracting Tcl commands and collecting statistics

also useful for parsing config files that have the same format as a Tcl script, but where each "command" represents a configuration entry rather than a real command.

example data:

data {/path/to/data
} ; tmpdata /path/to/tmpdata
#this is a comment
cache /path/to/cache /path/to/another/cache ;#this is another comment
cache /path/to/a/third/cache
admin_account httpdadmin

fonts /path/to/fonts
fonts /path/to/more/fonts
#! /bin/env tclsh

proc cmdStream {fh callback _state} {
        upvar $_state state
        set data {}
        set empty [expr ![regexp {\S} $data]]
        while {$empty || ![info complete $data]} {
                if {[eof $fh]} {
                        if {[regexp {\S} $data]} {
                                return -code error \
                                        "incomplete syntax in data at line $state(linecount)"
                        } else {
                set newdata [gets $fh]
                append data \n $newdata
                set empty [expr ![regexp {\S} $newdata]]
                if {$empty} {
                        incr state(emptycount)
                incr state(linecount)
        incr state(listcount)
        uplevel 1 [list $callback $data $_state]

proc cmdSplit {body _state} {
        upvar $_state state
        set commands {}
        set chunk ""
        foreach line [split $body "\n"] {
                append chunk $line
                if {[info complete "$chunk\n"]} {
                        # $chunk ends in a complete Tcl command, and none of the
                        # newlines within it end a complete Tcl command.  If there
                        # are multiple Tcl commands in $chunk, they must be
                        # separated by semi-colons.
                        set cmd ""
                        foreach part [split $chunk ";"] {
                                append cmd $part
                                if {[info complete "$cmd\n"]} {
                                        set cmd [string trimleft $cmd]
                                        # Drop empty commands and comments
                                        if {[string match {} $cmd]} {
                                                incr state(emptycount)
                                        } elseif {[string match \#* $cmd]} {
                                                incr state(commentcount)
                                        } else {
                                                lappend commands $cmd
                                        if {[string match \#* $cmd]} {
                                                set cmd "\#;"
                                        } else {
                                                set cmd ""
                                } else {
                                        # No complete command yet.
                                        # Replace semicolon and continue
                                        append cmd ";"
                        set chunk ""
                } else {
                        # No end of command yet.  Put the newline back and continue
                        append chunk "\n"
         if {![string match {} [string trimright $chunk]]} {
                return -code error "Can't parse body into a\
                                sequence of commands.\n\tIncomplete\
        return $commands

proc cmd {item _state} {
        upvar $_state state
        foreach cmd [cmdSplit $item state] {
                puts "a command!: $cmd"

for {set fh [open data]} {![eof $fh]} {} {
        cmdStream $fh cmd state
close $fh
puts "total lines: $state(linecount)"
puts "total lists: $state(listcount)"
puts "total comments: $state(commentcount)"
puts "total empty: $state(emptycount)"
unset state