summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/doctools2idx/structure.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tcllib/modules/doctools2idx/structure.tcl')
-rw-r--r--tcllib/modules/doctools2idx/structure.tcl288
1 files changed, 288 insertions, 0 deletions
diff --git a/tcllib/modules/doctools2idx/structure.tcl b/tcllib/modules/doctools2idx/structure.tcl
new file mode 100644
index 0000000..c7e3285
--- /dev/null
+++ b/tcllib/modules/doctools2idx/structure.tcl
@@ -0,0 +1,288 @@
+# -*- tcl -*-
+# Copyright (c) 2009 Andreas Kupries <andreas_kupries@sourceforge.net>
+
+# Verification of serialized indices, and conversion between
+# serialized indices and other data structures.
+
+# # ## ### ##### ######## ############# #####################
+## Requirements
+
+package require Tcl 8.4 ; # Required runtime.
+package require snit ; # OO system.
+
+# # ## ### ##### ######## ############# #####################
+##
+
+snit::type ::doctools::idx::structure {
+ # # ## ### ##### ######## #############
+ ## Public API
+
+ # Check that the proposed serialization of a keyword index is
+ # indeed such.
+
+ typemethod verify {serial {canonvar {}}} {
+ # Basic syntax: Length and outer type code
+ if {[llength $serial] != 2} {
+ return -code error $ourprefix$ourshort
+ }
+
+ foreach {tag contents} $serial break
+ #struct::list assign $serial tag contents
+
+ if {$tag ne $ourcode} {
+ return -code error $ourprefix[format $ourtag $tag]
+ }
+
+ if {[llength $contents] != 8} {
+ return -code error $ourprefix$ourcshort
+ }
+
+ # Unpack the contents, then check that all necessary keys are
+ # present. Together with the length check we can then also be
+ # sure that no other key is present either.
+ array set idx $contents
+
+ foreach k {label title keywords references} {
+ if {[info exists idx($k)]} continue
+ return -code error $ourprefix[format $ourmiss $k]
+ }
+
+ # Pull the keys and check their use (n duplicates allowed). At
+ # the same time we collect the references they are associated
+ # with.
+
+ set refs {}
+ set keys {}
+ array set kw {}
+
+ foreach {k reflist} $idx(keywords) {
+ lappend keys $k
+ set kw($k) {}
+ foreach r $reflist { lappend refs $r }
+ }
+
+ # Fail if keys are duplicated
+ if {[llength [array names kw]] != [llength $keys]} {
+ return -code error $ourprefix$ourkdup
+ }
+
+ # Pull the references and check their values, and use.
+ array set rd {}
+ set refids {}
+ foreach {id rdef} $idx(references) {
+ if {[llength $rdef] != 2} {
+ return -code error $ourprefix$ourrshort
+ }
+ set rtag [lindex $rdef 0]
+ if {($rtag ne "manpage") && ($rtag ne "url")} {
+ return -code error $ourprefix[format $ourrtag $rtag]
+ }
+ lappend refids $id
+ set rd($id) {}
+ }
+
+ # Fail if reference ids are duplicated
+ if {[llength [array names rd]] != [llength $refids]} {
+ return -code error $ourprefix$ourrdup
+ }
+
+ # Fail if we have references in keys without decl, or
+ # references not used by any key.
+ if {[lsort -dict [lsort -unique $refs]] ne [lsort -dict $refids]} {
+ return -code error $ourprefix$ourrmismatch
+ }
+
+ if {$canonvar ne {}} {
+ upvar 1 $canonvar iscanonical
+
+ # Now various checks if the keys and identifiers are
+ # properly sorted to make this a canonical serialization.
+ set iscanonical 1
+
+ foreach {a _ b _ c _ d _} $contents break
+ #struct::list assign $contents a _ b _ c _ d _
+ if {
+ ([list $a $b $c $d] ne {label keywords references title}) ||
+ ($keys ne [lsort -dict [array names kw]]) ||
+ ($refids ne [lsort -dict [array names rd]])
+ } {
+ set iscanonical 0
+ }
+ }
+
+ # Everything checked out.
+ return
+ }
+
+ typemethod verify-as-canonical {serial} {
+ $type verify $serial iscanonical
+ if {!$iscanonical} {
+ #puts <$kw>\n<[lsort -dict [lsort -unique $kw]]>
+ return -code error $ourprefix$ourdupsort
+ }
+ return
+ }
+
+ typemethod canonicalize {serial} {
+ $type verify $serial iscanonical
+ if {$iscanonical} { return $serial }
+
+ # Unpack the serialization.
+ array set idx $serial
+ array set idx $idx(doctools::idx)
+ unset idx(doctools::idx)
+ array set k $idx(keywords)
+ array set r $idx(references)
+
+ # Scan and reorder ...
+ set keywords {}
+ foreach kw [lsort -dict [array names k]] {
+ # Sort references in a keyword by their _labels_.
+ set tmp {}
+ foreach rid $k($kw) { lappend tmp [list $rid [lindex $r($rid) 1]] }
+ set refs {}
+ foreach item [lsort -dict -index 1 $tmp] {
+ lappend refs [lindex $item 0]
+ }
+ lappend keywords $kw $refs
+ }
+
+ set references {}
+ foreach rid [lsort -dict [array names r]] {
+ lappend references $rid $r($rid)
+ }
+
+ # Construct result
+ set serial [list doctools::idx \
+ [list \
+ label $idx(label) \
+ keywords $keywords \
+ references $references \
+ title $idx(title)]]
+
+ return $serial
+ }
+
+ # Merge the serialization of two indices into a new serialization.
+
+ typemethod merge {seriala serialb} {
+ $type verify $seriala
+ $type verify $serialb
+
+ # Merge using title and label of the second index, and the new
+ # key definitions come after the existing, overriding as
+ # needed.
+
+ # Unpack the definitions...
+
+ array set a $seriala ; array set a $a(doctools::idx) ; unset a(doctools::idx)
+ array set b $serialb ; array set a $b(doctools::idx) ; unset b(doctools::idx)
+
+ # Merge keywords...
+
+ array set k $a(keywords)
+ foreach {kw reflist} $b(keywords) {
+ if {![info exists k($kw)]} { set k($kw) {} }
+ foreach r $reflist { lappend k($kw) }
+ }
+
+ # Merge references... Here we may have conflicting
+ # declarations for the same id.
+
+ array set r $a(references)
+ foreach {rid rdecl} $b(references) {
+ if {[info exists r($rid)]} {
+ if {$r($rid) ne $rdecl} {
+ return -code error [format $ourmergeerr $r($rid) $rdecl $rid]
+ }
+ continue
+ }
+ set r($rid) $decl
+ }
+
+ # Now construct the result, from the inside out, with proper
+ # sorting at all levels.
+
+ set keywords {}
+ foreach kw [lsort -dict [array names k]] {
+ # Sort references in a keyword by their _labels_.
+ set tmp {}
+ foreach rid $k($kw) { lappend tmp [list $rid [lindex $r($rid) 1]] }
+ set refs {}
+ foreach item [lsort -dict -index 1 $tmp] {
+ lappend refs [lindex $item 0]
+ }
+ lappend keywords $kw $refs
+ }
+
+ set references {}
+ foreach rid [lsort -dict [array names r]] {
+ lappend references $rid $r($rid)
+ }
+
+ set serial [list doctools::idx \
+ [list \
+ label $b(label) \
+ keywords $keywords \
+ references $references \
+ title $b(title)]]
+
+ # Caller has to verify, ensure contract.
+ #$type verify-as-canonical $serial
+ return $serial
+ }
+
+ # Converts an index serialization into a human readable string for
+ # test results. It assumes that the serialization is at least
+ # structurally sound.
+
+ typemethod print {serial} {
+ array set i $serial
+ array set i $i(doctools::idx)
+ array set r $i(references)
+ set lines {}
+ lappend lines [list doctools::idx $i(label) $i(title)]
+ foreach {key reflist} $i(keywords) {
+ lappend lines ....$key
+ foreach ref $reflist {
+ lappend lines ........[linsert $r($ref) end $ref]
+ }
+ }
+ return [join $lines \n]
+ }
+
+ # # ## ### ##### ######## #############
+
+ typevariable ourcode doctools::idx
+ typevariable ourprefix {error in serialization:}
+ # # Test cases (doctools-idx-structure-)
+ typevariable ourshort { dictionary too short, expected exactly one key} ; # 6.0
+ typevariable ourtag { bad type tag "%s"} ; # 6.1
+ typevariable ourcshort { dictionary too short, expected exactly four keys} ; # 6.2
+ typevariable ourmiss { missing expected key "%s"} ; # 6.3, 6.4, 6.5, 6.6
+ typevariable ourkdup { duplicate keywords} ; # 6.8
+ typevariable ourrshort { reference list wrong, need exactly 2} ; # 6.12
+ typevariable ourrtag { bad reference tag "%s"} ; # 6.13
+ typevariable ourrdup { duplicate reference identifiers} ; # 6.14
+ typevariable ourrmismatch { use and declaration of references not matching} ; # 6.10, 6.11
+ # Message for non-canonical serialization when expecting canonical form
+ typevariable ourdupsort { duplicate and/or unsorted keywords/identifiers} ; # 6.7, 6.9, 6.15
+
+ typevariable ourmergeerr {Mismatching declarations '%s' vs. '%s' for '%s'}
+
+ # # ## ### ##### ######## #############
+ ## Configuration
+
+ pragma -hasinstances no ; # singleton
+ pragma -hastypeinfo no ; # no introspection
+ pragma -hastypedestroy no ; # immortal
+
+ ##
+ # # ## ### ##### ######## #############
+}
+
+# # ## ### ##### ######## ############# #####################
+## Ready
+
+package provide doctools::idx::structure 0.1
+return