xref: /openbmc/openbmc-test-automation/lib/gen_print.py (revision 249fbcc0be4641e2d43987badc764e1f73a6ebeb)
1#!/usr/bin/env python3
2
3r"""
4This module provides many print functions such as sprint_var, sprint_time, sprint_error, sprint_call_stack.
5"""
6
7import argparse
8import copy
9import grp
10import inspect
11import os
12import re
13import socket
14import sys
15import time
16
17try:
18    import __builtin__
19except ImportError:
20    import builtins as __builtin__
21
22import collections
23import logging
24
25from wrap_utils import *
26
27try:
28    robot_env = 1
29    from robot.libraries.BuiltIn import BuiltIn
30    from robot.utils import DotDict, NormalizedDict
31
32    # Having access to the robot libraries alone does not indicate that we are in a robot environment.  The
33    # following try block should confirm that.
34    try:
35        var_value = BuiltIn().get_variable_value("${SUITE_NAME}", "")
36    except BaseException:
37        robot_env = 0
38except ImportError:
39    robot_env = 0
40
41import gen_arg as ga
42
43# Setting these variables for use both inside this module and by programs importing this module.
44pgm_file_path = sys.argv[0]
45pgm_name = os.path.basename(pgm_file_path)
46pgm_dir_path = (
47    os.path.normpath(re.sub("/" + pgm_name, "", pgm_file_path)) + os.path.sep
48)
49
50
51# Some functions (e.g. sprint_pgm_header) have need of a program name value that looks more like a valid
52# variable name.  Therefore, we'll swap odd characters like "." out for underscores.
53pgm_name_var_name = pgm_name.replace(".", "_")
54
55# Initialize global values used as defaults by print_time, print_var, etc.
56dft_indent = 0
57
58# Calculate default column width for print_var functions based on environment variable settings.  The
59# objective is to make the variable values line up nicely with the time stamps.
60dft_col1_width = 29
61
62NANOSECONDS = os.environ.get("NANOSECONDS", "1")
63
64if NANOSECONDS == "1":
65    dft_col1_width = dft_col1_width + 7
66
67SHOW_ELAPSED_TIME = os.environ.get("SHOW_ELAPSED_TIME", "1")
68
69if SHOW_ELAPSED_TIME == "1":
70    if NANOSECONDS == "1":
71        dft_col1_width = dft_col1_width + 14
72    else:
73        dft_col1_width = dft_col1_width + 7
74
75# Initialize some time variables used in module functions.
76start_time = time.time()
77# sprint_time_last_seconds is used to calculate elapsed seconds.
78sprint_time_last_seconds = [start_time, start_time]
79# Define global index for the sprint_time_last_seconds list.
80last_seconds_ix = 0
81
82
83def set_last_seconds_ix(ix):
84    r"""
85    Set the "last_seconds_ix" module variable to the index value.
86
87    Description of argument(s):
88    ix                              The index value to be set into the module global last_seconds_ix variable.
89    """
90    global last_seconds_ix
91    last_seconds_ix = ix
92
93
94# Since output from the lprint_ functions goes to a different location than the output from the print_
95# functions (e.g. a file vs. the console), sprint_time_last_seconds has been created as a list rather than a
96# simple integer so that it can store multiple sprint_time_last_seconds values.  Standard print_ functions
97# defined in this file will use sprint_time_last_seconds[0] and the lprint_ functions will use
98# sprint_time_last_seconds[1].
99def standard_print_last_seconds_ix():
100    r"""
101    Return the standard print last_seconds index value to the caller.
102    """
103    return 0
104
105
106def lprint_last_seconds_ix():
107    r"""
108    Return lprint last_seconds index value to the caller.
109    """
110    return 1
111
112
113# The user can set environment variable "GEN_PRINT_DEBUG" to get debug output from this module.
114gen_print_debug = int(os.environ.get("GEN_PRINT_DEBUG", 0))
115
116
117def sprint_func_name(stack_frame_ix=None):
118    r"""
119    Return the function name associated with the indicated stack frame.
120
121    Description of argument(s):
122    stack_frame_ix                  The index of the stack frame whose function name should be returned.  If
123                                    the caller does not specify a value, this function will set the value to
124                                    1 which is the index of the caller's stack frame.  If the caller is the
125                                    wrapper function "print_func_name", this function will bump it up by 1.
126    """
127
128    # If user specified no stack_frame_ix, we'll set it to a proper default value.
129    if stack_frame_ix is None:
130        func_name = sys._getframe().f_code.co_name
131        caller_func_name = sys._getframe(1).f_code.co_name
132        if func_name[1:] == caller_func_name:
133            stack_frame_ix = 2
134        else:
135            stack_frame_ix = 1
136
137    func_name = sys._getframe(stack_frame_ix).f_code.co_name
138
139    return func_name
140
141
142def work_around_inspect_stack_cwd_failure():
143    r"""
144    Work around the inspect.stack() getcwd() failure by making "/tmp" the current working directory.
145
146    NOTES: If the current working directory has been deleted, inspect.stack() will fail with "OSError: [Errno
147    2] No such file or directory" because it tries to do a getcwd().
148
149    This function will try to prevent this failure by detecting the scenario in advance and making "/tmp" the
150    current working directory.
151    """
152    try:
153        os.getcwd()
154    except OSError:
155        os.chdir("/tmp")
156
157
158def get_line_indent(line):
159    r"""
160    Return the number of spaces at the beginning of the line.
161    """
162
163    return len(line) - len(line.lstrip(" "))
164
165
166# get_arg_name is not a print function per se.  It has been included in this module because it is used by
167# sprint_var which is defined in this module.
168def get_arg_name(var, arg_num=1, stack_frame_ix=1):
169    r"""
170    Return the "name" of an argument passed to a function.  This could be a literal or a variable name.
171
172    Description of argument(s):
173    var                             The variable whose name is to be returned.
174    arg_num                         The arg number whose name is to be returned.  To illustrate how arg_num
175                                    is processed, suppose that a programmer codes this line: "rc, outbuf =
176                                    my_func(var1, var2)" and suppose that my_func has this line of code:
177                                    "result = gp.get_arg_name(0, arg_num, 2)".  If arg_num is positive, the
178                                    indicated argument is returned.  For example, if arg_num is 1, "var1"
179                                    would be returned, If arg_num is 2, "var2" would be returned.  If arg_num
180                                    exceeds the number of arguments, get_arg_name will simply return a
181                                    complete list of the arguments.  If arg_num is 0, get_arg_name will
182                                    return the name of the target function as specified in the calling line
183                                    ("my_func" in this case).  To clarify, if the caller of the target
184                                    function uses an alias function name, the alias name would be returned.
185                                    If arg_num is negative, an lvalue variable name is returned.  Continuing
186                                    with the given example, if arg_num is -2 the 2nd parm to the left of the
187                                    "=" ("rc" in this case) should be returned.  If arg_num is -1, the 1st
188                                    parm to the left of the "=" ("out_buf" in this case) should be returned.
189                                    If arg_num is less than -2, an entire dictionary is returned.  The keys
190                                    to the dictionary for this example would be -2 and -1.
191    stack_frame_ix                  The stack frame index of the target function.  This value must be 1 or
192                                    greater.  1 would indicate get_arg_name's stack frame.  2 would be the
193                                    caller of get_arg_name's stack frame, etc.
194
195    Example 1:
196
197    my_var = "mike"
198    var_name = get_arg_name(my_var)
199
200    In this example, var_name will receive the value "my_var".
201
202    Example 2:
203
204    def test1(var):
205        # Getting the var name of the first arg to this function, test1.  Note, in this case, it doesn't
206        # matter what is passed as the first arg to get_arg_name since it is the caller's variable name that
207        # matters.
208        dummy = 1
209        arg_num = 1
210        stack_frame = 2
211        var_name = get_arg_name(dummy, arg_num, stack_frame)
212
213    # Mainline...
214
215    another_var = "whatever"
216    test1(another_var)
217
218    In this example, var_name will be set to "another_var".
219
220    """
221
222    # Note: To avoid infinite recursion, avoid calling any function that calls this function (e.g.
223    # sprint_var, valid_value, etc.).
224
225    # The user can set environment variable "GET_ARG_NAME_DEBUG" to get debug output from this function.
226    local_debug = int(os.environ.get("GET_ARG_NAME_DEBUG", 0))
227    # In addition to GET_ARG_NAME_DEBUG, the user can set environment variable "GET_ARG_NAME_SHOW_SOURCE" to
228    # have this function include source code in the debug output.
229    local_debug_show_source = int(
230        os.environ.get("GET_ARG_NAME_SHOW_SOURCE", 0)
231    )
232
233    if stack_frame_ix < 1:
234        print_error(
235            'Programmer error - Variable "stack_frame_ix" has an'
236            + ' invalid value of "'
237            + str(stack_frame_ix)
238            + '".  The'
239            + " value must be an integer that is greater than or equal"
240            + " to 1.\n"
241        )
242        return
243
244    if local_debug:
245        debug_indent = 2
246        print("")
247        print_dashes(0, 120)
248        print(sprint_func_name() + "() parms:")
249        print_varx("var", var, indent=debug_indent)
250        print_varx("arg_num", arg_num, indent=debug_indent)
251        print_varx("stack_frame_ix", stack_frame_ix, indent=debug_indent)
252        print("")
253        print_call_stack(debug_indent, 2)
254
255    work_around_inspect_stack_cwd_failure()
256    for count in range(0, 2):
257        try:
258            (
259                frame,
260                filename,
261                cur_line_no,
262                function_name,
263                lines,
264                index,
265            ) = inspect.stack()[stack_frame_ix]
266        except IndexError:
267            print_error(
268                "Programmer error - The caller has asked for"
269                + ' information about the stack frame at index "'
270                + str(stack_frame_ix)
271                + '".  However, the stack'
272                + " only contains "
273                + str(len(inspect.stack()))
274                + " entries.  Therefore the stack frame index is out"
275                + " of range.\n"
276            )
277            return
278        if filename != "<string>":
279            break
280        # filename of "<string>" may mean that the function in question was defined dynamically and
281        # therefore its code stack is inaccessible.  This may happen with functions like "rqprint_var".  In
282        # this case, we'll increment the stack_frame_ix and try again.
283        stack_frame_ix += 1
284        if local_debug:
285            print("Adjusted stack_frame_ix...")
286            print_varx("stack_frame_ix", stack_frame_ix, indent=debug_indent)
287
288    real_called_func_name = sprint_func_name(stack_frame_ix)
289
290    module = inspect.getmodule(frame)
291
292    # Though one would expect inspect.getsourcelines(frame) to get all module source lines if the frame is
293    # "<module>", it doesn't do that.  Therefore, for this special case, do inspect.getsourcelines(module).
294    if function_name == "<module>":
295        source_lines, source_line_num = inspect.getsourcelines(module)
296        line_ix = cur_line_no - source_line_num - 1
297    else:
298        source_lines, source_line_num = inspect.getsourcelines(frame)
299        line_ix = cur_line_no - source_line_num
300
301    if local_debug:
302        print("\n  Variables retrieved from inspect.stack() function:")
303        print_varx("frame", frame, indent=debug_indent + 2)
304        print_varx("filename", filename, indent=debug_indent + 2)
305        print_varx("cur_line_no", cur_line_no, indent=debug_indent + 2)
306        print_varx("function_name", function_name, indent=debug_indent + 2)
307        print_varx("lines", lines, indent=debug_indent + 2)
308        print_varx("index", index, indent=debug_indent + 2)
309        print_varx("source_line_num", source_line_num, indent=debug_indent)
310        print_varx("line_ix", line_ix, indent=debug_indent)
311        if local_debug_show_source:
312            print_varx("source_lines", source_lines, indent=debug_indent)
313        print_varx(
314            "real_called_func_name", real_called_func_name, indent=debug_indent
315        )
316
317    # Get a list of all functions defined for the module.  Note that this doesn't work consistently when
318    # _run_exitfuncs is at the top of the stack (i.e. if we're running an exit function).  I've coded a
319    # work-around below for this deficiency.
320    all_functions = inspect.getmembers(module, inspect.isfunction)
321
322    # Get called_func_id by searching for our function in the list of all functions.
323    called_func_id = None
324    for func_name, function in all_functions:
325        if func_name == real_called_func_name:
326            called_func_id = id(function)
327            break
328    # NOTE: The only time I've found that called_func_id can't be found is when we're running from an exit
329    # function.
330
331    # Look for other functions in module with matching id.
332    aliases = set([real_called_func_name])
333    for func_name, function in all_functions:
334        if func_name == real_called_func_name:
335            continue
336        func_id = id(function)
337        if func_id == called_func_id:
338            aliases.add(func_name)
339
340    # In most cases, my general purpose code above will find all aliases.  However, for the odd case (i.e.
341    # running from exit function), I've added code to handle pvar, qpvar, dpvar, etc. aliases explicitly
342    # since they are defined in this module and used frequently.
343    # pvar is an alias for print_var.
344    aliases.add(re.sub("print_var", "pvar", real_called_func_name))
345
346    # The call to the function could be encased in a recast (e.g. int(func_name())).
347    recast_regex = "([^ ]+\\([ ]*)?"
348    import_name_regex = "([a-zA-Z0-9_]+\\.)?"
349    func_name_regex = (
350        recast_regex + import_name_regex + "(" + "|".join(aliases) + ")"
351    )
352    pre_args_regex = ".*" + func_name_regex + "[ ]*\\("
353
354    # Search backward through source lines looking for the calling function name.
355    found = False
356    for start_line_ix in range(line_ix, 0, -1):
357        # Skip comment lines.
358        if re.match(r"[ ]*#", source_lines[start_line_ix]):
359            continue
360        if re.match(pre_args_regex, source_lines[start_line_ix]):
361            found = True
362            break
363    if not found:
364        print_error(
365            "Programmer error - Could not find the source line with"
366            + ' a reference to function "'
367            + real_called_func_name
368            + '".\n'
369        )
370        return
371
372    # Search forward through the source lines looking for a line whose indentation is the same or less than
373    # the start line.  The end of our composite line should be the line preceding that line.
374    start_indent = get_line_indent(source_lines[start_line_ix])
375    end_line_ix = line_ix
376    for end_line_ix in range(line_ix + 1, len(source_lines)):
377        if source_lines[end_line_ix].strip() == "":
378            continue
379        line_indent = get_line_indent(source_lines[end_line_ix])
380        if line_indent <= start_indent:
381            end_line_ix -= 1
382            break
383    if start_line_ix != 0:
384        # Check to see whether the start line is a continuation of the prior line.
385        prior_line = source_lines[start_line_ix - 1]
386        prior_line_stripped = re.sub(r"[ ]*\\([\r\n]$)", " \\1", prior_line)
387        prior_line_indent = get_line_indent(prior_line)
388        if (
389            prior_line != prior_line_stripped
390            and prior_line_indent < start_indent
391        ):
392            start_line_ix -= 1
393            # Remove the backslash (continuation char) from prior line.
394            source_lines[start_line_ix] = prior_line_stripped
395
396    # Join the start line through the end line into a composite line.
397    composite_line = "".join(
398        map(str.strip, source_lines[start_line_ix : end_line_ix + 1])
399    )
400    # Insert one space after first "=" if there isn't one already.
401    composite_line = re.sub("=[ ]*([^ ])", "= \\1", composite_line, 1)
402
403    lvalue_regex = "[ ]*=[ ]+" + func_name_regex + ".*"
404    lvalue_string = re.sub(lvalue_regex, "", composite_line)
405    if lvalue_string == composite_line:
406        # i.e. the regex did not match so there are no lvalues.
407        lvalue_string = ""
408    lvalues_list = list(filter(None, map(str.strip, lvalue_string.split(","))))
409    try:
410        lvalues = collections.OrderedDict()
411    except AttributeError:
412        # A non-ordered dict doesn't look as nice when printed but it will do.
413        lvalues = {}
414    ix = len(lvalues_list) * -1
415    for lvalue in lvalues_list:
416        lvalues[ix] = lvalue
417        ix += 1
418    lvalue_prefix_regex = "(.*=[ ]+)?"
419    called_func_name_regex = (
420        lvalue_prefix_regex + func_name_regex + "[ ]*\\(.*"
421    )
422    called_func_name = re.sub(called_func_name_regex, "\\4", composite_line)
423    arg_list_etc = "(" + re.sub(pre_args_regex, "", composite_line)
424    if local_debug:
425        print_varx("aliases", aliases, indent=debug_indent)
426        print_varx("import_name_regex", import_name_regex, indent=debug_indent)
427        print_varx("func_name_regex", func_name_regex, indent=debug_indent)
428        print_varx("pre_args_regex", pre_args_regex, indent=debug_indent)
429        print_varx("start_line_ix", start_line_ix, indent=debug_indent)
430        print_varx("end_line_ix", end_line_ix, indent=debug_indent)
431        print_varx("composite_line", composite_line, indent=debug_indent)
432        print_varx("lvalue_regex", lvalue_regex, indent=debug_indent)
433        print_varx("lvalue_string", lvalue_string, indent=debug_indent)
434        print_varx("lvalues", lvalues, indent=debug_indent)
435        print_varx(
436            "called_func_name_regex",
437            called_func_name_regex,
438            indent=debug_indent,
439        )
440        print_varx("called_func_name", called_func_name, indent=debug_indent)
441        print_varx("arg_list_etc", arg_list_etc, indent=debug_indent)
442
443    # Parse arg list...
444    # Initialize...
445    nest_level = -1
446    arg_ix = 0
447    args_list = [""]
448    for ix in range(0, len(arg_list_etc)):
449        char = arg_list_etc[ix]
450        # Set the nest_level based on whether we've encounted a parenthesis.
451        if char == "(":
452            nest_level += 1
453            if nest_level == 0:
454                continue
455        elif char == ")":
456            nest_level -= 1
457            if nest_level < 0:
458                break
459
460        # If we reach a comma at base nest level, we are done processing an argument so we increment arg_ix
461        # and initialize a new args_list entry.
462        if char == "," and nest_level == 0:
463            arg_ix += 1
464            args_list.append("")
465            continue
466
467        # For any other character, we append it it to the current arg list entry.
468        args_list[arg_ix] += char
469
470    # Trim whitespace from each list entry.
471    args_list = [arg.strip() for arg in args_list]
472
473    if arg_num < 0:
474        if abs(arg_num) > len(lvalues):
475            argument = lvalues
476        else:
477            argument = lvalues[arg_num]
478    elif arg_num == 0:
479        argument = called_func_name
480    else:
481        if arg_num > len(args_list):
482            argument = args_list
483        else:
484            argument = args_list[arg_num - 1]
485
486    if local_debug:
487        print_varx("args_list", args_list, indent=debug_indent)
488        print_varx("argument", argument, indent=debug_indent)
489        print_dashes(0, 120)
490
491    return argument
492
493
494def sprint_time(buffer=""):
495    r"""
496    Return the time in the following format.
497
498    Example:
499
500    The following python code...
501
502    sys.stdout.write(sprint_time())
503    sys.stdout.write("Hi.\n")
504
505    Will result in the following type of output:
506
507    #(CDT) 2016/07/08 15:25:35 - Hi.
508
509    Example:
510
511    The following python code...
512
513    sys.stdout.write(sprint_time("Hi.\n"))
514
515    Will result in the following type of output:
516
517    #(CDT) 2016/08/03 17:12:05 - Hi.
518
519    The following environment variables will affect the formatting as described:
520    NANOSECONDS                     This will cause the time stamps to be precise to the microsecond (Yes, it
521                                    probably should have been named MICROSECONDS but the convention was set
522                                    long ago so we're sticking with it).  Example of the output when
523                                    environment variable NANOSECONDS=1.
524
525    #(CDT) 2016/08/03 17:16:25.510469 - Hi.
526
527    SHOW_ELAPSED_TIME               This will cause the elapsed time to be included in the output.  This is
528                                    the amount of time that has elapsed since the last time this function was
529                                    called.  The precision of the elapsed time field is also affected by the
530                                    value of the NANOSECONDS environment variable.  Example of the output
531                                    when environment variable NANOSECONDS=0 and SHOW_ELAPSED_TIME=1.
532
533    #(CDT) 2016/08/03 17:17:40 -    0 - Hi.
534
535    Example of the output when environment variable NANOSECONDS=1 and SHOW_ELAPSED_TIME=1.
536
537    #(CDT) 2016/08/03 17:18:47.317339 -    0.000046 - Hi.
538
539    Description of argument(s).
540    buffer                          This will be appended to the formatted time string.
541    """
542
543    global NANOSECONDS
544    global SHOW_ELAPSED_TIME
545    global sprint_time_last_seconds
546    global last_seconds_ix
547
548    seconds = time.time()
549    loc_time = time.localtime(seconds)
550    nanoseconds = "%0.6f" % seconds
551    pos = nanoseconds.find(".")
552    nanoseconds = nanoseconds[pos:]
553
554    time_string = time.strftime("#(%Z) %Y/%m/%d %H:%M:%S", loc_time)
555    if NANOSECONDS == "1":
556        time_string = time_string + nanoseconds
557
558    if SHOW_ELAPSED_TIME == "1":
559        cur_time_seconds = seconds
560        math_string = (
561            "%9.9f" % cur_time_seconds
562            + " - "
563            + "%9.9f" % sprint_time_last_seconds[last_seconds_ix]
564        )
565        elapsed_seconds = eval(math_string)
566        if NANOSECONDS == "1":
567            elapsed_seconds = "%11.6f" % elapsed_seconds
568        else:
569            elapsed_seconds = "%4i" % elapsed_seconds
570        sprint_time_last_seconds[last_seconds_ix] = cur_time_seconds
571        time_string = time_string + " - " + elapsed_seconds
572
573    return time_string + " - " + buffer
574
575
576def sprint_timen(buffer=""):
577    r"""
578    Append a line feed to the buffer, pass it to sprint_time and return the result.
579    """
580
581    return sprint_time(buffer + "\n")
582
583
584def sprint_error(buffer=""):
585    r"""
586    Return a standardized error string.  This includes:
587      - A time stamp
588      - The "**ERROR**" string
589      - The caller's buffer string.
590
591    Example:
592
593    The following python code...
594
595    print(sprint_error("Oops.\n"))
596
597    Will result in the following type of output:
598
599    #(CDT) 2016/08/03 17:12:05 - **ERROR** Oops.
600
601    Description of argument(s).
602    buffer                          This will be appended to the formatted error string.
603    """
604
605    return sprint_time() + "**ERROR** " + buffer
606
607
608# Implement "constants" with functions.
609def digit_length_in_bits():
610    r"""
611    Return the digit length in bits.
612    """
613
614    return 4
615
616
617def word_length_in_digits():
618    r"""
619    Return the word length in digits.
620    """
621
622    return 8
623
624
625def bit_length(number):
626    r"""
627    Return the bit length of the number.
628
629    Description of argument(s):
630    number                          The number to be analyzed.
631    """
632
633    if number < 0:
634        # Convert negative numbers to positive and subtract one.  The following example illustrates the
635        # reason for this:
636        # Consider a single nibble whose signed values can range from -8 to 7 (0x8 to 0x7).  A value of 0x7
637        # equals 0b0111.  Therefore, its length in bits is 3.  Since the negative bit (i.e. 0b1000) is not
638        # set, the value 7 clearly will fit in one nibble.  With -8 = 0x8 = 0b1000, one has the smallest
639        # negative value that will fit.  Note that it requires 3 bits of 0.  So by converting a number value
640        # of -8 to a working_number of 7, this function can accurately calculate the number of bits and
641        # therefore nibbles required to represent the number in print.
642        working_number = abs(number) - 1
643    else:
644        working_number = number
645
646    # Handle the special case of the number 0.
647    if working_number == 0:
648        return 0
649
650    return len(bin(working_number)) - 2
651
652
653def get_req_num_hex_digits(number):
654    r"""
655    Return the required number of hex digits required to display the given number.
656
657    The returned value will always be rounded up to the nearest multiple of 8.
658
659    Description of argument(s):
660    number                          The number to be analyzed.
661    """
662
663    if number < 0:
664        # Convert negative numbers to positive and subtract one.  The following example illustrates the
665        # reason for this:
666        # Consider a single nibble whose signed values can range from -8 to 7 (0x8 to 0x7).  A value of 0x7
667        # equals 0b0111.  Therefore, its length in bits is 3.  Since the negative bit (i.e. 0b1000) is not
668        # set, the value 7 clearly will fit in one nibble.  With -8 = 0x8 = 0b1000, one has the smallest
669        # negative value that will fit.  Note that it requires 3 bits of 0.  So by converting a number value
670        # of -8 to a working_number of 7, this function can accurately calculate the number of bits and
671        # therefore nibbles required to represent the number in print.
672        working_number = abs(number) - 1
673    else:
674        working_number = number
675
676    # Handle the special case of the number 0.
677    if working_number == 0:
678        return word_length_in_digits()
679
680    num_length_in_bits = bit_length(working_number)
681    num_hex_digits, remainder = divmod(
682        num_length_in_bits, digit_length_in_bits()
683    )
684    if remainder > 0:
685        # Example: the number 7 requires 3 bits.  The divmod above produces, 0 with remainder of 3.  So
686        # because we have a remainder, we increment num_hex_digits from 0 to 1.
687        num_hex_digits += 1
688
689    # Check to see whether the negative bit is set.  This is the left-most bit in the highest order digit.
690    negative_mask = 2 ** (num_hex_digits * 4 - 1)
691    if working_number & negative_mask:
692        # If a number that is intended to be positive has its negative bit on, an additional digit will be
693        # required to represent it correctly in print.
694        num_hex_digits += 1
695
696    num_words, remainder = divmod(num_hex_digits, word_length_in_digits())
697    if remainder > 0 or num_words == 0:
698        num_words += 1
699
700    # Round up to the next word length in digits.
701    return num_words * word_length_in_digits()
702
703
704def dft_num_hex_digits():
705    r"""
706    Return the default number of hex digits to be used to represent a hex number in print.
707
708    The value returned is a function of sys.maxsize.
709    """
710
711    global _gen_print_dft_num_hex_digits_
712    try:
713        return _gen_print_dft_num_hex_digits_
714    except NameError:
715        _gen_print_dft_num_hex_digits_ = get_req_num_hex_digits(sys.maxsize)
716        return _gen_print_dft_num_hex_digits_
717
718
719# Create constant functions to describe various types of dictionaries.
720def dict_type():
721    return 1
722
723
724def ordered_dict_type():
725    return 2
726
727
728def dot_dict_type():
729    return 3
730
731
732def normalized_dict_type():
733    return 4
734
735
736def proxy_dict_type():
737    return 5
738
739
740def is_dict(var_value):
741    r"""
742    Return non-zero if var_value is a type of dictionary and 0 if it is not.
743
744    The specific non-zero value returned will indicate what type of dictionary var_value is (see constant
745    functions above).
746
747    Description of argument(s):
748    var_value                       The object to be analyzed to determine whether it is a dictionary and if
749                                    so, what type of dictionary.
750    """
751
752    if isinstance(var_value, dict):
753        return dict_type()
754    try:
755        if isinstance(var_value, collections.OrderedDict):
756            return ordered_dict_type()
757    except AttributeError:
758        pass
759    try:
760        if isinstance(var_value, DotDict):
761            return dot_dict_type()
762    except NameError:
763        pass
764    try:
765        if isinstance(var_value, NormalizedDict):
766            return normalized_dict_type()
767    except NameError:
768        pass
769    try:
770        if str(type(var_value)).split("'")[1] == "dictproxy":
771            return proxy_dict_type()
772    except NameError:
773        pass
774    return 0
775
776
777def get_int_types():
778    r"""
779    Return a tuple consisting of the valid integer data types for the system and version of python being run.
780
781    Example:
782    (int, long)
783    """
784
785    try:
786        int_types = (int, long)
787    except NameError:
788        int_types = (int,)
789    return int_types
790
791
792def get_string_types():
793    r"""
794    Return a tuple consisting of the valid string data types for the system and version of python being run.
795
796    Example:
797    (str, unicode)
798    """
799
800    try:
801        string_types = (str, unicode)
802    except NameError:
803        string_types = (bytes, str)
804    return string_types
805
806
807def valid_fmts():
808    r"""
809    Return a list of the valid formats that can be specified for the fmt argument of the sprint_varx function
810    (defined below).
811    """
812
813    return [
814        "hexa",
815        "octal",
816        "binary",
817        "blank",
818        "verbose",
819        "quote_keys",
820        "show_type",
821        "strip_brackets",
822        "no_header",
823        "quote_values",
824    ]
825
826
827def create_fmt_definition():
828    r"""
829    Create a string consisting of function-definition code that can be executed to create constant fmt
830    definition functions.
831
832    These functions can be used by callers of sprint_var/sprint_varx to set the fmt argument correctly.
833
834    Likewise, the sprint_varx function will use these generated functions to correctly interpret the fmt
835    argument.
836
837    Example output from this function:
838
839    def hexa():
840        return 0x00000001
841    def octal_fmt():
842        return 0x00000002
843    etc.
844    """
845
846    buffer = ""
847    bits = 0x00000001
848    for fmt_name in valid_fmts():
849        buffer += "def " + fmt_name + "():\n"
850        buffer += "    return " + "0x%08x" % bits + "\n"
851        bits = bits << 1
852    return buffer
853
854
855# Dynamically create fmt definitions (for use with the fmt argument of sprint_varx function):
856exec(create_fmt_definition())
857
858
859def terse():
860    r"""
861    Constant function to return fmt value of 0.
862
863    Now that sprint_varx defaults to printing in terse format, the terse option is deprecated.  This function
864    is here for backward compatibility.
865
866    Once the repo has been purged of the use of terse, this function can be removed.
867    """
868
869    return 0
870
871
872def list_pop(a_list, index=0, default=None):
873    r"""
874    Pop the list entry indicated by the index and return the entry.  If no such entry exists, return default.
875
876    Note that the list passed to this function will be modified.
877
878    Description of argument(s):
879    a_list                          The list from which an entry is to be popped.
880    index                           The index indicating which entry is to be popped.
881    default                         The value to be returned if there is no entry at the given index location.
882    """
883    try:
884        return a_list.pop(index)
885    except IndexError:
886        return default
887
888
889def parse_fmt(fmt):
890    r"""
891    Parse the fmt argument and return a tuple consisting of a format and a child format.
892
893    This function was written for use by the sprint_varx function defined in this module.
894
895    When sprint_varx is processing a multi-level object such as a list or dictionary (which in turn may
896    contain other lists or dictionaries), it will use the fmt value to dictate the print formatting of the
897    current level and the child_fmt value to dictate the print formatting of subordinate levels.  Consider
898    the following example:
899
900    python code example:
901
902    ord_dict = \
903        collections.OrderedDict([
904            ('one', 1),
905            ('two', 2),
906            ('sub',
907             collections.OrderedDict([
908                ('three', 3), ('four', 4)]))])
909
910    print_var(ord_dict)
911
912    This would generate the following output:
913
914    ord_dict:
915      [one]:                     1
916      [two]:                     2
917      [sub]:
918        [three]:                 3
919        [four]:                  4
920
921    The first level in this example is the line that simply says "ord_dict".  The second level is comprised
922    of the dictionary entries with the keys 'one', 'two' and 'sub'.  The third level is comprised of the last
923    2 lines (i.e. printed values 3 and 4).
924
925    Given the data structure shown above, the programmer could code the following where fmt is a simple
926    integer value set by calling the verbose() function.
927
928    print_var(ord_dict, fmt=verbose())
929
930    The output would look like this:
931
932    ord_dict:
933      ord_dict[one]:             1
934      ord_dict[two]:             2
935      ord_dict[sub]:
936        ord_dict[sub][three]:    3
937        ord_dict[sub][four]:     4
938
939    Note the verbose format where the name of the object ("ord_dict") is repeated on every line.
940
941    If the programmer wishes to get more granular with the fmt argument, he/she can specify it as a list
942    where each entry corresponds to a level of the object being printed.  The last such list entry governs
943    the print formatting of all subordinate parts of the given object.
944
945    Look at each of the following code examples and their corresponding output.  See how the show_type()
946    formatting affects the printing depending on which position it occupies in the fmt list argument:
947
948    print_var(ord_dict, fmt=[show_type()])
949
950    ord_dict: <collections.OrderedDict>
951      ord_dict[one]:             1 <int>
952      ord_dict[two]:             2 <int>
953      ord_dict[sub]: <collections.OrderedDict>
954        ord_dict[sub][three]:    3 <int>
955        ord_dict[sub][four]:     4 <int>
956
957    print_var(ord_dict, fmt=[0, show_type()])
958
959    ord_dict:
960      ord_dict[one]:             1 <int>
961      ord_dict[two]:             2 <int>
962      ord_dict[sub]: <collections.OrderedDict>
963        ord_dict[sub][three]:    3 <int>
964        ord_dict[sub][four]:     4 <int>
965
966    print_var(ord_dict, fmt=[0, 0, show_type()])
967
968    ord_dict:
969      ord_dict[one]:             1
970      ord_dict[two]:             2
971      ord_dict[sub]:
972        ord_dict[sub][three]:    3 <int>
973        ord_dict[sub][four]:     4 <int>
974
975    Description of argument(s):
976    fmt                             The format argument such as is passed to sprint_varx.  This argument may
977                                    be an integer or a list of integers.  See the prolog of sprint_varx for
978                                    more details.
979    """
980
981    # Make a deep copy of the fmt argument in order to avoid modifying the caller's fmt value when it is a
982    # list.
983    fmt = copy.deepcopy(fmt)
984    try:
985        # Assume fmt is a list.  Pop the first element from the list.
986        first_element = list_pop(fmt, index=0, default=0)
987        # Return the first list element along with either 1) the remainder of the fmt list if not null or 2)
988        # another copy of the first element.
989        return first_element, fmt if len(fmt) else first_element
990    except AttributeError:
991        # fmt is not a list so treat it as a simple integer value.
992        return fmt, fmt
993
994
995def sprint_varx(
996    var_name,
997    var_value,
998    fmt=0,
999    indent=dft_indent,
1000    col1_width=dft_col1_width,
1001    trailing_char="\n",
1002    key_list=None,
1003    delim=":",
1004):
1005    r"""
1006    Print the var name/value passed to it.  If the caller lets col1_width default, the printing lines up
1007    nicely with output generated by the print_time functions.
1008
1009    Note that the sprint_var function (defined below) can be used to call this function so that the
1010    programmer does not need to pass the var_name.  sprint_var will figure out the var_name.  The sprint_var
1011    function is the one that would normally be used by the general user.
1012
1013    For example, the following python code:
1014
1015    first_name = "Mike"
1016    print_time("Doing this...\n")
1017    print_varx("first_name", first_name)
1018    print_time("Doing that...\n")
1019
1020    Will generate output like this:
1021
1022    #(CDT) 2016/08/10 17:34:42.847374 -    0.001285 - Doing this...
1023    first_name:                                       Mike
1024    #(CDT) 2016/08/10 17:34:42.847510 -    0.000136 - Doing that...
1025
1026    This function recognizes several complex types of data such as dict, list or tuple.
1027
1028    For example, the following python code:
1029
1030    my_dict = dict(one=1, two=2, three=3)
1031    print_var(my_dict)
1032
1033    Will generate the following output:
1034
1035    my_dict:
1036      my_dict[three]:                                 3
1037      my_dict[two]:                                   2
1038      my_dict[one]:                                   1
1039
1040    Description of argument(s).
1041    var_name                        The name of the variable to be printed.
1042    var_value                       The value of the variable to be printed.
1043    fmt                             A bit map to dictate the format of the output.  For printing multi-level
1044                                    objects like lists and dictionaries, this argument may also be a list of
1045                                    bit maps.  The first list element pertains to the highest level of
1046                                    output, the second element pertains to the 2nd level of output, etc.  The
1047                                    last element in the list pertains to all subordinate levels.  The bits
1048                                    can be set using the dynamically created functionhs above.  Example:
1049                                    sprint_varx("var1", var1, fmt=verbose()).  Note that these values can be
1050                                    OR'ed together: print_var(var1, hexa() | verbose()).  If the caller ORs
1051                                    mutually exclusive bits (hexa() | octal()), behavior is not guaranteed.
1052                                    The following features are supported:
1053        hexa                        Print all integer values in hexadecimal format.
1054        octal                       Print all integer values in octal format.
1055        binary                      Print all integer values in binary format.
1056        blank                       For blank string values, print "<blank>" instead of an actual blank.
1057        verbose                     For structured values like dictionaries, lists, etc. repeat the name of
1058                                    the variable on each line to the right of the key or subscript value.
1059                                    Example: print "my_dict[key1]" instead of just "[key1]".
1060        quote_keys                  Quote dictionary keys in the output.  Example: my_dict['key1'] instead of
1061                                    my_dict[key1].
1062        show_type                   Show the type of the data in angled brackets just to the right of the
1063                                    data.
1064        strip_brackets              Strip the brackets from the variable name portion of the output.  This is
1065                                    applicable when printing complex objects like lists or dictionaries.
1066        no_header                   For complex objects like dictionaries, do not include a header line.
1067                                    This necessarily means that the member lines will be indented 2
1068                                    characters less than they otherwise would have been.
1069        quote_values                Quote the values printed.
1070    indent                          The number of spaces to indent the output.
1071    col1_width                      The width of the output column containing the variable name.  The default
1072                                    value of this is adjusted so that the var_value lines up with text
1073                                    printed via the print_time function.
1074    trailing_char                   The character to be used at the end of the returned string.  The default
1075                                    value is a line feed.
1076    key_list                        A list of which dictionary keys should be printed.  All others keys will
1077                                    be skipped.  Each value in key_list will be regarded as a regular
1078                                    expression and it will be regarded as anchored to the beginning and ends
1079                                    of the dictionary key being referenced.  For example if key_list is
1080                                    ["one", "two"], the resulting regex used will be "^one|two$", i.e. only
1081                                    keys "one" and "two" from the var_value dictionary will be printed.  As
1082                                    another example, if the caller were to specify a key_list of ["one.*"],
1083                                    then only dictionary keys whose names begin with "one" will be printed.
1084                                    Note: This argument pertains only to var_values which are dictionaries.
1085    delim                           The value to be used to delimit the variable name from the variable value
1086                                    in the output.
1087    """
1088
1089    fmt, child_fmt = parse_fmt(fmt)
1090
1091    if fmt & show_type():
1092        type_str = "<" + str(type(var_value)).split("'")[1] + ">"
1093    # Compose object type categories.
1094    int_types = get_int_types()
1095    string_types = get_string_types()
1096    simple_types = int_types + string_types + (float, bool, type, type(None))
1097    # Determine the type.
1098    if type(var_value) in simple_types:
1099        # The data type is simple in the sense that it has no subordinate parts.
1100        # Adjust col1_width.
1101        col1_width = col1_width - indent
1102        # Set default value for value_format.
1103        value_format = "%s"
1104        # Process format requests.
1105        if type(var_value) in int_types:
1106            # Process format values pertaining to int types.
1107            if fmt & hexa():
1108                num_hex_digits = max(
1109                    dft_num_hex_digits(), get_req_num_hex_digits(var_value)
1110                )
1111                # Convert a negative number to its positive twos complement for proper printing.  For
1112                # example, instead of printing -1 as "0x-000000000000001" it will be printed as
1113                # "0xffffffffffffffff".
1114                var_value = var_value & (2 ** (num_hex_digits * 4) - 1)
1115                value_format = "0x%0" + str(num_hex_digits) + "x"
1116            elif fmt & octal():
1117                value_format = "0o%016o"
1118            elif fmt & binary():
1119                num_digits, remainder = divmod(
1120                    max(bit_length(var_value), 1), 8
1121                )
1122                num_digits *= 8
1123                if remainder:
1124                    num_digits += 8
1125                num_digits += 2
1126                value_format = "#0" + str(num_digits) + "b"
1127                var_value = format(var_value, value_format)
1128                value_format = "%s"
1129        elif type(var_value) in string_types:
1130            # Process format values pertaining to string types.
1131            if fmt & blank() and var_value == "":
1132                value_format = "%s"
1133                var_value = "<blank>"
1134        elif type(var_value) is type:
1135            var_value = str(var_value).split("'")[1]
1136        format_string = (
1137            "%" + str(indent) + "s%-" + str(col1_width) + "s" + value_format
1138        )
1139        if fmt & show_type():
1140            if var_value != "":
1141                format_string += " "
1142            format_string += type_str
1143        format_string += trailing_char
1144        if fmt & quote_values():
1145            var_value = "'" + var_value + "'"
1146        if not (fmt & verbose()):
1147            # Strip everything leading up to the first left square brace.
1148            var_name = re.sub(r".*\[", "[", var_name)
1149        if fmt & strip_brackets():
1150            var_name = re.sub(r"[\[\]]", "", var_name)
1151        if value_format == "0x%08x":
1152            return format_string % (
1153                "",
1154                str(var_name) + delim,
1155                var_value & 0xFFFFFFFF,
1156            )
1157        else:
1158            return format_string % ("", str(var_name) + delim, var_value)
1159    else:
1160        # The data type is complex in the sense that it has subordinate parts.
1161        if fmt & no_header():
1162            buffer = ""
1163        else:
1164            # Create header line.
1165            if not (fmt & verbose()):
1166                # Strip everything leading up to the first square brace.
1167                loc_var_name = re.sub(r".*\[", "[", var_name)
1168            else:
1169                loc_var_name = var_name
1170            if fmt & strip_brackets():
1171                loc_var_name = re.sub(r"[\[\]]", "", loc_var_name)
1172            format_string = "%" + str(indent) + "s%s\n"
1173            buffer = format_string % ("", loc_var_name + ":")
1174            if fmt & show_type():
1175                buffer = buffer.replace("\n", " " + type_str + "\n")
1176            indent += 2
1177        try:
1178            length = len(var_value)
1179        except TypeError:
1180            length = 0
1181        ix = 0
1182        loc_trailing_char = "\n"
1183        if is_dict(var_value):
1184            if type(child_fmt) is list:
1185                child_quote_keys = child_fmt[0] & quote_keys()
1186            else:
1187                child_quote_keys = child_fmt & quote_keys()
1188            for key, value in var_value.items():
1189                if key_list is not None:
1190                    key_list_regex = "^" + "|".join(key_list) + "$"
1191                    if not re.match(key_list_regex, key):
1192                        continue
1193                ix += 1
1194                if ix == length:
1195                    loc_trailing_char = trailing_char
1196                if child_quote_keys:
1197                    key = "'" + key + "'"
1198                key = "[" + str(key) + "]"
1199                buffer += sprint_varx(
1200                    var_name + key,
1201                    value,
1202                    child_fmt,
1203                    indent,
1204                    col1_width,
1205                    loc_trailing_char,
1206                    key_list,
1207                    delim,
1208                )
1209        elif type(var_value) in (list, tuple, set):
1210            for key, value in enumerate(var_value):
1211                ix += 1
1212                if ix == length:
1213                    loc_trailing_char = trailing_char
1214                key = "[" + str(key) + "]"
1215                buffer += sprint_varx(
1216                    var_name + key,
1217                    value,
1218                    child_fmt,
1219                    indent,
1220                    col1_width,
1221                    loc_trailing_char,
1222                    key_list,
1223                    delim,
1224                )
1225        elif isinstance(var_value, argparse.Namespace):
1226            for key in var_value.__dict__:
1227                ix += 1
1228                if ix == length:
1229                    loc_trailing_char = trailing_char
1230                cmd_buf = (
1231                    'buffer += sprint_varx(var_name + "." + str(key)'
1232                    + ", var_value."
1233                    + key
1234                    + ", child_fmt, indent,"
1235                    + " col1_width, loc_trailing_char, key_list,"
1236                    + " delim)"
1237                )
1238                exec(cmd_buf)
1239        else:
1240            var_type = type(var_value).__name__
1241            func_name = sys._getframe().f_code.co_name
1242            var_value = (
1243                "<" + var_type + " type not supported by " + func_name + "()>"
1244            )
1245            value_format = "%s"
1246            indent -= 2
1247            # Adjust col1_width.
1248            col1_width = col1_width - indent
1249            format_string = (
1250                "%"
1251                + str(indent)
1252                + "s%-"
1253                + str(col1_width)
1254                + "s"
1255                + value_format
1256                + trailing_char
1257            )
1258            return format_string % ("", str(var_name) + ":", var_value)
1259
1260        return buffer
1261
1262    return ""
1263
1264
1265def sprint_var(*args, **kwargs):
1266    r"""
1267    Figure out the name of the first argument for the caller and then call sprint_varx with it.  Therefore,
1268    the following 2 calls are equivalent:
1269    sprint_varx("var1", var1)
1270    sprint_var(var1)
1271
1272    See sprint_varx for description of arguments.
1273    """
1274
1275    stack_frame = 2
1276    caller_func_name = sprint_func_name(2)
1277    if caller_func_name.endswith("print_var"):
1278        stack_frame += 1
1279    # Get the name of the first variable passed to this function.
1280    var_name = get_arg_name(None, 1, stack_frame)
1281    return sprint_varx(var_name, *args, **kwargs)
1282
1283
1284def sprint_vars(*args, **kwargs):
1285    r"""
1286    Sprint the values of one or more variables.
1287
1288    Description of argument(s):
1289    args                            The variable values which are to be printed.
1290    kwargs                          See sprint_varx (above) for description of additional arguments.
1291    """
1292
1293    stack_frame = 2
1294    caller_func_name = sprint_func_name(2)
1295    if caller_func_name.endswith("print_vars"):
1296        stack_frame += 1
1297
1298    buffer = ""
1299    arg_num = 1
1300    for var_value in args:
1301        var_name = get_arg_name(None, arg_num, stack_frame)
1302        buffer += sprint_varx(var_name, var_value, **kwargs)
1303        arg_num += 1
1304
1305    return buffer
1306
1307
1308def sprint_dashes(indent=dft_indent, width=80, line_feed=1, char="-"):
1309    r"""
1310    Return a string of dashes to the caller.
1311
1312    Description of argument(s):
1313    indent                          The number of characters to indent the output.
1314    width                           The width of the string of dashes.
1315    line_feed                       Indicates whether the output should end with a line feed.
1316    char                            The character to be repeated in the output string.
1317    """
1318
1319    width = int(width)
1320    buffer = " " * int(indent) + char * width
1321    if line_feed:
1322        buffer += "\n"
1323
1324    return buffer
1325
1326
1327def sindent(text="", indent=0):
1328    r"""
1329    Pre-pend the specified number of characters to the text string (i.e. indent it) and return it.
1330
1331    Description of argument(s):
1332    text                            The string to be indented.
1333    indent                          The number of characters to indent the string.
1334    """
1335
1336    format_string = "%" + str(indent) + "s%s"
1337    buffer = format_string % ("", text)
1338
1339    return buffer
1340
1341
1342func_line_style_std = None
1343func_line_style_short = 1
1344
1345
1346def sprint_func_line(stack_frame, style=None, max_width=160):
1347    r"""
1348    For the given stack_frame, return a formatted string containing the function name and all its arguments.
1349
1350    Example:
1351
1352    func1(last_name = 'walsh', first_name = 'mikey')
1353
1354    Description of argument(s):
1355    stack_frame                     A stack frame (such as is returned by inspect.stack()).
1356    style                           Indicates the style or formatting of the result string.  Acceptable
1357                                    values are shown above.
1358    max_width                       The max width of the result.  If it exceeds this length, it will be
1359                                    truncated on the right.
1360
1361    Description of styles:
1362    func_line_style_std             The standard formatting.
1363    func_line_style_short           1) The self parm (associated with methods) will be dropped. 2) The args
1364                                    and kwargs values will be treated as special.  In both cases the arg name
1365                                    ('args' or 'kwargs') will be dropped and only the values will be shown.
1366    """
1367
1368    func_name = str(stack_frame[3])
1369    if func_name == "?":
1370        # "?" is the name used when code is not in a function.
1371        func_name = "(none)"
1372
1373    if func_name == "<module>":
1374        # If the func_name is the "main" program, we simply get the command line call string.
1375        func_and_args = " ".join(sys.argv)
1376    else:
1377        # Get the program arguments.
1378        args, varargs, keywords, locals = inspect.getargvalues(stack_frame[0])
1379
1380        args_list = []
1381        for arg_name in filter(None, args + [varargs, keywords]):
1382            # Get the arg value from frame locals.
1383            arg_value = locals[arg_name]
1384            if arg_name == "self":
1385                if style == func_line_style_short:
1386                    continue
1387                # Manipulations to improve output for class methods.
1388                func_name = arg_value.__class__.__name__ + "." + func_name
1389                args_list.append(arg_name + " = <self>")
1390            elif (
1391                style == func_line_style_short
1392                and arg_name == "args"
1393                and type(arg_value) in (list, tuple)
1394            ):
1395                if len(arg_value) == 0:
1396                    continue
1397                args_list.append(repr(", ".join(arg_value)))
1398            elif (
1399                style == func_line_style_short
1400                and arg_name == "kwargs"
1401                and type(arg_value) is dict
1402            ):
1403                for key, value in arg_value.items():
1404                    args_list.append(key + "=" + repr(value))
1405            else:
1406                args_list.append(arg_name + " = " + repr(arg_value))
1407        args_str = "(" + ", ".join(map(str, args_list)) + ")"
1408
1409        # Now we need to print this in a nicely-wrapped way.
1410        func_and_args = func_name + args_str
1411
1412    if len(func_and_args) > max_width:
1413        func_and_args = func_and_args[0:max_width] + "..."
1414    return func_and_args
1415
1416
1417def sprint_call_stack(indent=0, stack_frame_ix=0, style=None):
1418    r"""
1419    Return a call stack report for the given point in the program with line numbers, function names and
1420    function parameters and arguments.
1421
1422    Sample output:
1423
1424    -------------------------------------------------------------------------
1425    Python function call stack
1426
1427    Line # Function name and arguments
1428    ------ ------------------------------------------------------------------
1429       424 sprint_call_stack()
1430         4 print_call_stack()
1431        31 func1(last_name = 'walsh', first_name = 'mikey')
1432        59 /tmp/scr5.py
1433    -------------------------------------------------------------------------
1434
1435    Description of argument(s):
1436    indent                          The number of characters to indent each line of output.
1437    stack_frame_ix                  The index of the first stack frame which is to be returned.
1438    style                           See the sprint_line_func prolog above for details.
1439    """
1440
1441    buffer = ""
1442    buffer += sprint_dashes(indent)
1443    buffer += sindent("Python function call stack\n\n", indent)
1444    buffer += sindent("Line # Function name and arguments\n", indent)
1445    buffer += sprint_dashes(indent, 6, 0) + " " + sprint_dashes(0, 73)
1446
1447    # Grab the current program stack.
1448    work_around_inspect_stack_cwd_failure()
1449    current_stack = inspect.stack()
1450
1451    # Process each frame in turn.
1452    format_string = "%6s %s\n"
1453    ix = 0
1454    for stack_frame in current_stack:
1455        if ix < stack_frame_ix:
1456            ix += 1
1457            continue
1458        # Make the line number shown to be the line where one finds the line shown.
1459        try:
1460            line_num = str(current_stack[ix + 1][2])
1461        except IndexError:
1462            line_num = ""
1463        func_and_args = sprint_func_line(stack_frame, style=style)
1464
1465        buffer += sindent(format_string % (line_num, func_and_args), indent)
1466        ix += 1
1467
1468    buffer += sprint_dashes(indent)
1469
1470    return buffer
1471
1472
1473def sprint_executing(stack_frame_ix=None, style=None, max_width=None):
1474    r"""
1475    Print a line indicating what function is executing and with what parameter values.  This is useful for
1476    debugging.
1477
1478    Sample output:
1479
1480    #(CDT) 2016/08/25 17:54:27 - Executing: func1(x = 1)
1481
1482    Description of argument(s):
1483    stack_frame_ix                  The index of the stack frame whose function info should be returned.  If
1484                                    the caller does not specify a value, this function will set the value to
1485                                    1 which is the index of the caller's stack frame.  If the caller is the
1486                                    wrapper function "print_executing", this function will bump it up by 1.
1487    style                           See the sprint_line_func prolog above for details.
1488    max_width                       See the sprint_line_func prolog above for details.
1489    """
1490
1491    # If user wants default stack_frame_ix.
1492    if stack_frame_ix is None:
1493        func_name = sys._getframe().f_code.co_name
1494        caller_func_name = sys._getframe(1).f_code.co_name
1495        if caller_func_name.endswith(func_name[1:]):
1496            stack_frame_ix = 2
1497        else:
1498            stack_frame_ix = 1
1499
1500    work_around_inspect_stack_cwd_failure()
1501    stack_frame = inspect.stack()[stack_frame_ix]
1502
1503    if max_width is None:
1504        max_width = 160 - (dft_col1_width + 11)
1505    func_and_args = sprint_func_line(stack_frame, style, max_width=max_width)
1506
1507    return sprint_time() + "Executing: " + func_and_args + "\n"
1508
1509
1510def sprint_pgm_header(indent=0, linefeed=1):
1511    r"""
1512    Return a standardized header that programs should print at the beginning of the run.  It includes useful
1513    information like command line, pid, userid, program parameters, etc.
1514
1515    Description of argument(s):
1516    indent                          The number of characters to indent each line of output.
1517    linefeed                        Indicates whether a line feed be included at the beginning and end of the
1518                                    report.
1519    """
1520
1521    col1_width = dft_col1_width + indent
1522
1523    buffer = ""
1524    if linefeed:
1525        buffer = "\n"
1526
1527    if robot_env:
1528        suite_name = BuiltIn().get_variable_value("${suite_name}")
1529        buffer += sindent(
1530            sprint_time('Running test suite "' + suite_name + '".\n'), indent
1531        )
1532
1533    buffer += sindent(sprint_time() + "Running " + pgm_name + ".\n", indent)
1534    buffer += sindent(
1535        sprint_time() + "Program parameter values, etc.:\n\n", indent
1536    )
1537    buffer += sprint_varx(
1538        "command_line", " ".join(sys.argv), 0, indent, col1_width
1539    )
1540    # We want the output to show a customized name for the pid and pgid but we want it to look like a valid
1541    # variable name.  Therefore, we'll use pgm_name_var_name which was set when this module was imported.
1542    buffer += sprint_varx(
1543        pgm_name_var_name + "_pid", os.getpid(), 0, indent, col1_width
1544    )
1545    buffer += sprint_varx(
1546        pgm_name_var_name + "_pgid", os.getpgrp(), 0, indent, col1_width
1547    )
1548    userid_num = str(os.geteuid())
1549    try:
1550        username = os.getlogin()
1551    except OSError:
1552        if userid_num == "0":
1553            username = "root"
1554        else:
1555            username = "?"
1556    buffer += sprint_varx(
1557        "uid", userid_num + " (" + username + ")", 0, indent, col1_width
1558    )
1559    buffer += sprint_varx(
1560        "gid",
1561        str(os.getgid()) + " (" + str(grp.getgrgid(os.getgid()).gr_name) + ")",
1562        0,
1563        indent,
1564        col1_width,
1565    )
1566    buffer += sprint_varx(
1567        "host_name", socket.gethostname(), 0, indent, col1_width
1568    )
1569    try:
1570        DISPLAY = os.environ["DISPLAY"]
1571    except KeyError:
1572        DISPLAY = ""
1573    buffer += sprint_var(DISPLAY, 0, indent, col1_width)
1574    PYTHON_VERSION = os.environ.get("PYTHON_VERSION", None)
1575    if PYTHON_VERSION is not None:
1576        buffer += sprint_var(PYTHON_VERSION, 0, indent, col1_width)
1577    PYTHON_PGM_PATH = os.environ.get("PYTHON_PGM_PATH", None)
1578    if PYTHON_PGM_PATH is not None:
1579        buffer += sprint_var(PYTHON_PGM_PATH, 0, indent, col1_width)
1580    python_version = sys.version.replace("\n", "")
1581    buffer += sprint_var(python_version, 0, indent, col1_width)
1582    ROBOT_VERSION = os.environ.get("ROBOT_VERSION", None)
1583    if ROBOT_VERSION is not None:
1584        buffer += sprint_var(ROBOT_VERSION, 0, indent, col1_width)
1585    ROBOT_PGM_PATH = os.environ.get("ROBOT_PGM_PATH", None)
1586    if ROBOT_PGM_PATH is not None:
1587        buffer += sprint_var(ROBOT_PGM_PATH, 0, indent, col1_width)
1588
1589    # __builtin__.arg_obj is created by the get_arg module function, gen_get_options.
1590    try:
1591        buffer += ga.sprint_args(__builtin__.arg_obj, indent)
1592    except AttributeError:
1593        pass
1594
1595    if robot_env:
1596        # Get value of global parm_list.
1597        parm_list = BuiltIn().get_variable_value("${parm_list}")
1598
1599        for parm in parm_list:
1600            parm_value = BuiltIn().get_variable_value("${" + parm + "}")
1601            buffer += sprint_varx(parm, parm_value, 0, indent, col1_width)
1602
1603        # Setting global program_pid.
1604        BuiltIn().set_global_variable("${program_pid}", os.getpid())
1605
1606    if linefeed:
1607        buffer += "\n"
1608
1609    return buffer
1610
1611
1612def sprint_error_report(
1613    error_text="\n", indent=2, format=None, stack_frame_ix=None
1614):
1615    r"""
1616    Return a string with a standardized report which includes the caller's error text, the call stack and the
1617    program header.
1618
1619    Description of argument(s):
1620    error_text                      The error text to be included in the report.  The caller should include
1621                                    any needed linefeeds.
1622    indent                          The number of characters to indent each line of output.
1623    format                          Long or short format.  Long includes extras like lines of dashes, call
1624                                    stack, etc.
1625    stack_frame_ix                  The index of the first stack frame which is to be shown in the
1626                                    print_call_stack portion of the error report.
1627    """
1628
1629    # Process input.
1630    indent = int(indent)
1631    if format is None:
1632        if robot_env:
1633            format = "short"
1634        else:
1635            format = "long"
1636    error_text = error_text.rstrip("\n") + "\n"
1637
1638    if format == "short":
1639        return sprint_error(error_text)
1640
1641    buffer = ""
1642    buffer += sprint_dashes(width=120, char="=")
1643    buffer += sprint_error(error_text)
1644    buffer += "\n"
1645    if not stack_frame_ix:
1646        # Calling sprint_call_stack with stack_frame_ix of 0 causes it to show itself and this function in
1647        # the call stack.  This is not helpful to a debugger and is therefore clutter.  We will adjust the
1648        # stack_frame_ix to hide that information.
1649        stack_frame_ix = 1
1650        caller_func_name = sprint_func_name(1)
1651        if caller_func_name.endswith("print_error_report"):
1652            stack_frame_ix += 1
1653        caller_func_name = sprint_func_name(2)
1654        if caller_func_name.endswith("print_error_report"):
1655            stack_frame_ix += 1
1656    buffer += sprint_call_stack(indent, stack_frame_ix)
1657    buffer += sprint_pgm_header(indent)
1658    buffer += sprint_dashes(width=120, char="=")
1659
1660    return buffer
1661
1662
1663def sprint_issuing(cmd_buf, test_mode=0):
1664    r"""
1665    Return a line indicating a command that the program is about to execute.
1666
1667    Sample output for a cmd_buf of "ls"
1668
1669    #(CDT) 2016/08/25 17:57:36 - Issuing: ls
1670
1671    Description of argument(s):
1672    cmd_buf                         The command to be executed by caller.
1673    test_mode                       With test_mode set, the output will look like this:
1674
1675    #(CDT) 2016/08/25 17:57:36 - (test_mode) Issuing: ls
1676
1677    """
1678
1679    buffer = sprint_time()
1680    if test_mode:
1681        buffer += "(test_mode) "
1682    if type(cmd_buf) is list:
1683        # Assume this is a robot command in the form of a list.
1684        cmd_buf = "  ".join([str(element) for element in cmd_buf])
1685
1686    try:
1687        passwd_value = os.environ.get(
1688            "OPENBMC_PASSWORD", ""
1689        ) or BuiltIn().get_variable_value("${OPENBMC_PASSWORD}", default="")
1690    except Exception as e:
1691        passwd_value = ""
1692        pass
1693
1694    # adds overhead checking for password masking.
1695    if passwd_value != "" and passwd_value in cmd_buf:
1696        buffer += (
1697            "Issuing: " + cmd_buf.replace(passwd_value, "**********") + "\n"
1698        )
1699    else:
1700        buffer += "Issuing: " + cmd_buf + "\n"
1701
1702    return buffer
1703
1704
1705def sprint_pgm_footer():
1706    r"""
1707    Return a standardized footer that programs should print at the end of the program run.  It includes
1708    useful information like total run time, etc.
1709    """
1710
1711    buffer = "\n" + sprint_time() + "Finished running " + pgm_name + ".\n\n"
1712
1713    total_time = time.time() - start_time
1714    total_time_string = "%0.6f" % total_time
1715
1716    buffer += sprint_varx(pgm_name_var_name + "_runtime", total_time_string)
1717    buffer += "\n"
1718
1719    return buffer
1720
1721
1722def sprint_file(file_path):
1723    r"""
1724    Return the file data as a string.
1725
1726    Description of argument(s):
1727    file_path                       The path to a file (e.g. "/tmp/file1").
1728    """
1729
1730    with open(file_path, "r") as file:
1731        buffer = file.read()
1732    return buffer
1733
1734
1735def sprint(buffer=""):
1736    r"""
1737    Simply return the user's buffer.  This function is used by the qprint and dprint functions defined
1738    dynamically below, i.e. it would not normally be called for general use.
1739
1740    Description of argument(s).
1741    buffer                          This will be returned to the caller.
1742    """
1743
1744    try:
1745        return str(buffer)
1746    except UnicodeEncodeError:
1747        return buffer
1748
1749
1750def sprintn(buffer=""):
1751    r"""
1752    Simply return the user's buffer with a line feed.  This function is used by the qprint and dprint
1753    functions defined dynamically below, i.e. it would not normally be called for general use.
1754
1755    Description of argument(s).
1756    buffer                          This will be returned to the caller.
1757    """
1758
1759    try:
1760        buffer = str(buffer) + "\n"
1761    except UnicodeEncodeError:
1762        buffer = buffer + "\n"
1763
1764    return buffer
1765
1766
1767def gp_print(buffer, stream="stdout"):
1768    r"""
1769    Print the buffer using either sys.stdout.write or BuiltIn().log_to_console depending on whether we are
1770    running in a robot environment.
1771
1772    This function is intended for use only by other functions in this module.
1773
1774    Description of argument(s):
1775    buffer                          The string to be printed.
1776    stream                          Either "stdout" or "stderr".
1777    """
1778
1779    if robot_env:
1780        BuiltIn().log_to_console(buffer, stream=stream, no_newline=True)
1781    else:
1782        if stream == "stdout":
1783            sys.stdout.write(buffer)
1784            sys.stdout.flush()
1785        else:
1786            sys.stderr.write(buffer)
1787            sys.stderr.flush()
1788
1789
1790def gp_log(buffer):
1791    r"""
1792    Log the buffer using either python logging or BuiltIn().log depending on whether we are running in a
1793    robot environment.
1794
1795    This function is intended for use only by other functions in this module.
1796
1797    Description of argument(s):
1798    buffer                          The string to be logged.
1799    """
1800
1801    if robot_env:
1802        BuiltIn().log(buffer)
1803    else:
1804        logging.warning(buffer)
1805
1806
1807def gp_debug_print(buffer):
1808    r"""
1809    Print with gp_print only if gen_print_debug is set.
1810
1811    This function is intended for use only by other functions in this module.
1812
1813    Description of argument(s):
1814    buffer                          The string to be printed.
1815    """
1816
1817    if not gen_print_debug:
1818        return
1819
1820    gp_print(buffer)
1821
1822
1823def get_var_value(var_value=None, default=1, var_name=None):
1824    r"""
1825    Return either var_value, the corresponding global value or default.
1826
1827    If var_value is not None, it will simply be returned.
1828
1829    If var_value is None, this function will return the corresponding global value of the variable in
1830    question.
1831
1832    Note: For global values, if we are in a robot environment, get_variable_value will be used.  Otherwise,
1833    the __builtin__ version of the variable is returned (which are set by gen_arg.py functions).
1834
1835    If there is no global value associated with the variable, default is returned.
1836
1837    This function is useful for other functions in setting default values for parameters.
1838
1839    Example use:
1840
1841    def my_func(quiet=None):
1842
1843      quiet = int(get_var_value(quiet, 0))
1844
1845    Example calls to my_func():
1846
1847    In the following example, the caller is explicitly asking to have quiet be set to 1.
1848
1849    my_func(quiet=1)
1850
1851    In the following example, quiet will be set to the global value of quiet, if defined, or to 0 (the
1852    default).
1853
1854    my_func()
1855
1856    Description of argument(s):
1857    var_value                       The value to be returned (if not equal to None).
1858    default                         The value that is returned if var_value is None and there is no
1859                                    corresponding global value defined.
1860    var_name                        The name of the variable whose value is to be returned.  Under most
1861                                    circumstances, this value need not be provided.  This function can figure
1862                                    out the name of the variable passed as var_value.  One exception to this
1863                                    would be if this function is called directly from a .robot file.
1864    """
1865
1866    if var_value is not None:
1867        return var_value
1868
1869    if var_name is None:
1870        var_name = get_arg_name(None, 1, 2)
1871
1872    if robot_env:
1873        var_value = BuiltIn().get_variable_value(
1874            "${" + var_name + "}", default
1875        )
1876    else:
1877        var_value = getattr(__builtin__, var_name, default)
1878
1879    return var_value
1880
1881
1882def get_stack_var(var_name, default="", init_stack_ix=2):
1883    r"""
1884    Starting with the caller's stack level, search upward in the call stack for a variable named var_name and
1885    return its value.  If the variable cannot be found in the stack, attempt to get the global value.  If the
1886    variable still cannot be found, return default.
1887
1888    Example code:
1889
1890    def func12():
1891        my_loc_var1 = get_stack_var('my_var1', "default value")
1892
1893    def func11():
1894        my_var1 = 11
1895        func12()
1896
1897    In this example, get_stack_var will find the value of my_var1 in func11's stack and will therefore return
1898    the value 11.  Therefore, my_loc_var1 would get set to 11.
1899
1900    Description of argument(s):
1901    var_name                        The name of the variable to be searched for.
1902    default                         The value to return if the the variable cannot be found.
1903    init_stack_ix                   The initial stack index from which to begin the search.  0 would be the
1904                                    index of this func1tion ("get_stack_var"), 1 would be the index of the
1905                                    function calling this function, etc.
1906    """
1907
1908    work_around_inspect_stack_cwd_failure()
1909    default = get_var_value(var_name=var_name, default=default)
1910    return next(
1911        (
1912            frame[0].f_locals[var_name]
1913            for frame in inspect.stack()[init_stack_ix:]
1914            if var_name in frame[0].f_locals
1915        ),
1916        default,
1917    )
1918
1919
1920# hidden_text is a list of passwords which are to be replaced with asterisks by print functions defined in
1921# this module.
1922hidden_text = []
1923# password_regex is created based on the contents of hidden_text.
1924password_regex = ""
1925
1926
1927def register_passwords(*args):
1928    r"""
1929    Register one or more passwords which are to be hidden in output produced by the print functions in this
1930    module.
1931
1932    Note:  Blank password values are NOT registered.  They are simply ignored.
1933
1934    Description of argument(s):
1935    args                            One or more password values.  If a given password value is already
1936                                    registered, this function will simply do nothing.
1937    """
1938
1939    global hidden_text
1940    global password_regex
1941
1942    for password in args:
1943        if password == "":
1944            break
1945        if password in hidden_text:
1946            break
1947
1948        # Place the password into the hidden_text list.
1949        hidden_text.append(password)
1950        # Create a corresponding password regular expression.  Escape regex special characters too.
1951        password_regex = (
1952            "(" + "|".join([re.escape(x) for x in hidden_text]) + ")"
1953        )
1954
1955
1956def replace_passwords(buffer):
1957    r"""
1958    Return the buffer but with all registered passwords replaced by a string of asterisks.
1959
1960
1961    Description of argument(s):
1962    buffer                          The string to be returned but with passwords replaced.
1963    """
1964
1965    global password_regex
1966
1967    if int(os.environ.get("DEBUG_SHOW_PASSWORDS", "0")):
1968        return buffer
1969
1970    if password_regex == "":
1971        # No passwords to replace.
1972        return buffer
1973
1974    return re.sub(password_regex, "********", buffer)
1975
1976
1977def create_print_wrapper_funcs(
1978    func_names, stderr_func_names, replace_dict, func_prefix=""
1979):
1980    r"""
1981    Generate code for print wrapper functions and return the generated code as a string.
1982
1983    To illustrate, suppose there is a "print_foo_bar" function in the func_names list.
1984    This function will...
1985    - Expect that there is an sprint_foo_bar function already in existence.
1986    - Create a print_foo_bar function which calls sprint_foo_bar and prints the result.
1987    - Create a qprint_foo_bar function which calls upon sprint_foo_bar only if global value quiet is 0.
1988    - Create a dprint_foo_bar function which calls upon sprint_foo_bar only if global value debug is 1.
1989
1990    Also, code will be generated to define aliases for each function as well.  Each alias will be created by
1991    replacing "print_" in the function name with "p"  For example, the alias for print_foo_bar will be
1992    pfoo_bar.
1993
1994    Description of argument(s):
1995    func_names                      A list of functions for which print wrapper function code is to be
1996                                    generated.
1997    stderr_func_names               A list of functions whose generated code should print to stderr rather
1998                                    than to stdout.
1999    replace_dict                    Please see the create_func_def_string function in wrap_utils.py for
2000                                    details on this parameter.  This parameter will be passed directly to
2001                                    create_func_def_string.
2002    func_prefix                     Prefix to be prepended to the generated function name.
2003    """
2004
2005    buffer = ""
2006
2007    for func_name in func_names:
2008        if func_name in stderr_func_names:
2009            replace_dict["output_stream"] = "stderr"
2010        else:
2011            replace_dict["output_stream"] = "stdout"
2012
2013        s_func_name = "s" + func_name
2014        q_func_name = "q" + func_name
2015        d_func_name = "d" + func_name
2016
2017        # We don't want to try to redefine the "print" function, thus the following if statement.
2018        if func_name != "print":
2019            func_def = create_func_def_string(
2020                s_func_name,
2021                func_prefix + func_name,
2022                print_func_template,
2023                replace_dict,
2024            )
2025            buffer += func_def
2026
2027        func_def = create_func_def_string(
2028            s_func_name,
2029            func_prefix + "q" + func_name,
2030            qprint_func_template,
2031            replace_dict,
2032        )
2033        buffer += func_def
2034
2035        func_def = create_func_def_string(
2036            s_func_name,
2037            func_prefix + "d" + func_name,
2038            dprint_func_template,
2039            replace_dict,
2040        )
2041        buffer += func_def
2042
2043        func_def = create_func_def_string(
2044            s_func_name,
2045            func_prefix + "l" + func_name,
2046            lprint_func_template,
2047            replace_dict,
2048        )
2049        buffer += func_def
2050
2051        # Create abbreviated aliases (e.g. spvar is an alias for sprint_var).
2052        alias = re.sub("print_", "p", func_name)
2053        alias = re.sub("print", "p", alias)
2054        prefixes = [
2055            func_prefix + "",
2056            "s",
2057            func_prefix + "q",
2058            func_prefix + "d",
2059            func_prefix + "l",
2060        ]
2061        for prefix in prefixes:
2062            if alias == "p":
2063                continue
2064            func_def = prefix + alias + " = " + prefix + func_name
2065            buffer += func_def + "\n"
2066
2067    return buffer
2068
2069
2070# In the following section of code, we will dynamically create print versions for each of the sprint
2071# functions defined above.  So, for example, where we have an sprint_time() function defined above that
2072# returns the time to the caller in a string, we will create a corresponding print_time() function that will
2073# print that string directly to stdout.
2074
2075# It can be complicated to follow what's being created below.  Here is an example of the print_time()
2076# function that will be created:
2077
2078# def print_time(buffer=''):
2079#     gp_print(replace_passwords(sprint_time(buffer=buffer)), stream='stdout')
2080
2081# For each print function defined below, there will also be a qprint, a dprint and an lprint version defined
2082# (e.g. qprint_time, dprint_time, lprint_time).
2083
2084# The q version of each print function will only print if the quiet variable is 0.
2085# The d version of each print function will only print if the debug variable is 1.
2086# The l version of each print function will print the contents as log data.  For conventional programs, this
2087# means use of the logging module.  For robot programs it means use of the BuiltIn().log() function.
2088
2089# Templates for the various print wrapper functions.
2090print_func_template = [
2091    "    <mod_qualifier>gp_print(<mod_qualifier>replace_passwords("
2092    + "<call_line>), stream='<output_stream>')"
2093]
2094
2095qprint_func_template = [
2096    '    quiet = <mod_qualifier>get_stack_var("quiet", 0)',
2097    "    if int(quiet): return",
2098] + print_func_template
2099
2100dprint_func_template = [
2101    '    debug = <mod_qualifier>get_stack_var("debug", 0)',
2102    "    if not int(debug): return",
2103] + print_func_template
2104
2105lprint_func_template = [
2106    "    <mod_qualifier>set_last_seconds_ix(<mod_qualifier>"
2107    + "lprint_last_seconds_ix())",
2108    "    <mod_qualifier>gp_log(<mod_qualifier>replace_passwords"
2109    + "(<call_line>))",
2110    "    <mod_qualifier>set_last_seconds_ix(<mod_qualifier>"
2111    + "standard_print_last_seconds_ix())",
2112]
2113
2114replace_dict = {"output_stream": "stdout", "mod_qualifier": ""}
2115
2116gp_debug_print("robot_env: " + str(robot_env) + "\n")
2117
2118# func_names contains a list of all print functions which should be created from their sprint counterparts.
2119func_names = [
2120    "print_time",
2121    "print_timen",
2122    "print_error",
2123    "print_varx",
2124    "print_var",
2125    "print_vars",
2126    "print_dashes",
2127    "indent",
2128    "print_call_stack",
2129    "print_func_name",
2130    "print_executing",
2131    "print_pgm_header",
2132    "print_issuing",
2133    "print_pgm_footer",
2134    "print_file",
2135    "print_error_report",
2136    "print",
2137    "printn",
2138]
2139
2140# stderr_func_names is a list of functions whose output should go to stderr rather than stdout.
2141stderr_func_names = ["print_error", "print_error_report"]
2142
2143func_defs = create_print_wrapper_funcs(
2144    func_names, stderr_func_names, replace_dict
2145)
2146gp_debug_print(func_defs)
2147exec(func_defs)
2148