blob: e6324c000c755bc951e93dc8acde1c7958d647ab [file] [log] [blame]
Michael Walsh9d41d462018-02-15 16:34:32 -06001#!/usr/bin/wish
2
Michael Walsh410b1782019-10-22 15:56:18 -05003# This file provides many valuable data processing procedures like lappend_unique, get_var, etc.
Michael Walsh9d41d462018-02-15 16:34:32 -06004
5
6proc lappend_unique { args } {
7
Michael Walsh410b1782019-10-22 15:56:18 -05008 # Add the each entry to a list if and only if they do not already exist in the list.
Michael Walsh9d41d462018-02-15 16:34:32 -06009
10 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050011 # args The first argument should be the list name. All other arguments are
12 # items to be added to the list.
Michael Walsh9d41d462018-02-15 16:34:32 -060013
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.
Michael Walsh410b1782019-10-22 15:56:18 -050037 # removal_list The list of items to be removed from master list.
Michael Walsh9d41d462018-02-15 16:34:32 -060038
Michael Walsh410b1782019-10-22 15:56:18 -050039 # For each element in the removal list, find the corresponding entry in the master list and remove it.
Michael Walsh9d41d462018-02-15 16:34:32 -060040 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
Michael Walsh410b1782019-10-22 15:56:18 -050052 # 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.
Michael Walsh9d41d462018-02-15 16:34:32 -060054
55 # Description of argument(s):
56 # list A list to be operated on.
Michael Walsh410b1782019-10-22 15:56:18 -050057 # expression A command expression to be run on each element in the list (e.g. '[string
58 # range $x 1 end]').
Michael Walsh9d41d462018-02-15 16:34:32 -060059
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
Michael Walsh410b1782019-10-22 15:56:18 -050072 # Create and return a new list consisting of all elements of the original list that do NOT pass the
73 # expression.
Michael Walsh9d41d462018-02-15 16:34:32 -060074
75 # Description of argument(s):
76 # list A list to be operated on.
Michael Walsh410b1782019-10-22 15:56:18 -050077 # 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.).
Michael Walsh9d41d462018-02-15 16:34:32 -060080
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
Michael Walsh410b1782019-10-22 15:56:18 -0500113 # Return the value of the variable expression or the value of default if the variable is not defined.
Michael Walsh9d41d462018-02-15 16:34:32 -0600114
115 # Example use:
116 # set PATH [get_var ::env(PATH) "/usr/bin"]
117
118 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500119 # 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.
Michael Walsh9d41d462018-02-15 16:34:32 -0600122
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
Michael Walsh410b1782019-10-22 15:56:18 -0500131 # If the variable named in var_name is either blank or non-existent, set its value to the default.
Michael Walsh9d41d462018-02-15 16:34:32 -0600132
133 # Example use:
134 # set_var_default indent 0
135
136 # Description of argument(s):
137 # var_name The name of a variable.
Michael Walsh410b1782019-10-22 15:56:18 -0500138 # default The default value to assign to the variable if the variable named in
139 # var_name is blank or non-existent.
Michael Walsh9d41d462018-02-15 16:34:32 -0600140
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
Michael Walsh410b1782019-10-22 15:56:18 -0500152 # Split a path into it's dir_path and base_name. The dir_path variable will include a trailing slash.
Michael Walsh9d41d462018-02-15 16:34:32 -0600153
154 # Description of argument(s):
155 # path The directory or file path.
Michael Walsh410b1782019-10-22 15:56:18 -0500156 # dir_path The variable to contain the resulting directory path which will include a
Michael Walsh9d41d462018-02-15 16:34:32 -0600157 # trailing slash.
Michael Walsh410b1782019-10-22 15:56:18 -0500158 # base_name The variable to contain the resulting base directory or file name.
Michael Walsh9d41d462018-02-15 16:34:32 -0600159
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
Gunnar Mills948e2e22018-03-23 12:54:27 -0500170 # Description of argument(s):
Michael Walsh9d41d462018-02-15 16:34:32 -0600171 # parm_file_path The path to the properties file.
172
173 # The properties file must have the following format:
174 # var_name=var_value
Michael Walsh410b1782019-10-22 15:56:18 -0500175 # 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.
Michael Walsh9d41d462018-02-15 16:34:32 -0600177 # var1="This one"
178 # Quotes are stripped so the resulting value for var1 is:
179 # This one
180
Michael Walsh410b1782019-10-22 15:56:18 -0500181 # Suggestion: The caller can then process the result as an array or a dictionary.
Michael Walsh9d41d462018-02-15 16:34:32 -0600182
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
Michael Walsh58f9a512018-07-18 10:33:15 -0500224proc convert_array_key {key {convert_commands} {prefix ""} } {
225
Michael Walsh410b1782019-10-22 15:56:18 -0500226 # Convert the key according to the caller's convert_commands and return the result.
Michael Walsh58f9a512018-07-18 10:33:15 -0500227
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
Michael Walsh9d41d462018-02-15 16:34:32 -0600253proc 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
Michael Walsh410b1782019-10-22 15:56:18 -0500258 # 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.
Michael Walsh58f9a512018-07-18 10:33:15 -0500260
Michael Walsh410b1782019-10-22 15:56:18 -0500261 # 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.
Michael Walsh9d41d462018-02-15 16:34:32 -0600264
Gunnar Mills948e2e22018-03-23 12:54:27 -0500265 # Description of argument(s):
Michael Walsh9d41d462018-02-15 16:34:32 -0600266 # source_arr The source array that is to be converted.
Michael Walsh410b1782019-10-22 15:56:18 -0500267 # 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).
Michael Walsh9d41d462018-02-15 16:34:32 -0600278
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
Michael Walsh58f9a512018-07-18 10:33:15 -0500301 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)
Michael Walsh9d41d462018-02-15 16:34:32 -0600309
Michael Walsh58f9a512018-07-18 10:33:15 -0500310 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
Michael Walsh9d41d462018-02-15 16:34:32 -0600313 }
Michael Walsh58f9a512018-07-18 10:33:15 -0500314 array donesearch source_arr_ref $search_token
315
316 } else {
317 # Initialize targ dictionary.
318 set target_arr_ref [list]
Michael Walsh410b1782019-10-22 15:56:18 -0500319 # Walk the source dictionary doing the conversion specified in convert_commands.
Michael Walsh58f9a512018-07-18 10:33:15 -0500320 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 }
Michael Walsh9d41d462018-02-15 16:34:32 -0600325 }
Michael Walsh9d41d462018-02-15 16:34:32 -0600326
327}
328
329
330proc expand_shell_string {buffer} {
331 upvar $buffer ref_buffer
332
Michael Walsh410b1782019-10-22 15:56:18 -0500333 # Call upon the shell to expand the string in "buffer", i.e. the shell will make substitutions for
334 # environment variables and glob expressions.
Michael Walsh9d41d462018-02-15 16:34:32 -0600335
Gunnar Mills948e2e22018-03-23 12:54:27 -0500336 # Description of argument(s):
Michael Walsh9d41d462018-02-15 16:34:32 -0600337 # 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
Michael Walsh410b1782019-10-22 15:56:18 -0500342 # Bash will compress extra space delimiters if you don't quote the string. So, we quote the argument to
343 # echo.
Michael Walsh9d41d462018-02-15 16:34:32 -0600344 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
Michael Walsh410b1782019-10-22 15:56:18 -0500355 # Add the add string to the end of the buffer if and only if it doesn't already end with the add_string.
Michael Walsh9d41d462018-02-15 16:34:32 -0600356
Gunnar Mills948e2e22018-03-23 12:54:27 -0500357 # Description of argument(s):
Michael Walsh9d41d462018-02-15 16:34:32 -0600358 # buffer The buffer to be modified.
Michael Walsh410b1782019-10-22 15:56:18 -0500359 # add_string The string to conditionally append to the buffer.
Michael Walsh9d41d462018-02-15 16:34:32 -0600360
361 regsub -all "${add_string}$" $ref_buffer {} ref_buffer
362 set ref_buffer "${ref_buffer}${add_string}"
363
364}
365
366