blob: aab6a62e9230eb1e900b9dc9e9fdd96dfa1fd2d9 [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
David Shaw721f9702020-06-25 14:55:13 -05005my_source [list data_proc.tcl call_stack.tcl escape.tcl]
Michael Walsh37c74f72018-02-15 17:12:25 -06006
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.
David Shaw721f9702020-06-25 14:55:13 -050092 lappend hidden_text [escape_regex_metachars $password]
Michael Walsh37c74f72018-02-15 17:12:25 -060093 }
94
Michael Walsh37c74f72018-02-15 17:12:25 -060095 set password_regex [join $hidden_text |]
96
97}
98
99
100proc replace_passwords {buffer} {
101
Michael Walsh410b1782019-10-22 15:56:18 -0500102 # Replace all registered password found in buffer with a string of asterisks and return the result.
Michael Walsh37c74f72018-02-15 17:12:25 -0600103
104 # Description of argument(s):
105 # buffer The string to be altered and returned.
106
Michael Walsh410b1782019-10-22 15:56:18 -0500107 # Note: If environment variable GEN_PRINT_DEBUG is set, this procedure will do nothing.
Michael Walsh37c74f72018-02-15 17:12:25 -0600108
109 global env
110 if { [get_var ::env(GEN_PRINT_DEBUG) 0] } { return $buffer }
Michael Walshb21aec12018-03-02 12:00:22 -0600111 if { [get_var ::env(DEBUG_SHOW_PASSWORDS) 0] } { return $buffer }
Michael Walsh37c74f72018-02-15 17:12:25 -0600112
113 global password_regex
114
115 # No passwords to replace?
116 if { $password_regex == "" } { return $buffer }
117
118 regsub -all "${password_regex}" $buffer {********} buffer
119 return $buffer
120
121}
122
123
124proc my_time { cmd_buf { iterations 100 } } {
125
126 # Run the "time" function on the given command string and print the results.
127
128 # The main benefit of running this vs just doing the "time" command directly:
129 # - This will print the results.
130
131 # Description of argument(s):
132 # cmd_buf The command string to be run.
Michael Walsh410b1782019-10-22 15:56:18 -0500133 # iterations The number of times to run the command string. Typically, more
134 # iterations yields more accurate results.
Michael Walsh37c74f72018-02-15 17:12:25 -0600135
136 print_issuing $cmd_buf
137 set result [time {uplevel 1 $cmd_buf} $iterations]
138
139 set raw_microseconds [lindex [split [lindex $result 0] .] 0]
140 set seconds [expr $raw_microseconds / 1000000]
141 set raw_microseconds [expr $raw_microseconds % 1000000]
142
143 set seconds_per_iteration [format "%i.%06i" ${seconds}\
144 ${raw_microseconds}]
145
146 print_var seconds_per_iteration
147
148}
149
150
Michael Walsh410b1782019-10-22 15:56:18 -0500151# If environment variable "GEN_PRINT_DEBUG" is set, this module will output debug data. This is primarily
152# intended for the developer of this module.
Michael Walsh37c74f72018-02-15 17:12:25 -0600153set GEN_PRINT_DEBUG [get_var ::env(GEN_PRINT_DEBUG) 0]
154
Michael Walsh410b1782019-10-22 15:56:18 -0500155# The user can set the following environment variables to influence the output from print_time and print_var
156# procedures. See the prologs of those procedures for details.
Michael Walsh37c74f72018-02-15 17:12:25 -0600157set NANOSECONDS [get_var ::env(NANOSECONDS) 0]
158set SHOW_ELAPSED_TIME [get_var ::env(SHOW_ELAPSED_TIME) 0]
159
Michael Walsh410b1782019-10-22 15:56:18 -0500160# _gtp_default_print_var_width_ is adjusted based on NANOSECONDS and SHOW_ELAPSED_TIME.
Michael Walsh37c74f72018-02-15 17:12:25 -0600161if { $NANOSECONDS } {
162 set _gtp_default_print_var_width_ 36
163 set width_incr 14
164} else {
165 set _gtp_default_print_var_width_ 29
166 set width_incr 7
167}
168if { $SHOW_ELAPSED_TIME } {
169 incr _gtp_default_print_var_width_ $width_incr
Michael Walsh410b1782019-10-22 15:56:18 -0500170 # Initializing _sprint_time_last_seconds_ which is a global value to remember the clock seconds from the
171 # last time sprint_time was called.
Michael Walsh37c74f72018-02-15 17:12:25 -0600172 set _gtp_sprint_time_last_micro_seconds_ [clock microseconds]
173}
Michael Walsh410b1782019-10-22 15:56:18 -0500174# tcl_precision is a built-in Tcl variable that specifies the number of digits to generate when converting
175# floating-point values to strings.
Michael Walsh37c74f72018-02-15 17:12:25 -0600176set tcl_precision 17
177
178
179proc sprint { { buffer {} } } {
180
181 # Simply return the user's buffer.
Michael Walsh410b1782019-10-22 15:56:18 -0500182 # This procedure is used by the qprint and dprint functions defined dynamically below, i.e. it would not
183 # normally be called for general use.
Michael Walsh37c74f72018-02-15 17:12:25 -0600184
185 # Description of arguments.
186 # buffer This will be returned to the caller.
187
188 return $buffer
189
190}
191
192
193proc sprintn { { buffer {} } } {
194
195 # Simply return the user's buffer plus a trailing line feed..
Michael Walsh410b1782019-10-22 15:56:18 -0500196 # This procedure is used by the qprintn and dprintn functions defined dynamically below, i.e. it would not
197 # normally be called for general use.
Michael Walsh37c74f72018-02-15 17:12:25 -0600198
199 # Description of arguments.
200 # buffer This will be returned to the caller.
201
202 return ${buffer}\n
203
204}
205
206
207proc sprint_time { { buffer {} } } {
208
209 # Return the time in a formatted manner as described below.
210
211 # Example:
212
213 # The following tcl code...
214
215 # puts -nonewline [sprint_time()]
216 # puts -nonewline ["Hi.\n"]
217
218 # Will result in the following type of output:
219
220 # #(CDT) 2016/07/08 15:25:35 - Hi.
221
222 # Example:
223
224 # The following tcl code...
225
226 # puts -nonewline [sprint_time("Hi.\n")]
227
228 # Will result in the following type of output:
229
230 # #(CDT) 2016/08/03 17:12:05 - Hi.
231
Michael Walsh410b1782019-10-22 15:56:18 -0500232 # The following environment variables will affect the formatting as described:
233 # NANOSECONDS This will cause the time stamps to be precise to the microsecond (Yes, it
234 # probably should have been named MICROSECONDS but the convention was set
235 # long ago so we're sticking with it). Example of the output when
236 # environment variable NANOSECONDS=1.
Michael Walsh37c74f72018-02-15 17:12:25 -0600237
238 # #(CDT) 2016/08/03 17:16:25.510469 - Hi.
239
Michael Walsh410b1782019-10-22 15:56:18 -0500240 # SHOW_ELAPSED_TIME This will cause the elapsed time to be included in the output. This is
241 # the amount of time that has elapsed since the last time this procedure
242 # was called. The precision of the elapsed time field is also affected by
243 # the value of the NANOSECONDS environment variable. Example of the output
244 # when environment variable NANOSECONDS=0 and SHOW_ELAPSED_TIME=1.
Michael Walsh37c74f72018-02-15 17:12:25 -0600245
246 # #(CDT) 2016/08/03 17:17:40 - 0 - Hi.
247
Michael Walsh410b1782019-10-22 15:56:18 -0500248 # Example of the output when environment variable NANOSECONDS=1 and SHOW_ELAPSED_TIME=1.
Michael Walsh37c74f72018-02-15 17:12:25 -0600249
250 # #(CDT) 2016/08/03 17:18:47.317339 - 0.000046 - Hi.
251
252 # Description of argument(s).
Michael Walsh410b1782019-10-22 15:56:18 -0500253 # buffer A string string whhich is to be appended to the formatted time string and
254 # returned.
Michael Walsh37c74f72018-02-15 17:12:25 -0600255
256 global NANOSECONDS
257 global _gtp_sprint_time_last_micro_seconds_
258 global SHOW_ELAPSED_TIME
259
260 # Get micro seconds since the epoch.
261 set epoch_micro [clock microseconds]
262 # Break the left and right of the decimal point.
263 set epoch_seconds [expr $epoch_micro / 1000000]
264 set epoch_decimal_micro [expr $epoch_micro % 1000000]
265
266 set format_string "#(%Z) %Y/%m/%d %H:%M:%S"
267 set return_string [clock format $epoch_seconds -format\
268 "#(%Z) %Y/%m/%d %H:%M:%S"]
269
270 if { $NANOSECONDS } {
271 append return_string ".[format "%06i" ${epoch_decimal_micro}]"
272 }
273
274 if { $SHOW_ELAPSED_TIME } {
275 set return_string "${return_string} - "
276
277 set elapsed_micro [expr $epoch_micro - \
278 $_gtp_sprint_time_last_micro_seconds_]
279 set elapsed_seconds [expr $elapsed_micro / 1000000]
280
281 if { $NANOSECONDS } {
282 set elapsed_decimal_micro [expr $elapsed_micro % 1000000]
283 set elapsed_float [format "%i.%06i" ${elapsed_seconds}\
284 ${elapsed_decimal_micro}]
285 set elapsed_time_buffer "[format "%11.6f" ${elapsed_float}]"
286 } else {
287 set elapsed_time_buffer "[format "%4i" $elapsed_seconds]"
288 }
289 set return_string "${return_string}${elapsed_time_buffer}"
290 }
291
292 set return_string "${return_string} - ${buffer}"
293
294 set _gtp_sprint_time_last_micro_seconds_ $epoch_micro
295
296 return $return_string
297
298}
299
300
301proc sprint_timen { args } {
302
303 # Return the value of sprint_time + a line feed.
304
305 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500306 # args All args are passed directly to subordinate function, sprint_time. See
Michael Walsh37c74f72018-02-15 17:12:25 -0600307 # that function's prolog for details.
308
309 return [sprint_time {*}$args]\n
310
311}
312
313
314proc sprint_error { { buffer {} } } {
315
316 # Return a standardized error string which includes the callers buffer text.
317
318 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500319 # buffer Text to be returned as part of the error message.
Michael Walsh37c74f72018-02-15 17:12:25 -0600320
321 return [sprint_time "**ERROR** $buffer"]
322
323}
324
325
326proc sprint_varx { var_name var_value { indent 0 } { width {} } { hex 0 } } {
327
Michael Walsh410b1782019-10-22 15:56:18 -0500328 # Return the name and value of the variable named in var_name in a formatted way.
Michael Walsh37c74f72018-02-15 17:12:25 -0600329
Michael Walsh410b1782019-10-22 15:56:18 -0500330 # This procedure will visually align the output to look good next to print_time output.
Michael Walsh37c74f72018-02-15 17:12:25 -0600331
332 # Example:
333
334 # Given the following code:
335
336 # print_timen "Initializing variables."
337 # set first_name "Joe"
338 # set last_name "Montana"
339 # set age 50
340 # print_varx last_name $last_name
341 # print_varx first_name $first_name 2
342 # print_varx age $age 2
343
Michael Walsh410b1782019-10-22 15:56:18 -0500344 # With environment variables NANOSECONDS and SHOW_ELAPSED_TIME both set, the following output is produced:
Michael Walsh37c74f72018-02-15 17:12:25 -0600345
346 # #(CST) 2017/12/14 16:38:28.259480 - 0.000651 - Initializing variables.
347 # last_name: Montana
348 # first_name: Joe
349 # age: 50
350
351 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500352 # var_name The name of the variable whose name and value are to be printed.
Michael Walsh37c74f72018-02-15 17:12:25 -0600353 # var_value The value to be printed.
Michael Walsh410b1782019-10-22 15:56:18 -0500354 # indent The number of spaces to indent each line of output.
355 # width The width of the column containing the variable name. By default this
356 # will align with the print_time text (see example above).
357 # hex Indicates that the variable value is to be printed in hexedecimal format.
358 # This is only valid if the variable value is an integer. If the variable
359 # is NOT an integer and is blank, this will be interpreted to mean "print
360 # the string '<blank>', rather than an actual blank value".
Michael Walsh37c74f72018-02-15 17:12:25 -0600361
362 # Note: This procedure relies on global var _gtp_default_print_var_width_
363
364 set_var_default indent 0
365
366 global _gtp_default_print_var_width_
367 set_var_default width $_gtp_default_print_var_width_
368
369 if { $indent > 0 } {
370 set width [expr $width - $indent]
371 }
372
373 if { $hex } {
374 if { [catch {format "0x%08x" "$var_value"} result] } {
375 if { $var_value == "" } { set var_value "<blank>" }
376 set hex 0
377 }
378 }
379
380 if { $hex } {
381 append buffer "[format "%-${indent}s%-${width}s0x%08x" "" "$var_name:" \
382 "$var_value"]"
383 } else {
384 append buffer "[format "%-${indent}s%-${width}s%s" "" "$var_name:" \
385 "$var_value"]"
386 }
387
388 return $buffer\n
389
390}
391
392
393proc sprint_var { var_name args } {
394
Michael Walsh410b1782019-10-22 15:56:18 -0500395 # Return the name and value of the variable named in var_name in a formatted way.
Michael Walsh37c74f72018-02-15 17:12:25 -0600396
Michael Walsh410b1782019-10-22 15:56:18 -0500397 # This procedure will visually align the output to look good next to print_time output.
Michael Walsh37c74f72018-02-15 17:12:25 -0600398
Michael Walsh410b1782019-10-22 15:56:18 -0500399 # Note: This procedure is the equivalent of sprint_varx with one difference: This function will figure
400 # out the value of the named variable whereas sprint_varx expects you to pass the value. This procedure in
401 # fact calls sprint_varx to do its work.
Michael Walsh37c74f72018-02-15 17:12:25 -0600402
Michael Walsh410b1782019-10-22 15:56:18 -0500403 # Note: This procedure will detect whether var_name is an array and print it accordingly (see the second
404 # example below).
Michael Walsh37c74f72018-02-15 17:12:25 -0600405
406 # Example:
407
408 # Given the following code:
409
410 # print_timen "Initializing variables."
411 # set first_name "Joe"
412 # set last_name "Montana"
413 # set age 50
414 # print_var last_name
415 # print_var first_name 2
416 # print_var age 2
417
Michael Walsh410b1782019-10-22 15:56:18 -0500418 # With environment variables NANOSECONDS and SHOW_ELAPSED_TIME both set, the following output is produced:
Michael Walsh37c74f72018-02-15 17:12:25 -0600419
420 # #(CST) 2017/12/14 16:38:28.259480 - 0.000651 - Initializing variables.
421 # last_name: Montana
422 # first_name: Joe
423 # age: 50
424
425 # Example:
426 # Given the following code:
427
428 # set data(0) cow
429 # set data(1) horse
430 # print_var data
431
432 # data:
433 # data(0): cow
434 # data(1): horse
435
436 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500437 # var_name The name of the variable whose name and value are to be printed.
438 # args The args understood by sprint_varx (after var_name and var_value). See
Michael Walsh37c74f72018-02-15 17:12:25 -0600439 # sprint_varx's prolog for details.
440
441 # Note: This procedure relies on global var _gtp_default_print_var_width_
442
Michael Walsh410b1782019-10-22 15:56:18 -0500443 # Determine who our caller is and therefore what upvar_level to use to get var_value.
Michael Walsh37c74f72018-02-15 17:12:25 -0600444 set stack_ix_adjust [calc_wrap_stack_ix_adjust]
445 set upvar_level [expr $stack_ix_adjust + 1]
446 upvar $upvar_level $var_name var_value
447
448 # Special processing for arrays:
449 if { [array exists var_value] } {
450 set indent [lindex $args 0]
451 set args [lrange $args 1 end]
452 set_var_default indent 0
453
454 append buffer [format "%-${indent}s%s\n" "" "$var_name:"]
455 incr indent 2
456 incr width -2
457
458 set search_token [array startsearch var_value]
459 while {[array anymore var_value $search_token]} {
460 set key [array nextelement var_value $search_token]
461 set arr_value $var_value($key)
462 append buffer [sprint_varx "${var_name}(${key})" $arr_value $indent\
463 {*}$args]
464 }
465 array donesearch var_value $search_token
466 return $buffer
467 }
468
Michael Walsh410b1782019-10-22 15:56:18 -0500469 # If var_value is not defined, catch the error and print its value as "variable not set".
Michael Walsh355b8ef2019-07-17 10:37:15 -0500470 if {[catch {set buffer [sprint_varx $var_name $var_value {*}$args]} error_text options]} {
471 set regex ":\[ \]no\[ \]such\[ \]variable"
472 if { [regexp -expanded ${regex} ${error_text}]} {
473 return [sprint_varx $var_name {** variable not set **} {*}$args]
474 } else {
475 print_dict options
476 exit 1
477 }
478 } else {
479 return $buffer
480 }
Michael Walsh37c74f72018-02-15 17:12:25 -0600481
482}
483
484
485proc sprint_list { var_name args } {
486
Michael Walsh410b1782019-10-22 15:56:18 -0500487 # Return the name and value of the list variable named in var_name in a formatted way.
Michael Walsh37c74f72018-02-15 17:12:25 -0600488
489 # This procedure is the equivalent of sprint_var but for lists.
490
491 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500492 # var_name The name of the variable whose name and value are to be printed.
493 # args The args understood by sprint_varx (after var_name and var_value). See
Michael Walsh37c74f72018-02-15 17:12:25 -0600494 # sprint_varx's prolog for details.
495
Michael Walsh410b1782019-10-22 15:56:18 -0500496 # Note: In TCL, there is no way to determine that a variable represents a list vs a string, etc. It is up
497 # to the programmer to decide how the data is to be interpreted. Thus the need for procedures such as this
498 # one. Consider the following code:
Michael Walsh37c74f72018-02-15 17:12:25 -0600499
500 # set my_list {one two three}
501 # print_var my_list
502 # print_list my_list
503
504 # Output from aforementioned code:
505 # my_list: one two three
506 # my_list:
507 # my_list[0]: one
508 # my_list[1]: two
509 # my_list[2]: three
510
Michael Walsh410b1782019-10-22 15:56:18 -0500511 # As far as print_var is concerned, my_list is a string and is printed accordingly. By using print_list,
512 # the programmer is asking to have the output shown as a list with list indices, etc.
Michael Walsh37c74f72018-02-15 17:12:25 -0600513
514 # Determine who our caller is and therefore what upvar_level to use.
515 set stack_ix_adjust [calc_wrap_stack_ix_adjust]
516 set upvar_level [expr $stack_ix_adjust + 1]
517 upvar $upvar_level $var_name var_value
518
519 set indent [lindex $args 0]
520 set args [lrange $args 1 end]
521 set_var_default indent 0
522
523 append buffer [format "%-${indent}s%s\n" "" "$var_name:"]
524 incr indent 2
525
526 set index 0
527 foreach element $var_value {
528 append buffer [sprint_varx "${var_name}\[${index}\]" $element $indent\
529 {*}$args]
530 incr index
531 }
532
533 return $buffer
534
535}
536
537
538proc sprint_dict { var_name args } {
539
Michael Walsh410b1782019-10-22 15:56:18 -0500540 # Return the name and value of the dictionary variable named in var_name in a formatted way.
Michael Walsh37c74f72018-02-15 17:12:25 -0600541
542 # This procedure is the equivalent of sprint_var but for dictionaries.
543
544 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500545 # var_name The name of the variable whose name and value are to be printed.
546 # args The args understood by sprint_varx (after var_name and var_value). See
Michael Walsh37c74f72018-02-15 17:12:25 -0600547 # sprint_varx's prolog for details.
548
Michael Walsh410b1782019-10-22 15:56:18 -0500549 # Note: In TCL, there is no way to determine that a variable represents a dictionary vs a string, etc. It
550 # is up to the programmer to decide how the data is to be interpreted. Thus the need for procedures such
551 # as this one. Consider the following code:
Michael Walsh37c74f72018-02-15 17:12:25 -0600552
553 # set my_dict [dict create first Joe last Montana age 50]
554 # print_var my_dict
555 # print_dict my_dict
556
557 # Output from aforementioned code:
Michael Walsh410b1782019-10-22 15:56:18 -0500558 # my_dict: first Joe last Montana age 50
Michael Walsh37c74f72018-02-15 17:12:25 -0600559 # my_dict:
560 # my_dict[first]: Joe
561 # my_dict[last]: Montana
562 # my_dict[age]: 50
563
Michael Walsh410b1782019-10-22 15:56:18 -0500564 # As far as print_var is concerned, my_dict is a string and is printed accordingly. By using print_dict,
565 # the programmer is asking to have the output shown as a dictionary with dictionary keys/values, etc.
Michael Walsh37c74f72018-02-15 17:12:25 -0600566
567 # Determine who our caller is and therefore what upvar_level to use.
568 set stack_ix_adjust [calc_wrap_stack_ix_adjust]
569 set upvar_level [expr $stack_ix_adjust + 1]
570 upvar $upvar_level $var_name var_value
571
572 set indent [lindex $args 0]
573 set args [lrange $args 1 end]
574 set_var_default indent 0
575
576 append buffer [format "%-${indent}s%s\n" "" "$var_name:"]
577 incr indent 2
578
579 foreach {key value} $var_value {
580 append buffer [sprint_varx "${var_name}\[${key}\]" $value $indent {*}$args]
581 incr index
582 }
583
584 return $buffer
585
586}
587
588
589proc sprint_vars { args } {
590
591 # Sprint the values of one or more variables.
592
593 # Description of arg(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500594 # args: A list of variable names to be printed. The first argument in the arg list found to be an
595 # integer (rather than a variable name) will be interpreted to be first of several possible sprint_var
596 # arguments (e.g. indent, width, hex). See the prologue for sprint_var above for descriptions of this
597 # variables.
Michael Walsh37c74f72018-02-15 17:12:25 -0600598
599 # Example usage:
600 # set var1 "hello"
601 # set var2 "there"
602 # set indent 2
603 # set buffer [sprint_vars var1 var2]
604 # or...
605 # set buffer [sprint_vars var1 var2 $indent]
606
607 # Look for integer arguments.
608 set first_int_ix [lsearch -regexp $args {^[0-9]+$}]
609 if { $first_int_ix == -1 } {
610 # If none are found, sub_args is set to empty.
611 set sub_args {}
612 } else {
613 # Set sub_args to the portion of the arg list that are integers.
614 set sub_args [lrange $args $first_int_ix end]
615 # Re-set args to exclude the integer values.
616 set args [lrange $args 0 [expr $first_int_ix - 1]]
617 }
618
619 foreach arg $args {
620 append buffer [sprint_var $arg {*}$sub_args]
621 }
622
623 return $buffer
624
625}
626
627
628proc sprint_dashes { { indent 0 } { width 80 } { line_feed 1 } { char "-" } } {
629
630 # Return a string of dashes to the caller.
631
632 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500633 # indent The number of characters to indent the output.
Michael Walsh37c74f72018-02-15 17:12:25 -0600634 # width The width of the string of dashes.
Michael Walsh410b1782019-10-22 15:56:18 -0500635 # line_feed Indicates whether the output should end with a line feed.
636 # char The character to be repeated in the output string. In other words, you
637 # can call on this function to print a string of any character (e.g. "=",
638 # "_", etc.).
Michael Walsh37c74f72018-02-15 17:12:25 -0600639
640 set_var_default indent 0
641 set_var_default width 80
642 set_var_default line_feed 1
643
644 append buffer [string repeat " " $indent][string repeat $char $width]
645 append buffer [string repeat "\n" $line_feed]
646
647 return $buffer
648
649}
650
651
652proc sprint_executing {{ include_args 1 }} {
653
654 # Return a string that looks something like this:
655 # #(CST) 2017/11/28 15:08:03.261466 - 0.015214 - Executing: proc1 hi
656
657 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500658 # include_args Indicates whether proc args should be included in the result.
Michael Walsh37c74f72018-02-15 17:12:25 -0600659
660 set stack_ix_adjust [calc_wrap_stack_ix_adjust]
661 set level [expr -(2 + $stack_ix_adjust)]
662 return "[sprint_time]Executing: [get_stack_proc_name $level $include_args]\n"
663
664}
665
666
667proc sprint_issuing { { cmd_buf "" } { test_mode 0 } } {
668
669 # Return a line indicating a command that the program is about to execute.
670
671 # Sample output for a cmd_buf of "ls"
672
673 # #(CDT) 2016/08/25 17:57:36 - Issuing: ls
674
675 # Description of arg(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500676 # cmd_buf The command to be executed by caller. If this is blank, this procedure
677 # will search up the stack for the first cmd_buf value to use.
678 # test_mode With test_mode set, your output will look like this:
Michael Walsh37c74f72018-02-15 17:12:25 -0600679
680 # #(CDT) 2016/08/25 17:57:36 - (test_mode) Issuing: ls
681
682 if { $cmd_buf == "" } {
683 set cmd_buf [get_stack_var cmd_buf {} 2]
684 }
685
686 append buffer [sprint_time]
687 if { $test_mode } {
688 append buffer "(test_mode) "
689 }
690 append buffer "Issuing: ${cmd_buf}\n"
691
692 return $buffer
693
694}
695
696
697proc sprint_call_stack { { indent 0 } } {
698
Michael Walsh410b1782019-10-22 15:56:18 -0500699 # Return a call stack report for the given point in the program with line numbers, procedure names and
700 # procedure parameters and arguments.
Michael Walsh37c74f72018-02-15 17:12:25 -0600701
702 # Sample output:
703
704 # ---------------------------------------------------------------------------
705 # TCL procedure call stack
706
707 # Line # Procedure name and arguments
708 # ------ --------------------------------------------------------------------
709 # 21 print_call_stack
710 # 32 proc1 257
711 # ---------------------------------------------------------------------------
712
713 # Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500714 # indent The number of characters to indent each line of output.
Michael Walsh37c74f72018-02-15 17:12:25 -0600715
716 append buffer "[sprint_dashes ${indent}]"
717 append buffer "[string repeat " " $indent]TCL procedure call stack\n\n"
718 append buffer "[string repeat " " $indent]"
719 append buffer "Line # Procedure name and arguments\n"
720 append buffer "[sprint_dashes $indent 6 0] [sprint_dashes 0 73]"
721
722 for {set ix [expr [info level]-1]} {$ix > 0} {incr ix -1} {
723 set frame_dict [info frame $ix]
724 set line_num [dict get $frame_dict line]
725 set proc_name_plus_args [dict get $frame_dict cmd]
726 append buffer [format "%-${indent}s%6i %s\n" "" $line_num\
727 $proc_name_plus_args]
728 }
729 append buffer "[sprint_dashes $indent]"
730
731 return $buffer
732
733}
734
735
736proc sprint_tcl_version {} {
737
738 # Return the name and value of tcl_version in a formatted way.
739
740 global tcl_version
741
742 return [sprint_var tcl_version]
743
744}
745
746
747proc sprint_error_report { { error_text "\n" } { indent 0 } } {
748
Michael Walsh410b1782019-10-22 15:56:18 -0500749 # Return a string with a standardized report which includes the caller's error text, the call stack and
750 # the program header.
Michael Walsh37c74f72018-02-15 17:12:25 -0600751
752 # Description of arg(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500753 # error_text The error text to be included in the report. The caller should include
754 # any needed linefeeds.
755 # indent The number of characters to indent each line of output.
Michael Walsh37c74f72018-02-15 17:12:25 -0600756
757 set width 120
758 set char "="
759 set line_feed 1
760 append buffer [sprint_dashes $indent $width $line_feed $char]
761 append buffer [string repeat " " $indent][sprint_error $error_text]
762 append buffer "\n"
763 append buffer [sprint_call_stack $indent]
764 append buffer [sprint_pgm_header $indent]
765 append buffer [sprint_dashes $indent $width $line_feed $char]
766
767 return $buffer
768
769}
770
771
772proc sprint_pgm_header { {indent 0} {linefeed 1} } {
773
Michael Walsh410b1782019-10-22 15:56:18 -0500774 # Return a standardized header that programs should print at the beginning of the run. It includes useful
775 # information like command line, pid, userid, program parameters, etc.
Michael Walsh37c74f72018-02-15 17:12:25 -0600776
777 # Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500778 # indent The number of characters to indent each line of output.
779 # linefeed Indicates whether a line feed be included at the beginning and end of the
780 # report.
Michael Walsh37c74f72018-02-15 17:12:25 -0600781
782 global program_name
783 global pgm_name_var_name
784 global argv0
785 global argv
786 global env
787 global _gtp_default_print_var_width_
788
789 set_var_default indent 0
790
791 set indent_str [string repeat " " $indent]
792 set width [expr $_gtp_default_print_var_width_ + $indent]
793
794 # Get variable values for output.
795 set command_line "$argv0 $argv"
796 set pid_var_name ${pgm_name_var_name}_pid
797 set $pid_var_name [pid]
798 set uid [get_var ::env(USER) 0]
799 set host_name [get_var ::env(HOSTNAME) 0]
800 set DISPLAY [get_var ::env(DISPLAY) 0]
801
802 # Generate the report.
803 if { $linefeed } { append buffer "\n" }
804 append buffer ${indent_str}[sprint_timen "Running ${program_name}."]
805 append buffer ${indent_str}[sprint_timen "Program parameter values, etc.:\n"]
806 append buffer [sprint_var command_line $indent $width]
807 append buffer [sprint_var $pid_var_name $indent $width]
808 append buffer [sprint_var uid $indent $width]
809 append buffer [sprint_var host_name $indent $width]
810 append buffer [sprint_var DISPLAY $indent $width]
811
812 # Print caller's parm names/values.
813 global longoptions
814 global pos_parms
815
816 regsub -all ":" "${longoptions} ${pos_parms}" {} parm_names
817
818 foreach parm_name $parm_names {
819 set cmd_buf "global $parm_name ; append buffer"
820 append cmd_buf " \[sprint_var $parm_name $indent $width\]"
821 eval $cmd_buf
822 }
823
824 if { $linefeed } { append buffer "\n" }
825
826 return $buffer
827
828}
829
830
831proc sprint_pgm_footer {} {
832
Michael Walsh410b1782019-10-22 15:56:18 -0500833 # Return a standardized footer that programs should print at the end of the program run. It includes
834 # useful information like total run time, etc.
Michael Walsh37c74f72018-02-15 17:12:25 -0600835
836 global program_name
837 global pgm_name_var_name
838 global start_time
839
840 # Calculate total runtime.
841 set total_time_micro [expr [clock microseconds] - $start_time]
842 # Break the left and right of the decimal point.
843 set total_seconds [expr $total_time_micro / 1000000]
844 set total_decimal_micro [expr $total_time_micro % 1000000]
845 set total_time_float [format "%i.%06i" ${total_seconds}\
846 ${total_decimal_micro}]
847 set total_time_string [format "%0.6f" $total_time_float]
848 set runtime_var_name ${pgm_name_var_name}_runtime
849 set $runtime_var_name $total_time_string
850
851 append buffer [sprint_timen "Finished running ${program_name}."]
852 append buffer "\n"
853 append buffer [sprint_var $runtime_var_name]
854 append buffer "\n"
855
856 return $buffer
857
858}
859
860
861proc sprint_arg_desc { arg_title arg_desc { indent 0 } { col1_width 25 }\
862 { line_width 80 } } {
863
864 # Return a formatted argument description.
865
866 # Example:
867 #
Michael Walsh410b1782019-10-22 15:56:18 -0500868 # set desc "When in the Course of human events, it becomes necessary for one people to dissolve the
869 # political bands which have connected them with another, and to assume among the powers of the earth, the
870 # separate and equal station to which the Laws of Nature and of Nature's God entitle them, a decent respect
871 # to the opinions of mankind requires that they should declare the causes which impel them to the
872 # separation."
Michael Walsh37c74f72018-02-15 17:12:25 -0600873
874 # set buffer [sprint_arg_desc "--declaration" $desc]
875 # puts $buffer
876
877 # Resulting output:
878 # --declaration When in the Course of human events, it becomes
879 # necessary for one people to dissolve the
880 # political bands which have connected them with
881 # another, and to assume among the powers of the
882 # earth, the separate and equal station to which
883 # the Laws of Nature and of Nature's God entitle
884 # them, a decent respect to the opinions of mankind
885 # requires that they should declare the causes
886 # which impel them to the separation.
887
888 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500889 # arg_title The content that you want to appear on the first line in column 1.
Michael Walsh37c74f72018-02-15 17:12:25 -0600890 # arg_desc The text that describes the argument.
891 # indent The number of characters to indent.
Michael Walsh410b1782019-10-22 15:56:18 -0500892 # col1_width The width of column 1, which is the column containing the arg_title.
Michael Walsh37c74f72018-02-15 17:12:25 -0600893 # line_width The total max width of each line of output.
894
895 set fold_width [expr $line_width - $col1_width]
896 set escaped_arg_desc [escape_bash_quotes "${arg_desc}"]
897
898 set cmd_buf "echo '${escaped_arg_desc}' | fold --spaces --width="
899 append cmd_buf "${fold_width} | sed -re 's/\[ \]+$//g'"
900 set out_buf [eval exec bash -c {$cmd_buf}]
901
902 set help_lines [split $out_buf "\n"]
903
904 set buffer {}
905
906 set line_num 1
907 foreach help_line $help_lines {
908 if { $line_num == 1 } {
909 if { [string length $arg_title] > $col1_width } {
910 # If the arg_title is already wider than column1, print it on its own
911 # line.
912 append buffer [format "%${indent}s%-${col1_width}s\n" ""\
913 "$arg_title"]
914 append buffer [format "%${indent}s%-${col1_width}s%s\n" "" ""\
915 "${help_line}"]
916 } else {
917 append buffer [format "%${indent}s%-${col1_width}s%s\n" ""\
918 "$arg_title" "${help_line}"]
919 }
920 } else {
921 append buffer [format "%${indent}s%-${col1_width}s%s\n" "" ""\
922 "${help_line}"]
923 }
924 incr line_num
925 }
926
927 return $buffer
928
929}
930
931
932# Define the create_print_wrapper_procs to help us create print wrappers.
933# First, create templates.
934# Notes:
935# - The resulting procedures will replace all registered passwords.
Michael Walsh410b1782019-10-22 15:56:18 -0500936# - The resulting "quiet" and "debug" print procedures will search the stack for quiet and debug,
937# respectively. That means that the if a procedure calls qprint_var and the procedure has a local version
938# 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 -0600939set print_proc_template " puts -nonewline<output_stream> \[replace_passwords"
940append print_proc_template " \[<base_proc_name> {*}\$args\]\]\n}\n"
941set qprint_proc_template " set quiet \[get_stack_var quiet 0\]\n if {"
942append qprint_proc_template " \$quiet } { return }\n${print_proc_template}"
943set dprint_proc_template " set debug \[get_stack_var debug 0\]\n if { !"
944append dprint_proc_template " \$debug } { return }\n${print_proc_template}"
945
946# Put each template into the print_proc_templates array.
947set print_proc_templates(p) $print_proc_template
948set print_proc_templates(q) $qprint_proc_template
949set print_proc_templates(d) $dprint_proc_template
950proc create_print_wrapper_procs {proc_names {stderr_proc_names {}} } {
951
Michael Walsh410b1782019-10-22 15:56:18 -0500952 # Generate code for print wrapper procs and return the generated code as a string.
Michael Walsh37c74f72018-02-15 17:12:25 -0600953
Michael Walsh410b1782019-10-22 15:56:18 -0500954 # To illustrate, suppose there is a "print_foo_bar" proc in the proc_names list.
Michael Walsh37c74f72018-02-15 17:12:25 -0600955 # This proc will...
956 # - Expect that there is an sprint_foo_bar proc already in existence.
Michael Walsh410b1782019-10-22 15:56:18 -0500957 # - Create a print_foo_bar proc which calls sprint_foo_bar and prints the result.
958 # - Create a qprint_foo_bar proc which calls upon sprint_foo_bar only if global value quiet is 0.
959 # - 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 -0600960
Michael Walsh410b1782019-10-22 15:56:18 -0500961 # Also, code will be generated to define aliases for each proc as well. Each alias will be created by
962 # 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 -0600963
964 # Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500965 # proc_names A list of procs for which print wrapper proc code is to be generated.
966 # stderr_proc_names A list of procs whose generated code should print to stderr rather than
967 # to stdout.
Michael Walsh37c74f72018-02-15 17:12:25 -0600968
969 global print_proc_template
970 global print_proc_templates
971
972 foreach proc_name $proc_names {
973
974 if { [expr [lsearch $stderr_proc_names $proc_name] == -1] } {
975 set replace_dict(output_stream) ""
976 } else {
977 set replace_dict(output_stream) " stderr"
978 }
979
980 set base_proc_name "s${proc_name}"
981 set replace_dict(base_proc_name) $base_proc_name
982
983 set wrap_proc_names(p) $proc_name
984 set wrap_proc_names(q) q${proc_name}
985 set wrap_proc_names(d) d${proc_name}
986
987 foreach template_key [list p q d] {
988 set wrap_proc_name $wrap_proc_names($template_key)
989 set call_line "proc ${wrap_proc_name} \{args\} \{\n"
990 set proc_body $print_proc_templates($template_key)
991 set proc_def ${call_line}${proc_body}
992 foreach {key value} [array get replace_dict] {
993 regsub -all "<$key>" $proc_def $value proc_def
994 }
995 regsub "print_" $wrap_proc_name "p" alias_proc_name
996 regsub "${wrap_proc_name}" $proc_def $alias_proc_name alias_def
997 append buffer "${proc_def}${alias_def}"
998 }
999 }
1000
1001 return $buffer
1002
1003}
1004
1005
1006# Get this file's path.
1007set frame_dict [info frame 0]
1008set file_path [dict get $frame_dict file]
1009# Get a list of this file's sprint procs.
1010set sprint_procs [get_file_proc_names $file_path sprint]
1011# Create a corresponding list of print_procs.
1012set proc_names [list_map $sprint_procs {[string range $x 1 end]}]
1013# Sort them for ease of debugging.
1014set proc_names [lsort $proc_names]
1015
1016set stderr_proc_names [list print_error print_error_report]
1017
1018set proc_def [create_print_wrapper_procs $proc_names $stderr_proc_names]
1019if { $GEN_PRINT_DEBUG } { puts $proc_def }
1020eval "${proc_def}"