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

Last change on this file since 3934 was 3330, checked in by gah, 11 years ago

merge (by hand) with Rappture1.2 branch

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