blob: aab6a62e9230eb1e900b9dc9e9fdd96dfa1fd2d9 [file] [log] [blame]
#!/usr/bin/wish
# This file provides many valuable print procedures such as sprint_var, sprint_time, sprint_error, etc.
my_source [list data_proc.tcl call_stack.tcl escape.tcl]
# Need "Expect" package for trap procedure.
package require Expect
# Setting the following variables for use both inside this file and by programs sourcing this file.
set program_path $argv0
set program_dir_path "[file dirname $argv0]/"
set program_name "[file tail $argv0]"
# Some procedures (e.g. sprint_pgm_header) need a program name value that looks more like a valid variable
# name. Therefore, we'll swap out odd characters (like ".") for underscores.
regsub {\.} $program_name "_" pgm_name_var_name
# Initialize some time variables used in procedures in this file.
set start_time [clock microseconds]
proc calc_wrap_stack_ix_adjust {} {
# Calculate and return a number which can be used as an offset into the call stack for wrapper procedures.
# NOTE: This procedure is designed expressly to work with this file's print procedures scheme (i.e.
# print_x is a wrapper for sprint_x, etc.). In other words, this procedure may not be well-suited for
# general use.
# Get a list of the procedures in the call stack beginning with our immediate caller on up to the
# top-level caller.
set call_stack [get_call_stack -2]
# The first stack entry is our immediate caller.
set caller [lindex $call_stack 0]
# Remove first entry from stack.
set call_stack [lreplace $call_stack 0 0]
# Strip any leading "s" to arrive at base_caller name (e.g. the corresponding base name for "sprint_var"
# would be "print_var").
set base_caller [string trimleft $caller s]
# Account for alias print procedures which have "p" vs "print_" (e.g. pvar vs print_var).
regsub "print_" $base_caller "p" alias_base_caller
# Initialize the stack_ix_adjust value.
set stack_ix_adjust 0
# Note: print_vars|pvars is a special case so we add it explicitly to the regex below.
set regex ".*(${base_caller}|${alias_base_caller}|print_vars|pvars)$"
foreach proc_name $call_stack {
# For every remaining stack item that looks like a wrapper (i.e. matches our regex), we increment the
# stack_ix_adjust.
if { [regexp -expanded $regex $proc_name]} {
incr stack_ix_adjust
continue
}
# If there is no match, then we are done.
break
}
return $stack_ix_adjust
}
# hidden_text is a list of passwords which are to be replaced with asterisks by print procedures defined in
# this file.
set hidden_text [list]
# password_regex is created from the contents of the hidden_text list above.
set password_regex ""
proc register_passwords {args} {
# Register one or more passwords which are to be hidden in output produced by the print procedures in this
# file.
# Note: Blank password values are NOT registered. They are simply ignored.
# Description of argument(s):
# args One or more password values. If a given password value is already
# registered, this procedure will simply ignore it, i.e. there will be no
# duplicate values in the hidden_text list.
global hidden_text
global password_regex
foreach password $args {
# Skip blank passwords.
if { $password == "" } { continue }
# Skip already-registered passwords.
if { [lsearch -exact $hidden_text $password] != -1 } { continue }
# Put the password into the global hidden_text list.
lappend hidden_text [escape_regex_metachars $password]
}
set password_regex [join $hidden_text |]
}
proc replace_passwords {buffer} {
# Replace all registered password found in buffer with a string of asterisks and return the result.
# Description of argument(s):
# buffer The string to be altered and returned.
# Note: If environment variable GEN_PRINT_DEBUG is set, this procedure will do nothing.
global env
if { [get_var ::env(GEN_PRINT_DEBUG) 0] } { return $buffer }
if { [get_var ::env(DEBUG_SHOW_PASSWORDS) 0] } { return $buffer }
global password_regex
# No passwords to replace?
if { $password_regex == "" } { return $buffer }
regsub -all "${password_regex}" $buffer {********} buffer
return $buffer
}
proc my_time { cmd_buf { iterations 100 } } {
# Run the "time" function on the given command string and print the results.
# The main benefit of running this vs just doing the "time" command directly:
# - This will print the results.
# Description of argument(s):
# cmd_buf The command string to be run.
# iterations The number of times to run the command string. Typically, more
# iterations yields more accurate results.
print_issuing $cmd_buf
set result [time {uplevel 1 $cmd_buf} $iterations]
set raw_microseconds [lindex [split [lindex $result 0] .] 0]
set seconds [expr $raw_microseconds / 1000000]
set raw_microseconds [expr $raw_microseconds % 1000000]
set seconds_per_iteration [format "%i.%06i" ${seconds}\
${raw_microseconds}]
print_var seconds_per_iteration
}
# If environment variable "GEN_PRINT_DEBUG" is set, this module will output debug data. This is primarily
# intended for the developer of this module.
set GEN_PRINT_DEBUG [get_var ::env(GEN_PRINT_DEBUG) 0]
# The user can set the following environment variables to influence the output from print_time and print_var
# procedures. See the prologs of those procedures for details.
set NANOSECONDS [get_var ::env(NANOSECONDS) 0]
set SHOW_ELAPSED_TIME [get_var ::env(SHOW_ELAPSED_TIME) 0]
# _gtp_default_print_var_width_ is adjusted based on NANOSECONDS and SHOW_ELAPSED_TIME.
if { $NANOSECONDS } {
set _gtp_default_print_var_width_ 36
set width_incr 14
} else {
set _gtp_default_print_var_width_ 29
set width_incr 7
}
if { $SHOW_ELAPSED_TIME } {
incr _gtp_default_print_var_width_ $width_incr
# Initializing _sprint_time_last_seconds_ which is a global value to remember the clock seconds from the
# last time sprint_time was called.
set _gtp_sprint_time_last_micro_seconds_ [clock microseconds]
}
# tcl_precision is a built-in Tcl variable that specifies the number of digits to generate when converting
# floating-point values to strings.
set tcl_precision 17
proc sprint { { buffer {} } } {
# Simply return the user's buffer.
# This procedure is used by the qprint and dprint functions defined dynamically below, i.e. it would not
# normally be called for general use.
# Description of arguments.
# buffer This will be returned to the caller.
return $buffer
}
proc sprintn { { buffer {} } } {
# Simply return the user's buffer plus a trailing line feed..
# This procedure is used by the qprintn and dprintn functions defined dynamically below, i.e. it would not
# normally be called for general use.
# Description of arguments.
# buffer This will be returned to the caller.
return ${buffer}\n
}
proc sprint_time { { buffer {} } } {
# Return the time in a formatted manner as described below.
# Example:
# The following tcl code...
# puts -nonewline [sprint_time()]
# puts -nonewline ["Hi.\n"]
# Will result in the following type of output:
# #(CDT) 2016/07/08 15:25:35 - Hi.
# Example:
# The following tcl code...
# puts -nonewline [sprint_time("Hi.\n")]
# Will result in the following type of output:
# #(CDT) 2016/08/03 17:12:05 - Hi.
# The following environment variables will affect the formatting as described:
# NANOSECONDS This will cause the time stamps to be precise to the microsecond (Yes, it
# probably should have been named MICROSECONDS but the convention was set
# long ago so we're sticking with it). Example of the output when
# environment variable NANOSECONDS=1.
# #(CDT) 2016/08/03 17:16:25.510469 - Hi.
# SHOW_ELAPSED_TIME This will cause the elapsed time to be included in the output. This is
# the amount of time that has elapsed since the last time this procedure
# was called. The precision of the elapsed time field is also affected by
# the value of the NANOSECONDS environment variable. Example of the output
# when environment variable NANOSECONDS=0 and SHOW_ELAPSED_TIME=1.
# #(CDT) 2016/08/03 17:17:40 - 0 - Hi.
# Example of the output when environment variable NANOSECONDS=1 and SHOW_ELAPSED_TIME=1.
# #(CDT) 2016/08/03 17:18:47.317339 - 0.000046 - Hi.
# Description of argument(s).
# buffer A string string whhich is to be appended to the formatted time string and
# returned.
global NANOSECONDS
global _gtp_sprint_time_last_micro_seconds_
global SHOW_ELAPSED_TIME
# Get micro seconds since the epoch.
set epoch_micro [clock microseconds]
# Break the left and right of the decimal point.
set epoch_seconds [expr $epoch_micro / 1000000]
set epoch_decimal_micro [expr $epoch_micro % 1000000]
set format_string "#(%Z) %Y/%m/%d %H:%M:%S"
set return_string [clock format $epoch_seconds -format\
"#(%Z) %Y/%m/%d %H:%M:%S"]
if { $NANOSECONDS } {
append return_string ".[format "%06i" ${epoch_decimal_micro}]"
}
if { $SHOW_ELAPSED_TIME } {
set return_string "${return_string} - "
set elapsed_micro [expr $epoch_micro - \
$_gtp_sprint_time_last_micro_seconds_]
set elapsed_seconds [expr $elapsed_micro / 1000000]
if { $NANOSECONDS } {
set elapsed_decimal_micro [expr $elapsed_micro % 1000000]
set elapsed_float [format "%i.%06i" ${elapsed_seconds}\
${elapsed_decimal_micro}]
set elapsed_time_buffer "[format "%11.6f" ${elapsed_float}]"
} else {
set elapsed_time_buffer "[format "%4i" $elapsed_seconds]"
}
set return_string "${return_string}${elapsed_time_buffer}"
}
set return_string "${return_string} - ${buffer}"
set _gtp_sprint_time_last_micro_seconds_ $epoch_micro
return $return_string
}
proc sprint_timen { args } {
# Return the value of sprint_time + a line feed.
# Description of argument(s):
# args All args are passed directly to subordinate function, sprint_time. See
# that function's prolog for details.
return [sprint_time {*}$args]\n
}
proc sprint_error { { buffer {} } } {
# Return a standardized error string which includes the callers buffer text.
# Description of argument(s):
# buffer Text to be returned as part of the error message.
return [sprint_time "**ERROR** $buffer"]
}
proc sprint_varx { var_name var_value { indent 0 } { width {} } { hex 0 } } {
# Return the name and value of the variable named in var_name in a formatted way.
# This procedure will visually align the output to look good next to print_time output.
# Example:
# Given the following code:
# print_timen "Initializing variables."
# set first_name "Joe"
# set last_name "Montana"
# set age 50
# print_varx last_name $last_name
# print_varx first_name $first_name 2
# print_varx age $age 2
# With environment variables NANOSECONDS and SHOW_ELAPSED_TIME both set, the following output is produced:
# #(CST) 2017/12/14 16:38:28.259480 - 0.000651 - Initializing variables.
# last_name: Montana
# first_name: Joe
# age: 50
# Description of argument(s):
# var_name The name of the variable whose name and value are to be printed.
# var_value The value to be printed.
# indent The number of spaces to indent each line of output.
# width The width of the column containing the variable name. By default this
# will align with the print_time text (see example above).
# hex Indicates that the variable value is to be printed in hexedecimal format.
# This is only valid if the variable value is an integer. If the variable
# is NOT an integer and is blank, this will be interpreted to mean "print
# the string '<blank>', rather than an actual blank value".
# Note: This procedure relies on global var _gtp_default_print_var_width_
set_var_default indent 0
global _gtp_default_print_var_width_
set_var_default width $_gtp_default_print_var_width_
if { $indent > 0 } {
set width [expr $width - $indent]
}
if { $hex } {
if { [catch {format "0x%08x" "$var_value"} result] } {
if { $var_value == "" } { set var_value "<blank>" }
set hex 0
}
}
if { $hex } {
append buffer "[format "%-${indent}s%-${width}s0x%08x" "" "$var_name:" \
"$var_value"]"
} else {
append buffer "[format "%-${indent}s%-${width}s%s" "" "$var_name:" \
"$var_value"]"
}
return $buffer\n
}
proc sprint_var { var_name args } {
# Return the name and value of the variable named in var_name in a formatted way.
# This procedure will visually align the output to look good next to print_time output.
# Note: This procedure is the equivalent of sprint_varx with one difference: This function will figure
# out the value of the named variable whereas sprint_varx expects you to pass the value. This procedure in
# fact calls sprint_varx to do its work.
# Note: This procedure will detect whether var_name is an array and print it accordingly (see the second
# example below).
# Example:
# Given the following code:
# print_timen "Initializing variables."
# set first_name "Joe"
# set last_name "Montana"
# set age 50
# print_var last_name
# print_var first_name 2
# print_var age 2
# With environment variables NANOSECONDS and SHOW_ELAPSED_TIME both set, the following output is produced:
# #(CST) 2017/12/14 16:38:28.259480 - 0.000651 - Initializing variables.
# last_name: Montana
# first_name: Joe
# age: 50
# Example:
# Given the following code:
# set data(0) cow
# set data(1) horse
# print_var data
# data:
# data(0): cow
# data(1): horse
# Description of argument(s):
# var_name The name of the variable whose name and value are to be printed.
# args The args understood by sprint_varx (after var_name and var_value). See
# sprint_varx's prolog for details.
# Note: This procedure relies on global var _gtp_default_print_var_width_
# Determine who our caller is and therefore what upvar_level to use to get var_value.
set stack_ix_adjust [calc_wrap_stack_ix_adjust]
set upvar_level [expr $stack_ix_adjust + 1]
upvar $upvar_level $var_name var_value
# Special processing for arrays:
if { [array exists var_value] } {
set indent [lindex $args 0]
set args [lrange $args 1 end]
set_var_default indent 0
append buffer [format "%-${indent}s%s\n" "" "$var_name:"]
incr indent 2
incr width -2
set search_token [array startsearch var_value]
while {[array anymore var_value $search_token]} {
set key [array nextelement var_value $search_token]
set arr_value $var_value($key)
append buffer [sprint_varx "${var_name}(${key})" $arr_value $indent\
{*}$args]
}
array donesearch var_value $search_token
return $buffer
}
# If var_value is not defined, catch the error and print its value as "variable not set".
if {[catch {set buffer [sprint_varx $var_name $var_value {*}$args]} error_text options]} {
set regex ":\[ \]no\[ \]such\[ \]variable"
if { [regexp -expanded ${regex} ${error_text}]} {
return [sprint_varx $var_name {** variable not set **} {*}$args]
} else {
print_dict options
exit 1
}
} else {
return $buffer
}
}
proc sprint_list { var_name args } {
# Return the name and value of the list variable named in var_name in a formatted way.
# This procedure is the equivalent of sprint_var but for lists.
# Description of argument(s):
# var_name The name of the variable whose name and value are to be printed.
# args The args understood by sprint_varx (after var_name and var_value). See
# sprint_varx's prolog for details.
# Note: In TCL, there is no way to determine that a variable represents a list vs a string, etc. It is up
# to the programmer to decide how the data is to be interpreted. Thus the need for procedures such as this
# one. Consider the following code:
# set my_list {one two three}
# print_var my_list
# print_list my_list
# Output from aforementioned code:
# my_list: one two three
# my_list:
# my_list[0]: one
# my_list[1]: two
# my_list[2]: three
# As far as print_var is concerned, my_list is a string and is printed accordingly. By using print_list,
# the programmer is asking to have the output shown as a list with list indices, etc.
# Determine who our caller is and therefore what upvar_level to use.
set stack_ix_adjust [calc_wrap_stack_ix_adjust]
set upvar_level [expr $stack_ix_adjust + 1]
upvar $upvar_level $var_name var_value
set indent [lindex $args 0]
set args [lrange $args 1 end]
set_var_default indent 0
append buffer [format "%-${indent}s%s\n" "" "$var_name:"]
incr indent 2
set index 0
foreach element $var_value {
append buffer [sprint_varx "${var_name}\[${index}\]" $element $indent\
{*}$args]
incr index
}
return $buffer
}
proc sprint_dict { var_name args } {
# Return the name and value of the dictionary variable named in var_name in a formatted way.
# This procedure is the equivalent of sprint_var but for dictionaries.
# Description of argument(s):
# var_name The name of the variable whose name and value are to be printed.
# args The args understood by sprint_varx (after var_name and var_value). See
# sprint_varx's prolog for details.
# Note: In TCL, there is no way to determine that a variable represents a dictionary vs a string, etc. It
# is up to the programmer to decide how the data is to be interpreted. Thus the need for procedures such
# as this one. Consider the following code:
# set my_dict [dict create first Joe last Montana age 50]
# print_var my_dict
# print_dict my_dict
# Output from aforementioned code:
# my_dict: first Joe last Montana age 50
# my_dict:
# my_dict[first]: Joe
# my_dict[last]: Montana
# my_dict[age]: 50
# As far as print_var is concerned, my_dict is a string and is printed accordingly. By using print_dict,
# the programmer is asking to have the output shown as a dictionary with dictionary keys/values, etc.
# Determine who our caller is and therefore what upvar_level to use.
set stack_ix_adjust [calc_wrap_stack_ix_adjust]
set upvar_level [expr $stack_ix_adjust + 1]
upvar $upvar_level $var_name var_value
set indent [lindex $args 0]
set args [lrange $args 1 end]
set_var_default indent 0
append buffer [format "%-${indent}s%s\n" "" "$var_name:"]
incr indent 2
foreach {key value} $var_value {
append buffer [sprint_varx "${var_name}\[${key}\]" $value $indent {*}$args]
incr index
}
return $buffer
}
proc sprint_vars { args } {
# Sprint the values of one or more variables.
# Description of arg(s):
# args: A list of variable names to be printed. The first argument in the arg list found to be an
# integer (rather than a variable name) will be interpreted to be first of several possible sprint_var
# arguments (e.g. indent, width, hex). See the prologue for sprint_var above for descriptions of this
# variables.
# Example usage:
# set var1 "hello"
# set var2 "there"
# set indent 2
# set buffer [sprint_vars var1 var2]
# or...
# set buffer [sprint_vars var1 var2 $indent]
# Look for integer arguments.
set first_int_ix [lsearch -regexp $args {^[0-9]+$}]
if { $first_int_ix == -1 } {
# If none are found, sub_args is set to empty.
set sub_args {}
} else {
# Set sub_args to the portion of the arg list that are integers.
set sub_args [lrange $args $first_int_ix end]
# Re-set args to exclude the integer values.
set args [lrange $args 0 [expr $first_int_ix - 1]]
}
foreach arg $args {
append buffer [sprint_var $arg {*}$sub_args]
}
return $buffer
}
proc sprint_dashes { { indent 0 } { width 80 } { line_feed 1 } { char "-" } } {
# Return a string of dashes to the caller.
# Description of argument(s):
# indent The number of characters to indent the output.
# width The width of the string of dashes.
# line_feed Indicates whether the output should end with a line feed.
# char The character to be repeated in the output string. In other words, you
# can call on this function to print a string of any character (e.g. "=",
# "_", etc.).
set_var_default indent 0
set_var_default width 80
set_var_default line_feed 1
append buffer [string repeat " " $indent][string repeat $char $width]
append buffer [string repeat "\n" $line_feed]
return $buffer
}
proc sprint_executing {{ include_args 1 }} {
# Return a string that looks something like this:
# #(CST) 2017/11/28 15:08:03.261466 - 0.015214 - Executing: proc1 hi
# Description of argument(s):
# include_args Indicates whether proc args should be included in the result.
set stack_ix_adjust [calc_wrap_stack_ix_adjust]
set level [expr -(2 + $stack_ix_adjust)]
return "[sprint_time]Executing: [get_stack_proc_name $level $include_args]\n"
}
proc sprint_issuing { { cmd_buf "" } { test_mode 0 } } {
# Return a line indicating a command that the program is about to execute.
# Sample output for a cmd_buf of "ls"
# #(CDT) 2016/08/25 17:57:36 - Issuing: ls
# Description of arg(s):
# cmd_buf The command to be executed by caller. If this is blank, this procedure
# will search up the stack for the first cmd_buf value to use.
# test_mode With test_mode set, your output will look like this:
# #(CDT) 2016/08/25 17:57:36 - (test_mode) Issuing: ls
if { $cmd_buf == "" } {
set cmd_buf [get_stack_var cmd_buf {} 2]
}
append buffer [sprint_time]
if { $test_mode } {
append buffer "(test_mode) "
}
append buffer "Issuing: ${cmd_buf}\n"
return $buffer
}
proc sprint_call_stack { { indent 0 } } {
# Return a call stack report for the given point in the program with line numbers, procedure names and
# procedure parameters and arguments.
# Sample output:
# ---------------------------------------------------------------------------
# TCL procedure call stack
# Line # Procedure name and arguments
# ------ --------------------------------------------------------------------
# 21 print_call_stack
# 32 proc1 257
# ---------------------------------------------------------------------------
# Description of arguments:
# indent The number of characters to indent each line of output.
append buffer "[sprint_dashes ${indent}]"
append buffer "[string repeat " " $indent]TCL procedure call stack\n\n"
append buffer "[string repeat " " $indent]"
append buffer "Line # Procedure name and arguments\n"
append buffer "[sprint_dashes $indent 6 0] [sprint_dashes 0 73]"
for {set ix [expr [info level]-1]} {$ix > 0} {incr ix -1} {
set frame_dict [info frame $ix]
set line_num [dict get $frame_dict line]
set proc_name_plus_args [dict get $frame_dict cmd]
append buffer [format "%-${indent}s%6i %s\n" "" $line_num\
$proc_name_plus_args]
}
append buffer "[sprint_dashes $indent]"
return $buffer
}
proc sprint_tcl_version {} {
# Return the name and value of tcl_version in a formatted way.
global tcl_version
return [sprint_var tcl_version]
}
proc sprint_error_report { { error_text "\n" } { indent 0 } } {
# Return a string with a standardized report which includes the caller's error text, the call stack and
# the program header.
# Description of arg(s):
# error_text The error text to be included in the report. The caller should include
# any needed linefeeds.
# indent The number of characters to indent each line of output.
set width 120
set char "="
set line_feed 1
append buffer [sprint_dashes $indent $width $line_feed $char]
append buffer [string repeat " " $indent][sprint_error $error_text]
append buffer "\n"
append buffer [sprint_call_stack $indent]
append buffer [sprint_pgm_header $indent]
append buffer [sprint_dashes $indent $width $line_feed $char]
return $buffer
}
proc sprint_pgm_header { {indent 0} {linefeed 1} } {
# Return a standardized header that programs should print at the beginning of the run. It includes useful
# information like command line, pid, userid, program parameters, etc.
# Description of arguments:
# indent The number of characters to indent each line of output.
# linefeed Indicates whether a line feed be included at the beginning and end of the
# report.
global program_name
global pgm_name_var_name
global argv0
global argv
global env
global _gtp_default_print_var_width_
set_var_default indent 0
set indent_str [string repeat " " $indent]
set width [expr $_gtp_default_print_var_width_ + $indent]
# Get variable values for output.
set command_line "$argv0 $argv"
set pid_var_name ${pgm_name_var_name}_pid
set $pid_var_name [pid]
set uid [get_var ::env(USER) 0]
set host_name [get_var ::env(HOSTNAME) 0]
set DISPLAY [get_var ::env(DISPLAY) 0]
# Generate the report.
if { $linefeed } { append buffer "\n" }
append buffer ${indent_str}[sprint_timen "Running ${program_name}."]
append buffer ${indent_str}[sprint_timen "Program parameter values, etc.:\n"]
append buffer [sprint_var command_line $indent $width]
append buffer [sprint_var $pid_var_name $indent $width]
append buffer [sprint_var uid $indent $width]
append buffer [sprint_var host_name $indent $width]
append buffer [sprint_var DISPLAY $indent $width]
# Print caller's parm names/values.
global longoptions
global pos_parms
regsub -all ":" "${longoptions} ${pos_parms}" {} parm_names
foreach parm_name $parm_names {
set cmd_buf "global $parm_name ; append buffer"
append cmd_buf " \[sprint_var $parm_name $indent $width\]"
eval $cmd_buf
}
if { $linefeed } { append buffer "\n" }
return $buffer
}
proc sprint_pgm_footer {} {
# Return a standardized footer that programs should print at the end of the program run. It includes
# useful information like total run time, etc.
global program_name
global pgm_name_var_name
global start_time
# Calculate total runtime.
set total_time_micro [expr [clock microseconds] - $start_time]
# Break the left and right of the decimal point.
set total_seconds [expr $total_time_micro / 1000000]
set total_decimal_micro [expr $total_time_micro % 1000000]
set total_time_float [format "%i.%06i" ${total_seconds}\
${total_decimal_micro}]
set total_time_string [format "%0.6f" $total_time_float]
set runtime_var_name ${pgm_name_var_name}_runtime
set $runtime_var_name $total_time_string
append buffer [sprint_timen "Finished running ${program_name}."]
append buffer "\n"
append buffer [sprint_var $runtime_var_name]
append buffer "\n"
return $buffer
}
proc sprint_arg_desc { arg_title arg_desc { indent 0 } { col1_width 25 }\
{ line_width 80 } } {
# Return a formatted argument description.
# Example:
#
# set desc "When in the Course of human events, it becomes necessary for one people to dissolve the
# political bands which have connected them with another, and to assume among the powers of the earth, the
# separate and equal station to which the Laws of Nature and of Nature's God entitle them, a decent respect
# to the opinions of mankind requires that they should declare the causes which impel them to the
# separation."
# set buffer [sprint_arg_desc "--declaration" $desc]
# puts $buffer
# Resulting output:
# --declaration When in the Course of human events, it becomes
# necessary for one people to dissolve the
# political bands which have connected them with
# another, and to assume among the powers of the
# earth, the separate and equal station to which
# the Laws of Nature and of Nature's God entitle
# them, a decent respect to the opinions of mankind
# requires that they should declare the causes
# which impel them to the separation.
# Description of argument(s):
# arg_title The content that you want to appear on the first line in column 1.
# arg_desc The text that describes the argument.
# indent The number of characters to indent.
# col1_width The width of column 1, which is the column containing the arg_title.
# line_width The total max width of each line of output.
set fold_width [expr $line_width - $col1_width]
set escaped_arg_desc [escape_bash_quotes "${arg_desc}"]
set cmd_buf "echo '${escaped_arg_desc}' | fold --spaces --width="
append cmd_buf "${fold_width} | sed -re 's/\[ \]+$//g'"
set out_buf [eval exec bash -c {$cmd_buf}]
set help_lines [split $out_buf "\n"]
set buffer {}
set line_num 1
foreach help_line $help_lines {
if { $line_num == 1 } {
if { [string length $arg_title] > $col1_width } {
# If the arg_title is already wider than column1, print it on its own
# line.
append buffer [format "%${indent}s%-${col1_width}s\n" ""\
"$arg_title"]
append buffer [format "%${indent}s%-${col1_width}s%s\n" "" ""\
"${help_line}"]
} else {
append buffer [format "%${indent}s%-${col1_width}s%s\n" ""\
"$arg_title" "${help_line}"]
}
} else {
append buffer [format "%${indent}s%-${col1_width}s%s\n" "" ""\
"${help_line}"]
}
incr line_num
}
return $buffer
}
# Define the create_print_wrapper_procs to help us create print wrappers.
# First, create templates.
# Notes:
# - The resulting procedures will replace all registered passwords.
# - The resulting "quiet" and "debug" print procedures will search the stack for quiet and debug,
# respectively. That means that the if a procedure calls qprint_var and the procedure has a local version
# of quiet set to 1, the print will not occur, even if there is a global version of quiet set to 0.
set print_proc_template " puts -nonewline<output_stream> \[replace_passwords"
append print_proc_template " \[<base_proc_name> {*}\$args\]\]\n}\n"
set qprint_proc_template " set quiet \[get_stack_var quiet 0\]\n if {"
append qprint_proc_template " \$quiet } { return }\n${print_proc_template}"
set dprint_proc_template " set debug \[get_stack_var debug 0\]\n if { !"
append dprint_proc_template " \$debug } { return }\n${print_proc_template}"
# Put each template into the print_proc_templates array.
set print_proc_templates(p) $print_proc_template
set print_proc_templates(q) $qprint_proc_template
set print_proc_templates(d) $dprint_proc_template
proc create_print_wrapper_procs {proc_names {stderr_proc_names {}} } {
# Generate code for print wrapper procs and return the generated code as a string.
# To illustrate, suppose there is a "print_foo_bar" proc in the proc_names list.
# This proc will...
# - Expect that there is an sprint_foo_bar proc already in existence.
# - Create a print_foo_bar proc which calls sprint_foo_bar and prints the result.
# - Create a qprint_foo_bar proc which calls upon sprint_foo_bar only if global value quiet is 0.
# - Create a dprint_foo_bar proc which calls upon sprint_foo_bar only if global value debug is 1.
# Also, code will be generated to define aliases for each proc as well. Each alias will be created by
# replacing "print_" in the proc name with "p" For example, the alias for print_foo_bar will be pfoo_bar.
# Description of argument(s):
# proc_names A list of procs for which print wrapper proc code is to be generated.
# stderr_proc_names A list of procs whose generated code should print to stderr rather than
# to stdout.
global print_proc_template
global print_proc_templates
foreach proc_name $proc_names {
if { [expr [lsearch $stderr_proc_names $proc_name] == -1] } {
set replace_dict(output_stream) ""
} else {
set replace_dict(output_stream) " stderr"
}
set base_proc_name "s${proc_name}"
set replace_dict(base_proc_name) $base_proc_name
set wrap_proc_names(p) $proc_name
set wrap_proc_names(q) q${proc_name}
set wrap_proc_names(d) d${proc_name}
foreach template_key [list p q d] {
set wrap_proc_name $wrap_proc_names($template_key)
set call_line "proc ${wrap_proc_name} \{args\} \{\n"
set proc_body $print_proc_templates($template_key)
set proc_def ${call_line}${proc_body}
foreach {key value} [array get replace_dict] {
regsub -all "<$key>" $proc_def $value proc_def
}
regsub "print_" $wrap_proc_name "p" alias_proc_name
regsub "${wrap_proc_name}" $proc_def $alias_proc_name alias_def
append buffer "${proc_def}${alias_def}"
}
}
return $buffer
}
# Get this file's path.
set frame_dict [info frame 0]
set file_path [dict get $frame_dict file]
# Get a list of this file's sprint procs.
set sprint_procs [get_file_proc_names $file_path sprint]
# Create a corresponding list of print_procs.
set proc_names [list_map $sprint_procs {[string range $x 1 end]}]
# Sort them for ease of debugging.
set proc_names [lsort $proc_names]
set stderr_proc_names [list print_error print_error_report]
set proc_def [create_print_wrapper_procs $proc_names $stderr_proc_names]
if { $GEN_PRINT_DEBUG } { puts $proc_def }
eval "${proc_def}"