Check-in [be32ebcb41]
Not logged in
Overview

SHA1 Hash:be32ebcb41cfeabaf07dcc60ebbb2166fff76483
Date: 2007-09-08 05:35:02
User: aku
Comment:Redid the logging system aka user feedback completely. Verbosity levels, influenced by the new -v switch. Indentations in the output removed, parsing by tools easier, still human readable. Adapted all users of the previous feedback code to use the new system.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified tools/import-cvs.tcl from [f419734d96] to [80c1f4022f].

@@ -42,63 +42,64 @@
 # Requirements
 
 package require Tcl 8.4
 package require cvs    ; # Frontend, reading from source repository
 package require fossil ; # Backend,  writing to destination repository.
+package require tools::log
+
+::tools::log::system import
 
 # -----------------------------------------------------------------------------
 
 proc main {} {
-    global argv tot nto cvs fossil ntrunk stopat
-
-    commandline
-
-    fossil::feedback Write ; # Setup progress feedback from the libraries
-    cvs::feedback    Write
+    global argv tot nto cvs fossil ntrunk stopat nmax ntfmt nmfmt
+
+    commandline
 
     cvs::at       $cvs  ; # Define location of CVS repository
     cvs::scan           ; # Gather revision data from the archives
     cvs::csets          ; # Group changes into sets
     cvs::rtree          ; # Build revision tree (trunk only right now).
 
     set tot 0.0
     set nto 0
 
-    Write info {Importing ...}
-    Write info {    Setting up cvs workspace and temporary fossil repository}
+    ::tools::log::write 0 import {Begin conversion}
+    ::tools::log::write 0 import {Setting up workspaces}
 
     cvs::workspace ; # cd's to workspace
     fossil::new    ; # Uses cwd as workspace to connect to.
 
-    set ntrunk [cvs::ntrunk]
+    set ntrunk [cvs::ntrunk] ; set ntfmt %[string length $ntrunk]s
+    set nmax   [cvs::ncsets] ; set nmfmt %[string length $nmax]s
+
     cvs::foreach_cset cset [cvs::root] {
 	import $cset
 	if {$stopat == $cset} exit
     }
     cvs::wsclear
 
-    Write info "    ========= [string repeat = 61]"
-    Write info "    Imported $nto [expr {($nto == 1) ? "changeset" : "changesets"}]"
-    Write info "    Within [format %.2f $tot] seconds (avg [format %.2f [expr {$tot/$nto}]] seconds/changeset)"
-
-    Write info {    Moving to final destination}
+    ::tools::log::write 0 import "========= [string repeat = 61]"
+    ::tools::log::write 0 import "Imported $nto [expr {($nto == 1) ? "changeset" : "changesets"}]"
+    ::tools::log::write 0 import "Within [format %.2f $tot] seconds (avg [format %.2f [expr {$tot/$nto}]] seconds/changeset)"
 
     fossil::destination $fossil
 
-    Write info Ok.
+    ::tools::log::write 0 import Ok.
     return
 }
 
 
 # -----------------------------------------------------------------------------
 
 proc commandline {} {
-    global argv cvs fossil nosign log debugcommit stopat
+    global argv cvs fossil nosign debugcommit stopat
 
     set nosign 0
     set debugcommit 0
     set stopat {}
+    set verbosity 0
 
     while {[string match "-*" [set opt [lindex $argv 0]]]} {
 	if {$opt eq "--nosign"} {
 	    set nosign 1
 	    set argv [lrange $argv 1 end]
@@ -110,10 +111,16 @@
 	    continue
 	}
 	if {$opt eq "--stopat"} {
 	    set stopat [lindex $argv 1]
 	    set argv   [lrange $argv 2 end]
+	    continue
+	}
+	if {$opt eq "-v"} {
+	    incr verbosity
+	    ::tools::log::verbosity $verbosity
+	    set argv   [lrange $argv 1 end]
 	    continue
 	}
 	usage
     }
     if {[llength $argv] != 2} usage
@@ -127,12 +134,10 @@
 	usage "CVS directory missing, not readable, or not a directory."
     } elseif {[file exists $fossil]} {
 	usage "Fossil destination repository exists already."
     }
 
-    set log [open ${fossil}.log w]
-
     fossil::debugcommit $debugcommit
     return
 }
 
 proc usage {{text {}}} {
@@ -142,19 +147,18 @@
     puts stderr "       $text"
     exit
 }
 
 proc import {cset} {
-    global tot nto nosign ntrunk stopat
-    Write info "    Importing $cset [string repeat = [expr {60 - [string length $cset]}]]"
-    Write info "        At $nto/$ntrunk ([format %.2f [expr {$nto*100.0/$ntrunk}]]%)"
+    global tot nto nosign ntrunk stopat ntfmt nmfmt
+    ::tools::log::write 0 import "ChangeSet [format $nmfmt $cset] @ [format $ntfmt $nto]/$ntrunk ([format %6.2f [expr {$nto*100.0/$ntrunk}]]%)"
 
     if {$stopat == $cset} {
 	fossil::commit 1 cvs2fossil $nosign \
 	    [cvs::wssetup $cset] \
 	    ::cvs::wsignore
-	Write info "        %% STOP"
+	::tools::log::write 1 import {%% STOP}
 	return
     }
 
     set usec [lindex [time {
 	foreach {uuid ad rm ch} [fossil::commit 0 cvs2fossil $nosign \
@@ -165,45 +169,23 @@
 
     set sec [expr {$usec/1e6}]
     set tot [expr {$tot + $sec}]
     incr nto
 
-    Write info "        == $uuid +${ad}-${rm}*${ch}"
-    Write info "        in $sec seconds"
+    ::tools::log::write 2 import "== $uuid +${ad}-${rm}*${ch}"
+    ::tools::log::write 2 import "st in  [format %.2f $sec] sec"
 
     set avg [expr {$tot/$nto}]
     set max [expr {$ntrunk * $avg}]
     set rem [expr {$max - $tot}]
 
-    Write info "        st avg [format %.2f $avg]"
-    Write info "        st run [format %7.2f $tot] sec [format %6.2f [expr {$tot/60}]] min [format %5.2f [expr {$tot/3600}]] hr"
-    Write info "        st end [format %7.2f $max] sec [format %6.2f [expr {$max/60}]] min [format %5.2f [expr {$max/3600}]] hr"
-    Write info "        st rem [format %7.2f $rem] sec [format %6.2f [expr {$rem/60}]] min [format %5.2f [expr {$rem/3600}]] hr"
-    return
-}
-
-# -----------------------------------------------------------------------------
-
-array set fl {
-    debug   {DEBUG  }
-    info    {       }
-    warning {Warning}
-    error   {ERROR  }
-}
-
-proc Write {l t} {
-    global fl log
-
-    if {[string index $t 0] eq "\r"} {
-	puts -nonewline stdout "\r$fl($l) [string range $t 0 end-1]"
-    } else {
-	puts stdout "$fl($l) $t"
-	puts $log   "$fl($l) $t"
-    }
-    flush stdout
+    ::tools::log::write 3 import "st avg [format %.2f $avg] sec"
+    ::tools::log::write 3 import "st run [format %7.2f $tot] sec [format %6.2f [expr {$tot/60}]] min [format %5.2f [expr {$tot/3600}]] hr"
+    ::tools::log::write 3 import "st end [format %7.2f $max] sec [format %6.2f [expr {$max/60}]] min [format %5.2f [expr {$max/3600}]] hr"
+    ::tools::log::write 3 import "st rem [format %7.2f $rem] sec [format %6.2f [expr {$rem/60}]] min [format %5.2f [expr {$rem/3600}]] hr"
     return
 }
 
 # -----------------------------------------------------------------------------
 
 main
 exit

Modified tools/lib/cvs.tcl from [e89af18f22] to [8597d1c1ae].

@@ -3,37 +3,34 @@
 
 # -----------------------------------------------------------------------------
 # Requirements
 
 package require Tcl 8.4
-package require fileutil  ; # Tcllib (cat)
-package require rcsparser ; # Handling the RCS archive files.
+package require fileutil      ; # Tcllib (traverse directory hierarchy)
+package require rcsparser     ; # Handling the RCS archive files.
+package require tools::log    ; # User feedback
 package require struct::tree
 
-namespace eval ::cvs {}
+namespace eval ::cvs {
+    tools::log::system cvs
+    namespace import ::tools::log::write
+}
 
 # -----------------------------------------------------------------------------
 # API
 
 # Define repository directory.
 
 proc ::cvs::at {path} {
     variable base [file normalize $path]
+    write 0 cvs "Base: $base"
     return
 }
 
 namespace eval ::cvs {
     # Toplevel repository directory
     variable base {}
-}
-
-# Define logging callback command
-
-proc ::cvs::feedback {logcmd} {
-    variable lc $logcmd
-    ::rcsparser::feedback $logcmd
-    return
 }
 
 # Scan repository, collect archives, parse them, and collect revision
 # information (file, revision -> date, author, commit message)
 
@@ -41,21 +38,21 @@
     variable base
     variable npaths
     variable rpaths
     variable timeline
 
-    Log info "Scanning CVS tree $base"
+    write 0 cvs {Scanning directory hierarchy}
 
     set n 0
     foreach rcs [fileutil::findByPattern $base -glob *,v] {
 	set rcs [fileutil::stripPath $base $rcs]
 	# Now rcs is relative to base
 
-	Log info "    Parsing archive $rcs"
+	write 1 cvs "Archive $rcs"
 
 	if {[string match CVSROOT* $rcs]} {
-	    Log info "                 => Ignoring admin file"
+	    write 2 cvs {Ignored. Administrative file}
 	    continue
 	}
 
 	# Derive the regular path from the rcs path. Meaning: Chop of
 	# the ",v" suffix, and remove a possible "Attic".
@@ -64,11 +61,12 @@
 	    set f [file join [file dirname [file dirname $f]] [file tail $f]]
 	    if {[file exists $base/$f,v]} {
 		# We have a regular archive and an Attic archive
 		# refering to the same user visible file. Ignore the
 		# file in the Attic.
-		Log info "                 => Ignoring attic for regular archive"
+
+		write 2 cvs "Ignored. Attic superceded by regular archive"
 
 		# TODO/CHECK. My method of co'ing exact file revisions
 		# per the info in the collected csets has the flaw
 		# that I may have to know exactly when what archive
 		# file to use, see above. It might be better to use
@@ -85,12 +83,10 @@
 	}
 
 	# Get the meta data we need (revisions, timeline, messages).
 	set meta [::rcsparser::process $base/$rcs]
 
-	Log info "         => $f"
-
 	set npaths($rcs) $f
 	set rpaths($f) $rcs
 
 	array set p $meta
 
@@ -103,11 +99,11 @@
 	    # seems to exist only! on its branch. The branches
 	    # information is set on the revision (extend rcsparser!),
 	    # symbols has a tag, refering to a branch, possibly magic.
 
 	    if {($rev eq "1.1") && ($op eq "R")} {
-		Log info "         => Dead first"
+		write 2 cvs {Dead root revision}
 	    }
 
 	    lappend timeline($ts) [list $op $ts $a $rev $f $cm]
 	}
 
@@ -115,11 +111,11 @@
 	#parray p
 
 	incr n
     }
 
-    Log info "Processed $n [expr {($n == 1) ? "file" : "files"}]"
+    write 0 cvs "Processed $n [expr {($n == 1) ? "file" : "files"}]"
     return
 }
 
 namespace eval ::cvs {
     # Path mappings. npaths: rcs file  -> user file
@@ -143,11 +139,11 @@
 
     array unset csets * ; array set csets {}
     array unset cmap  * ; array set cmap  {}
     set ncs 0
 
-    Log info "Processing timeline"
+    write 0 cvs "Processing timeline"
 
     set n 0
     CSClear
     foreach ts [lsort -dict [array names timeline]] {
 
@@ -167,13 +163,14 @@
 	    CSAdd $entry
 	    incr n
 	}
     }
 
-    Log info "Processed $n [expr {($n == 1) ? "entry" : "entries"}]"
+    write 0 cvs "Processed $n [expr {($n == 1) ? "entry" : "entries"}]"
+
     set n [array size csets]
-    Log info "Found     $n [expr {($n == 1) ? "changeset" : "changesets"}]"
+    write 0 cvs "Found     $n [expr {($n == 1) ? "changeset" : "changesets"}]"
     return
 }
 
 
 namespace eval ::cvs {
@@ -192,11 +189,11 @@
 proc ::cvs::rtree {} {
     variable csets
     variable rtree {}
     variable ntrunk 0
 
-    Log info "Extracting the trunk"
+    write 0 cvs "Extracting the trunk"
 
     set rtree [struct::tree ::cvs::RT]
     $rtree rename root 0 ; # Root is first changeset, always.
     set trunk 0
     set ntrunk 1 ; # Root is on the trunk.
@@ -218,12 +215,12 @@
 	$rtree insert $trunk end $c
 	set trunk $c
 	incr ntrunk
     }
 
-    Log info "Processed $ntrunk trunk  [expr {($ntrunk == 1) ? "changeset" : "changesets"}]"
-    Log info "Ignored   $b branch [expr {($b == 1) ? "changeset" : "changesets"}]"
+    write 0 cvs "Processed $ntrunk trunk  [expr {($ntrunk == 1) ? "changeset" : "changesets"}]"
+    write 0 cvs "Ignored   $b branch [expr {($b == 1) ? "changeset" : "changesets"}]"
     return
 }
 
 namespace eval ::cvs {
     # Tree holding trunk and branch information (struct::tree).
@@ -237,11 +234,11 @@
     variable cwd [pwd]
     variable workspace [fileutil::tempfile importF_cvs_ws_]
     file delete $workspace
     file mkdir  $workspace
 
-    Log info "    Workspace: $workspace"
+    write 0 cvs "Workspace:  $workspace"
 
     cd     $workspace ; # Checkouts go here.
     return $workspace
 }
 
@@ -266,19 +263,19 @@
 
     # pwd = workspace
 
     foreach {u cm s e rd fs} $csets($c) break
 
-    Log info "        @  $s"
+    write 1 cvs "@  $s"
 
     foreach l [split [string trim $cm] \n] {
-	Log info "        |  $l"
+	write 1 cvs "|  $l"
     }
 
     foreach {f or} $fs {
 	foreach {op r} $or break
-	Log info "        -- $op $f $r"
+	write 2 cvs "$op  $f $r"
 
 	if {$op eq "R"} {
 	    # Remove file from workspace. Prune empty directories.
 	    #
 	    # NOTE: A dead-first file (rev 1.1 dead) will never have
@@ -312,11 +309,11 @@
 		    # a consequence the fossil repository will not
 		    # contain the full history of the named file. By
 		    # ignoring the problem we however get as much as
 		    # is possible.
 
-		    Log info "        EE Corrupted archive file. Inaccessible revision."
+		    write 0 cvs "EE Corrupted archive file. Inaccessible revision."
 		    continue
 		}
 		return -code error $msg
 	    }
 	}
@@ -345,11 +342,11 @@
 	set code [catch {uplevel 1 $script} res]
 
 	# 0 - ok, 1 - error, 2 - return, 3 - break, 4 - continue
 	switch -- $code {
 	    0 {}
-	    1 { return -errorcode $::errorcode -code error $res }
+	    1 { return -errorcode $::errorCode -errorinfo $::errorInfo -code error $res }
 	    2 {}
 	    3 { return }
 	    4 {}
 	    default {
 		return -code $code $result
@@ -372,10 +369,15 @@
 }
 
 proc ::cvs::ntrunk {} {
     variable ntrunk
     return  $ntrunk
+}
+
+proc ::cvs::ncsets {} {
+    variable ncs
+    return  $ncs
 }
 
 proc ::cvs::uuid {c uuid} {
     variable rtree
     $rtree set $c uuid $uuid
@@ -470,28 +472,12 @@
     foreach {f or} $f {
 	foreach {o r} $or break
 	puts "$b $o $f $r"
     }
     return
-}
-
-# -----------------------------------------------------------------------------
-# Internal helper commands
-
-proc ::cvs::Log {level text} {
-    variable lc
-    uplevel #0 [linsert $lc end $level $text]
-    return
-}
-
-proc ::cvs::Nop {args} {}
-
-namespace eval ::cvs {
-    # Logging callback. No logging by default.
-    variable lc ::cvs::Nop
 }
 
 # -----------------------------------------------------------------------------
 # Ready
 
 package provide cvs 1.0
 return

Modified tools/lib/fossil.tcl from [4c39a15ec8] to [c547c7c383].

@@ -3,12 +3,16 @@
 
 # -----------------------------------------------------------------------------
 # Requirements
 
 package require Tcl 8.4
-
-namespace eval ::fossil {}
+package require tools::log ; # User feedback
+
+namespace eval ::fossil {
+    tools::log::system fossil
+    namespace import ::tools::log::write
+}
 
 # -----------------------------------------------------------------------------
 # API
 
 # Define repository file, and connect to workspace in CWD.
@@ -19,20 +23,13 @@
 
     # pwd = workspace
     exec $fossil new  $fr ; # create and
     exec $fossil open $fr ; # connect
 
-    Log info "    Fossil:    $fr"
-
-    return $fr
-}
-
-# Define logging callback command
-
-proc ::fossil::feedback {logcmd} {
-    variable lc $logcmd
-    return
+    write 0 fossil "Repository: $fr"
+
+    return $fr
 }
 
 # Move generated fossil repository to final destination
 
 proc ::fossil::destination {path} {
@@ -85,24 +82,24 @@
 	if {[IGNORE $ignore $path]} continue
 
 	if {![file exists $path]} {
 	    exec $fossil rm $path
 	    incr removed
-	    Log info "        ** - $path"
+	    write 2 fossil "-  $path"
 	} else {
 	    incr changed
-	    Log info "        ** * $path"
+	    write 2 fossil "*  $path"
 	}
     }
 
     # Now look for unregistered added files.
 
     foreach path [split [exec $fossil extra] \n] {
 	if {[IGNORE $ignore $path]} continue
 	exec $fossil add $path
 	incr added
-	Log info "        ** + $path"
+	write 2 fossil "+  $path"
     }
 
     # Now commit, using the provided meta data, and capture the uuid
     # of the new baseline.
 
@@ -136,11 +133,11 @@
 	# 'Nothing changed' can happen for changesets containing only
 	# dead-first revisions of one or more files. For fossil we
 	# re-use the last baseline. TODO: Mark them as branchpoint,
 	# and for what file.
 
-	Log info "        UNCHANGED, keeping last"
+	write 1 fossil "UNCHANGED, keeping last"
 
 	return [list $lastuuid 0 0 0]
     }
 
     set line [string trim $line]
@@ -153,25 +150,12 @@
 # -----------------------------------------------------------------------------
 # Internal helper commands
 
 proc ::fossil::IGNORE {ignore path} {
     return [uplevel #0 [linsert $ignore end $path]]
-}
-
-proc ::fossil::Log {level text} {
-    variable lc
-    uplevel #0 [linsert $lc end $level $text]
-    return
-}
-
-proc ::fossil::Nop {args} {}
-
-namespace eval ::fossil {
-    # Logging callback. No logging by default.
-    variable lc ::fossil::Nop
 }
 
 # -----------------------------------------------------------------------------
 # Ready
 
 package provide fossil 1.0
 return

Added tools/lib/log.tcl version [5fe2d143d0]

@@ -1,1 +1,147 @@
+# -----------------------------------------------------------------------------
+# Tool packages. Logging (aka User feedback).
+
+# -----------------------------------------------------------------------------
+# Requirements
+
+package require Tcl 8.4
+namespace eval ::tools::log {}
+
+# -----------------------------------------------------------------------------
+# API
+
+# Feedback generation.
+#
+#	tools::log::write    verbosity system text  - Write message to the log.
+#	tools::log::progress verbosity system n max - Drive a progress display.
+
+# Administrative operations.
+#
+#	tools::log::verbosity level  - Set the verbosity level of the application.
+#	tools::log::verbosity?       - Query the verbosity level of the application.
+#	tools::log::setCmd cmdprefix - Set callback for output
+#	tools::log::system name      - Register a system (enables tabular log formatting).
+
+# Callback API ( Executed at the global level).
+#
+#	cmdprefix 'write'    system text
+#	cmdprefix 'progress' system n max
+
+# Standard callbacks defined by the package itself write to stdout.
+
+# -----------------------------------------------------------------------------
+# API Implementation - Feedback generation.
+
+# Write the message 'text' to log, for the named 'system'. The message
+# is written if and only if the message verbosity is less or equal the
+# chosen verbosity. A message of verbosity 0 cannot be blocked.
+
+proc ::tools::log::write {verbosity system text} {
+    variable loglevel
+    variable logcmd
+    variable sysfmt
+    if {$verbosity > $loglevel} return
+    uplevel #0 [linsert $logcmd end write [format $sysfmt $system] $text]
+    return
+}
+
+# Similar to write, especially in the handling of the verbosity, to
+# drive progress displays. It signals that for some long running
+# operation we are at tick 'n' of at most 'max' ticks.
+
+proc ::tools::log::progress {verbosity system n max} {
+    variable loglevel
+    variable logcmd
+    variable sysfmt
+    if {$verbosity > $loglevel} return
+    uplevel #0 [linsert $logcmd end progress [format $sysfmt $system] $n $max]
+    return
+}
+
+# -----------------------------------------------------------------------------
+# API Implementation - Administrative operations.
+
+# Set verbosity to the chosen 'level'. Only messages with a level less
+# or equal to this one will be shown.
+
+proc ::tools::log::verbosity {level} {
+    variable loglevel
+    if {$level < 1} {set level 0}
+    set loglevel $level
+    return
+}
+
+# Query the currently set verbosity.
+
+proc ::tools::log::verbosity? {} {
+    variable loglevel
+    return  $loglevel
+}
+
+# Set the log callback handling the actual output of messages going
+# through the package.
+
+proc ::tools::log::setCmd {cmdprefix} {
+    variable logcmd $cmdprefix
+    return
+}
+
+# Register a system name, to enable tabular formatting. This is done
+# by setting up a format specifier with a proper width. This is
+# handled in the generation command, before the output callback is
+# invoked.
+
+proc ::tools::log::system {name} {
+    variable sysfmt
+    variable syslen
+
+    set nlen [string length $name]
+    if {$nlen < $syslen} return
+
+    set syslen $nlen
+    set sysfmt %-${syslen}s
+    return
+}
+
+# -----------------------------------------------------------------------------
+# Internal operations - Standard output operation
+
+# Dispatch to the handlers of the possible operations.
+
+proc ::tools::log::OUT {op args} {
+    eval [linsert $args 0 ::tools::log::OUT/$op]
+    return
+}
+
+# Write handler. Each message is a line.
+
+proc ::tools::log::OUT/write {system text} {
+    puts "$system $text"
+    return
+}
+
+# Progress handler. Using \r to return to the beginning of the current
+# line without advancing.
+
+proc ::tools::log::OUT/progress {system n max} {
+    puts -nonewline "$system [format %[string length $max]s $n]/$max\r"
+    flush stdout
+    return
+}
+
+# -----------------------------------------------------------------------------
+
+namespace eval ::tools::log {
+    variable loglevel 0                 ; # Allow only uninteruptible messages.
+    variable logcmd   ::tools::log::OUT ; # Standard output to stdout.
+    variable sysfmt %s                  ; # Non-tabular formatting.
+    variable syslen 0                   ; # Ditto.
+
+    namespace export write progress
+}
+
+# -----------------------------------------------------------------------------
+# Ready
 
+package provide tools::log 1.0
+return

Modified tools/lib/pkgIndex.tcl from [bb6c58a2d8] to [a7c8370641].

@@ -1,4 +1,5 @@
 if {![package vsatisfies [package require Tcl] 8.4]} return
 package ifneeded rcsparser 1.0 [list source [file join $dir rcsparser.tcl]]
 package ifneeded cvs       1.0 [list source [file join $dir cvs.tcl]]
 package ifneeded fossil    1.0 [list source [file join $dir fossil.tcl]]
+package ifneeded tools::log 1.0 [list source [file join $dir log.tcl]]

Modified tools/lib/rcsparser.tcl from [ec0acbb634] to [ba9e03a5e7].

@@ -1,25 +1,53 @@
-
 # -----------------------------------------------------------------------------
-# Parse RCS files (,v) - ignore the deltas - we need only the commit messages
-# Recursive Descent Parser
+# Tool packages. Parsing RCS files.
+#
+# Some of the information in RCS files is skipped over, most
+# importantly the actual delta texts. The users of this parser need
+# only the meta-data about when revisions were added, the tree
+# (branching) structure, commit messages.
+#
+# The parser is based on Recursive Descent.
 
 # -----------------------------------------------------------------------------
 # Requirements
 
 package require Tcl 8.4
-package require fileutil ; # Tcllib (cat)
-
-namespace eval ::rcsparser {}
+package require fileutil   ; # Tcllib (cat)
+package require tools::log ; # User feedback
+
+namespace eval ::rcsparser {
+    tools::log::system rcs
+    namespace import ::tools::log::progress
+}
 
 # -----------------------------------------------------------------------------
 # API
 
-proc ::rcsparser::feedback {logcmd} {
-    variable lc $logcmd
-    return
-}
+# rcsparser::process file
+#
+# Parses the rcs file and returns a dictionary containing the meta
+# data. The following keys are used
+#
+# Key		Meaning
+# ---		-------
+# 'head'	head revision
+# 'branch'	?
+# 'symbol'	dict (symbol -> revision)
+# 'lock'	dict (symbol -> revision)
+# 'comment'	file comment
+# 'expand'	?
+# 'date'	dict (revision -> date)
+# 'author'	dict (revision -> author)
+# 'state'	dict (revision -> state)
+# 'parent'	dict (revision -> parent revision)
+# 'commit'	dict (revision -> commit message)
+#
+# The state 'dead' has special meaning, the user should know that.
+
+# -----------------------------------------------------------------------------
+# API Implementation
 
 proc ::rcsparser::process {path} {
     set data [fileutil::cat -encoding binary $path]
     array set res {}
     set res(size) [file size $path]
@@ -29,36 +57,22 @@
     Admin
     Deltas
     Description
     DeltaTexts
 
-    Feedback \r
-
     # Remove parser state
     catch {unset res(id)}
     catch {unset res(lastval)}
     unset res(size)
     unset res(nsize)
     unset res(done)
 
-    # res: 'head'    -> head revision
-    #      'branch'  -> ?
-    #      'symbol'  -> (sym -> revision)
-    #      'lock'    -> (sym -> revision)
-    #      'comment' -> file comment
-    #      'expand'  -> ?
-    #      'date'    -> (revision -> date)
-    #      'author'  -> (revision -> author)
-    #      'state'   -> (revision -> state)
-    #      'parent'  -> (revision -> parent revision)
-    #      'commit'  -> (revision -> commit message)
-
     return [array get res]
 }
 
 # -----------------------------------------------------------------------------
-# Internal helper commands
+# Internal - Recursive Descent functions implementing the syntax.
 
 proc ::rcsparser::Admin {} {
     upvar 1 data data res res
     Head ; Branch ; Access ; Symbols ; Locks ; Strict ; Comment ; Expand
     return
@@ -190,10 +204,11 @@
     Literal text ; String 1
     return
 }
 
 # -----------------------------------------------------------------------------
+# Internal - Lexicographical commands and data aquisition preparation
 
 proc ::rcsparser::Ident {} {
     upvar 1 data data res res
 
     #puts I@?<[string range $data 0 10]...>
@@ -257,10 +272,13 @@
     Get $val
     Next
     return
 }
 
+# -----------------------------------------------------------------------------
+# Internal - Data aquisition
+
 proc ::rcsparser::Def {key} {
     upvar 1 data data res res
     set res($key) $res(lastval)
     unset res(lastval)
     return
@@ -294,28 +312,14 @@
     upvar 1 match match data data res res
     foreach {s e} $match break ; incr e
     set data [string range $data $e end]
     set res(done) [expr {$res(size) - [string length $data]}]
 
-    Feedback "\r    [format "%$res(nsize)s" $res(done)]/$res(size) "
+    progress 2 rcs $res(done) $res(size)
     return
 }
-
-# -----------------------------------------------------------------------------
-
-namespace eval ::rcsparser {
-    variable lc ::rcs::Nop
-}
-
-proc ::rcsparser::Feedback {text} {
-    variable lc
-    uplevel #0 [linsert $lc end info $text]
-    return
-}
-
-proc ::rcsparser::Nop {args} {}
 
 # -----------------------------------------------------------------------------
 # Ready
 
 package provide rcsparser 1.0
 return