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