Fix too many ]s in try
[tcl-tlc-base.git] / scripts / datasource.itcl
blob5f4e865f7f517fd04e414e2cafee853c81cd99ed
1 # vim: ft=tcl foldmarker=<<<,>>>
3 # item_schema example: <<<
4 # variable item_schema {
5 # "Schema" {schema}
6 # "Table" {table combobox \
7 # -choices {sql_features sql_languages sql_packages} \
8 # -initial_choice sql_languages
9 # }
10 # "Owner" {owner}
11 # "Has Indexes" {indexed checkbutton}
12 # "Has Rules" {ruled checkbutton}
13 # "Has Triggers" {hastriggers checkbutton}
14 # }
15 # >>>
17 class tlc::Datasource {
18 inherit tlc::Handlers
20 constructor {args} {}
21 destructor {}
23 public {
24 variable criteria {}
25 variable criteria_values ""
26 variable quote 1
27 variable id_column 0 ;# column to use as the ID column
28 variable criteria_map {}
29 variable defaults {}
30 variable item_schema {}
32 method get_item_schema {}
33 method set_criteria_map {mapping}
34 method get_criteria {}
35 method set_criteria {arraylist}
36 method get_criteria_values {}
37 method set_criteria_values {arraylist}
38 method set_field_defs {arraylist}
39 method get_field_defs {}
40 method set_defaults {rowarray}
41 method get_defaults {}
43 method get_list {criteria {headersvar {}}}
44 method get_labelled_list {criteria {headersvar {}}}
45 method get_id_column {}
46 method add_item {row {col_list ""}}
47 method update_item {oldrow newrow {old_col_list ""} {new_col_list ""}}
48 method remove_item {row {col_list ""}}
49 method extract_id {row}
50 method get_full_row {id}
51 method can_do {action args}
53 method lookup {key match {mode -exact}}
54 method slice {column args}
55 method build_map {from_column args}
58 protected {
59 variable can_do
60 variable acriteria
61 variable acriteria_values
62 variable last_headers {}
64 method replace_criteria {str {criteria_arraylist {}}
65 {recursion_level 0}}
66 method resolve_row {row col_list}
67 method criteria_changed {}
72 body tlc::Datasource::constructor {args} { #<<<1
73 array set can_do {}
75 eval configure $args
79 body tlc::Datasource::destructor {} { #<<<1
80 invoke_handlers destroyed
84 body tlc::Datasource::replace_criteria {str {criteria_arraylist {}} {recursion_level 0}} { #<<<1
85 # TODO: figure out a "proper" way to allow substitutions, and still allow
86 # 1) %var% strign literals that match to pass through
87 # 2) a barrier against infinite recursion
89 set ret $str
90 if {$criteria_arraylist eq {}} {
91 set driving_force $criteria_values
92 } else {
93 set driving_force $criteria_arraylist
95 set map_directives {}
96 array set valmappings $criteria_map
97 foreach {idx val} $driving_force {
98 if {[info exists valmappings($idx)]} {
99 array set tmp $valmappings($idx)
100 if {[info exists tmp($val)]} {
101 lappend map_directives "%${idx}%" "$tmp($val)"
102 } else {
103 lappend map_directives "%${idx}%" "$val"
105 } else {
106 lappend map_directives "%${idx}%" "$val"
109 set ret [string map "$map_directives" "$str"]
110 # now, check for recursive re-inclusion of criteria within criteria only
111 foreach {idx val} $criteria_values {
112 if {[string first "%$idx%" $ret]>-1} {
113 if {$recursion_level<5} {
114 set ret [replace_criteria $ret $driving_force
115 [incr recursion_level]]
117 break
120 return $ret
124 body tlc::Datasource::get_criteria {} { #<<<1
125 # this basically gives the form defination give from the intersection of
126 # the Criteria value and the names of the field_defs array-var
128 return $criteria
132 body tlc::Datasource::get_criteria_values {} { #<<<1
133 return $criteria_values
137 body tlc::Datasource::set_criteria {arraylist} { #<<<1
138 # sets the criteria (and acriteria) from an array-style list. the list
139 # element style is: {criteria_label} {criteria_varname {form style}}
141 set criteria $arraylist
145 body tlc::Datasource::set_criteria_values {arraylist} { #<<<1
146 # sets the replacement values for the criteria tokenlist from an
147 # array-style list the list element style is:
148 # {criteria_varname} {variable_value}
150 set criteria_values $arraylist
154 body tlc::Datasource::set_field_defs {arraylist} { #<<<1
155 set field_defs $arraylist
158 body tlc::Datasource::get_field_defs {} { #<<<1
159 return $field_defs
162 body tlc::Datasource::resolve_row {row col_list} { #<<<1
163 # resolves a list of values (row) and a column name list (col_list) into an
164 # array-style list
165 # inputs: row (raw data list); col_list: column names for items in row
166 # returns: array-style list of the style: {col_name} {col_value}
168 foreach col $col_list val $row {
169 lappend ret $col $val
172 return $ret
176 body tlc::Datasource::get_labelled_list {criteria {headersvar {}}} { #<<<1
177 # does the same as a get_list, but each row contains interleaved
178 # header-names with each field; makes the loading of an array or a treeview
179 # structure a lot easier for the client
181 if {$headersvar != {}} {
182 upvar $headersvar headers
184 set rawlist [get_list $criteria headers]
185 set llist ""
186 foreach rawrow $rawlist {
187 set lrow ""
188 foreach rawcol $rawrow head $headers {
189 lappend lrow $head $rawcol
191 lappend llist $lrow
194 # puts "\n\nDatasource::get_labelled_list: returning: ($llist)"
195 return $llist
199 body tlc::Datasource::get_list {criteria {headersvar {}}} { #<<<1
203 body tlc::Datasource::get_id_column {} { #<<<1
204 return [list $id_column [lindex $last_headers $id_column]]
208 body tlc::Datasource::add_item {row {col_list {}}} { #<<<1
212 body tlc::Datasource::update_item {oldrow newrow {old_col_list ""} {new_col_list ""}} { #<<<1
216 body tlc::Datasource::remove_item {row {col_list ""}} { #<<<1
220 body tlc::Datasource::extract_id {row} { #<<<1
221 return [lindex $row $id_column]
225 body tlc::Datasource::criteria_changed {} { #<<<1
229 body tlc::Datasource::set_criteria_map {mapping} { #<<<1
230 set criteria_map $mapping
234 body tlc::Datasource::get_item_schema {} { #<<<1
235 return $item_schema
238 body tlc::Datasource::set_defaults {rowarray} { #<<<1
239 set defaults $rowarray
242 body tlc::Datasource::get_defaults {} { #<<<1
243 return $defaults
246 body tlc::Datasource::get_full_row {id} { #<<<1
247 # purpose: to return all fields defined in the filed definitions for the id specified -- to be used
248 # by a client who will be doing an update later
249 # returns: array-style list of {col} {val} {col} {val} ...
250 # this is to be implemented by the client
253 body tlc::Datasource::can_do {action args} { #<<<1
254 if {[llength $args] == 0} {
255 return [expr {[info exists can_do($action)] && $can_do($action)}]
256 } elseif {[llength $args] == 1} {
257 set can_do($action) [expr {[lindex $args 0]}]
258 } else {
259 error "Wrong # of args: must be action ?newvalue?"
264 body tlc::Datasource::lookup {key match {mode -exact}} { #<<<1
265 switch -- $mode {
266 -exact -
267 -glob -
268 -regexp {}
270 default {
271 error "Invalid match mode: \"$mode\", must be one of -exact, -glob or -regexp" "" [list bad_match_mode $mode]
275 set rows [get_list {} headers]
277 set idx [lsearch $headers $key]
278 if {$idx == -1} {
279 error "Invalid key: \"$key\", must be one of \"[join $headers {", "}]\""
282 if {[package vsatisfies $::tcl_version 8.5]} {
283 set matches [lsearch -all -inline $mode -index $idx $rows $match]
284 } else {
285 set matches {}
286 foreach row $rows {
287 switch $mode -- [lindex $row $idx] $match {
288 lappend matches $row
289 } default {
290 continue
295 set build {}
296 foreach row $matches {
297 set a {}
298 foreach h $headers v $row {
299 lappend a $h $v
301 lappend build $a
304 return $build
308 body tlc::Datasource::slice {column args} { #<<<1
309 # slice returns all instances of a column in the datasource, sorted
310 # to taste
312 set raw [get_list {} headers]
314 # Check that the specified column is valid <<<
315 if {[lsearch $headers $column] == -1} {
316 error "Invalid slice column \"$column\", should be one of ([join $headers {, }])" "" \
317 [list invalid_column $column]
319 # Check that the specified column is valid >>>
321 # Parse options <<<
322 set sortcolumn $column
323 set sortmode "dictionary"
324 set sortdir increasing
326 set remaining $args
327 while {[llength $remaining] > 0} {
328 set option [lindex $remaining 0]
329 set remaining [lrange $remaining 1 end]
331 if {[string index $option 0] != "-"} {
332 error "Expecting an option, got \"$option\"" "" [list syntax_error]
335 switch -- $option {
336 -orderby - -sort { #<<<
337 set sortcolumn [lindex $remaining 0]
338 set remaining [lrange $remaining 1 end]
340 if {[lsearch $headers $sortcolumn] == -1} {
341 error "Specified sort column ($sortcolumn) doesn't exist. Should be one of ([join $headers {, }])" "" \
342 [list invalid_sortcolumn $sortcolumn $headers]
345 if {[string index [lindex $remaining 0] 0] != "-"} {
346 set sortdir [lindex $remaining 0]
347 set remaining [lrange $remaining 1 end]
349 switch -- $sortdir {
350 asc - ascending - increasing {
351 set sortdir increasing
354 desc - descending - decreasing {
355 set sortdir decreasing
358 default {
359 error "Invalid sortdir specified: ($sortdir)" "" \
360 [list invalid_sortdir $sortdir]
364 #>>>
367 -sortmode { #<<<
368 set sortmode [lindex $remaining 0]
369 set remaining [lrange $sortmode 1 end]
371 switch -- $sortmode {
372 ascii - dictionary - integer - real {}
374 default {
375 error "Invalid sortmode: \"$sortmode\"" "" \
376 [list invalid_sortmode $sortmode]
379 #>>>
382 default {
383 error "Invalid option \"$option\"" "" \
384 [list invalid_option $option]
388 # Parse options >>>
390 set sort_col_idx [lsearch $headers $sortcolumn]
391 set slice_col_idx [lsearch $headers $column]
393 set build {}
394 foreach row [lsort -$sortmode -$sortdir -index $sort_col_idx $raw] {
395 lappend build [lindex $row $slice_col_idx]
398 return $build
402 body tlc::Datasource::build_map {from_column args} { #<<<1
403 set rows [get_list {} headers]
405 switch -- [llength $args] {
407 error "Must specify a target column format"
411 set format "%s"
412 set to_column_list [list [lindex $args 0]]
415 default {
416 set to_column_list [lassign $args format]
419 set from_idx [lsearch $headers $from_column]
420 if {$from_idx == -1} {
421 error "From column \"$from_column\" doesn't exist"
423 set to_idx_list {}
424 foreach col $to_column_list {
425 set to_idx [lsearch $headers $col]
426 if {$to_idx == -1} {
427 error "To column \"$col\" doesn't exist"
429 lappend to_idx_list $to_idx
432 set res [dict create]
433 foreach row $rows {
434 set to_cols {}
435 foreach idx $to_idx_list {
436 lappend to_cols [lindex $row $idx]
438 dict lappend res \
439 [lindex $row $from_idx] \
440 [format $format {*}$to_cols]
443 return $res