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