Commenter

This is a utility to parse Tcl source files and associate out-line comments with procs.

# Commenter - a utility to parse tcl source files and associate out-line
# comments with procs.
#
    
package require fileutil
package provide Commenter 1.0
    
namespace eval Commenter {
    
    # gather leading comment block
    proc leadin {text} {
    	set leadin {}
    	set lnum 0
    	foreach line $text {
    	    set line [string trim $line " \t"]
    	    incr lnum
    	    if {[string match \#* $line]} {
    		lappend leadin $line
    	    } else {
    		return [list $leadin [lrange $text $lnum end]]
    	    }
    	}
    	return [list $leadin {}]
    }
    
    # parse a tcl source into a dict containing:
    # provides - the package provided by this source
    # requires - the packages required by this source
    # contexts - the contexts provided by this source (namespace, global, snit)
    # entities - the procs, vars, options and methods provided by this source
    
    proc parse {text} {
    	set text [split $text \n]
    
    	# leadin is a comment block terminated by a non-comment
    	lassign [leadin $text] leadin text
    	set lnum [llength $leadin]
    	#puts stderr "$lnum line leadin"
    
    	set comment {}	;# free-standing comment block
    	set current ""	;# current context
    	array set context [list "" [list name "" type global comment $leadin]]	;# context of declaration
    
    	set requires {} ;# packages required
    	set provides ""	;# package provided
    	set accum ""	;# we're not in proc scope
    
    	foreach line $text {
    	    set line [string trim $line " \t"]
    	    incr lnum
    
    	    if {$accum ne ""} {
    		# we're in proc context
    		append accum \n $line
    		#puts stderr $accum
    		if {![info complete $accum]} {
    		    continue
    		} else {
    		    set accum ""	;# we're out of proc context
    		}
    	    }
    
    	    switch -glob -- $line {
    		\#* {
    		    # accumulate comments en bloc
    		    lappend comment $line
    		}
    
    		package* {
    		    # provide
    		    # require
    		    regsub {[ \t]+} $line " " line
    		    set line [split $line]
    		    switch -- [lindex $line 1] {
    			provide {
    			    set provides [lindex $line 2]
    			    #puts stderr "provides $provides"
    			}
    			require {
    			    if {[string match -* [lindex $line 2]]} {
    				lappend requires [lindex $line 3]
    			    } else {
    				lappend requires [lindex $line 2]
    			    }
    			    #puts stderr "requires $requires"
    			}
    		    }
    		    set comment {}
    		}
    
    		namespace* {
    		    # eval
    		    regsub {[ \t]+} $line " " line
    		    set line [split $line]
    		    if {[lindex $line 1] eq "eval"} {
    			set name [lindex $line 2]
    			set context($name) [list name $name comment $comment type namespace]
    			set current $name
    			set comment {}
    		    }
    		    #puts stderr "namespace $name"
    		}
    
    		snit::type* {
    		    regsub {[ \t]+} $line " " line
    		    set line [split $line]
    		    set name [lindex $line 1]
    		    set context($name) [list name $name comment $comment type snit]
    		    #puts stderr "snit $name"
    		}
    
    		variable* -
    		option* -
    		method* -
    		proc* {
    		    regsub {[ \t]+} $line " " line
    		    set accum $line	;# enter proc context
    
    		    set line [split $line]
    		    set name [lindex $line 1]
    		    if {[lindex $line 0] in {variable option}} {
    			set trailing [lindex [split [join $line] \#] 1]
    			lappend comment $trailing
    		    }
    		    set entities($name) [list name $name comment $comment line $lnum context $current type [lindex $line 0]]
    		    set comment {}
    		    #puts stderr "[lindex $line 0] $name"
    		}
    	    }
    	}
    	return [list contexts [array get context] entities [array get entities] provides $provides requires $requires]
    }
    
    # same as parse, but operates on the contents of a file
    proc parseF {path} {
    	return [parse [::fileutil::cat $path]]
    }
    
    namespace export -clear *
    namespace ensemble create -subcommands {}
}
    
if {[info exists argv0] && ($argv0 eq [info script])} {
    set path [info script]
    puts stderr [Commenter parseF $path]
}