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