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