1#!/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}" 1088