1# -*- tcl -*- 2# (C) 2009 Andreas Kupries <andreas_kupries@users.sourceforge.net> 3## 4# ### 5 6namespace eval ::sak::readme {} 7 8# ### 9 10proc ::sak::readme::usage {} { 11 package require sak::help 12 puts stdout \n[sak::help::on readme] 13 exit 1 14} 15 16proc ::sak::readme::run {} { 17 global package_name package_version 18 19 getpackage struct::set struct/sets.tcl 20 getpackage struct::matrix struct/matrix.tcl 21 getpackage textutil::adjust textutil/adjust.tcl 22 23 # package -> list(version) 24 set old_version [loadoldv [location_PACKAGES]] 25 array set releasep [loadpkglist [location_PACKAGES]] 26 array set currentp [ipackages] 27 28 # Determine which packages are potentially changed, from the set 29 # of modules touched since the last release, as per their 30 # changelog ... (future: md5sum of files in a module, and 31 # file/package association). 32 33 set modifiedm [modified-modules] 34 array set changed {} 35 foreach p [array names currentp] { 36 foreach {vlist module} $currentp($p) break 37 set currentp($p) $vlist 38 set changed($p) [struct::set contains $modifiedm $module] 39 } 40 41 LoadNotes 42 43 # Containers for results 44 struct::matrix NEW ; NEW add columns 4 ; # module, package, version, notes 45 struct::matrix CHG ; CHG add columns 5 ; # module, package, old/new version, notes 46 struct::matrix ICH ; ICH add columns 5 ; # module, package, old/new version, notes 47 struct::matrix CNT ; CNT add columns 5; 48 set UCH {} 49 50 NEW add row {Module Package {New Version} Comments} 51 52 CHG add row [list {} {} "$package_name $old_version" "$package_name $package_version" {}] 53 CHG add row {Module Package {Old Version} {New Version} Comments} 54 55 ICH add row [list {} {} "$package_name $old_version" "$package_name $package_version" {}] 56 ICH add row {Module Package {Old Version} {New Version} Comments} 57 58 set newp {} ; set chgp {} ; set ichp {} 59 set newm {} ; set chgm {} ; set ichm {} ; set uchm {} 60 set nm 0 61 set np 0 62 63 # Process all packages in all modules ... 64 foreach m [lsort -dict [modules]] { 65 puts stderr ...$m 66 incr nm 67 68 foreach name [lsort -dict [Provided $m]] { 69 #puts stderr ......$p 70 incr np 71 72 # Define list of versions, if undefined so far. 73 if {![info exists currentp($name)]} { 74 set currentp($name) {} 75 } 76 77 # Detect and process new packages. 78 79 if {![info exists releasep($name)]} { 80 # New package. 81 foreach v $currentp($name) { 82 puts stderr .........NEW 83 NEW add row [list $m $name $v [Note $m $name]] 84 lappend newm $m 85 lappend newp $name 86 } 87 continue 88 } 89 90 # The package is not new, but possibly changed. And even 91 # if the version has not changed it may have been, this is 92 # indicated by changed(), which is based on the ChangeLog. 93 94 set vequal [struct::set equal $releasep($name) $currentp($name)] 95 set note [Note $m $name] 96 97 if {$vequal && ($note ne {})} { 98 if {$note eq "---"} { 99 # The note declares the package as unchanged. 100 puts stderr .........UNCHANGED/1 101 lappend uchm $m 102 lappend UCH $name 103 } else { 104 # Note for package without version changes => must be invisible 105 puts stderr .........INVISIBLE-CHANGE 106 Enter $m $name $note ICH 107 lappend ichm $m 108 lappend ichp $name 109 } 110 continue 111 } 112 113 if {!$changed($name) && $vequal} { 114 # Versions are unchanged, changelog also indicates no 115 # change. No particular attention here. 116 117 puts stderr .........UNCHANGED/2 118 lappend uchm $m 119 lappend UCH $name 120 continue 121 } 122 123 if {$changed($name) && !$vequal} { 124 # Both changelog and version number indicate a 125 # change. Small alert, have to classify the order of 126 # changes. But not if there is a note, this is assumed 127 # to be the classification. 128 129 if {$note eq {}} { 130 set note "\t=== Classify changes." 131 } 132 Enter $m $name $note 133 lappend chgm $m 134 lappend chgp $name 135 continue 136 } 137 138 # Changed according to ChangeLog, Version is not. ALERT. 139 # or: Versions changed, but according to changelog nothing 140 # in the code. ALERT. 141 142 # Suppress the alert if we have a note, and dispatch per 143 # the note's contents (some tags are special, instructions 144 # to us here). 145 146 if {($note eq {})} { 147 if {$changed($name)} { 148 # Changed according to ChangeLog, Version is not. ALERT. 149 set note "\t<<< MISMATCH. Version ==, ChangeLog ++" 150 } else { 151 set note "\t<<< MISMATCH. ChangeLog ==, Version ++" 152 } 153 } 154 155 Enter $m $name $note 156 lappend chgm $m 157 lappend chgp $name 158 } 159 } 160 161 # .... process the matrices and others results, make them presentable ... 162 163 set newp [llength [lsort -uniq $newp]] 164 set newm [llength [lsort -uniq $newm]] 165 if {$newp} { 166 CNT add row [list $newp {new packages} in $newm modules] 167 } 168 169 set chgp [llength [lsort -uniq $chgp]] 170 set chgm [llength [lsort -uniq $chgm]] 171 if {$chgp} { 172 CNT add row [list $chgp {changed packages} in $chgm modules] 173 } 174 175 set ichp [llength [lsort -uniq $ichp]] 176 set ichm [llength [lsort -uniq $ichm]] 177 if {$ichp} { 178 CNT add row [list $ichp {internally changed packages} in $ichm modules] 179 } 180 181 set uchp [llength [lsort -uniq $UCH]] 182 set uchm [llength [lsort -uniq $uchm]] 183 if {$uchp} { 184 CNT add row [list $uchp {unchanged packages} in $uchm modules] 185 } 186 187 CNT add row [list $np {packages, total} in $nm {modules, total}] 188 189 Header Overview 190 puts "" 191 if {[CNT rows] > 0} { 192 puts [Indent " " [Detrail [CNT format 2string]]] 193 } 194 puts "" 195 196 if {[NEW rows] > 1} { 197 Header "New in $package_name $package_version" 198 puts "" 199 Sep NEW - [Clean NEW 1 0] 200 puts [Indent " " [Detrail [NEW format 2string]]] 201 puts "" 202 } 203 204 if {[CHG rows] > 2} { 205 Header "Changes from $package_name $old_version to $package_version" 206 puts "" 207 Sep CHG - [Clean CHG 2 0] 208 puts [Indent " " [Detrail [CHG format 2string]]] 209 puts "" 210 } 211 212 if {[ICH rows] > 2} { 213 Header "Invisible changes (documentation, testsuites)" 214 puts "" 215 Sep ICH - [Clean ICH 2 0] 216 puts [Indent " " [Detrail [ICH format 2string]]] 217 puts "" 218 } 219 220 if {[llength $UCH]} { 221 Header Unchanged 222 puts "" 223 puts [Indent " " [textutil::adjust::adjust \ 224 [join [lsort -dict $UCH] {, }] -length 64]] 225 } 226 227 variable legend 228 puts $legend 229 return 230} 231 232proc ::sak::readme::Header {s {sep =}} { 233 puts $s 234 puts [string repeat $sep [string length $s]] 235 return 236} 237 238proc ::sak::readme::Enter {m name note {mat CHG}} { 239 upvar 1 currentp currentp releasep releasep 240 241 # To handle multiple versions we match the found versions up by 242 # major version. We assume that we have only one version per major 243 # version. This allows us to detect changes within each major 244 # version, new major versions, etc. 245 246 array set om {} ; foreach v $releasep($name) {set om([lindex [split $v .] 0]) $v} 247 array set cm {} ; foreach v $currentp($name) {set cm([lindex [split $v .] 0]) $v} 248 249 set all [lsort -dict [struct::set union [array names om] [array names cm]]] 250 251 sakdebug { 252 puts @@@@@@@@@@@@@@@@ 253 parray om 254 parray cm 255 puts all\ $all 256 puts @@@@@@@@@@@@@@@@ 257 } 258 259 foreach v $all { 260 if {[info exists om($v)]} {set ov $om($v)} else {set ov ""} 261 if {[info exists cm($v)]} {set cv $cm($v)} else {set cv ""} 262 $mat add row [list $m $name $ov $cv $note] 263 } 264 return 265} 266 267proc ::sak::readme::Clean {m start col} { 268 set n [$m rows] 269 set marks [list $start] 270 set last {} 271 set lastm -1 272 set sq 0 273 274 for {set i $start} {$i < $n} {incr i} { 275 set str [$m get cell $col $i] 276 277 if {$str eq $last} { 278 set sq 1 279 $m set cell $col $i {} 280 if {$lastm >= 0} { 281 #puts stderr "@ $i / <$last> / <$str> / ++ $lastm" 282 lappend marks $lastm 283 set lastm -1 284 } else { 285 #puts stderr "@ $i / <$last> / <$str> /" 286 } 287 } else { 288 set last $str 289 set lastm $i 290 if {$sq} { 291 #puts stderr "@ $i / <$last> / <$str> / ++ $i /saved" 292 lappend marks $i 293 set sq 0 294 } else { 295 #puts stderr "@ $i / <$last> / <$str> / saved" 296 } 297 } 298 } 299 return [lsort -uniq -increasing -integer $marks] 300} 301 302proc ::sak::readme::Sep {m char marks} { 303 304 #puts stderr "$m = $marks" 305 306 set n [$m columns] 307 set sep {} 308 for {set i 0} {$i < $n} {incr i} { 309 lappend sep [string repeat $char [expr {2+[$m columnwidth $i]}]] 310 } 311 312 foreach k [linsert [lsort -decreasing -integer -uniq $marks] 0 end] { 313 $m insert row $k $sep 314 } 315 return 316} 317 318proc ::sak::readme::Indent {pfx text} { 319 return ${pfx}[join [split $text \n] \n$pfx] 320} 321 322proc ::sak::readme::Detrail {text} { 323 set res {} 324 foreach line [split $text \n] { 325 lappend res [string trimright $line] 326 } 327 return [join $res \n] 328} 329 330proc ::sak::readme::Note {m p} { 331 # Look for a note, and present to caller, if any. 332 variable notes 333 #parray notes 334 set k [list $m $p] 335 #puts <$k> 336 if {[info exists notes($k)]} { 337 return [join $notes($k) { }] 338 } 339 return "" 340} 341 342proc ::sak::readme::Provided {m} { 343 set result {} 344 foreach {p ___} [ppackages $m] { 345 lappend result $p 346 } 347 return $result 348} 349 350proc ::sak::readme::LoadNotes {} { 351 global distribution 352 variable notes 353 array set notes {} 354 355 catch { 356 set f [file join $distribution .NOTE] 357 set f [open $f r] 358 while {![eof $f]} { 359 if {[gets $f line] < 0} continue 360 set line [string trim $line] 361 if {$line == {}} continue 362 foreach {k t} $line break 363 set notes($k) $t 364 } 365 close $f 366 } msg 367 return 368} 369 370proc ::sak::readme::loadoldv {fname} { 371 set f [open $fname r] 372 foreach line [split [read $f] \n] { 373 set line [string trim $line] 374 if {[string match @* $line]} { 375 foreach {__ __ v} $line break 376 close $f 377 return $v 378 } 379 } 380 close $f 381 return -code error {Version not found} 382} 383 384## 385# ### 386 387namespace eval ::sak::readme { 388 variable legend { 389Legend Change Details Comments 390 ------ ------- --------- 391 Major API: ** incompatible ** API changes. 392 393 Minor EF : Extended functionality, API. 394 I : Major rewrite, but no API change 395 396 Patch B : Bug fixes. 397 EX : New examples. 398 P : Performance enhancement. 399 400 None T : Testsuite changes. 401 D : Documentation updates. 402 } 403} 404 405package provide sak::readme 1.0 406