blob: 39590886fc5a3753557168e2e2bde7ef2f00866f [file] [log] [blame]
Michael Walsh37c74f72018-02-15 17:12:25 -06001#!/usr/bin/wish
2
Michael Walsh410b1782019-10-22 15:56:18 -05003# This file provides many valuable print procedures such as sprint_var, sprint_time, sprint_error, etc.
Michael Walsh37c74f72018-02-15 17:12:25 -06004
5my_source [list data_proc.tcl call_stack.tcl]
6
7# Need "Expect" package for trap procedure.
8package require Expect
9
10
Michael Walsh410b1782019-10-22 15:56:18 -050011# Setting the following variables for use both inside this file and by programs sourcing this file.
Michael Walsh37c74f72018-02-15 17:12:25 -060012set program_path $argv0
13set program_dir_path "[file dirname $argv0]/"
14set program_name "[file tail $argv0]"
Michael Walsh410b1782019-10-22 15:56:18 -050015# Some procedures (e.g. sprint_pgm_header) need a program name value that looks more like a valid variable
16# name. Therefore, we'll swap out odd characters (like ".") for underscores.
Michael Walsh37c74f72018-02-15 17:12:25 -060017regsub {\.} $program_name "_" pgm_name_var_name
18
19# Initialize some time variables used in procedures in this file.
20set start_time [clock microseconds]
21
22
23proc calc_wrap_stack_ix_adjust {} {
24
Michael Walsh410b1782019-10-22 15:56:18 -050025 # Calculate and return a number which can be used as an offset into the call stack for wrapper procedures.
Michael Walsh37c74f72018-02-15 17:12:25 -060026
Michael Walsh410b1782019-10-22 15:56:18 -050027 # NOTE: This procedure is designed expressly to work with this file's print procedures scheme (i.e.
28 # print_x is a wrapper for sprint_x, etc.). In other words, this procedure may not be well-suited for
29 # general use.
Michael Walsh37c74f72018-02-15 17:12:25 -060030
Michael Walsh410b1782019-10-22 15:56:18 -050031 # Get a list of the procedures in the call stack beginning with our immediate caller on up to the
32 # top-level caller.
Michael Walsh37c74f72018-02-15 17:12:25 -060033 set call_stack [get_call_stack -2]
34
35 # The first stack entry is our immediate caller.
36 set caller [lindex $call_stack 0]
37 # Remove first entry from stack.
38 set call_stack [lreplace $call_stack 0 0]
Michael Walsh410b1782019-10-22 15:56:18 -050039 # Strip any leading "s" to arrive at base_caller name (e.g. the corresponding base name for "sprint_var"
40 # would be "print_var").
Michael Walsh37c74f72018-02-15 17:12:25 -060041 set base_caller [string trimleft $caller s]
Michael Walsh410b1782019-10-22 15:56:18 -050042 # Account for alias print procedures which have "p" vs "print_" (e.g. pvar vs print_var).
Michael Walsh37c74f72018-02-15 17:12:25 -060043 regsub "print_" $base_caller "p" alias_base_caller
44
45 # Initialize the stack_ix_adjust value.
46 set stack_ix_adjust 0
Michael Walsh410b1782019-10-22 15:56:18 -050047 # Note: print_vars|pvars is a special case so we add it explicitly to the regex below.
Michael Walsh37c74f72018-02-15 17:12:25 -060048 set regex ".*(${base_caller}|${alias_base_caller}|print_vars|pvars)$"
49 foreach proc_name $call_stack {
Michael Walsh410b1782019-10-22 15:56:18 -050050 # For every remaining stack item that looks like a wrapper (i.e. matches our regex), we increment the
51 # stack_ix_adjust.
Michael Walsh37c74f72018-02-15 17:12:25 -060052 if { [regexp -expanded $regex $proc_name]} {
53 incr stack_ix_adjust
54 continue
55 }
56 # If there is no match, then we are done.
57 break
58 }
59
60 return $stack_ix_adjust
61
62}
63
64
Michael Walsh410b1782019-10-22 15:56:18 -050065# hidden_text is a list of passwords which are to be replaced with asterisks by print procedures defined in
66# this file.
Michael Walsh37c74f72018-02-15 17:12:25 -060067set hidden_text [list]
68# password_regex is created from the contents of the hidden_text list above.
69set password_regex ""
70
71proc register_passwords {args} {
72
Michael Walsh410b1782019-10-22 15:56:18 -050073 # Register one or more passwords which are to be hidden in output produced by the print procedures in this
74 # file.
Michael Walsh37c74f72018-02-15 17:12:25 -060075
76 # Note: Blank password values are NOT registered. They are simply ignored.
77
78 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050079 # args One or more password values. If a given password value is already
80 # registered, this procedure will simply ignore it, i.e. there will be no
81 # duplicate values in the hidden_text list.
Michael Walsh37c74f72018-02-15 17:12:25 -060082
83 global hidden_text
84 global password_regex
85
86 foreach password $args {
87 # Skip blank passwords.
88 if { $password == "" } { continue }
89 # Skip already-registered passwords.
90 if { [lsearch -exact $hidden_text $password] != -1 } { continue }
91 # Put the password into the global hidden_text list.
92 lappend hidden_text $password
93 }
94
Gunnar Millsacc7c562019-08-20 13:12:46 -050095 # TODO: Escape metachars in the password_regex.
Michael Walsh37c74f72018-02-15 17:12:25 -060096 set password_regex [join $hidden_text |]
97
98}
99
100
101proc replace_passwords {buffer} {
102
Michael Walsh410b1782019-10-22 15:56:18 -0500103 # Replace all registered password found in buffer with a string of asterisks and return the result.
Michael Walsh37c74f72018-02-15 17:12:25 -0600104
105 # Description of argument(s):
106 # buffer The string to be altered and returned.
107
Michael Walsh410b1782019-10-22 15:56:18 -0500108 # Note: If environment variable GEN_PRINT_DEBUG is set, this procedure will do nothing.
Michael Walsh37c74f72018-02-15 17:12:25 -0600109
110 global env
111 if { [get_var ::env(GEN_PRINT_DEBUG) 0] } { return $buffer }
Michael Walshb21aec12018-03-02 12:00:22 -0600112 if { [get_var ::env(DEBUG_SHOW_PASSWORDS) 0] } { return $buffer }
Michael Walsh37c74f72018-02-15 17:12:25 -0600113
114 global password_regex
115
116 # No passwords to replace?
117 if { $password_regex == "" } { return $buffer }
118
119 regsub -all "${password_regex}" $buffer {********} buffer
120 return $buffer
121
122}
123
124
125proc my_time { cmd_buf { iterations 100 } } {
126
127 # Run the "time" function on the given command string and print the results.
128
129 # The main benefit of running this vs just doing the "time" command directly:
130 # - This will print the results.
131
132 # Description of argument(s):
133 # cmd_buf The command string to be run.
Michael Walsh410b1782019-10-22 15:56:18 -0500134 # iterations The number of times to run the command string. Typically, more
135 # iterations yields more accurate results.
Michael Walsh37c74f72018-02-15 17:12:25 -0600136
137 print_issuing $cmd_buf
138 set result [time {uplevel 1 $cmd_buf} $iterations]
139
140 set raw_microseconds [lindex [split [lindex $result 0] .] 0]
141 set seconds [expr $raw_microseconds / 1000000]
142 set raw_microseconds [expr $raw_microseconds % 1000000]
143
144 set seconds_per_iteration [format "%i.%06i" ${seconds}\
145 ${raw_microseconds}]
146
147 print_var seconds_per_iteration
148
149}
150
151
Michael Walsh410b1782019-10-22 15:56:18 -0500152# If environment variable "GEN_PRINT_DEBUG" is set, this module will output debug data. This is primarily
153# intended for the developer of this module.
Michael Walsh37c74f72018-02-15 17:12:25 -0600154set GEN_PRINT_DEBUG [get_var ::env(GEN_PRINT_DEBUG) 0]
155
Michael Walsh410b1782019-10-22 15:56:18 -0500156# The user can set the following environment variables to influence the output from print_time and print_var
157# procedures. See the prologs of those procedures for details.
Michael Walsh37c74f72018-02-15 17:12:25 -0600158set NANOSECONDS [get_var ::env(NANOSECONDS) 0]
159set SHOW_ELAPSED_TIME [get_var ::env(SHOW_ELAPSED_TIME) 0]
160
Michael Walsh410b1782019-10-22 15:56:18 -0500161# _gtp_default_print_var_width_ is adjusted based on NANOSECONDS and SHOW_ELAPSED_TIME.
Michael Walsh37c74f72018-02-15 17:12:25 -0600162if { $NANOSECONDS } {
163 set _gtp_default_print_var_width_ 36
164 set width_incr 14
165} else {
166 set _gtp_default_print_var_width_ 29
167 set width_incr 7
168}
169if { $SHOW_ELAPSED_TIME } {
170 incr _gtp_default_print_var_width_ $width_incr
Michael Walsh410b1782019-10-22 15:56:18 -0500171 # Initializing _sprint_time_last_seconds_ which is a global value to remember the clock seconds from the
172 # last time sprint_time was called.
Michael Walsh37c74f72018-02-15 17:12:25 -0600173 set _gtp_sprint_time_last_micro_seconds_ [clock microseconds]
174}
Michael Walsh410b1782019-10-22 15:56:18 -0500175# tcl_precision is a built-in Tcl variable that specifies the number of digits to generate when converting
176# floating-point values to strings.
Michael Walsh37c74f72018-02-15 17:12:25 -0600177set tcl_precision 17
178
179
180proc sprint { { buffer {} } } {
181
182 # Simply return the user's buffer.
Michael Walsh410b1782019-10-22 15:56:18 -0500183 # This procedure is used by the qprint and dprint functions defined dynamically below, i.e. it would not
184 # normally be called for general use.
Michael Walsh37c74f72018-02-15 17:12:25 -0600185
186 # Description of arguments.
187 # buffer This will be returned to the caller.
188
189 return $buffer
190
191}
192
193
194proc sprintn { { buffer {} } } {
195
196 # Simply return the user's buffer plus a trailing line feed..
Michael Walsh410b1782019-10-22 15:56:18 -0500197 # This procedure is used by the qprintn and dprintn functions defined dynamically below, i.e. it would not
198 # normally be called for general use.
Michael Walsh37c74f72018-02-15 17:12:25 -0600199
200 # Description of arguments.
201 # buffer This will be returned to the caller.
202
203 return ${buffer}\n
204
205}
206
207
208proc sprint_time { { buffer {} } } {
209
210 # Return the time in a formatted manner as described below.
211
212 # Example:
213
214 # The following tcl code...
215
216 # puts -nonewline [sprint_time()]
217 # puts -nonewline ["Hi.\n"]
218
219 # Will result in the following type of output:
220
221 # #(CDT) 2016/07/08 15:25:35 - Hi.
222
223 # Example:
224
225 # The following tcl code...
226
227 # puts -nonewline [sprint_time("Hi.\n")]
228
229 # Will result in the following type of output:
230
231 # #(CDT) 2016/08/03 17:12:05 - Hi.
232
Michael Walsh410b1782019-10-22 15:56:18 -0500233 # The following environment variables will affect the formatting as described:
234 # NANOSECONDS This will cause the time stamps to be precise to the microsecond (Yes, it
235 # probably should have been named MICROSECONDS but the convention was set
236 # long ago so we're sticking with it). Example of the output when
237 # environment variable NANOSECONDS=1.
Michael Walsh37c74f72018-02-15 17:12:25 -0600238
239 # #(CDT) 2016/08/03 17:16:25.510469 - Hi.
240
Michael Walsh410b1782019-10-22 15:56:18 -0500241 # SHOW_ELAPSED_TIME This will cause the elapsed time to be included in the output. This is
242 # the amount of time that has elapsed since the last time this procedure
243 # was called. The precision of the elapsed time field is also affected by
244 # the value of the NANOSECONDS environment variable. Example of the output
245 # when environment variable NANOSECONDS=0 and SHOW_ELAPSED_TIME=1.
Michael Walsh37c74f72018-02-15 17:12:25 -0600246
247 # #(CDT) 2016/08/03 17:17:40 - 0 - Hi.
248
Michael Walsh410b1782019-10-22 15:56:18 -0500249 # Example of the output when environment variable NANOSECONDS=1 and SHOW_ELAPSED_TIME=1.
Michael Walsh37c74f72018-02-15 17:12:25 -0600250
251 # #(CDT) 2016/08/03 17:18:47.317339 - 0.000046 - Hi.
252
253 # Description of argument(s).
Michael Walsh410b1782019-10-22 15:56:18 -0500254 # buffer A string string whhich is to be appended to the formatted time string and
255 # returned.
Michael Walsh37c74f72018-02-15 17:12:25 -0600256
257 global NANOSECONDS
258 global _gtp_sprint_time_last_micro_seconds_
259 global SHOW_ELAPSED_TIME
260
261 # Get micro seconds since the epoch.
262 set epoch_micro [clock microseconds]
263 # Break the left and right of the decimal point.
264 set epoch_seconds [expr $epoch_micro / 1000000]
265 set epoch_decimal_micro [expr $epoch_micro % 1000000]
266
267 set format_string "#(%Z) %Y/%m/%d %H:%M:%S"
268 set return_string [clock format $epoch_seconds -format\
269 "#(%Z) %Y/%m/%d %H:%M:%S"]
270
271 if { $NANOSECONDS } {
272 append return_string ".[format "%06i" ${epoch_decimal_micro}]"
273 }
274
275 if { $SHOW_ELAPSED_TIME } {
276 set return_string "${return_string} - "
277
278 set elapsed_micro [expr $epoch_micro - \
279 $_gtp_sprint_time_last_micro_seconds_]
280 set elapsed_seconds [expr $elapsed_micro / 1000000]
281
282 if { $NANOSECONDS } {
283 set elapsed_decimal_micro [expr $elapsed_micro % 1000000]
284 set elapsed_float [format "%i.%06i" ${elapsed_seconds}\
285 ${elapsed_decimal_micro}]
286 set elapsed_time_buffer "[format "%11.6f" ${elapsed_float}]"
287 } else {
288 set elapsed_time_buffer "[format "%4i" $elapsed_seconds]"
289 }
290 set return_string "${return_string}${elapsed_time_buffer}"
291 }
292
293 set return_string "${return_string} - ${buffer}"
294
295 set _gtp_sprint_time_last_micro_seconds_ $epoch_micro
296
297 return $return_string
298
299}
300
301
302proc sprint_timen { args } {
303
304 # Return the value of sprint_time + a line feed.
305
306 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500307 # args All args are passed directly to subordinate function, sprint_time. See
Michael Walsh37c74f72018-02-15 17:12:25 -0600308 # that function's prolog for details.
309
310 return [sprint_time {*}$args]\n
311
312}
313
314
315proc sprint_error { { buffer {} } } {
316
317 # Return a standardized error string which includes the callers buffer text.
318
319 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500320 # buffer Text to be returned as part of the error message.
Michael Walsh37c74f72018-02-15 17:12:25 -0600321
322 return [sprint_time "**ERROR** $buffer"]
323
324}
325
326
327proc sprint_varx { var_name var_value { indent 0 } { width {} } { hex 0 } } {
328
Michael Walsh410b1782019-10-22 15:56:18 -0500329 # Return the name and value of the variable named in var_name in a formatted way.
Michael Walsh37c74f72018-02-15 17:12:25 -0600330
Michael Walsh410b1782019-10-22 15:56:18 -0500331 # This procedure will visually align the output to look good next to print_time output.
Michael Walsh37c74f72018-02-15 17:12:25 -0600332
333 # Example:
334
335 # Given the following code:
336
337 # print_timen "Initializing variables."
338 # set first_name "Joe"
339 # set last_name "Montana"
340 # set age 50
341 # print_varx last_name $last_name
342 # print_varx first_name $first_name 2
343 # print_varx age $age 2
344
Michael Walsh410b1782019-10-22 15:56:18 -0500345 # With environment variables NANOSECONDS and SHOW_ELAPSED_TIME both set, the following output is produced:
Michael Walsh37c74f72018-02-15 17:12:25 -0600346
347 # #(CST) 2017/12/14 16:38:28.259480 - 0.000651 - Initializing variables.
348 # last_name: Montana
349 # first_name: Joe
350 # age: 50
351
352 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500353 # var_name The name of the variable whose name and value are to be printed.
Michael Walsh37c74f72018-02-15 17:12:25 -0600354 # var_value The value to be printed.
Michael Walsh410b1782019-10-22 15:56:18 -0500355 # indent The number of spaces to indent each line of output.
356 # width The width of the column containing the variable name. By default this
357 # will align with the print_time text (see example above).
358 # hex Indicates that the variable value is to be printed in hexedecimal format.
359 # This is only valid if the variable value is an integer. If the variable
360 # is NOT an integer and is blank, this will be interpreted to mean "print
361 # the string '<blank>', rather than an actual blank value".
Michael Walsh37c74f72018-02-15 17:12:25 -0600362
363 # Note: This procedure relies on global var _gtp_default_print_var_width_
364
365 set_var_default indent 0
366
367 global _gtp_default_print_var_width_
368 set_var_default width $_gtp_default_print_var_width_
369
370 if { $indent > 0 } {
371 set width [expr $width - $indent]
372 }
373
374 if { $hex } {
375 if { [catch {format "0x%08x" "$var_value"} result] } {
376 if { $var_value == "" } { set var_value "<blank>" }
377 set hex 0
378 }
379 }
380
381 if { $hex } {
382 append buffer "[format "%-${indent}s%-${width}s0x%08x" "" "$var_name:" \
383 "$var_value"]"
384 } else {
385 append buffer "[format "%-${indent}s%-${width}s%s" "" "$var_name:" \
386 "$var_value"]"
387 }
388
389 return $buffer\n
390
391}
392
393
394proc sprint_var { var_name args } {
395
Michael Walsh410b1782019-10-22 15:56:18 -0500396 # Return the name and value of the variable named in var_name in a formatted way.
Michael Walsh37c74f72018-02-15 17:12:25 -0600397
Michael Walsh410b1782019-10-22 15:56:18 -0500398 # This procedure will visually align the output to look good next to print_time output.
Michael Walsh37c74f72018-02-15 17:12:25 -0600399
Michael Walsh410b1782019-10-22 15:56:18 -0500400 # Note: This procedure is the equivalent of sprint_varx with one difference: This function will figure
401 # out the value of the named variable whereas sprint_varx expects you to pass the value. This procedure in
402 # fact calls sprint_varx to do its work.
Michael Walsh37c74f72018-02-15 17:12:25 -0600403
Michael Walsh410b1782019-10-22 15:56:18 -0500404 # Note: This procedure will detect whether var_name is an array and print it accordingly (see the second
405 # example below).
Michael Walsh37c74f72018-02-15 17:12:25 -0600406
407 # Example:
408
409 # Given the following code:
410
411 # print_timen "Initializing variables."
412 # set first_name "Joe"
413 # set last_name "Montana"
414 # set age 50
415 # print_var last_name
416 # print_var first_name 2
417 # print_var age 2
418
Michael Walsh410b1782019-10-22 15:56:18 -0500419 # With environment variables NANOSECONDS and SHOW_ELAPSED_TIME both set, the following output is produced:
Michael Walsh37c74f72018-02-15 17:12:25 -0600420
421 # #(CST) 2017/12/14 16:38:28.259480 - 0.000651 - Initializing variables.
422 # last_name: Montana
423 # first_name: Joe
424 # age: 50
425
426 # Example:
427 # Given the following code:
428
429 # set data(0) cow
430 # set data(1) horse
431 # print_var data
432
433 # data:
434 # data(0): cow
435 # data(1): horse
436
437 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500438 # var_name The name of the variable whose name and value are to be printed.
439 # args The args understood by sprint_varx (after var_name and var_value). See
Michael Walsh37c74f72018-02-15 17:12:25 -0600440 # sprint_varx's prolog for details.
441
442 # Note: This procedure relies on global var _gtp_default_print_var_width_
443
Michael Walsh410b1782019-10-22 15:56:18 -0500444 # Determine who our caller is and therefore what upvar_level to use to get var_value.
Michael Walsh37c74f72018-02-15 17:12:25 -0600445 set stack_ix_adjust [calc_wrap_stack_ix_adjust]
446 set upvar_level [expr $stack_ix_adjust + 1]
447 upvar $upvar_level $var_name var_value
448
449 # Special processing for arrays:
450 if { [array exists var_value] } {
451 set indent [lindex $args 0]
452 set args [lrange $args 1 end]
453 set_var_default indent 0
454
455 append buffer [format "%-${indent}s%s\n" "" "$var_name:"]
456 incr indent 2
457 incr width -2
458
459 set search_token [array startsearch var_value]
460 while {[array anymore var_value $search_token]} {
461 set key [array nextelement var_value $search_token]
462 set arr_value $var_value($key)
463 append buffer [sprint_varx "${var_name}(${key})" $arr_value $indent\
464 {*}$args]
465 }
466 array donesearch var_value $search_token
467 return $buffer
468 }
469
Michael Walsh410b1782019-10-22 15:56:18 -0500470 # If var_value is not defined, catch the error and print its value as "variable not set".
Michael Walsh355b8ef2019-07-17 10:37:15 -0500471 if {[catch {set buffer [sprint_varx $var_name $var_value {*}$args]} error_text options]} {
472 set regex ":\[ \]no\[ \]such\[ \]variable"
473 if { [regexp -expanded ${regex} ${error_text}]} {
474 return [sprint_varx $var_name {** variable not set **} {*}$args]
475 } else {
476 print_dict options
477 exit 1
478 }
479 } else {
480 return $buffer
481 }
Michael Walsh37c74f72018-02-15 17:12:25 -0600482
483}
484
485
486proc sprint_list { var_name args } {
487
Michael Walsh410b1782019-10-22 15:56:18 -0500488 # Return the name and value of the list variable named in var_name in a formatted way.
Michael Walsh37c74f72018-02-15 17:12:25 -0600489
490 # This procedure is the equivalent of sprint_var but for lists.
491
492 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500493 # var_name The name of the variable whose name and value are to be printed.
494 # args The args understood by sprint_varx (after var_name and var_value). See
Michael Walsh37c74f72018-02-15 17:12:25 -0600495 # sprint_varx's prolog for details.
496
Michael Walsh410b1782019-10-22 15:56:18 -0500497 # Note: In TCL, there is no way to determine that a variable represents a list vs a string, etc. It is up
498 # to the programmer to decide how the data is to be interpreted. Thus the need for procedures such as this
499 # one. Consider the following code:
Michael Walsh37c74f72018-02-15 17:12:25 -0600500
501 # set my_list {one two three}
502 # print_var my_list
503 # print_list my_list
504
505 # Output from aforementioned code:
506 # my_list: one two three
507 # my_list:
508 # my_list[0]: one
509 # my_list[1]: two
510 # my_list[2]: three
511
Michael Walsh410b1782019-10-22 15:56:18 -0500512 # As far as print_var is concerned, my_list is a string and is printed accordingly. By using print_list,
513 # the programmer is asking to have the output shown as a list with list indices, etc.
Michael Walsh37c74f72018-02-15 17:12:25 -0600514
515 # Determine who our caller is and therefore what upvar_level to use.
516 set stack_ix_adjust [calc_wrap_stack_ix_adjust]
517 set upvar_level [expr $stack_ix_adjust + 1]
518 upvar $upvar_level $var_name var_value
519
520 set indent [lindex $args 0]
521 set args [lrange $args 1 end]
522 set_var_default indent 0
523
524 append buffer [format "%-${indent}s%s\n" "" "$var_name:"]
525 incr indent 2
526
527 set index 0
528 foreach element $var_value {
529 append buffer [sprint_varx "${var_name}\[${index}\]" $element $indent\
530 {*}$args]
531 incr index
532 }
533
534 return $buffer
535
536}
537
538
539proc sprint_dict { var_name args } {
540
Michael Walsh410b1782019-10-22 15:56:18 -0500541 # Return the name and value of the dictionary variable named in var_name in a formatted way.
Michael Walsh37c74f72018-02-15 17:12:25 -0600542
543 # This procedure is the equivalent of sprint_var but for dictionaries.
544
545 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500546 # var_name The name of the variable whose name and value are to be printed.
547 # args The args understood by sprint_varx (after var_name and var_value). See
Michael Walsh37c74f72018-02-15 17:12:25 -0600548 # sprint_varx's prolog for details.
549
Michael Walsh410b1782019-10-22 15:56:18 -0500550 # Note: In TCL, there is no way to determine that a variable represents a dictionary vs a string, etc. It
551 # is up to the programmer to decide how the data is to be interpreted. Thus the need for procedures such
552 # as this one. Consider the following code:
Michael Walsh37c74f72018-02-15 17:12:25 -0600553
554 # set my_dict [dict create first Joe last Montana age 50]
555 # print_var my_dict
556 # print_dict my_dict
557
558 # Output from aforementioned code:
Michael Walsh410b1782019-10-22 15:56:18 -0500559 # my_dict: first Joe last Montana age 50
Michael Walsh37c74f72018-02-15 17:12:25 -0600560 # my_dict:
561 # my_dict[first]: Joe
562 # my_dict[last]: Montana
563 # my_dict[age]: 50
564
Michael Walsh410b1782019-10-22 15:56:18 -0500565 # As far as print_var is concerned, my_dict is a string and is printed accordingly. By using print_dict,
566 # the programmer is asking to have the output shown as a dictionary with dictionary keys/values, etc.
Michael Walsh37c74f72018-02-15 17:12:25 -0600567
568 # Determine who our caller is and therefore what upvar_level to use.
569 set stack_ix_adjust [calc_wrap_stack_ix_adjust]
570 set upvar_level [expr $stack_ix_adjust + 1]
571 upvar $upvar_level $var_name var_value
572
573 set indent [lindex $args 0]
574 set args [lrange $args 1 end]
575 set_var_default indent 0
576
577 append buffer [format "%-${indent}s%s\n" "" "$var_name:"]
578 incr indent 2
579
580 foreach {key value} $var_value {
581 append buffer [sprint_varx "${var_name}\[${key}\]" $value $indent {*}$args]
582 incr index
583 }
584
585 return $buffer
586
587}
588
589
590proc sprint_vars { args } {
591
592 # Sprint the values of one or more variables.
593
594 # Description of arg(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500595 # args: A list of variable names to be printed. The first argument in the arg list found to be an
596 # integer (rather than a variable name) will be interpreted to be first of several possible sprint_var
597 # arguments (e.g. indent, width, hex). See the prologue for sprint_var above for descriptions of this
598 # variables.
Michael Walsh37c74f72018-02-15 17:12:25 -0600599
600 # Example usage:
601 # set var1 "hello"
602 # set var2 "there"
603 # set indent 2
604 # set buffer [sprint_vars var1 var2]
605 # or...
606 # set buffer [sprint_vars var1 var2 $indent]
607
608 # Look for integer arguments.
609 set first_int_ix [lsearch -regexp $args {^[0-9]+$}]
610 if { $first_int_ix == -1 } {
611 # If none are found, sub_args is set to empty.
612 set sub_args {}
613 } else {
614 # Set sub_args to the portion of the arg list that are integers.
615 set sub_args [lrange $args $first_int_ix end]
616 # Re-set args to exclude the integer values.
617 set args [lrange $args 0 [expr $first_int_ix - 1]]
618 }
619
620 foreach arg $args {
621 append buffer [sprint_var $arg {*}$sub_args]
622 }
623
624 return $buffer
625
626}
627
628
629proc sprint_dashes { { indent 0 } { width 80 } { line_feed 1 } { char "-" } } {
630
631 # Return a string of dashes to the caller.
632
633 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500634 # indent The number of characters to indent the output.
Michael Walsh37c74f72018-02-15 17:12:25 -0600635 # width The width of the string of dashes.
Michael Walsh410b1782019-10-22 15:56:18 -0500636 # line_feed Indicates whether the output should end with a line feed.
637 # char The character to be repeated in the output string. In other words, you
638 # can call on this function to print a string of any character (e.g. "=",
639 # "_", etc.).
Michael Walsh37c74f72018-02-15 17:12:25 -0600640
641 set_var_default indent 0
642 set_var_default width 80
643 set_var_default line_feed 1
644
645 append buffer [string repeat " " $indent][string repeat $char $width]
646 append buffer [string repeat "\n" $line_feed]
647
648 return $buffer
649
650}
651
652
653proc sprint_executing {{ include_args 1 }} {
654
655 # Return a string that looks something like this:
656 # #(CST) 2017/11/28 15:08:03.261466 - 0.015214 - Executing: proc1 hi
657
658 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500659 # include_args Indicates whether proc args should be included in the result.
Michael Walsh37c74f72018-02-15 17:12:25 -0600660
661 set stack_ix_adjust [calc_wrap_stack_ix_adjust]
662 set level [expr -(2 + $stack_ix_adjust)]
663 return "[sprint_time]Executing: [get_stack_proc_name $level $include_args]\n"
664
665}
666
667
668proc sprint_issuing { { cmd_buf "" } { test_mode 0 } } {
669
670 # Return a line indicating a command that the program is about to execute.
671
672 # Sample output for a cmd_buf of "ls"
673
674 # #(CDT) 2016/08/25 17:57:36 - Issuing: ls
675
676 # Description of arg(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500677 # cmd_buf The command to be executed by caller. If this is blank, this procedure
678 # will search up the stack for the first cmd_buf value to use.
679 # test_mode With test_mode set, your output will look like this:
Michael Walsh37c74f72018-02-15 17:12:25 -0600680
681 # #(CDT) 2016/08/25 17:57:36 - (test_mode) Issuing: ls
682
683 if { $cmd_buf == "" } {
684 set cmd_buf [get_stack_var cmd_buf {} 2]
685 }
686
687 append buffer [sprint_time]
688 if { $test_mode } {
689 append buffer "(test_mode) "
690 }
691 append buffer "Issuing: ${cmd_buf}\n"
692
693 return $buffer
694
695}
696
697
698proc sprint_call_stack { { indent 0 } } {
699
Michael Walsh410b1782019-10-22 15:56:18 -0500700 # Return a call stack report for the given point in the program with line numbers, procedure names and
701 # procedure parameters and arguments.
Michael Walsh37c74f72018-02-15 17:12:25 -0600702
703 # Sample output:
704
705 # ---------------------------------------------------------------------------
706 # TCL procedure call stack
707
708 # Line # Procedure name and arguments
709 # ------ --------------------------------------------------------------------
710 # 21 print_call_stack
711 # 32 proc1 257
712 # ---------------------------------------------------------------------------
713
714 # Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500715 # indent The number of characters to indent each line of output.
Michael Walsh37c74f72018-02-15 17:12:25 -0600716
717 append buffer "[sprint_dashes ${indent}]"
718 append buffer "[string repeat " " $indent]TCL procedure call stack\n\n"
719 append buffer "[string repeat " " $indent]"
720 append buffer "Line # Procedure name and arguments\n"
721 append buffer "[sprint_dashes $indent 6 0] [sprint_dashes 0 73]"
722
723 for {set ix [expr [info level]-1]} {$ix > 0} {incr ix -1} {
724 set frame_dict [info frame $ix]
725 set line_num [dict get $frame_dict line]
726 set proc_name_plus_args [dict get $frame_dict cmd]
727 append buffer [format "%-${indent}s%6i %s\n" "" $line_num\
728 $proc_name_plus_args]
729 }
730 append buffer "[sprint_dashes $indent]"
731
732 return $buffer
733
734}
735
736
737proc sprint_tcl_version {} {
738
739 # Return the name and value of tcl_version in a formatted way.
740
741 global tcl_version
742
743 return [sprint_var tcl_version]
744
745}
746
747
748proc sprint_error_report { { error_text "\n" } { indent 0 } } {
749
Michael Walsh410b1782019-10-22 15:56:18 -0500750 # Return a string with a standardized report which includes the caller's error text, the call stack and
751 # the program header.
Michael Walsh37c74f72018-02-15 17:12:25 -0600752
753 # Description of arg(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500754 # error_text The error text to be included in the report. The caller should include
755 # any needed linefeeds.
756 # indent The number of characters to indent each line of output.
Michael Walsh37c74f72018-02-15 17:12:25 -0600757
758 set width 120
759 set char "="
760 set line_feed 1
761 append buffer [sprint_dashes $indent $width $line_feed $char]
762 append buffer [string repeat " " $indent][sprint_error $error_text]
763 append buffer "\n"
764 append buffer [sprint_call_stack $indent]
765 append buffer [sprint_pgm_header $indent]
766 append buffer [sprint_dashes $indent $width $line_feed $char]
767
768 return $buffer
769
770}
771
772
773proc sprint_pgm_header { {indent 0} {linefeed 1} } {
774
Michael Walsh410b1782019-10-22 15:56:18 -0500775 # Return a standardized header that programs should print at the beginning of the run. It includes useful
776 # information like command line, pid, userid, program parameters, etc.
Michael Walsh37c74f72018-02-15 17:12:25 -0600777
778 # Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500779 # indent The number of characters to indent each line of output.
780 # linefeed Indicates whether a line feed be included at the beginning and end of the
781 # report.
Michael Walsh37c74f72018-02-15 17:12:25 -0600782
783 global program_name
784 global pgm_name_var_name
785 global argv0
786 global argv
787 global env
788 global _gtp_default_print_var_width_
789
790 set_var_default indent 0
791
792 set indent_str [string repeat " " $indent]
793 set width [expr $_gtp_default_print_var_width_ + $indent]
794
795 # Get variable values for output.
796 set command_line "$argv0 $argv"
797 set pid_var_name ${pgm_name_var_name}_pid
798 set $pid_var_name [pid]
799 set uid [get_var ::env(USER) 0]
800 set host_name [get_var ::env(HOSTNAME) 0]
801 set DISPLAY [get_var ::env(DISPLAY) 0]
802
803 # Generate the report.
804 if { $linefeed } { append buffer "\n" }
805 append buffer ${indent_str}[sprint_timen "Running ${program_name}."]
806 append buffer ${indent_str}[sprint_timen "Program parameter values, etc.:\n"]
807 append buffer [sprint_var command_line $indent $width]
808 append buffer [sprint_var $pid_var_name $indent $width]
809 append buffer [sprint_var uid $indent $width]
810 append buffer [sprint_var host_name $indent $width]
811 append buffer [sprint_var DISPLAY $indent $width]
812
813 # Print caller's parm names/values.
814 global longoptions
815 global pos_parms
816
817 regsub -all ":" "${longoptions} ${pos_parms}" {} parm_names
818
819 foreach parm_name $parm_names {
820 set cmd_buf "global $parm_name ; append buffer"
821 append cmd_buf " \[sprint_var $parm_name $indent $width\]"
822 eval $cmd_buf
823 }
824
825 if { $linefeed } { append buffer "\n" }
826
827 return $buffer
828
829}
830
831
832proc sprint_pgm_footer {} {
833
Michael Walsh410b1782019-10-22 15:56:18 -0500834 # Return a standardized footer that programs should print at the end of the program run. It includes
835 # useful information like total run time, etc.
Michael Walsh37c74f72018-02-15 17:12:25 -0600836
837 global program_name
838 global pgm_name_var_name
839 global start_time
840
841 # Calculate total runtime.
842 set total_time_micro [expr [clock microseconds] - $start_time]
843 # Break the left and right of the decimal point.
844 set total_seconds [expr $total_time_micro / 1000000]
845 set total_decimal_micro [expr $total_time_micro % 1000000]
846 set total_time_float [format "%i.%06i" ${total_seconds}\
847 ${total_decimal_micro}]
848 set total_time_string [format "%0.6f" $total_time_float]
849 set runtime_var_name ${pgm_name_var_name}_runtime
850 set $runtime_var_name $total_time_string
851
852 append buffer [sprint_timen "Finished running ${program_name}."]
853 append buffer "\n"
854 append buffer [sprint_var $runtime_var_name]
855 append buffer "\n"
856
857 return $buffer
858
859}
860
861
862proc sprint_arg_desc { arg_title arg_desc { indent 0 } { col1_width 25 }\
863 { line_width 80 } } {
864
865 # Return a formatted argument description.
866
867 # Example:
868 #
Michael Walsh410b1782019-10-22 15:56:18 -0500869 # set desc "When in the Course of human events, it becomes necessary for one people to dissolve the
870 # political bands which have connected them with another, and to assume among the powers of the earth, the
871 # separate and equal station to which the Laws of Nature and of Nature's God entitle them, a decent respect
872 # to the opinions of mankind requires that they should declare the causes which impel them to the
873 # separation."
Michael Walsh37c74f72018-02-15 17:12:25 -0600874
875 # set buffer [sprint_arg_desc "--declaration" $desc]
876 # puts $buffer
877
878 # Resulting output:
879 # --declaration When in the Course of human events, it becomes
880 # necessary for one people to dissolve the
881 # political bands which have connected them with
882 # another, and to assume among the powers of the
883 # earth, the separate and equal station to which
884 # the Laws of Nature and of Nature's God entitle
885 # them, a decent respect to the opinions of mankind
886 # requires that they should declare the causes
887 # which impel them to the separation.
888
889 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500890 # arg_title The content that you want to appear on the first line in column 1.
Michael Walsh37c74f72018-02-15 17:12:25 -0600891 # arg_desc The text that describes the argument.
892 # indent The number of characters to indent.
Michael Walsh410b1782019-10-22 15:56:18 -0500893 # col1_width The width of column 1, which is the column containing the arg_title.
Michael Walsh37c74f72018-02-15 17:12:25 -0600894 # line_width The total max width of each line of output.
895
896 set fold_width [expr $line_width - $col1_width]
897 set escaped_arg_desc [escape_bash_quotes "${arg_desc}"]
898
899 set cmd_buf "echo '${escaped_arg_desc}' | fold --spaces --width="
900 append cmd_buf "${fold_width} | sed -re 's/\[ \]+$//g'"
901 set out_buf [eval exec bash -c {$cmd_buf}]
902
903 set help_lines [split $out_buf "\n"]
904
905 set buffer {}
906
907 set line_num 1
908 foreach help_line $help_lines {
909 if { $line_num == 1 } {
910 if { [string length $arg_title] > $col1_width } {
911 # If the arg_title is already wider than column1, print it on its own
912 # line.
913 append buffer [format "%${indent}s%-${col1_width}s\n" ""\
914 "$arg_title"]
915 append buffer [format "%${indent}s%-${col1_width}s%s\n" "" ""\
916 "${help_line}"]
917 } else {
918 append buffer [format "%${indent}s%-${col1_width}s%s\n" ""\
919 "$arg_title" "${help_line}"]
920 }
921 } else {
922 append buffer [format "%${indent}s%-${col1_width}s%s\n" "" ""\
923 "${help_line}"]
924 }
925 incr line_num
926 }
927
928 return $buffer
929
930}
931
932
933# Define the create_print_wrapper_procs to help us create print wrappers.
934# First, create templates.
935# Notes:
936# - The resulting procedures will replace all registered passwords.
Michael Walsh410b1782019-10-22 15:56:18 -0500937# - The resulting "quiet" and "debug" print procedures will search the stack for quiet and debug,
938# respectively. That means that the if a procedure calls qprint_var and the procedure has a local version
939# of quiet set to 1, the print will not occur, even if there is a global version of quiet set to 0.
Michael Walsh37c74f72018-02-15 17:12:25 -0600940set print_proc_template " puts -nonewline<output_stream> \[replace_passwords"
941append print_proc_template " \[<base_proc_name> {*}\$args\]\]\n}\n"
942set qprint_proc_template " set quiet \[get_stack_var quiet 0\]\n if {"
943append qprint_proc_template " \$quiet } { return }\n${print_proc_template}"
944set dprint_proc_template " set debug \[get_stack_var debug 0\]\n if { !"
945append dprint_proc_template " \$debug } { return }\n${print_proc_template}"
946
947# Put each template into the print_proc_templates array.
948set print_proc_templates(p) $print_proc_template
949set print_proc_templates(q) $qprint_proc_template
950set print_proc_templates(d) $dprint_proc_template
951proc create_print_wrapper_procs {proc_names {stderr_proc_names {}} } {
952
Michael Walsh410b1782019-10-22 15:56:18 -0500953 # Generate code for print wrapper procs and return the generated code as a string.
Michael Walsh37c74f72018-02-15 17:12:25 -0600954
Michael Walsh410b1782019-10-22 15:56:18 -0500955 # To illustrate, suppose there is a "print_foo_bar" proc in the proc_names list.
Michael Walsh37c74f72018-02-15 17:12:25 -0600956 # This proc will...
957 # - Expect that there is an sprint_foo_bar proc already in existence.
Michael Walsh410b1782019-10-22 15:56:18 -0500958 # - Create a print_foo_bar proc which calls sprint_foo_bar and prints the result.
959 # - Create a qprint_foo_bar proc which calls upon sprint_foo_bar only if global value quiet is 0.
960 # - Create a dprint_foo_bar proc which calls upon sprint_foo_bar only if global value debug is 1.
Michael Walsh37c74f72018-02-15 17:12:25 -0600961
Michael Walsh410b1782019-10-22 15:56:18 -0500962 # Also, code will be generated to define aliases for each proc as well. Each alias will be created by
963 # replacing "print_" in the proc name with "p" For example, the alias for print_foo_bar will be pfoo_bar.
Michael Walsh37c74f72018-02-15 17:12:25 -0600964
965 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500966 # proc_names A list of procs for which print wrapper proc code is to be generated.
967 # stderr_proc_names A list of procs whose generated code should print to stderr rather than
968 # to stdout.
Michael Walsh37c74f72018-02-15 17:12:25 -0600969
970 global print_proc_template
971 global print_proc_templates
972
973 foreach proc_name $proc_names {
974
975 if { [expr [lsearch $stderr_proc_names $proc_name] == -1] } {
976 set replace_dict(output_stream) ""
977 } else {
978 set replace_dict(output_stream) " stderr"
979 }
980
981 set base_proc_name "s${proc_name}"
982 set replace_dict(base_proc_name) $base_proc_name
983
984 set wrap_proc_names(p) $proc_name
985 set wrap_proc_names(q) q${proc_name}
986 set wrap_proc_names(d) d${proc_name}
987
988 foreach template_key [list p q d] {
989 set wrap_proc_name $wrap_proc_names($template_key)
990 set call_line "proc ${wrap_proc_name} \{args\} \{\n"
991 set proc_body $print_proc_templates($template_key)
992 set proc_def ${call_line}${proc_body}
993 foreach {key value} [array get replace_dict] {
994 regsub -all "<$key>" $proc_def $value proc_def
995 }
996 regsub "print_" $wrap_proc_name "p" alias_proc_name
997 regsub "${wrap_proc_name}" $proc_def $alias_proc_name alias_def
998 append buffer "${proc_def}${alias_def}"
999 }
1000 }
1001
1002 return $buffer
1003
1004}
1005
1006
1007# Get this file's path.
1008set frame_dict [info frame 0]
1009set file_path [dict get $frame_dict file]
1010# Get a list of this file's sprint procs.
1011set sprint_procs [get_file_proc_names $file_path sprint]
1012# Create a corresponding list of print_procs.
1013set proc_names [list_map $sprint_procs {[string range $x 1 end]}]
1014# Sort them for ease of debugging.
1015set proc_names [lsort $proc_names]
1016
1017set stderr_proc_names [list print_error print_error_report]
1018
1019set proc_def [create_print_wrapper_procs $proc_names $stderr_proc_names]
1020if { $GEN_PRINT_DEBUG } { puts $proc_def }
1021eval "${proc_def}"