Version 2 of cmdStream

Updated 2014-09-18 02:22:50 by pooryorick

PYK:

http://ynform.org/w/Pub/TclCmdStream

makes use of a slightly-modified version of scriptSplit

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
    path/to/moredata
} ; 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 {
                return
            }
        }
        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 scriptSplit {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\
                command:\n-----\n$chunk\n-----"
    }
    return $commands
}

proc cmd {item _state} {
    upvar $_state state
    foreach cmd [scriptSplit $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