source: trunk/gui/scripts/tuples.tcl @ 2414

Last change on this file since 2414 was 1929, checked in by gah, 14 years ago
File size: 14.8 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: tuples - represents a series of tuples for arbitrary data
3#
4#  This object represents a series of tuples.  Each tuple can contain
5#  one or more elements--for example, (a) or (a,b,c).  Each column
6#  in the tuple has a well-defined name and metadata.  Columns can
7#  be added even after data has been stored in the tuple list.
8# ======================================================================
9#  AUTHOR:  Michael McLennan, Purdue University
10#  Copyright (c) 2004-2005  Purdue Research Foundation
11#
12#  See the file "license.terms" for information on usage and
13#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14# ======================================================================
15package require Itcl
16
17itcl::class Rappture::Tuples {
18    constructor {args} { # defined below }
19
20    public method column {option args}
21    public method insert {pos args}
22    public method delete {{from ""} {to ""}}
23    public method put {args}
24    public method get {args}
25    public method find {args}
26    public method size {}
27
28    protected method _range {{from ""} {to ""}}
29
30    private variable _colnames ""  ;# list of column names
31    private variable _col2info     ;# maps column name => column options
32    private variable _tuples       ;# maps index => tuple data
33
34    private common _counter 0      ;# for auto-generated column names
35}
36                                                                               
37# ----------------------------------------------------------------------
38# CONSTRUCTOR
39# ----------------------------------------------------------------------
40itcl::body Rappture::Tuples::constructor {args} {
41    eval configure $args
42}
43
44# ----------------------------------------------------------------------
45# USAGE: column insert <pos> ?-name n? ?-label l? ?-default v?
46# USAGE: column delete ?<name> <name>...?
47# USAGE: column names ?<pattern>?
48# USAGE: column info <name>
49#
50# Used by clients to manipulate the columns associated with this
51# list of tuples.  Each column is identified by a short name.  If
52# a name is not supplied when the column is created, then one is
53# generated automatically.  The column names can be queried back
54# in the order they appear in a tuple by using the "column names"
55# command.
56# ----------------------------------------------------------------------
57itcl::body Rappture::Tuples::column {option args} {
58    switch -- $option {
59        insert {
60            # parse the incoming args
61            if {[llength $args] < 1} {
62                error "wrong # args: should be \"column insert pos ?-name n? ?-label l? ?-default v?\""
63            }
64            set pos [lindex $args 0]
65            set args [lrange $args 1 end]
66            Rappture::getopts args params {
67                value -name #auto
68                value -label ""
69                value -default ""
70            }
71            if {[llength $args] != 0} {
72                error "wrong # args: should be \"column insert pos ?-name n? ?-label l? ?-default v?\""
73            }
74
75            # insert the new column
76            set cname $params(-name)
77            if {$params(-name) == "#auto"} {
78                set cname "column[incr _counter]"
79            }
80            if {[lsearch -exact $_colnames $cname] >= 0} {
81                error "column name \"$cname\" already exists"
82            }
83            set _colnames [linsert $_colnames $pos $cname]
84            set _col2info($cname-label) $params(-label)
85            set _col2info($cname-default) $params(-default)
86
87            # run through all existing tuples and insert the default val
88            set max [array size _tuples]
89            for {set i 0} {$i < $max} {incr i} {
90                set oldval $_tuples($i)
91                set _tuples($i) [linsert $oldval $pos $params(-default)]
92            }
93        }
94        delete {
95            foreach cname $args {
96                set pos [lsearch -exact $_colnames $cname]
97                if {$pos < 0} {
98                    error "bad column name \"$cname\""
99                }
100                set _colnames [lreplace $_colnames $pos $pos]
101                unset _col2info($cname-label)
102                unset _col2info($cname-default)
103
104                # run through all existing tuples and delete the column
105                set max [array size _tuples]
106                for {set i 0} {$i < $max} {incr i} {
107                    set oldval $_tuples($i)
108                    set _tuples($i) [lreplace $oldval $pos $pos]
109                }
110            }
111        }
112        names {
113            if {[llength $args] == 0} {
114                return $_colnames
115            } elseif {[llength $args] == 1} {
116                set pattern [lindex $args 0]
117                set rlist ""
118                foreach cname $_colnames {
119                    if {[string match $pattern $cname]} {
120                        lappend rlist $cname
121                    }
122                }
123                return $rlist
124            } else {
125                error "wrong # args: should be \"column names ?pattern?\""
126            }
127        }
128        info {
129            if {[llength $args] != 1} {
130                error "wrong # args: should be \"column info name\""
131            }
132            set cname [lindex $args 0]
133            set pos [lsearch -exact $_colnames $cname]
134            if {$pos < 0} {
135                error "bad column name \"$cname\""
136            }
137            return [list -label $_col2info($cname-label) -default $_col2info($cname-default)]
138        }
139        default {
140            error "bad option \"$option\": should be delete, info, insert, names"
141        }
142    }
143}
144
145# ----------------------------------------------------------------------
146# USAGE: insert <pos> ?<tuple> <tuple> ...?
147#
148# Used by clients to insert one or more tuples into this list at
149# the given position <pos>.  Each <tuple> is a Tcl list of values
150# in order corresponding to the column names.
151# ----------------------------------------------------------------------
152itcl::body Rappture::Tuples::insert {pos args} {
153    set cols [llength $_colnames]
154    set max [array size _tuples]
155
156    if {"end" == $pos} {
157        set pos $max
158    } elseif {![string is integer $pos]} {
159        error "bad position \"$pos\": should be integer or \"end\""
160    } elseif {$pos < 0} {
161        set pos 0
162    } elseif {$pos > $max} {
163        set pos $max
164    }
165
166    # make some room to insert these tuples
167    set need [llength $args]
168    for {set i [expr {$max-1}]} {$i >= $pos} {incr i -1} {
169        set _tuples([expr {$i+$need}]) $_tuples($i)
170    }
171
172    # add the tuples at the specified pos
173    foreach t $args {
174        # make sure each tuple has enough columns
175        while {[llength $t] < $cols} {
176            lappend t ""
177        }
178        set _tuples($pos) $t
179        incr pos
180    }
181}
182
183# ----------------------------------------------------------------------
184# USAGE: delete ?<from>? ?<to>?
185#
186# Used by clients to delete one or more tuples in this list.  With
187# no args, it deletes all tuples.  With a single <from> arg, it deletes
188# the tuple at that number.  With both args, it deletes tuples in the
189# specified range.
190# ----------------------------------------------------------------------
191itcl::body Rappture::Tuples::delete {{from ""} {to ""}} {
192    if {"" == $from && "" == $to} {
193        catch {unset _tuples}
194        return
195    }
196    if {[array size _tuples] == 0} {
197        return  ;# nothing to delete
198    }
199
200    set last [expr {[array size _tuples]-1}]
201    foreach {from to} [_range $from $to] break
202
203    # delete all tuples in the specified range
204    set gap [expr {$to-$from+1}]
205    for {set i $from} {$i <= $last-$gap} {incr i} {
206        set _tuples($i) $_tuples([expr {$i+$gap}])
207    }
208    for {} {$i <= $last} {incr i} {
209        unset _tuples($i)
210    }
211}
212
213# ----------------------------------------------------------------------
214# USAGE: put ?-format <columns>? <pos> <tuple>
215#
216# Used by clients to store a different tuple value at the specified
217# position <pos> in this list.  Normally, it stores the entire
218# <tuple> at the specified slot, which must already exist.  (Use
219# insert to create new slots.)  If the -format option is specified,
220# then it interprets <tuple> according to the names in the -format,
221# and updates only specific columns at that slot.
222# ----------------------------------------------------------------------
223itcl::body Rappture::Tuples::put {args} {
224    Rappture::getopts args params {
225        value -format ""
226    }
227    if {[llength $args] != 2} {
228        error "wrong # args: should be \"put ?-format cols? pos tuple\""
229    }
230    foreach {pos tuple} $args break
231    foreach {pos dummy} [_range $pos ""] break  ;# fix index
232
233    if {![info exists _tuples($pos)]} {
234        error "index $pos doesn't exist"
235    }
236
237    if {[string length $params(-format)] == 0} {
238        # no format -- add tuple as-is (with proper number of columns)
239        set cols [llength $_colnames]
240        while {[llength $tuple] < $cols} {
241            lappend tuple ""
242        }
243        set _tuples($pos) $tuple
244    } else {
245        # convert column names to indices
246        set nlist ""
247        foreach cname $params(-format) {
248            set n [lsearch -exact $_colnames $cname]
249            if {$n < 0} {
250                error "bad column name \"$cname\""
251            }
252            lappend nlist $n
253        }
254
255        # convert data only for those indices
256        set val $_tuples($pos)
257        foreach n $nlist t $tuple {
258            set val [lreplace $val $n $n $t]
259        }
260        set _tuples($pos) $val
261    }
262}
263
264# ----------------------------------------------------------------------
265# USAGE: get ?-format <columns>? ?<from>? ?<to>?
266#
267# Used by clients to query data from this list of tuples.  Returns
268# a list of tuples in the specified range, or a list of all tuples
269# if the range is not specified.
270# ----------------------------------------------------------------------
271itcl::body Rappture::Tuples::get {args} {
272    Rappture::getopts args params {
273        value -format ""
274    }
275    if {[llength $args] > 2} {
276        error "wrong # args: should be \"get ?-format cols? ?from? ?to?\""
277    }
278    set from ""
279    set to ""
280    foreach {from to} $args break
281    foreach {from to} [_range $from $to] break
282
283    # empty? then return nothing
284    if {[array size _tuples] == 0} {
285        return ""
286    }
287
288    set rlist ""
289    if {[string length $params(-format)] == 0} {
290        # no format string -- return everything as-is
291        for {set i $from} {$i <= $to} {incr i} {
292            lappend rlist $_tuples($i)
293        }
294    } else {
295        # convert column names to indices
296        set nlist ""
297        foreach cname $params(-format) {
298            set n [lsearch -exact $_colnames $cname]
299            if {$n < 0} {
300                error "bad column name \"$cname\""
301            }
302            lappend nlist $n
303        }
304        set single [expr {[llength $nlist] == 1}]
305
306        # convert data only for those indices
307        for {set i $from} {$i <= $to} {incr i} {
308            set t ""
309            foreach n $nlist {
310                if {$single} {
311                    set t [lindex $_tuples($i) $n]
312                } else {
313                    lappend t [lindex $_tuples($i) $n]
314                }
315            }
316            lappend rlist $t
317        }
318    }
319    return $rlist
320}
321
322# ----------------------------------------------------------------------
323# USAGE: find ?-format <columns>? ?<tuple>?
324#
325# Used by clients to search for all or part of a <tuple> on the
326# list.  Without the extra -format option, this searches for an
327# exact match of the <tuple> and returns a list of indices that
328# match.  With the -format option, it checks the values for only
329# the specified <columns>, and again returns a list of indices
330# with matching values.
331# ----------------------------------------------------------------------
332itcl::body Rappture::Tuples::find {args} {
333    Rappture::getopts args params {
334        value -format ""
335    }
336    if {[llength $args] > 1} {
337        error "wrong # args: should be \"find ?-format cols? ?tuple?\""
338    }
339
340    # convert column names to indices
341    set nlist ""
342    foreach cname $params(-format) {
343        set n [lsearch -exact $_colnames $cname]
344        if {$n < 0} {
345            error "bad column name \"$cname\""
346        }
347        lappend nlist $n
348    }
349
350    # scan through all entries and find matching values
351    set rlist ""
352    set last [expr {[array size _tuples]-1}]
353    if {[llength $args] == 0} {
354        # no tuple? then all match
355        for {set i 0} {$i <= $last} {incr i} {
356            lappend rlist $i
357        }
358    } else {
359        set tuple [lindex $args 0]
360        if {[llength $nlist] == 0} {
361            # no format? then look for an exact match
362            for {set i 0} {$i <= $last} {incr i} {
363                if {[string equal $tuple $_tuples($i)]} {
364                    lappend rlist $i
365                }
366            }
367        } else {
368            # match only the columns in the -format
369            for {set i 0} {$i <= $last} {incr i} {
370                set matching 1
371                foreach n $nlist t $tuple {
372                    set val [lindex $_tuples($i) $n]
373                    if {![string equal $t $val]} {
374                        set matching 0
375                        break
376                    }
377                }
378                if {$matching} {
379                    lappend rlist $i
380                }
381            }
382        }
383    }
384    return $rlist
385}
386
387# ----------------------------------------------------------------------
388# USAGE: size
389#
390# Used by clients to determine the number of tuples stored on this
391# list.  Returns the size of the list.
392# ----------------------------------------------------------------------
393itcl::body Rappture::Tuples::size {} {
394    return [array size _tuples]
395}
396
397# ----------------------------------------------------------------------
398# USAGE: _range ?<from>? ?<to>?
399#
400# Used internally to convert a <from>/<to> range to a range of real
401# number values.  If both are "", then the range is the entire range
402# of data.  The <from> and <to> values can be integers or the keyword
403# "end".
404# ----------------------------------------------------------------------
405itcl::body Rappture::Tuples::_range {{from ""} {to ""}} {
406    set last [expr {[array size _tuples]-1}]
407    if {"" == $from && "" == $to} {
408        return [list 0 $last]
409    }
410
411    if {"end" == $from} {
412        set from $last
413    } elseif {![string is integer $from]} {
414        error "bad position \"$from\": should be integer or \"end\""
415    }
416    if {$from < 0} {
417        set from 0
418    } elseif {$from > $last} {
419        set from $last
420    }
421
422    if {"" == $to} {
423        set to $from
424    } elseif {"end" == $to} {
425        set to $last
426    } elseif {![string is integer $to]} {
427        error "bad position \"$to\": should be integer or \"end\""
428    }
429    if {$to < 0} {
430        set to 0
431    } elseif {$to > $last} {
432        set to $last
433    }
434
435    if {$from > $to} {
436        # make sure to/from are in proper order
437        set tmp $from
438        set from $to
439        set to $tmp
440    }
441    return [list $from $to]
442}
Note: See TracBrowser for help on using the repository browser.