1#!/bin/sh
2# -*- tcl -*- \
3exec tclsh "$0" ${1+"$@"}
4
5# --------------------------------------------------------------
6# Perform various checks and operations on the distribution.
7# SAK = Swiss Army Knife.
8
9set distribution   [file dirname [info script]]
10lappend auto_path  [file join $distribution modules]
11
12set critcldefault {}
13set critclnotes   {}
14set dist_excluded {}
15
16proc package_name    {text} {global package_name    ; set package_name    $text}
17proc package_version {text} {global package_version ; set package_version $text}
18proc dist_exclude    {path} {global dist_excluded   ; lappend dist_excluded $path}
19proc critcl {name files} {
20    global critclmodules
21    set    critclmodules($name) $files
22    return
23}
24proc critcl_main {name files} {
25    global critcldefault
26    set critcldefault $name
27    critcl $name $files
28    return
29}
30proc critcl_notes {text} {
31    global critclnotes
32    set critclnotes [string map {{\n    } \n} $text]
33    return
34}
35
36source [file join $distribution support installation version.tcl] ; # Get version information.
37
38set package_nv ${package_name}-${package_version}
39
40catch {eval file delete -force [glob [file rootname [info script]].tmp.*]}
41
42# --------------------------------------------------------------
43# SAK internal debugging support.
44
45# Configuration, change as needed
46set  debug 0
47
48if {$debug} {
49    proc sakdebug {script} {uplevel 1 $script ; return}
50} else {
51    proc sakdebug {args} {}
52}
53
54# --------------------------------------------------------------
55# Internal helper to load packages straight out of the local directory
56# tree. Not something from an installation, possibly incompatible.
57
58proc getpackage {package tclmodule} {
59    global distribution
60    if {[catch {package present $package}]} {
61	set src [file join \
62		$distribution modules \
63		$tclmodule]
64	if {[file exists $src]} {
65	    uplevel #0 [list source $src]
66	} else {
67	    # Fallback
68	    package require $package
69	}
70    }
71}
72
73# --------------------------------------------------------------
74
75proc tclfiles {} {
76    global distribution
77    getpackage fileutil fileutil/fileutil.tcl
78    set fl [fileutil::findByPattern $distribution -glob *.tcl]
79    # Remove files under SCCS. They are repository, not sources to check.
80    set tmp {}
81    foreach f $fl {
82	if {[string match *SCCS* $f]} continue
83	lappend tmp $f
84    }
85    proc tclfiles {} [list return $tmp]
86    return $tmp
87}
88
89proc modtclfiles {modules} {
90    global mfiles guide
91    load_modinfo
92    set mfiles [list]
93    foreach m $modules {
94	eval $guide($m,pkg) $m __dummy__
95    }
96    return $mfiles
97}
98
99proc modules {} {
100    global distribution
101    set fl [list]
102    foreach f [glob -nocomplain [file join $distribution modules *]] {
103	if {![file isdirectory $f]} {continue}
104	if {[string match CVS [file tail $f]]} {continue}
105
106	if {![file exists [file join $f pkgIndex.tcl]]} {continue}
107
108	lappend fl [file tail $f]
109    }
110    set fl [lsort $fl]
111    proc modules {} [list return $fl]
112    return $fl
113}
114
115proc modules_mod {m} {
116    return [expr {[lsearch -exact [modules] $m] >= 0}]
117}
118
119proc dealias {modules} {
120    set _ {}
121    foreach m $modules {
122	if {[file exists $m]} {
123	    set m [file tail $m]
124	}
125	lappend _ $m
126    }
127    return $_
128}
129
130proc load_modinfo {} {
131    global distribution modules guide
132    source [file join $distribution support installation modules.tcl] ; # Get list of installed modules.
133    source [file join $distribution support installation actions.tcl] ; # Get installer support code.
134    proc load_modinfo {} {}
135    return
136}
137
138proc imodules {} {global modules ; load_modinfo ; return $modules}
139
140proc imodules_mod {m} {
141    global modules
142    load_modinfo
143    return [expr {[lsearch -exact $modules $m] > 0}]
144}
145
146# Result: dict (package name --> list of package versions).
147
148proc loadpkglist {fname} {
149    set f [open $fname r]
150    foreach line [split [read $f] \n] {
151	set line [string trim $line]
152	if {[string match @* $line]} continue
153	if {$line == {}} continue
154	foreach {n v} $line break
155	lappend p($n) $v
156	set p($n) [lsort -uniq -dict $p($n)]
157    }
158    close $f
159    return [array get p]
160}
161
162# Result: dict (package name => list of (list of package versions, module)).
163
164proc ipackages {args} {
165    # Determine indexed packages (ifneeded, pkgIndex.tcl)
166
167    global distribution
168
169    if {[llength $args] == 0} {set args [modules]}
170
171    array set p {}
172    foreach m $args {
173	set f [open [file join $distribution modules $m pkgIndex.tcl] r]
174	foreach line [split [read $f] \n] {
175	    if { [regexp {#}        $line]} {continue}
176	    if {![regexp {ifneeded} $line]} {continue}
177	    regsub {^.*ifneeded } $line {} line
178	    regsub {([0-9]) \[.*$}  $line {\1} line
179
180	    foreach {n v} $line break
181
182	    if {![info exists p($n)]} {
183		set p($n) [list $v $m]
184	    } else {
185		# We have multiple versions of the same package. We
186		# remember all versions.
187
188		foreach {vlist m} $p($n) break
189		lappend vlist $v
190		set p($n) [list [lsort -uniq -dict $vlist] $m]
191	    }
192	}
193	close $f
194    }
195    return [array get p]
196}
197
198
199# Result: dict (package name --> list of package versions).
200
201proc ppackages {args} {
202    # Determine provided packages (provide, *.tcl - pkgIndex.tcl)
203    # We cache results for a bit of speed, some stuff uses this
204    # multiple times for the same arguments.
205
206    global ppcache
207    if {[info exists ppcache($args)]} {
208	return $ppcache($args)
209    }
210
211    global    p pf currentfile
212    array set p {}
213
214    if {[llength $args] == 0} {
215	set files [tclfiles]
216    } else {
217	set files [modtclfiles $args]
218    }
219
220    getpackage fileutil fileutil/fileutil.tcl
221    set capout [fileutil::tempfile] ; set capcout [open $capout w]
222    set caperr [fileutil::tempfile] ; set capcerr [open $caperr w]
223
224    array set notprovided {}
225
226    foreach f $files {
227	# We ignore package indices and all files not in a module.
228
229	if {[string equal pkgIndex.tcl [file tail $f]]} {continue}
230	if {![regexp modules $f]}                       {continue}
231
232	# We use two methods to extract the version information from a
233	# module and its packages. First we do a static scan for
234	# appropriate statements. If that did not work out we try to
235	# execute the script in a modified interpreter which lets us
236	# pick up dynamically generated version data (like stored in
237	# variables). If the second method fails as well we give up.
238
239	# Method I. Static scan.
240
241	# We do heuristic scanning of the code to locate suitable
242	# package provide statements.
243
244	set fh [open $f r]
245
246	set currentfile [eval file join [lrange [file split $f] end-1 end]]
247
248	set ok -1
249	foreach line [split [read $fh] \n] {
250	    if {[regexp "\#\\s*@sak\\s+notprovided\\s+(\[^\\s\]+)" $line -> nppname]} {
251		sakdebug {puts stderr "PRAGMA notprovided = $nppname"}
252		set notprovided($nppname) .
253	    }
254
255	    regsub "\#.*$" $line {} line
256	    if {![regexp {provide} $line]} {continue}
257	    if {![regexp {package} $line]} {continue}
258
259	    # Now a stronger check for the actual command
260	    if {![regexp {package[ 	][ 	]*provide} $line]} {continue}
261
262	    set xline $line
263	    regsub {^.*provide } $line {} line
264	    regsub {\].*$}       $line {\1} line
265
266	    sakdebug {puts stderr __$f\ _________$line}
267
268	    foreach {n v} $line break
269
270	    # HACK ...
271	    # Module 'page', package 'page::gen::peg::cpkg'.
272	    # Has a provide statement inside a template codeblock.
273	    # Name is placeholder @@. Ignore this specific name.
274	    # Better would be to use general static Tcl parsing
275	    # to find that the string is a variable value.
276
277	    if {[string equal $n @@]} continue
278
279	    if {[regexp {^[0-9]+(\.[0-9]+)*$} $v]} {
280		lappend p($n) $v
281		set p($n) [lsort -uniq -dict $p($n)]
282		set pf($n,$v) $currentfile
283		set ok 1
284
285		# We continue the scan. The file may provide several
286		# versions of the same package, or multiple packages.
287		continue
288	    }
289
290	    # 'package provide foo' are tests. Ignore.
291	    if {$v == ""} continue
292
293	    # We do not set the state to bad if we found ok provide
294	    # statements before, only if nothing was found before.
295	    if {$ok < 0} {
296		set ok 0
297
298		# No good version found on the current line. We scan
299		# further through the file and hope for more luck.
300
301		sakdebug {puts stderr @_$f\ _________$xline\t<$n>\t($v)}
302	    }
303	}
304	close $fh
305
306	# Method II. Restricted Execution.
307	# We now try to run the code through a safe interpreter
308	# and hope for better luck regarding package information.
309
310	if {$ok == -1} {sakdebug {puts stderr $f\ IGNORE}}
311	if {$ok == 0} {
312	    sakdebug {puts -nonewline stderr $f\ EVAL}
313
314	    # Source the code into a sub-interpreter. The sub
315	    # interpreter overloads 'package provide' so that the
316	    # information about new packages goes directly to us. We
317	    # also make sure that the sub interpreter doesn't kill us,
318	    # and will not get stuck early by trying to load other
319	    # files, or when creating procedures in namespaces which
320	    # do not exist due to us disabling most of the package
321	    # management.
322
323	    set fh [open $f r]
324
325	    set ip [interp create]
326
327	    # Kill control structures. Namespace is required, but we
328	    # skip everything related to loading of packages,
329	    # i.e. 'command import'.
330
331	    $ip eval {
332		rename ::if        ::_if_
333		rename ::namespace ::_namespace_
334
335		proc ::if {args} {}
336		proc ::namespace {cmd args} {
337		    #puts stderr "_nscmd_ $cmd"
338		    ::_if_ {[string equal $cmd import]} return
339		    #puts stderr "_nsdo_ $cmd $args"
340		    return [uplevel 1 [linsert $args 0 ::_namespace_ $cmd]]
341		}
342	    }
343
344	    # Kill more package stuff, and ensure that unknown
345	    # commands are neither loaded nor abort execution. We also
346	    # stop anything trying to kill the application at large.
347
348	    interp alias $ip package {} xPackage
349	    interp alias $ip source  {} xNULL
350	    interp alias $ip unknown {} xNULL
351	    interp alias $ip proc    {} xNULL
352	    interp alias $ip exit    {} xNULL
353
354	    # From here on no redefinitions anymore, proc == xNULL !!
355
356	    $ip eval {close stdout} ; interp share {} $capcout $ip
357	    $ip eval {close stderr} ; interp share {} $capcerr $ip
358
359	    if {[catch {$ip eval [read $fh]} msg]} {
360		sakdebug {puts stderr "ERROR in $currentfile:\n$::errorInfo\n"}
361	    }
362
363	    sakdebug {puts stderr ""}
364
365	    close $fh
366	    interp delete $ip
367	}
368    }
369
370    close $capcout ; file delete $capout
371    close $capcerr ; file delete $caperr
372
373    # Process the accumulated pragma information, remove all the
374    # packages which exist but not really, in terms of indexing.
375
376    foreach n [array names notprovided] {
377	catch { unset p($n) }
378	array unset pf $n,*
379    }
380
381    set   pp [array get p]
382    unset p
383
384    set ppcache($args) $pp
385    return $pp
386}
387
388proc xNULL    {args} {}
389proc xPackage {cmd args} {
390    if {[string equal $cmd provide]} {
391	global p pf currentfile
392	foreach {n v} $args break
393
394	# No version specified, this is an inquiry, we ignore these.
395	if {$v == {}} {return}
396
397	sakdebug {puts stderr \tOK\ $n\ =\ $v}
398
399	lappend p($n) $v
400	set p($n) [lsort -uniq -dict $p($n)]
401	set pf($n,$v) $currentfile
402    }
403    return
404}
405
406proc sep {} {puts ~~~~~~~~~~~~~~~~~~~~~~~~}
407
408proc gd-cleanup {} {
409    global package_nv
410
411    puts {Cleaning up...}
412
413    set        fl [glob -nocomplain ${package_nv}*]
414    foreach f $fl {
415	puts "    Deleting $f ..."
416	catch {file delete -force $f}
417    }
418    return
419}
420
421proc gd-gen-archives {} {
422    global package_name package_nv
423
424    puts {Generating archives...}
425
426    set tar [auto_execok tar]
427    if {$tar != {}} {
428        puts "    Gzipped tarball (${package_nv}.tar.gz)..."
429        catch {
430            exec $tar cf - ${package_nv} | gzip --best > ${package_nv}.tar.gz
431        }
432
433        set bzip [auto_execok bzip2]
434        if {$bzip != {}} {
435            puts "    Bzipped tarball (${package_nv}.tar.bz2)..."
436            exec tar cf - ${package_nv} | bzip2 > ${package_nv}.tar.bz2
437        }
438    }
439
440    set zip [auto_execok zip]
441    if {$zip != {}} {
442        puts "    Zip archive     (${package_nv}.zip)..."
443        catch {
444            exec $zip -r ${package_nv}.zip ${package_nv}
445        }
446    }
447
448    set sdx [auto_execok sdx]
449    if {$sdx != {}} {
450	file copy -force [file join ${package_nv} support installation main.tcl] \
451		[file join ${package_nv} main.tcl]
452	file rename ${package_nv} ${package_name}.vfs
453
454	puts "    Starkit         (${package_nv}.kit)..."
455	exec sdx wrap ${package_name}
456	file rename   ${package_name} ${package_nv}.kit
457
458	if {![file exists tclkit]} {
459	    puts "    No tclkit present in current working directory, no starpack."
460	} else {
461	    puts "    Starpack        (${package_nv}.exe)..."
462	    exec sdx wrap ${package_name} -runtime tclkit
463	    file rename   ${package_name} ${package_nv}.exe
464	}
465
466	file rename ${package_name}.vfs ${package_nv}
467    }
468
469    puts {    Keeping directory for other archive types}
470
471    ## Keep the directory for 'sdx' - kit/pack
472    return
473}
474
475proc xcopyfile {src dest} {
476    # dest can be dir or file
477    global  mfiles
478    lappend mfiles $src
479    return
480}
481
482proc xcopy {src dest recurse {pattern *}} {
483    foreach file [glob [file join $src $pattern]] {
484        set base [file tail $file]
485	set sub  [file join $dest $base]
486	if {0 == [string compare CVS $base]} {continue}
487        if {[file isdirectory $file]} then {
488	    if {$recurse} {
489		xcopy $file $sub $recurse $pattern
490	    }
491        } else {
492            xcopyfile $file $sub
493        }
494    }
495}
496
497
498proc xxcopy {src dest recurse {pattern *}} {
499    global package_name
500
501    file mkdir $dest
502    foreach file [glob -nocomplain [file join $src $pattern]] {
503        set base [file tail $file]
504	set sub  [file join $dest $base]
505
506	# Exclude CVS, SCCS, ... automatically, and possibly the temp
507	# hierarchy itself too.
508
509	if {0 == [string compare CVS        $base]} {continue}
510	if {0 == [string compare SCCS       $base]} {continue}
511	if {0 == [string compare BitKeeper  $base]} {continue}
512	if {[string match ${package_name}-* $base]} {continue}
513	if {[string match *~                $base]} {continue}
514
515        if {[file isdirectory $file]} then {
516	    if {$recurse} {
517		file mkdir  $sub
518		xxcopy $file $sub $recurse $pattern
519	    }
520        } else {
521	    puts -nonewline stdout . ; flush stdout
522            file copy -force $file $sub
523        }
524    }
525}
526
527proc gd-assemble {} {
528    global package_nv distribution dist_excluded
529
530    puts "Assembling distribution in directory '${package_nv}'"
531
532    xxcopy $distribution ${package_nv} 1
533
534    foreach f $dist_excluded {
535	file delete -force [file join $package_nv $f]
536    }
537    puts ""
538    return
539}
540
541proc normalize-version {v} {
542    # Strip everything after the first non-version character, and any
543    # trailing dots left behind by that, to avoid the insertion of bad
544    # version numbers into the generated .tap file.
545
546    regsub {[^0-9.].*$} $v {} v
547    return [string trimright $v .]
548}
549
550proc gd-gen-tap {} {
551    getpackage textutil textutil/textutil.tcl
552    getpackage fileutil fileutil/fileutil.tcl
553
554    global package_name package_version distribution tcl_platform
555
556    set pname [textutil::cap $package_name]
557
558    set modules   [imodules]
559    array set pd  [getpdesc]
560    set     lines [list]
561    # Header
562    lappend lines {format  {TclDevKit Project File}}
563    lappend lines {fmtver  2.0}
564    lappend lines {fmttool {TclDevKit TclApp PackageDefinition} 2.5}
565    lappend lines {}
566    lappend lines "##  Saved at : [clock format [clock seconds]]"
567    lappend lines "##  By       : $tcl_platform(user)"
568    lappend lines {##}
569    lappend lines "##  Generated by \"[file tail [info script]] tap\""
570    lappend lines "##  of $package_name $package_version"
571    lappend lines {}
572    lappend lines {########}
573    lappend lines {#####}
574    lappend lines {###}
575    lappend lines {##}
576    lappend lines {#}
577
578    # Bundle definition
579    lappend lines {}
580    lappend lines {# ###############}
581    lappend lines {# Complete bundle}
582    lappend lines {}
583    lappend lines [list Package [list $package_name [normalize-version $package_version]]]
584    lappend lines "Base     @TAP_DIR@"
585    lappend lines "Platform *"
586    lappend lines "Desc     \{$pname: Bundle of all packages\}"
587    lappend lines "Path     pkgIndex.tcl"
588    lappend lines "Path     [join $modules "\nPath     "]"
589
590    set  strip [llength [file split $distribution]]
591    incr strip 2
592
593    foreach m $modules {
594	# File set of module ...
595
596	lappend lines {}
597	lappend lines "# #########[::textutil::strRepeat {#} [string length $m]]" ; # {}
598	lappend lines "# Module \"$m\""
599	set n 0
600	foreach {p vlist} [ppackages $m] {
601	    foreach v $vlist {
602		lappend lines "# \[[format %1d [incr n]]\]    | \"$p\" ($v)"
603	    }
604	}
605	if {$n > 1} {
606	    # Multiple packages (*). We create one hidden package to
607	    # contain all the files and then have all the true
608	    # packages in the module refer to it.
609	    #
610	    # (*) This can also be one package for which we have
611	    # several versions. Or a combination thereof.
612
613	    array set _ {}
614	    foreach {p vlist} [ppackages $m] {
615		catch {set _([lindex $pd($p) 0]) .}
616	    }
617	    set desc [string trim [join [array names _] ", "] " \n\t\r,"]
618	    if {$desc == ""} {set desc "$pname module"}
619	    unset _
620
621	    lappend lines "# -------+"
622	    lappend lines {}
623	    lappend lines [list Package [list __$m 0.0]]
624	    lappend lines "Platform *"
625	    lappend lines "Desc     \{$desc\}"
626	    lappend lines Hidden
627	    lappend lines "Base     @TAP_DIR@/$m"
628
629	    foreach f [lsort -dict [modtclfiles $m]] {
630		lappend lines "Path     [fileutil::stripN $f $strip]"
631	    }
632
633	    # Packages in the module ...
634	    foreach {p vlist} [ppackages $m] {
635		# NO DANGER. As we are listing only the packages P for
636		# the module any other version of P in a different
637		# module is _not_ listed here.
638
639		set desc ""
640		catch {set desc [string trim [lindex $pd($p) 1]]}
641		if {$desc == ""} {set desc "$pname package"}
642
643		foreach v $vlist {
644		    lappend lines {}
645		    lappend lines [list Package [list $p [normalize-version $v]]]
646		    lappend lines "See   [list __$m]"
647		    lappend lines "Platform *"
648		    lappend lines "Desc     \{$desc\}"
649		}
650	    }
651	} else {
652	    # A single package in the module. And only one version of
653	    # it as well. Otherwise we are in the multi-pkg branch.
654
655	    foreach {p vlist} [ppackages $m] break
656	    set desc ""
657	    catch {set desc [string trim [lindex $pd($p) 1]]}
658	    if {$desc == ""} {set desc "$pname package"}
659
660	    set v [lindex $vlist 0]
661
662	    lappend lines "# -------+"
663	    lappend lines {}
664	    lappend lines [list Package [list $p [normalize-version $v]]]
665	    lappend lines "Platform *"
666	    lappend lines "Desc     \{$desc\}"
667	    lappend lines "Base     @TAP_DIR@/$m"
668
669	    foreach f [lsort -dict [modtclfiles $m]] {
670		lappend lines "Path     [fileutil::stripN $f $strip]"
671	    }
672	}
673	lappend lines {}
674	lappend lines {#}
675	lappend lines "# #########[::textutil::strRepeat {#} [string length $m]]"
676    }
677
678    lappend lines {}
679    lappend lines {#}
680    lappend lines {##}
681    lappend lines {###}
682    lappend lines {#####}
683    lappend lines {########}
684
685    # Write definition
686    set    f [open [file join $distribution ${package_name}.tap] w]
687    puts  $f [join $lines \n]
688    close $f
689    return
690}
691
692proc getpdesc  {} {
693    global argv ; if {![checkmod]} return
694
695    package require sak::doc
696    sak::doc::Gen desc l $argv
697
698    array set _ {}
699    foreach file [glob -nocomplain doc/desc/*.l] {
700        set f [open $file r]
701	foreach l [split [read $f] \n] {
702	    foreach {p sd d} $l break
703	    set _($p) [list $sd $d]
704	}
705        close $f
706    }
707    file delete -force doc/desc
708
709    return [array get _]
710}
711
712proc gd-gen-rpmspec {} {
713    global package_version package_name distribution
714
715    set in  [file join $distribution support releases package_rpm.txt]
716    set out [file join $distribution ${package_name}.spec]
717
718    write_out $out [string map \
719			[list \
720			     @PACKAGE_VERSION@ $package_version \
721			     @PACKAGE_NAME@    $package_name] \
722			[get_input $in]]
723    return
724}
725
726proc gd-gen-yml {} {
727    # YAML is the format used for the FreePAN archive network.
728    # http://freepan.org/
729
730    global package_version package_name distribution
731
732    set in  [file join $distribution support releases package_yml.txt]
733    set out [file join $distribution ${package_name}.yml]
734
735    write_out $out [string map \
736			[list \
737			     @PACKAGE_VERSION@ $package_version \
738			     @PACKAGE_NAME@    $package_name] \
739			[get_input $in]]
740    return
741}
742
743proc docfiles {} {
744    global distribution
745
746    getpackage fileutil fileutil/fileutil.tcl
747
748    set res [list]
749    foreach f [fileutil::findByPattern $distribution -glob *.man] {
750	# Remove files under SCCS. They are repository, not sources to check.
751	if {[string match *SCCS* $f]} continue
752	lappend res [file rootname [file tail $f]].n
753    }
754    proc docfiles {} [list return $res]
755    return $res
756}
757
758proc gd-tip55 {} {
759    global package_version package_name distribution contributors
760    contributors
761
762    set in  [file join $distribution support releases package_tip55.txt]
763    set out [file join $distribution DESCRIPTION.txt]
764
765    set md [string map \
766		[list \
767		     @PACKAGE_VERSION@ $package_version \
768		     @PACKAGE_NAME@    $package_name] \
769		[get_input $in]]
770
771    foreach person [lsort [array names contributors]] {
772        set mail $contributors($person)
773        regsub {@}  $mail " at " mail
774        regsub -all {\.} $mail " dot " mail
775        append md "Contributor: $person <$mail>\n"
776    }
777
778    write_out $out $md
779    return
780}
781
782# Fill the global array of contributors to the bundle by processing
783# the ChangeLog entries.
784#
785proc contributors {} {
786    global distribution contributors
787    if {![info exists contributors] || [array size contributors] == 0} {
788        get_contributors [file join $distribution ChangeLog]
789
790        foreach f [glob -nocomplain [file join $distribution modules *]] {
791            if {![file isdirectory $f]} {continue}
792            if {[string match CVS [file tail $f]]} {continue}
793            if {![file exists [file join $f ChangeLog]]} {continue}
794            get_contributors [file join $f ChangeLog]
795        }
796    }
797}
798
799proc get_contributors {changelog} {
800    global contributors
801    set f [open $changelog r]
802    while {![eof $f]} {
803        gets $f line
804        if {[regexp {^[\d-]+\s+(.*?)<(.*?)>} $line r name mail]} {
805            set name [string trim $name]
806            if {![info exists names($name)]} {
807                set contributors($name) $mail
808            }
809        }
810    }
811    close $f
812}
813
814proc validate_imodules_cmp {imvar dmvar} {
815    upvar $imvar im $dmvar dm
816
817    foreach m [lsort [array names im]] {
818	if {![info exists dm($m)]} {
819	    puts "  Installed, does not exist: $m"
820	}
821    }
822    foreach m [lsort [array names dm]] {
823	if {![info exists im($m)]} {
824	    puts "  Missing in installer:      $m"
825	}
826    }
827    return
828}
829
830proc validate_imodules {} {
831    foreach m [imodules] {set im($m) .}
832    foreach m [modules]  {set dm($m) .}
833
834    validate_imodules_cmp im dm
835    return
836}
837
838proc validate_imodules_mod {m} {
839    array set im {}
840    array set dm {}
841    if {[imodules_mod $m]} {set im($m) .}
842    if {[modules_mod  $m]} {set dm($m) .}
843
844    validate_imodules_cmp im dm
845    return
846}
847proc validate_versions_cmp {ipvar ppvar} {
848    global pf
849    getpackage struct::set struct/sets.tcl
850
851    upvar $ipvar ip $ppvar pp
852    set maxl 0
853    foreach name [array names ip] {if {[string length $name] > $maxl} {set maxl [string length $name]}}
854    foreach name [array names pp] {if {[string length $name] > $maxl} {set maxl [string length $name]}}
855
856    foreach p [lsort [array names ip]] {
857	if {![info exists pp($p)]} {
858	    puts "  Indexed, no provider:           $p"
859	}
860    }
861    foreach p [lsort [array names pp]] {
862	if {![info exists ip($p)]} {
863	    foreach k [array names pf $p,*] {
864		puts "  Provided, not indexed:          [format "%-*s | %s" $maxl $p $pf($k)]"
865	    }
866	}
867    }
868    foreach p [lsort [array names ip]] {
869	if {![info exists pp($p)]}               continue
870	if {[struct::set equal $pp($p) $ip($p)]} continue
871
872	# Compute intersection and set differences.
873	foreach {__ pmi imp} [struct::set intersect3 $pp($p) $ip($p)] break
874
875	puts "  Index/provided versions differ: [format "%-*s | %8s | %8s" $maxl $p $imp $pmi]"
876    }
877}
878
879proc validate_versions {} {
880    foreach {p vm}    [ipackages] {set ip($p) [lindex $vm 0]}
881    foreach {p vlist} [ppackages] {set pp($p) $vlist}
882
883    validate_versions_cmp ip pp
884    return
885}
886
887proc validate_versions_mod {m} {
888    foreach {p vm}    [ipackages $m] {set ip($p) [lindex $vm 0]}
889    foreach {p vlist} [ppackages $m] {set pp($p) $vlist}
890
891    validate_versions_cmp ip pp
892    return
893}
894
895proc validate_testsuite_mod {m} {
896    global distribution
897    if {[llength [glob -nocomplain [file join $distribution modules $m *.test]]] == 0} {
898	puts "  Without testsuite : $m"
899    }
900    return
901}
902
903proc bench_mod {mlist paths interp flags norm format verbose output} {
904    global distribution env tcl_platform
905
906    getpackage logger logger/logger.tcl
907    getpackage bench  bench/bench.tcl
908
909    ::logger::setlevel $verbose
910
911    set pattern tclsh*
912    if {$interp != {}} {
913	set pattern [file tail $interp]
914	set paths [list [file dirname $interp]]
915    } elseif {![llength $paths]} {
916	# Using the environment PATH is not a good default for
917	# SAK. Use the interpreter running SAK as the default.
918	if 0 {
919	    set paths [split $env(PATH) \
920			   [expr {($tcl_platform(platform) == "windows") ? ";" : ":"}]]
921	}
922	set interp [info nameofexecutable]
923	set pattern [file tail $interp]
924	set paths [list [file dirname $interp]]
925    }
926
927    set interps [bench::versions \
928	    [bench::locate $pattern $paths]]
929
930    if {![llength $interps]} {
931	puts "No interpreters found"
932	return
933    }
934
935    if {[llength $flags]} {
936	set cmd [linsert $flags 0 bench::run]
937    } else {
938	set cmd [list bench::run]
939    }
940
941    array set DATA {}
942
943    foreach m $mlist {
944	set files [glob -nocomplain [file join $distribution modules $m *.bench]]
945	if {![llength $files]} {
946	    bench::log::warn "No benchmark files found for module \"$m\""
947	    continue
948	}
949
950	set run $cmd
951	lappend run $interps $files
952	array set DATA [eval $run]
953    }
954
955    _bench_write $output [array get DATA] $norm $format
956    return
957}
958
959proc bench_all {flags norm format verbose output} {
960    bench_mod [modules] $flags $norm $format $verbose $output
961    return
962}
963
964
965proc _bench_write {output data norm format} {
966    if {$norm != {}} {
967	getpackage logger logger/logger.tcl
968	getpackage bench  bench/bench.tcl
969
970	set data [bench::norm $data $norm]
971    }
972
973    set data [bench::out::$format $data]
974
975    if {$output == {}} {
976	puts $data
977    } else {
978	set    output [open $output w]
979	puts  $output "# -*- tcl -*- bench/$format"
980	puts  $output $data
981	close $output
982    }
983}
984
985proc validate_testsuites {} {
986    foreach m [modules] {
987	validate_testsuite_mod $m
988    }
989    return
990}
991
992proc validate_pkgIndex_mod {m} {
993    global distribution
994    if {[llength [glob -nocomplain [file join $distribution modules $m pkgIndex.tcl]]] == 0} {
995	puts "  Without package index : $m"
996    }
997    return
998}
999
1000proc validate_pkgIndex {} {
1001    global distribution
1002    foreach m [modules] {
1003	validate_pkgIndex_mod $m
1004    }
1005    return
1006}
1007
1008proc validate_doc_existence_mod {m} {
1009    global distribution
1010    if {[llength [glob -nocomplain [file join $distribution modules $m {*.[13n]}]]] == 0} {
1011	if {[llength [glob -nocomplain [file join $distribution modules $m {*.man}]]] == 0} {
1012	    puts "  Without * any ** manpages : $m"
1013	}
1014    } elseif {[llength [glob -nocomplain [file join $distribution modules $m {*.man}]]] == 0} {
1015	puts "  Without doctools manpages : $m"
1016    } else {
1017	foreach f [glob -nocomplain [file join $distribution modules $m {*.[13n]}]] {
1018	    if {![file exists [file rootname $f].man]} {
1019		puts "     no .man equivalent : $f"
1020	    }
1021	}
1022    }
1023    return
1024}
1025
1026proc validate_doc_existence {} {
1027    global distribution
1028    foreach m [modules] {
1029	validate_doc_existence_mod $m
1030    }
1031    return
1032}
1033
1034
1035proc validate_doc_markup_mod {m} {
1036    package require sak::doc
1037    sak::doc::Gen null null [list $m]
1038    return
1039}
1040
1041proc validate_doc_markup {} {
1042    package require sak::doc
1043    sak::doc::Gen null null [modules]
1044    return
1045}
1046
1047proc run-frink {args} {
1048    global distribution
1049
1050    set tmp [file rootname [info script]].tmp.[pid]
1051
1052    if {[llength $args] == 0} {
1053	set files [tclfiles]
1054    } else {
1055	set files [lsort -dict [modtclfiles $args]]
1056    }
1057
1058    foreach f $files {
1059	puts "FRINK ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
1060	puts "$f..."
1061	puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
1062
1063	catch {exec frink 2> $tmp -HJ $f}
1064	set data [get_input $tmp]
1065	if {[string length $data] > 0} {
1066	    puts $data
1067	}
1068    }
1069    catch {file delete -force $tmp}
1070    return
1071}
1072
1073proc run-procheck {args} {
1074    global distribution
1075
1076    if {[llength $args] == 0} {
1077	set files [tclfiles]
1078    } else {
1079	set files [lsort -dict [modtclfiles $args]]
1080    }
1081
1082    foreach f $files {
1083	puts "PROCHECK ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
1084	puts "$f ..."
1085	puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
1086
1087	catch {exec procheck >@ stdout $f}
1088    }
1089    return
1090}
1091
1092proc run-tclchecker {args} {
1093    global distribution
1094
1095    if {[llength $args] == 0} {
1096	set files [tclfiles]
1097    } else {
1098	set files [lsort -dict [modtclfiles $args]]
1099    }
1100
1101    foreach f $files {
1102	puts "TCLCHECKER ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
1103	puts "$f ..."
1104	puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
1105
1106	catch {exec tclchecker >@ stdout $f}
1107    }
1108    return
1109}
1110
1111proc run-nagelfar {args} {
1112    global distribution
1113
1114    if {[llength $args] == 0} {
1115	set files [tclfiles]
1116    } else {
1117	set files [lsort -dict [modtclfiles $args]]
1118    }
1119
1120    foreach f $files {
1121	puts "NAGELFAR ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
1122	puts "$f ..."
1123	puts "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
1124
1125	catch {exec nagelfar >@ stdout $f}
1126    }
1127    return
1128}
1129
1130
1131proc get_input {f} {return [read [set if [open $f r]]][close $if]}
1132
1133proc write_out {f text} {
1134    catch {file delete -force $f}
1135    puts -nonewline [set of [open $f w]] $text
1136    close $of
1137}
1138
1139proc location_PACKAGES {} {
1140    global distribution
1141    return [file join $distribution support releases PACKAGES]
1142}
1143
1144proc gd-gen-packages {} {
1145    global package_version distribution
1146
1147    set P [location_PACKAGES]
1148    file copy -force $P $P.LAST
1149    set f [open $P w]
1150    puts $f "@@ RELEASE $package_version"
1151    puts $f ""
1152
1153    array set packages {}
1154    foreach {p vm} [ipackages] {
1155	set packages($p) [lindex $vm 0]
1156    }
1157
1158    nparray packages $f
1159    close $f
1160}
1161
1162
1163
1164proc modified-modules {} {
1165    global distribution
1166
1167    set mlist [modules]
1168    set modified [list]
1169
1170    foreach m $mlist {
1171	set cl [file join $distribution modules $m ChangeLog]
1172	if {![file exists $cl]} {
1173	    lappend modified [list $m no-changelog]
1174	    continue
1175	}
1176	# Look for 'Released and tagged' within
1177	# the first four lines of the file. If
1178	# not present assume that the line is
1179	# deeper down, indicatating that the module
1180	# has been modified since the last release.
1181
1182	set f [open $cl r]
1183	set n 0
1184	set mod 1
1185	while {$n < 5} {
1186	    gets $f line
1187	    incr n
1188	    if {[string match -nocase "*Released and tagged*" $line]} {
1189		if {$n <= 4} {set mod 0 ; break}
1190	    }
1191	}
1192	if {$mod} {
1193	    lappend modified $m
1194	}
1195	close $f
1196    }
1197
1198    return $modified
1199}
1200
1201# --------------------------------------------------------------
1202# Handle modules using docstrip
1203
1204proc docstripUser {m} {
1205    global distribution
1206
1207    set mdir [file join $distribution modules $m]
1208
1209    if {[llength [glob -nocomplain -dir $mdir *.stitch]]} {return 1}
1210    return 0
1211}
1212
1213proc docstripRegen {m} {
1214    global distribution
1215    puts "$m ..."
1216
1217    getpackage docstrip docstrip/docstrip.tcl
1218
1219    set mdir [file join $distribution modules $m]
1220
1221    foreach sf [glob -nocomplain -dir $mdir *.stitch] {
1222	puts "* [file tail $sf] ..."
1223
1224	set here [pwd]
1225	set fail [catch {
1226	    cd [file dirname $sf]
1227	    docstripRunStitch [file tail $sf]
1228	} msg]
1229	cd $here
1230	if {$fail} {
1231	    puts "  [join [split $::errorInfo \n] "\n  "]"
1232	}
1233    }
1234    return
1235}
1236
1237proc docstripRunStitch {sf} {
1238    # Run the stitch file in a restricted sandbox ...
1239
1240    set box [restrictedIp {
1241	input   ::dsrs::Input
1242	options ::dsrs::Options
1243	stitch  ::dsrs::Stitch
1244	reset   ::dsrs::Reset
1245    }]
1246
1247    ::dsrs::Init
1248    set fail [catch {interp eval $box [get_input $sf]} msg]
1249    if {$fail} {
1250	puts "    [join [split $::errorInfo \n] "\n    "]"
1251    } else {
1252	::dsrs::Final
1253    }
1254
1255    interp delete $box
1256    return
1257}
1258
1259proc emptyIp {} {
1260    set box [interp create]
1261    foreach c [interp eval $box {info commands}] {
1262	if {[string equal $c "rename"]} continue
1263	interp eval $box [list rename $c {}]
1264    }
1265    # Rename command goes last.
1266    interp eval $box [list rename rename {}]
1267    return $box
1268}
1269
1270proc restrictedIp {dict} {
1271    set box [emptyIp]
1272    foreach {cmd localcmd} $dict {
1273	interp alias $box $cmd {} $localcmd
1274    }
1275    return $box
1276}
1277
1278# --------------------------------------------------------------
1279# docstrip low level operations for stitching.
1280
1281namespace eval ::dsrs {
1282    # Standard preamble to preambles
1283
1284    variable preamble {}
1285    append   preamble                                       \n
1286    append   preamble "This is the file `@output@',"        \n
1287    append   preamble "generated with the SAK utility"      \n
1288    append   preamble "(sak docstrip/regen)."               \n
1289    append   preamble                                       \n
1290    append   preamble "The original source files were:"     \n
1291    append   preamble                                       \n
1292    append   preamble "@input@  (with options: `@guards@')" \n
1293    append   preamble                                       \n
1294
1295    # Standard postamble to postambles
1296
1297    variable postamble {}
1298    append   postamble                           \n
1299    append   postamble                           \n
1300    append   postamble "End of file `@output@'."
1301
1302    # Default values for the options which are relevant to the
1303    # application itself and thus have to be defined always.
1304    # They are processed as global options, as part of argv.
1305
1306    variable defaults {-metaprefix {%} -preamble {} -postamble {}}
1307
1308    variable options ; array set options {}
1309    variable outputs ; array set outputs {}
1310    variable inputs  ; array set inputs  {}
1311    variable input   {}
1312}
1313
1314proc ::dsrs::Init {} {
1315    variable outputs ; unset outputs ; array set outputs {}
1316    variable inputs  ; unset inputs  ; array set inputs  {}
1317    variable input   {}
1318
1319    Reset ; # options
1320    return
1321}
1322
1323proc ::dsrs::Reset {} {
1324    variable defaults
1325    variable options ; unset options ; array set options {}
1326    eval [linsert $defaults 0 Options]
1327    return
1328}
1329
1330proc ::dsrs::Input {sourcefile} {
1331    # Relative to current directory = directory containing the active
1332    # stitch file.
1333
1334    variable input $sourcefile
1335}
1336
1337proc ::dsrs::Options {args} {
1338    variable options
1339    variable preamble
1340    variable postamble
1341
1342    while {[llength $args]} {
1343	set opt [lindex $args 0]
1344
1345	switch -exact -- $opt {
1346	    -nopreamble -
1347	    -nopostamble {
1348		set o -[string range $opt 3 end]
1349		set options($o) ""
1350		set args [lrange $args 1 end]
1351	    }
1352	    -preamble {
1353		set val $preamble[lindex $args 1]
1354		set options($opt) $val
1355		set args [lrange $args 2 end]
1356	    }
1357	    -postamble {
1358		set val [lindex $args 1]$postamble
1359		set options($opt) $val
1360		set args [lrange $args 2 end]
1361	    }
1362	    -metaprefix -
1363	    -onerror    -
1364	    -trimlines  {
1365		set val [lindex $args 1]
1366		set options($opt) $val
1367		set args [lrange $args 2 end]
1368	    }
1369	    default {
1370		return -code error "Unknown option: \"$opt\""
1371	    }
1372	}
1373    }
1374    return
1375}
1376
1377proc ::dsrs::Stitch {outputfile guards} {
1378    variable options
1379    variable inputs
1380    variable input
1381    variable outputs
1382    variable preamble
1383    variable postamble
1384
1385    if {[string equal $input {}]} {
1386	return -code error "No input file defined"
1387    }
1388
1389    if {![info exist inputs($input)]} {
1390	set inputs($input) [get_input $input]
1391    }
1392
1393    set intext $inputs($input)
1394    set otext  ""
1395
1396    set c   $options(-metaprefix)
1397    set cc  $c$c
1398
1399    set pmap [list @output@ $outputfile \
1400		  @input@   $input  \
1401		  @guards@  $guards]
1402
1403    if {[info exists options(-preamble)]} {
1404	set pre $options(-preamble)
1405
1406	if {![string equal $pre ""]} {
1407	    append otext [Subst $pre $pmap $cc] \n
1408	}
1409    }
1410
1411    array set o [array get options]
1412    catch {unset o(-preamble)}
1413    catch {unset o(-postamble)}
1414    set opt [array get o]
1415
1416    append otext [eval [linsert $opt 0 docstrip::extract $intext $guards]]
1417
1418    if {[info exists options(-postamble)]} {
1419	set post $options(-postamble)
1420
1421	if {![string equal $post ""]} {
1422	    append otext [Subst $post $pmap $cc]
1423	}
1424    }
1425
1426    # Accumulate outputs in memory
1427
1428    append outputs($outputfile) $otext
1429    return
1430}
1431
1432proc ::dsrs::Subst {text pmap cc} {
1433    return [string trim "$cc [join [split [string map $pmap $text] \n] "\n$cc "]"]
1434}
1435
1436proc ::dsrs::Final {} {
1437    variable outputs
1438    foreach o [array names outputs] {
1439	puts "  = Writing $o ..."
1440
1441	if {[string equal \
1442		 docstrip/docstrip.tcl \
1443		 [file join [file tail [pwd]] $o]]} {
1444
1445	    # We are writing over code required by ourselves.
1446	    # For easy recovery in case of problems we save
1447	    # the original
1448
1449	    puts "    *Saving original of code important to docstrip/regen itself*"
1450	    write_out $o.bak [get_input $o]
1451	}
1452
1453	write_out $o $outputs($o)
1454    }
1455}
1456
1457# --------------------------------------------------------------
1458# Configuration
1459
1460proc __name    {} {global package_name    ; puts -nonewline $package_name}
1461proc __version {} {global package_version ; puts -nonewline $package_version}
1462proc __minor   {} {global package_version ; puts -nonewline [lindex [split $package_version .] 1]}
1463proc __major   {} {global package_version ; puts -nonewline [lindex [split $package_version .] 0]}
1464
1465# --------------------------------------------------------------
1466# Development
1467
1468proc __imodules {} {puts [imodules]}
1469proc __modules  {} {puts [modules]}
1470proc __lmodules {} {puts [join [modules] \n]}
1471
1472
1473proc nparray {a {chan stdout}} {
1474    upvar $a packages
1475
1476    set maxl 0
1477    foreach name [lsort [array names packages]] {
1478        if {[string length $name] > $maxl} {
1479            set maxl [string length $name]
1480        }
1481    }
1482    foreach name [lsort [array names packages]] {
1483	foreach v $packages($name) {
1484	    puts $chan [format "%-*s %s" $maxl $name $v]
1485	}
1486    }
1487    return
1488}
1489
1490proc __packages {} {
1491    array set packages {}
1492    foreach {p vm} [ipackages] {
1493	set packages($p) [lindex $vm 0]
1494    }
1495    nparray packages
1496    return
1497}
1498
1499proc __provided {} {
1500    array set packages [ppackages]
1501    nparray packages
1502    return
1503}
1504
1505
1506proc __vcompare {} {
1507    global argv
1508    set oldplist [lindex $argv 0]
1509    pkg-compare $oldplist
1510    return
1511}
1512
1513proc __rstatus {} {
1514    global distribution approved
1515
1516    catch {
1517	set f [file join $distribution .APPROVE]
1518	set f [open $f r]
1519	while {![eof $f]} {
1520	    if {[gets $f line] < 0} continue
1521	    set line [string trim $line]
1522	    if {$line == {}} continue
1523	    set approved($line) .
1524	}
1525	close $f
1526    }
1527    pkg-compare [location_PACKAGES]
1528    return
1529}
1530
1531proc pkg-compare {oldplist} {
1532    global approved ; array set approved {}
1533
1534    getpackage struct::set struct/sets.tcl
1535
1536    array set curpkg [ipackages]
1537    array set oldpkg [loadpkglist $oldplist]
1538    array set mod {}
1539    array set changed {}
1540    foreach m [modified-modules] {
1541	set mod($m) .
1542    }
1543
1544    foreach p [array names curpkg] {
1545	set __($p) .
1546	foreach {vlist module} $curpkg($p) break
1547	set curpkg($p) $vlist
1548	set changed($p) [info exists mod($module)]
1549    }
1550    foreach p [array names oldpkg] {set __($p) .}
1551    set unified [lsort [array names __]]
1552    unset __
1553
1554    set maxl 0
1555    foreach name $unified {
1556        if {[string length $name] > $maxl} {
1557            set maxl [string length $name]
1558        }
1559    }
1560
1561    set maxm 0
1562    foreach m [modules] {
1563        if {[string length $m] > $maxm} {
1564            set maxm [string length $m]
1565        }
1566    }
1567
1568    set lastm ""
1569    foreach m [lsort -dict [modules]] {
1570	set packages {}
1571	foreach {p ___} [ppackages $m] {
1572	    lappend packages $p
1573	}
1574	foreach name [lsort -dict $packages] {
1575	    set skip 0
1576	    set suffix ""
1577	    set prefix "   "
1578	    if {![info exists curpkg($name)]} {set curpkg($name) {}}
1579	    if {![info exists oldpkg($name)]} {
1580		set oldpkg($name) {}
1581		set suffix " NEW"
1582		set prefix "Nn "
1583		set skip 1
1584	    }
1585	    if {!$skip} {
1586		# Draw attention to changed packages where version is
1587		# unchanged.
1588
1589		set vequal [struct::set equal $oldpkg($name) $curpkg($name)]
1590
1591		if {$changed($name)} {
1592		    if {$vequal} {
1593			# Changed according to ChangeLog, Version is not. ALERT.
1594			set prefix "!! "
1595			set suffix "\t<<< MISMATCH. Version ==, ChangeLog ++"
1596		    } else {
1597			# Both changelog and version number indicate a change.
1598			# Small alert, have to classify the order of changes.
1599			set prefix "cv "
1600			set suffix "\t=== Classify changes."
1601		    }
1602		} else {
1603		    if {$vequal} {
1604			# Versions are unchanged, changelog also indicates no change.
1605			# No particular attention here.
1606		    } else {
1607			# Versions changed, but according to changelog nothing in code. ALERT.
1608			set prefix "!! "
1609			set suffix "\t<<< MISMATCH. ChangeLog ==, Version ++"
1610		    }
1611		}
1612		if {[info exists approved($name)]} {
1613		    set prefix "   "
1614		    set suffix ""
1615		}
1616	    }
1617
1618	    # To handle multiple versions we match the found versions up
1619	    # by major version. We assume that we have only one version
1620	    # per major version. This allows us to detect changes within
1621	    # each major version, new major versions, etc.
1622
1623	    array set om {} ; foreach v $oldpkg($name) {set om([lindex [split $v .] 0]) $v}
1624	    array set cm {} ; foreach v $curpkg($name) {set cm([lindex [split $v .] 0]) $v}
1625
1626	    set all [lsort -dict [struct::set union [array names om] [array names cm]]]
1627
1628	    sakdebug {
1629		puts @@@@@@@@@@@@@@@@
1630		parray om
1631		parray cm
1632		puts all\ $all
1633		puts @@@@@@@@@@@@@@@@
1634	    }
1635
1636	    foreach v $all {
1637		if {![string equal $m $lastm]} {
1638		    set mdis $m
1639		} else {
1640		    set mdis ""
1641		}
1642		set lastm $m
1643
1644		if {[info exists om($v)]} {set ov $om($v)} else {set ov "--"}
1645		if {[info exists cm($v)]} {set cv $cm($v)} else {set cv "--"}
1646
1647		puts stdout ${prefix}[format "%-*s %-*s %-*s %-*s" \
1648					  $maxm $mdis $maxl $name 8 $ov 8 $cv]$suffix
1649	    }
1650
1651	    unset om cm
1652	}
1653    }
1654    return
1655}
1656
1657proc checkmod {} {
1658    global argv
1659    package require sak::util
1660    return [sak::util::checkModules argv]
1661}
1662
1663# -------------------------------------------------------------------------
1664# Critcl stuff
1665# -------------------------------------------------------------------------
1666
1667# Build critcl modules. If no args then build the default critcl module.
1668proc __critcl {} {
1669    global argv critcl critclmodules critcldefault critclnotes tcl_platform
1670    if {$tcl_platform(platform) == "windows"} {
1671
1672	# Windows is a bit more complicated. We have to choose an
1673	# interpreter, and a starkit for it, and call both.
1674	#
1675	# We prefer tclkitsh, but try to make do with a tclsh. That
1676	# one will have to have all the necessary packages to support
1677	# starkits. ActiveTcl for example.
1678
1679	set interpreter {}
1680	foreach i {critcl.exe tclkitsh tclsh} {
1681	    set interpreter [auto_execok $i]
1682	    if {$interpreter != {}} break
1683	}
1684
1685	if {$interpreter == {}} {
1686            return -code error \
1687		    "failed to find either tclkitsh.exe or tclsh.exe in path"
1688	}
1689
1690	# The critcl starkit can come out of the environment, or we
1691	# try to locate it using several possible names. We try to
1692	# find it if and only if we did not find a critcl starpack
1693	# before.
1694
1695	if {[file tail $interpreter] == "critcl.exe"} {
1696	    set critcl $interpreter
1697	} else {
1698	    set kit {}
1699            if {[info exists ::env(CRITCL)]} {
1700                set kit $::env(CRITCL)
1701            } else {
1702		foreach k {critcl.kit critcl} {
1703		    set kit [auto_execok $k]
1704		    if {$kit != {}} break
1705		}
1706            }
1707
1708            if {$kit == {}} {
1709                return -code error "failed to find critcl.kit or critcl in \
1710                  path.\n\
1711                  You may wish to set the CRITCL environment variable to the\
1712                  location of your critcl(.kit) file."
1713            }
1714            set critcl [concat $interpreter $kit]
1715        }
1716    } else {
1717        # My, isn't it simpler under unix.
1718        set critcl [auto_execok critcl]
1719    }
1720
1721    set flags ""
1722    while {[string match -* [set option [lindex $argv 0]]]} {
1723        # -debug and -clean only work with critcl >= v04
1724        switch -exact -- $option {
1725            -keep  { append flags " -keep" }
1726            -debug { append flags " -debug" }
1727            -clean { append flags " -clean" }
1728            -- { set argv [lreplace $argv 0 0]; break }
1729            default { break }
1730        }
1731        set argv [lreplace $argv 0 0]
1732    }
1733
1734    if {$critcl != {}} {
1735        if {[llength $argv] == 0} {
1736            puts stderr "[string repeat - 72]"
1737	    puts stderr "Building critcl components."
1738	    if {$critclnotes != {}} {
1739		puts stderr $critclnotes
1740	    }
1741	    puts stderr "[string repeat - 72]"
1742
1743            critcl_module $critcldefault $flags
1744        } else {
1745            foreach m [dealias $argv] {
1746                if {[info exists critclmodules($m)]} {
1747                    critcl_module $m $flags
1748                } else {
1749                    puts "warning: $m is not a critcl module"
1750                }
1751            }
1752        }
1753    } else {
1754        puts "error: cannot find a critcl to run."
1755        return 1
1756    }
1757    return
1758}
1759
1760# Prints a list of all the modules supporting critcl enhancement.
1761proc __critcl-modules {} {
1762    global critclmodules critcldefault
1763    foreach m [lsort -dict [array names critclmodules]] {
1764	if {$m == $critcldefault} {
1765	    puts "$m **"
1766	} else {
1767	    puts $m
1768	}
1769    }
1770    return
1771}
1772
1773proc critcl_module {pkg {extra ""}} {
1774    global critcl distribution critclmodules critcldefault
1775    if {$pkg == $critcldefault} {
1776	set files {}
1777	foreach f $critclmodules($critcldefault) {
1778	    lappend files [file join $distribution modules $f]
1779	}
1780        foreach m [array names critclmodules] {
1781	    if {$m == $critcldefault} continue
1782            foreach f $critclmodules($m) {
1783                lappend files [file join $distribution modules $f]
1784            }
1785        }
1786    } else {
1787        foreach f $critclmodules($pkg) {
1788            lappend files [file join $distribution modules $f]
1789        }
1790    }
1791    set target [file join $distribution modules]
1792    catch {
1793        puts "$critcl $extra -force -libdir [list $target] -pkg [list $pkg] $files"
1794        eval exec $critcl $extra -force -libdir [list $target] -pkg [list $pkg] $files
1795    } r
1796    puts $r
1797    return
1798}
1799
1800# -------------------------------------------------------------------------
1801
1802proc __bench/edit {} {
1803    global argv argv0
1804
1805    set format text
1806    set output {}
1807
1808    while {[string match -* [set option [lindex $argv 0]]]} {
1809	set val [lindex $argv 1]
1810        switch -exact -- $option {
1811	    -format {
1812		switch -exact -- $val {
1813		    raw - csv - text {}
1814		    default {
1815			return -error "Bad format \"$val\", expected text, csv, or raw"
1816		    }
1817		}
1818		set format $val
1819	    }
1820	    -o    {set output $val}
1821            -- {
1822		set argv [lrange $argv 1 end]
1823		break
1824	    }
1825            default { break }
1826        }
1827        set argv [lrange $argv 2 end]
1828    }
1829
1830    switch -exact -- $format {
1831	raw {}
1832	csv {
1833	    getpackage csv             csv/csv.tcl
1834	    getpackage bench::out::csv bench/bench_wcsv.tcl
1835	}
1836	text {
1837	    getpackage report           report/report.tcl
1838	    getpackage struct::matrix   struct/matrix.tcl
1839	    getpackage bench::out::text bench/bench_wtext.tcl
1840	}
1841    }
1842
1843    getpackage bench::in bench/bench_read.tcl
1844    getpackage bench     bench/bench.tcl
1845
1846    if {[llength $argv] != 3} {
1847	puts "Usage: $argv0 benchdata column newvalue"
1848    }
1849
1850    foreach {in col new} $argv break
1851
1852    _bench_write $output \
1853	[bench::edit \
1854	     [bench::in::read $in] \
1855	     $col $new] \
1856	{} $format
1857    return
1858}
1859
1860proc __bench/del {} {
1861    global argv argv0
1862
1863    set format text
1864    set output {}
1865
1866    while {[string match -* [set option [lindex $argv 0]]]} {
1867	set val [lindex $argv 1]
1868        switch -exact -- $option {
1869	    -format {
1870		switch -exact -- $val {
1871		    raw - csv - text {}
1872		    default {
1873			return -error "Bad format \"$val\", expected text, csv, or raw"
1874		    }
1875		}
1876		set format $val
1877	    }
1878	    -o    {set output $val}
1879            -- {
1880		set argv [lrange $argv 1 end]
1881		break
1882	    }
1883            default { break }
1884        }
1885        set argv [lrange $argv 2 end]
1886    }
1887
1888    switch -exact -- $format {
1889	raw {}
1890	csv {
1891	    getpackage csv             csv/csv.tcl
1892	    getpackage bench::out::csv bench/bench_wcsv.tcl
1893	}
1894	text {
1895	    getpackage report           report/report.tcl
1896	    getpackage struct::matrix   struct/matrix.tcl
1897	    getpackage bench::out::text bench/bench_wtext.tcl
1898	}
1899    }
1900
1901    getpackage bench::in bench/bench_read.tcl
1902    getpackage bench     bench/bench.tcl
1903
1904    if {[llength $argv] < 2} {
1905	puts "Usage: $argv0 benchdata column..."
1906    }
1907
1908    set in [lindex $argv 0]
1909
1910    set data [bench::in::read $in]
1911
1912    foreach c [lrange $argv 1 end] {
1913	set data [bench::del $data $c]
1914    }
1915
1916    _bench_write $output $data {} $format
1917    return
1918}
1919
1920proc __bench/show {} {
1921    global argv
1922
1923    set format text
1924    set output {}
1925    set norm   {}
1926
1927    while {[string match -* [set option [lindex $argv 0]]]} {
1928	set val [lindex $argv 1]
1929        switch -exact -- $option {
1930	    -format {
1931		switch -exact -- $val {
1932		    raw - csv - text {}
1933		    default {
1934			return -error "Bad format \"$val\", expected text, csv, or raw"
1935		    }
1936		}
1937		set format $val
1938	    }
1939	    -o    {set output $val}
1940	    -norm {set norm $val}
1941            -- {
1942		set argv [lrange $argv 1 end]
1943		break
1944	    }
1945            default { break }
1946        }
1947        set argv [lrange $argv 2 end]
1948    }
1949
1950    switch -exact -- $format {
1951	raw {}
1952	csv {
1953	    getpackage csv             csv/csv.tcl
1954	    getpackage bench::out::csv bench/bench_wcsv.tcl
1955	}
1956	text {
1957	    getpackage report           report/report.tcl
1958	    getpackage struct::matrix   struct/matrix.tcl
1959	    getpackage bench::out::text bench/bench_wtext.tcl
1960	}
1961    }
1962
1963    getpackage bench::in bench/bench_read.tcl
1964
1965    array set DATA {}
1966
1967    foreach path $argv {
1968	array set DATA [bench::in::read $path]
1969    }
1970
1971    _bench_write $output [array get DATA] $norm $format
1972    return
1973}
1974
1975proc __bench {} {
1976    global argv
1977
1978    # I. Process command line arguments for the
1979    #    benchmark commands - Validation, possible
1980    #    translation ...
1981
1982    set flags   {}
1983    set norm    {}
1984    set format  text
1985    set verbose warn
1986    set output  {}
1987    set paths   {}
1988    set interp  {}
1989
1990    while {[string match -* [set option [lindex $argv 0]]]} {
1991	set val [lindex $argv 1]
1992        switch -exact -- $option {
1993	    -throwerrors {lappend flags -errors $val}
1994	    -match -
1995	    -rmatch -
1996	    -iters -
1997	    -threads {lappend flags $option $val}
1998	    -o       {set output $val}
1999	    -norm    {set norm $val}
2000	    -path    {lappend paths $val}
2001	    -interp  {set interp $val}
2002	    -format  {
2003		switch -exact -- $val {
2004		    raw - csv - text {}
2005		    default {
2006			return -error "Bad format \"$val\", expected text, csv, or raw"
2007		    }
2008		}
2009		set format $val
2010	    }
2011	    -verbose {
2012		set verbose info
2013		set argv [lrange $argv 1 end]
2014		continue
2015	    }
2016	    -debug {
2017		set verbose debug
2018		set argv [lrange $argv 1 end]
2019		continue
2020	    }
2021            -- {
2022		set argv [lrange $argv 1 end]
2023		break
2024	    }
2025            default { break }
2026        }
2027        set argv [lrange $argv 2 end]
2028    }
2029
2030    switch -exact -- $format {
2031	raw {}
2032	csv {
2033	    getpackage csv             csv/csv.tcl
2034	    getpackage bench::out::csv bench/bench_wcsv.tcl
2035	}
2036	text {
2037	    getpackage report           report/report.tcl
2038	    getpackage struct::matrix   struct/matrix.tcl
2039	    getpackage bench::out::text bench/bench_wtext.tcl
2040	}
2041    }
2042
2043    # Choose between benchmarking everything, or
2044    # only selected modules.
2045
2046    if {[llength $argv] == 0} {
2047	_bench_all $paths $interp $flags $norm $format $verbose $output
2048    } else {
2049	if {![checkmod]} {return}
2050	_bench_module [dealias $argv] $paths $interp $flags $norm $format $verbose $output
2051    }
2052    return
2053}
2054
2055proc _bench_module {mlist paths interp flags norm format verbose output} {
2056    global package_name package_version
2057
2058    puts "Benchmarking $package_name $package_version development"
2059    puts "======================================================"
2060    bench_mod $mlist $paths $interp $flags $norm $format $verbose $output
2061    puts "------------------------------------------------------"
2062    puts ""
2063    return
2064}
2065
2066proc _bench_all {paths flags interp norm format verbose output} {
2067    _bench_module [modules] $paths $interp $flags $norm $format $verbose $output
2068    return
2069}
2070
2071# -------------------------------------------------------------------------
2072
2073proc __oldvalidate_v {} {
2074    global argv
2075    if {[llength $argv] == 0} {
2076	_validate_all_v
2077    } else {
2078	if {![checkmod]} {return}
2079	foreach m [dealias $argv] {
2080	    _validate_module_v $m
2081	}
2082    }
2083    return
2084}
2085
2086proc _validate_all_v {} {
2087    global package_name package_version
2088    set i 0
2089
2090    puts "Validating $package_name $package_version development"
2091    puts "==================================================="
2092    puts "[incr i]: Consistency of package versions ..."
2093    puts "------------------------------------------------------"
2094    validate_versions
2095    puts "------------------------------------------------------"
2096    puts ""
2097    return
2098}
2099
2100proc _validate_module_v {m} {
2101    global package_name package_version
2102    set i 0
2103
2104    puts "Validating $package_name $package_version development -- $m"
2105    puts "==================================================="
2106    puts "[incr i]: Consistency of package versions ..."
2107    puts "------------------------------------------------------"
2108    validate_versions_mod $m
2109    puts "------------------------------------------------------"
2110    puts ""
2111    return
2112}
2113
2114
2115proc __oldvalidate {} {
2116    global argv
2117    if {[llength $argv] == 0} {
2118	_validate_all
2119    } else {
2120	if {![checkmod]} {return}
2121	foreach m $argv {
2122	    _validate_module $m
2123	}
2124    }
2125    return
2126}
2127
2128proc _validate_all {} {
2129    global package_name package_version
2130    set i 0
2131
2132    puts "Validating $package_name $package_version development"
2133    puts "==================================================="
2134    puts "[incr i]: Existence of testsuites ..."
2135    puts "------------------------------------------------------"
2136    validate_testsuites
2137    puts "------------------------------------------------------"
2138    puts ""
2139
2140    puts "[incr i]: Existence of package indices ..."
2141    puts "------------------------------------------------------"
2142    validate_pkgIndex
2143    puts "------------------------------------------------------"
2144    puts ""
2145
2146    puts "[incr i]: Consistency of package versions ..."
2147    puts "------------------------------------------------------"
2148    validate_versions
2149    puts "------------------------------------------------------"
2150    puts ""
2151
2152    puts "[incr i]: Installed vs. developed modules ..."
2153    puts "------------------------------------------------------"
2154    validate_imodules
2155    puts "------------------------------------------------------"
2156    puts ""
2157
2158    puts "[incr i]: Existence of documentation ..."
2159    puts "------------------------------------------------------"
2160    validate_doc_existence
2161    puts "------------------------------------------------------"
2162    puts ""
2163
2164    puts "[incr i]: Validate documentation markup (doctools) ..."
2165    puts "------------------------------------------------------"
2166    validate_doc_markup
2167    puts "------------------------------------------------------"
2168    puts ""
2169
2170    puts "[incr i]: Static syntax check ..."
2171    puts "------------------------------------------------------"
2172
2173    set frink      [auto_execok frink]
2174    set procheck   [auto_execok procheck]
2175    set tclchecker [auto_execok tclchecker]
2176    set nagelfar [auto_execok nagelfar]
2177
2178    if {$frink == {}} {puts "  Tool 'frink'    not found, no check"}
2179    if {($procheck == {}) || ($tclchecker == {})} {
2180	puts "  Tools 'procheck'/'tclchecker' not found, no check"
2181    }
2182    if {$nagelfar == {}} {puts "  Tool 'nagelfar' not found, no check"}
2183
2184    if {($frink == {}) || ($procheck == {}) || ($tclchecker == {})
2185        || ($nagelfar == {})} {
2186	puts "------------------------------------------------------"
2187    }
2188    if {($frink == {}) && ($procheck == {}) && ($tclchecker == {})
2189        && ($nagelfar == {})} {
2190	return
2191    }
2192    if {$frink != {}} {
2193	run-frink
2194	puts "------------------------------------------------------"
2195    }
2196    if {$tclchecker != {}} {
2197	run-tclchecker
2198	puts "------------------------------------------------------"
2199    } elseif {$procheck != {}} {
2200	run-procheck
2201	puts "------------------------------------------------------"
2202    }
2203    if {$nagelfar    !={}} {
2204    	run-nagelfar
2205	puts "------------------------------------------------------"
2206    }
2207    puts ""
2208    return
2209}
2210
2211proc _validate_module {m} {
2212    global package_name package_version
2213    set i 0
2214
2215    puts "Validating $package_name $package_version development -- $m"
2216    puts "==================================================="
2217    puts "[incr i]: Existence of testsuites ..."
2218    puts "------------------------------------------------------"
2219    validate_testsuite_mod $m
2220    puts "------------------------------------------------------"
2221    puts ""
2222
2223    puts "[incr i]: Existence of package indices ..."
2224    puts "------------------------------------------------------"
2225    validate_pkgIndex_mod $m
2226    puts "------------------------------------------------------"
2227    puts ""
2228
2229    puts "[incr i]: Consistency of package versions ..."
2230    puts "------------------------------------------------------"
2231    validate_versions_mod $m
2232    puts "------------------------------------------------------"
2233    puts ""
2234
2235    #puts "[incr i]: Installed vs. developed modules ..."
2236    puts "------------------------------------------------------"
2237    validate_imodules_mod $m
2238    puts "------------------------------------------------------"
2239    puts ""
2240
2241    puts "[incr i]: Existence of documentation ..."
2242    puts "------------------------------------------------------"
2243    validate_doc_existence_mod $m
2244    puts "------------------------------------------------------"
2245    puts ""
2246
2247    puts "[incr i]: Validate documentation markup (doctools) ..."
2248    puts "------------------------------------------------------"
2249    validate_doc_markup_mod $m
2250    puts "------------------------------------------------------"
2251    puts ""
2252
2253    puts "[incr i]: Static syntax check ..."
2254    puts "------------------------------------------------------"
2255
2256    set frink    [auto_execok frink]
2257    set procheck [auto_execok procheck]
2258    set nagelfar [auto_execok nagelfar]
2259    set tclchecker [auto_execok tclchecker]
2260
2261    if {$frink    == {}} {puts "  Tool 'frink'    not found, no check"}
2262    if {($procheck == {}) || ($tclchecker == {})} {
2263	puts "  Tools 'procheck'/'tclchecker' not found, no check"
2264    }
2265    if {$nagelfar == {}} {puts "  Tool 'nagelfar' not found, no check"}
2266
2267    if {($frink == {}) || ($procheck == {}) || ($tclchecker == {}) ||
2268    	($nagelfar == {})} {
2269	puts "------------------------------------------------------"
2270    }
2271    if {($frink == {}) && ($procheck == {}) && ($nagelfar == {})
2272        && ($tclchecker == {})} {
2273	return
2274    }
2275    if {$frink    != {}} {
2276	run-frink $m
2277	puts "------------------------------------------------------"
2278    }
2279    if {$tclchecker != {}} {
2280	run-tclchecker $m
2281	puts "------------------------------------------------------"
2282    } elseif {$procheck != {}} {
2283	run-procheck $m
2284	puts "------------------------------------------------------"
2285    }
2286    if {$nagelfar    !={}} {
2287    	run-nagelfar $m
2288	puts "------------------------------------------------------"
2289    }
2290    puts ""
2291
2292    return
2293}
2294
2295# --------------------------------------------------------------
2296# Release engineering
2297
2298proc __gendist {} {
2299    gd-cleanup
2300    gd-tip55
2301    gd-gen-rpmspec
2302    gd-gen-tap
2303    gd-gen-yml
2304    gd-assemble
2305    gd-gen-archives
2306
2307    puts ...Done
2308    return
2309}
2310
2311proc __gentip55 {} {
2312    gd-tip55
2313    puts "Created DESCRIPTION.txt"
2314    return
2315}
2316
2317proc __yml {} {
2318    global package_name
2319    gd-gen-yml
2320    puts "Created YAML spec file \"${package_name}.yml\""
2321    return
2322}
2323
2324proc __contributors {} {
2325    global contributors
2326    contributors
2327    foreach person [lsort [array names contributors]] {
2328        puts "$person <$contributors($person)>"
2329    }
2330    return
2331}
2332
2333proc __tap {} {
2334    global package_name
2335    gd-gen-tap
2336    puts "Created Tcl Dev Kit \"${package_name}.tap\""
2337}
2338
2339proc __rpmspec {} {
2340    global package_name
2341    gd-gen-rpmspec
2342    puts "Created RPM spec file \"${package_name}.spec\""
2343}
2344
2345
2346proc __release {} {
2347    # Regenerate PACKAGES, and extend
2348
2349    global argv argv0 distribution package_name package_version
2350
2351    getpackage textutil textutil/textutil.tcl
2352
2353    if {[llength $argv] != 2} {
2354	puts stderr "$argv0: wrong#args: release name sf-user-id"
2355	exit 1
2356    }
2357
2358    foreach {name sfuser} $argv break
2359    set email "<${sfuser}@users.sourceforge.net>"
2360    set pname [textutil::cap $package_name]
2361
2362    set notice "[clock format [clock seconds] -format "%Y-%m-%d"]  $name  $email
2363
2364	*
2365	* Released and tagged $pname $package_version ========================
2366	*
2367
2368"
2369
2370    set logs [list [file join $distribution ChangeLog]]
2371    foreach m [modules] {
2372	set m [file join $distribution modules $m ChangeLog]
2373	if {![file exists $m]} continue
2374	lappend logs $m
2375    }
2376
2377    foreach f $logs {
2378	puts "\tAdding release notice to $f"
2379	set fh [open $f r] ; set data [read $fh] ; close $fh
2380	set fh [open $f w] ; puts -nonewline $fh $notice$data ; close $fh
2381    }
2382
2383    gd-gen-packages
2384    return
2385}
2386
2387proc __approve {} {
2388    global argv distribution
2389
2390    # Record the package as approved. This will suppress any alerts
2391    # for that package by rstatus. Required for packages which have
2392    # been classified, and for packages where a MISMATCH is bogus (due
2393    # to several packages sharing a ChangeLog)
2394
2395    set f [open [file join $distribution .APPROVE] a]
2396    foreach package $argv {
2397	puts $f $package
2398    }
2399    close $f
2400    return
2401}
2402
2403# --------------------------------------------------------------
2404# Documentation
2405
2406proc __desc  {} {
2407    global argv ; if {![checkmod]} return
2408    array set pd [getpdesc]
2409
2410    getpackage struct::matrix struct/matrix.tcl
2411    getpackage textutil       textutil/textutil.tcl
2412
2413    struct::matrix m
2414    m add columns 3
2415
2416    puts {Descriptions...}
2417    if {[llength $argv] == 0} {set argv [modules]}
2418
2419    foreach m [lsort [dealias $argv]] {
2420	array set _ {}
2421	set pkg {}
2422	foreach {p vlist} [ppackages $m] {
2423	    catch {set _([lindex $pd($p) 0]) .}
2424	    lappend pkg $p
2425	}
2426	set desc [string trim [join [array names _] ", "] " \n\t\r,"]
2427	set desc [textutil::adjust $desc -length 20]
2428	unset _
2429
2430	m add row [list $m $desc]
2431	m add row {}
2432
2433	foreach p [lsort -dictionary $pkg] {
2434	    set desc ""
2435	    catch {set desc [lindex $pd($p) 1]}
2436	    if {$desc != ""} {
2437		set desc [string trim $desc]
2438		set desc [textutil::adjust $desc -length 50]
2439		m add row [list {} $p $desc]
2440	    } else {
2441		m add row [list {**} $p ]
2442	    }
2443	}
2444	m add row {}
2445    }
2446
2447    m format 2chan
2448    puts ""
2449    return
2450}
2451
2452proc __desc/2  {} {
2453    global argv ; if {![checkmod]} return
2454    array set pd [getpdesc]
2455
2456    getpackage struct::matrix struct/matrix.tcl
2457    getpackage textutil       textutil/textutil.tcl
2458
2459    puts {Descriptions...}
2460    if {[llength $argv] == 0} {set argv [modules]}
2461
2462    foreach m [lsort [dealias $argv]] {
2463	struct::matrix m
2464	m add columns 3
2465
2466	m add row {}
2467
2468	set pkg {}
2469	foreach {p vlist} [ppackages $m] {lappend pkg $p}
2470
2471	foreach p [lsort -dictionary $pkg] {
2472	    set desc ""
2473	    set sdes ""
2474	    catch {set desc [lindex $pd($p) 1]}
2475	    catch {set sdes [lindex $pd($p) 0]}
2476
2477	    if {$desc != ""} {
2478		set desc [string trim $desc]
2479		#set desc [textutil::adjust $desc -length 50]
2480	    }
2481
2482	    if {$desc != ""} {
2483		set desc [string trim $desc]
2484		#set desc [textutil::adjust $desc -length 50]
2485	    }
2486
2487	    m add row [list $p "  $sdes" "  $desc"]
2488	}
2489	m format 2chan
2490	puts ""
2491	m destroy
2492    }
2493
2494    return
2495}
2496
2497# --------------------------------------------------------------
2498
2499proc __docstrip/users {} {
2500    # Print the list of modules using docstrip for their code.
2501
2502    set argv [modules]
2503    foreach m [lsort $argv] {
2504	if {[docstripUser $m]} {
2505	    puts $m
2506	}
2507    }
2508
2509    return
2510}
2511
2512proc __docstrip/regen {} {
2513    # Regenerate modules based on docstrip.
2514
2515    global argv ; if {![checkmod]} return
2516    if {[llength $argv] == 0} {set argv [modules]}
2517
2518    foreach m [lsort [dealias $argv]] {
2519	if {[docstripUser $m]} {
2520	    docstripRegen $m
2521	}
2522    }
2523
2524    return
2525}
2526
2527# --------------------------------------------------------------
2528## Make sak specific packages visible.
2529
2530lappend auto_path [file join $distribution support devel sak]
2531
2532# --------------------------------------------------------------
2533## Dispatcher to the sak commands.
2534
2535set  cmd  [lindex $argv 0]
2536set  argv [lrange $argv 1 end]
2537incr argc -1
2538
2539# Prefer a command implementation found in the support tree.
2540# Then see if the command is implemented here, in this file.
2541# At last fail and report possible commands.
2542
2543set base  [file dirname [info script]]
2544set sbase [file join $base support devel sak]
2545set cbase [file join $sbase $cmd]
2546set cmdf  [file join $cbase cmd.tcl]
2547
2548if {[file exists $cmdf] && [file readable $cmdf]} {
2549    source $cmdf
2550    exit 0
2551}
2552
2553if {[llength [info procs __$cmd]] == 0} {
2554    puts stderr "$argv0 : Illegal command \"$cmd\""
2555    set fl {}
2556    foreach p [info procs __*] {
2557	lappend fl [string range $p 2 end]
2558    }
2559    foreach p [glob -nocomplain -directory $sbase */cmd.tcl] {
2560	lappend fl [lindex [file split $p] end-1]
2561    }
2562
2563    regsub -all . $argv0 { } blank
2564    puts stderr "$blank : Should have been [linsert [join [lsort -uniq $fl] ", "] end-1 or]"
2565    exit 1
2566}
2567
2568__$cmd
2569exit 0
2570