1#!/usr/bin/wish
2
3# This file provides many valuable print procedures such as sprint_var, sprint_time, sprint_error, etc.
4
5my_source [list data_proc.tcl call_stack.tcl]
6
7# Need "Expect" package for trap procedure.
8package require Expect
9
10
11# Setting the following variables for use both inside this file and by programs sourcing this file.
12set program_path $argv0
13set program_dir_path "[file dirname $argv0]/"
14set program_name "[file tail $argv0]"
15# 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.
17regsub {\.} $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
25  # Calculate and return a number which can be used as an offset into the call stack for wrapper procedures.
26
27  # 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.
30
31  # Get a list of the procedures in the call stack beginning with our immediate caller on up to the
32  # top-level caller.
33  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]
39  # Strip any leading "s" to arrive at base_caller name (e.g. the corresponding base name for "sprint_var"
40  # would be "print_var").
41  set base_caller [string trimleft $caller s]
42  # Account for alias print procedures which have "p" vs "print_" (e.g. pvar vs print_var).
43  regsub "print_" $base_caller "p" alias_base_caller
44
45  # Initialize the stack_ix_adjust value.
46  set stack_ix_adjust 0
47  # Note: print_vars|pvars is a special case so we add it explicitly to the regex below.
48  set regex ".*(${base_caller}|${alias_base_caller}|print_vars|pvars)$"
49  foreach proc_name $call_stack {
50    # For every remaining stack item that looks like a wrapper (i.e. matches our regex), we increment the
51    # stack_ix_adjust.
52    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
65# hidden_text is a list of passwords which are to be replaced with asterisks by print procedures defined in
66# this file.
67set 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
73  # Register one or more passwords which are to be hidden in output produced by the print procedures in this
74  # file.
75
76  # Note: Blank password values are NOT registered.  They are simply ignored.
77
78  # Description of argument(s):
79  # 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.
82
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
95  # TODO: Escape metachars in the password_regex.
96  set password_regex [join $hidden_text |]
97
98}
99
100
101proc replace_passwords {buffer} {
102
103  # Replace all registered password found in buffer with a string of asterisks and return the result.
104
105  # Description of argument(s):
106  # buffer                          The string to be altered and returned.
107
108  # Note:  If environment variable GEN_PRINT_DEBUG is set, this procedure will do nothing.
109
110  global env
111  if { [get_var ::env(GEN_PRINT_DEBUG) 0] } { return $buffer }
112  if { [get_var ::env(DEBUG_SHOW_PASSWORDS) 0] } { return $buffer }
113
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.
134  # iterations                      The number of times to run the command string.  Typically, more
135  #                                 iterations yields more accurate results.
136
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
152# 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.
154set GEN_PRINT_DEBUG [get_var ::env(GEN_PRINT_DEBUG) 0]
155
156# 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.
158set NANOSECONDS [get_var ::env(NANOSECONDS) 0]
159set SHOW_ELAPSED_TIME [get_var ::env(SHOW_ELAPSED_TIME) 0]
160
161# _gtp_default_print_var_width_ is adjusted based on NANOSECONDS and SHOW_ELAPSED_TIME.
162if { $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
171  # Initializing _sprint_time_last_seconds_ which is a global value to remember the clock seconds from the
172  # last time sprint_time was called.
173  set _gtp_sprint_time_last_micro_seconds_ [clock microseconds]
174}
175# tcl_precision is a built-in Tcl variable that specifies the number of digits to generate when converting
176# floating-point values to strings.
177set tcl_precision 17
178
179
180proc sprint { { buffer {} } } {
181
182  # Simply return the user's buffer.
183  # 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.
185
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..
197  # 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.
199
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
233  # 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.
238
239  # #(CDT) 2016/08/03 17:16:25.510469 - Hi.
240
241  # 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.
246
247  # #(CDT) 2016/08/03 17:17:40 -    0 - Hi.
248
249  # Example of the output when environment variable NANOSECONDS=1 and SHOW_ELAPSED_TIME=1.
250
251  # #(CDT) 2016/08/03 17:18:47.317339 -    0.000046 - Hi.
252
253  # Description of argument(s).
254  # buffer                          A string string whhich is to be appended to the formatted time string and
255  #                                 returned.
256
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):
307  # args                            All args are passed directly to subordinate function, sprint_time.  See
308  #                                 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):
320  # buffer                          Text to be returned as part of the error message.
321
322  return [sprint_time "**ERROR** $buffer"]
323
324}
325
326
327proc sprint_varx { var_name var_value { indent 0 } { width {} } { hex 0 } } {
328
329  # Return the name and value of the variable named in var_name in a formatted way.
330
331  # This procedure will visually align the output to look good next to print_time output.
332
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
345  # With environment variables NANOSECONDS and SHOW_ELAPSED_TIME both set, the following output is produced:
346
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):
353  # var_name                        The name of the variable whose name and value are to be printed.
354  # var_value                       The value to be printed.
355  # 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".
362
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
396  # Return the name and value of the variable named in var_name in a formatted way.
397
398  # This procedure will visually align the output to look good next to print_time output.
399
400  # 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.
403
404  # Note: This procedure will detect whether var_name is an array and print it accordingly (see the second
405  # example below).
406
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
419  # With environment variables NANOSECONDS and SHOW_ELAPSED_TIME both set, the following output is produced:
420
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):
438  # 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
440  #                                 sprint_varx's prolog for details.
441
442  # Note: This procedure relies on global var _gtp_default_print_var_width_
443
444  # Determine who our caller is and therefore what upvar_level to use to get var_value.
445  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
470  # If var_value is not defined, catch the error and print its value as "variable not set".
471  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  }
482
483}
484
485
486proc sprint_list { var_name args } {
487
488  # Return the name and value of the list variable named in var_name in a formatted way.
489
490  # This procedure is the equivalent of sprint_var but for lists.
491
492  # Description of argument(s):
493  # 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
495  #                                 sprint_varx's prolog for details.
496
497  # 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:
500
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
512  # 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.
514
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
541  # Return the name and value of the dictionary variable named in var_name in a formatted way.
542
543  # This procedure is the equivalent of sprint_var but for dictionaries.
544
545  # Description of argument(s):
546  # 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
548  #                                 sprint_varx's prolog for details.
549
550  # 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:
553
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:
559  # my_dict:                                         first Joe last Montana age 50
560  # my_dict:
561  #  my_dict[first]:                                 Joe
562  #  my_dict[last]:                                  Montana
563  #  my_dict[age]:                                   50
564
565  # 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.
567
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):
595  # 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.
599
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):
634  # indent                          The number of characters to indent the output.
635  # width                           The width of the string of dashes.
636  # 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.).
640
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):
659  # include_args                    Indicates whether proc args should be included in the result.
660
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):
677  # 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:
680
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
700  # Return a call stack report for the given point in the program with line numbers, procedure names and
701  # procedure parameters and arguments.
702
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:
715  # indent                          The number of characters to indent each line of output.
716
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
750  # Return a string with a standardized report which includes the caller's error text, the call stack and
751  # the program header.
752
753  # Description of arg(s):
754  # 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.
757
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
775  # 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.
777
778  # Description of arguments:
779  # 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.
782
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
834  # 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.
836
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  #
869  # 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."
874
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):
890  # arg_title                       The content that you want to appear on the first line in column 1.
891  # arg_desc                        The text that describes the argument.
892  # indent                          The number of characters to indent.
893  # col1_width                      The width of column 1, which is the column containing the arg_title.
894  # 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.
937# - 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.
940set 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
953  # Generate code for print wrapper procs and return the generated code as a string.
954
955  # To illustrate, suppose there is a "print_foo_bar" proc in the proc_names list.
956  # This proc will...
957  # - Expect that there is an sprint_foo_bar proc already in existence.
958  # - 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.
961
962  # 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.
964
965  # Description of argument(s):
966  # 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.
969
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}"
1022