1#!/usr/bin/wish
2
3# This file provides many valuable data processing procedures like
4# lappend_unique, get_var, etc.
5
6
7proc lappend_unique { args } {
8
9  # Add the each entry to a list if and only if they do not already exist in
10  # the list.
11
12  # Description of argument(s):
13  # args                            The first argument should be the list
14  #                                 name.  All other arguments are items to be
15  #                                 added to the list.
16
17  set list_name [lindex $args 0]
18  # Remove first entry from args list.
19  set args [lreplace $args 0 0]
20
21  upvar 1 $list_name list
22
23  if { ! [info exists list] } { set list {} }
24
25  foreach arg $args {
26    if { [lsearch -exact $list "${arg}"] != -1 } { continue }
27    lappend list $arg
28  }
29
30}
31
32
33proc lsubtract {main_list_name removal_list} {
34  upvar $main_list_name ref_main_list
35
36  # Remove any entry from the main list that is contained in removal list.
37
38  # Description of argument(s):
39  # main_list_name                  The name of your master list.
40  # removal_list                    The list of items to be removed from
41  #                                 master list.
42
43  # For each element in the removal list, find the corresponding entry in the
44  # master list and remove it.
45  for {set removal_ix 0} {$removal_ix < [llength $removal_list ]}\
46      {incr removal_ix} {
47    set value [lindex $removal_list $removal_ix]
48    set master_ix [lsearch $ref_main_list $value]
49    set ref_main_list [lreplace $ref_main_list $master_ix $master_ix]
50  }
51
52}
53
54
55proc list_map { list expression } {
56
57  # Create and return a new list where each element of the new list is a
58  # result of running the given expression on the corresponding entry from the
59  # original list.
60
61  # Description of argument(s):
62  # list                            A list to be operated on.
63  # expression                      A command expression to be run on each
64  #                                 element in the list (e.g. '[string range
65  #                                 $x 1 end]').
66
67  foreach x $list {
68    set cmd_buf "lappend new_list ${expression}"
69    eval $cmd_buf
70  }
71
72  return $new_list
73
74}
75
76
77proc list_filter { list expression } {
78
79  # Create and return a new list consisting of all elements of the original
80  # list that do NOT pass the expression.
81
82  # Description of argument(s):
83  # list                            A list to be operated on.
84  # expression                      A command expression to be run on each
85  #                                 element in the list (e.g. 'regexp
86  #                                 -expanded {^[[:blank:]]*\#|^[[:blank:]]*$}
87  #                                 $x', 'string equal $x ""', etc.).
88
89  set new_list {}
90
91  foreach x $list {
92    set cmd_buf "set result \[${expression}\]"
93    eval $cmd_buf
94    if { ! $result } { lappend new_list $x }
95  }
96
97  return $new_list
98
99}
100
101
102proc list_filter_comments { list } {
103
104  # Filter comments from list and return new_list as a result.
105
106  # Description of argument(s):
107  # list                            A list to be operated on.
108
109  set comment_regexp {^[[:blank:]]*\#|^[[:blank:]]*$}
110
111  set new_list [list_filter $list "regexp -expanded {$comment_regexp} \$x"]
112
113  return $new_list
114
115}
116
117
118proc get_var { var_var { default ""} } {
119  upvar 1 $var_var var_ref
120
121  # Return the value of the variable expression or the value of default if
122  # the variable is not defined.
123
124  # Example use:
125  # set PATH [get_var ::env(PATH) "/usr/bin"]
126
127  # Description of argument(s):
128  # var_var                         The name of a variable (e.g.
129  #                                 "::env(NANOSECOND)" or "var1").
130  # default                         The default value to return if the
131  #                                 variable named in var_var does not exist.
132
133  expr { [info exists var_ref] ? [return $var_ref] : [return $default] }
134
135}
136
137
138proc set_var_default { var_name { default ""} } {
139  upvar 1 $var_name var_ref
140
141  # If the variable named in var_name is either blank or non-existent, set
142  # its value to the default.
143
144  # Example use:
145  # set_var_default indent 0
146
147  # Description of argument(s):
148  # var_name                        The name of a variable.
149  # default                         The default value to assign to the
150  #                                 variable if the variable named in var_name
151  #                                 is blank or non-existent.
152
153  if { ! ([info exists var_ref] && $var_ref != "") } {
154    set var_ref $default
155  }
156
157}
158
159
160proc split_path {path dir_path base_name} {
161  upvar $dir_path ref_dir_path
162  upvar $base_name ref_base_name
163
164  # Split a path into it's dir_path and base_name.  The dir_path variable
165  # will include a trailing slash.
166
167  # Description of argument(s):
168  # path                            The directory or file path.
169  # dir_path                        The variable to contain the resulting
170  #                                 directory path which will include a
171  #                                 trailing slash.
172  # base_name                       The variable to contain the resulting base
173  #                                 directory or file name.
174
175  set ref_dir_path "[file dirname ${path}]/"
176  set ref_base_name "[file tail $path]"
177
178}
179
180
181proc read_properties_file {parm_file_path} {
182
183  # Read properties files and return key/value pairs as a list.
184
185  # Description of argument(s):
186  # parm_file_path                  The path to the properties file.
187
188  # The properties file must have the following format:
189  # var_name=var_value
190  # Comment lines (those beginning with a "#") and blank lines are allowed
191  # and will be ignored.  Leading and trailing single or double quotes will be
192  # stripped from the value.  E.g.
193  # var1="This one"
194  # Quotes are stripped so the resulting value for var1 is:
195  # This one
196
197  # Suggestion: The caller can then process the result as an array or a
198  # dictionary.
199
200  # Example usage:
201
202  # array set properties [read_properties_file $file_path]
203  # print_var properties
204
205  # With the following result...
206
207  # properties:
208  #   properties(command):  string
209
210  # Or...
211
212  # set properties [read_properties_file $file_path]
213  # print_dict properties
214
215  # With the following result...
216
217  # properties:
218  #   properties[command]:  string
219
220  # Initialize properties array.
221
222  set properties [list]
223
224  # Read the entire file into a list, filtering comments out.
225  set file_descriptor [open $parm_file_path r]
226  set file_data [list_filter_comments [split [read $file_descriptor] "\n"]]
227  close $file_descriptor
228
229  foreach line $file_data {
230    # Split <var_name>=<var_value> into component parts.
231    set pair [split $line =]
232    lappend properties [lindex ${pair} 0]
233    lappend properties [string trim [lindex ${pair} 1] {"}]
234  }
235
236  return $properties
237
238}
239
240
241proc convert_array_key {key {convert_commands} {prefix ""} } {
242
243  # Convert the key according to the caller's convert_commands and return the
244  # result.
245
246  # This is designed as a helper procedure to be called by convert_array_keys.
247
248  # See convert_array_keys for description of arguments.
249
250  set new_key $key
251  foreach command $convert_commands {
252    if { $command == "prefix" } {
253      regsub -all "^$prefix" $new_key {} new_key
254      set new_key "$prefix$new_key"
255    } elseif { $command == "rm_prefix" } {
256      regsub -all "^$prefix" $new_key {} new_key
257      set new_key "$new_key"
258    }
259    if { $command == "upper" } {
260      set new_key [string toupper $new_key]
261    } elseif { $command == "lower" } {
262      set new_key [string tolower $new_key]
263    }
264  }
265
266  return $new_key
267
268}
269
270
271proc convert_array_keys {source_arr target_arr {convert_commands}\
272  {prefix ""} } {
273  upvar $source_arr source_arr_ref
274  upvar $target_arr target_arr_ref
275
276  # Convert the keys of source_arr according to the caller's convert_commands
277  # and put the resulting array in target_arr. If this procedure fails for any
278  # reason, it will return non-zero.
279
280  # Note that despite the name of this procedure, it will also work on a
281  # dictionary.  In other words, if source_arr is NOT an array, it will be
282  # processed as a dictionary and target_arr will be created as a dictionary
283  # as well.
284
285  # Description of argument(s):
286  # source_arr                      The source array that is to be converted.
287  # target_arr                      The target array that results from the
288  #                                 conversion.
289  # convert_commands                A list of custom commands that indicate
290  #                                 the type of conversion(s) the caller
291  #                                 wishes to see. Currently the accepted
292  #                                 values are as follows:
293  #   upper                         Convert key value to uppercase.
294  #   lower                         Convert key value to lowercase.
295  #   prefix                        Prepend prefix to the key, provided that
296  #                                 it does not already exist. If upper or
297  #                                 lower is included in convert_commands
298  #                                 list, the prefix will be converted to the
299  #                                 specified case as well.
300  #   rm_prefix                     Remove a prefix that is prepended,
301  #                                 provided that it exists.
302  # prefix                          The prefix to be used for "prefix" and
303  #                                 "rm_prefix" commands (see convert_commands
304  #                                 text above).
305
306  # Validate arguments.
307  if { [lsearch $convert_commands lower] != -1 } {
308    if { [lsearch $convert_commands upper] != -1 } {
309      return -code error "Cannot convert to both upper and lower cases."
310    }
311  }
312
313  if { [lsearch $convert_commands rm_prefix] != -1} {
314    if { [lsearch $convert_commands prefix] != -1} {
315      return -code error "Cannot add and remove a prefix."
316    }
317  }
318
319  if { [lsearch $convert_commands prefix] != -1 ||\
320       [lsearch $convert_commands rm_prefix] != -1 } {
321    if { [lsearch $convert_commands upper] != -1 } {
322      set prefix [string toupper $prefix]
323    } elseif { [lsearch $convert_commands lower] != -1 } {
324      set prefix [string tolower $prefix]
325    }
326  }
327
328  if { [array exists source_arr_ref] } {
329    # Initialize targ array.
330    array set target_arr_ref {}
331    # Walk the source array doing the conversion specified in convert_commands.
332    set search_token [array startsearch source_arr_ref]
333    while {[array anymore source_arr_ref $search_token]} {
334      set key [array nextelement source_arr_ref $search_token]
335      set value $source_arr_ref($key)
336
337      set new_key [convert_array_key $key $convert_commands $prefix]
338      set cmd_buf "set target_arr_ref($new_key) $value"
339      eval $cmd_buf
340    }
341    array donesearch source_arr_ref $search_token
342
343  } else {
344    # Initialize targ dictionary.
345    set target_arr_ref [list]
346    # Walk the source dictionary doing the conversion specified in
347    # convert_commands.
348    foreach {key value} $source_arr_ref {
349      set new_key [convert_array_key $key $convert_commands $prefix]
350      set cmd_buf "dict append target_arr_ref $new_key \$value"
351      eval $cmd_buf
352    }
353  }
354
355}
356
357
358proc expand_shell_string {buffer} {
359  upvar $buffer ref_buffer
360
361  # Call upon the shell to expand the string in "buffer", i.e. the shell will
362  # make substitutions for environment variables and glob expressions.
363
364  # Description of argument(s):
365  # buffer                          The buffer to be expanded.
366
367  # This is done to keep echo from interpreting all of the double quotes away.
368  regsub -all {\"} $ref_buffer "\\\"" ref_buffer
369
370  # Bash will compress extra space delimiters if you don't quote the string.
371  # So, we quote the argument to echo.
372  if {[catch {set ref_buffer [exec bash -c "echo \"$ref_buffer\""]} result]} {
373    puts stderr $result
374    exit 1
375  }
376
377}
378
379
380proc add_trailing_string { buffer { add_string "/" } } {
381  upvar $buffer ref_buffer
382
383  # Add the add string to the end of the buffer if and only if it doesn't
384  # already end with the add_string.
385
386  # Description of argument(s):
387  # buffer                          The buffer to be modified.
388  # add_string                      The string to conditionally append to the
389  #                                 buffer.
390
391  regsub -all "${add_string}$" $ref_buffer {} ref_buffer
392  set ref_buffer "${ref_buffer}${add_string}"
393
394}
395
396
397