xref: /openbmc/openbmc-test-automation/lib/gen_print.py (revision 7049fbaf5cb3f1c7a7d73833e59cea28d3ce82fb)
1 #!/usr/bin/env python3
2 
3 r"""
4 This module provides many print functions such as sprint_var, sprint_time, sprint_error, sprint_call_stack.
5 """
6 
7 import argparse
8 import copy
9 import grp
10 import inspect
11 import os
12 import re
13 import socket
14 import sys
15 import time
16 
17 try:
18     import __builtin__
19 except ImportError:
20     import builtins as __builtin__
21 
22 import collections
23 import logging
24 
25 from wrap_utils import *
26 
27 try:
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
38 except ImportError:
39     robot_env = 0
40 
41 import gen_arg as ga
42 
43 # Setting these variables for use both inside this module and by programs importing this module.
44 pgm_file_path = sys.argv[0]
45 pgm_name = os.path.basename(pgm_file_path)
46 pgm_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.
53 pgm_name_var_name = pgm_name.replace(".", "_")
54 
55 # Initialize global values used as defaults by print_time, print_var, etc.
56 dft_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.
60 dft_col1_width = 29
61 
62 NANOSECONDS = os.environ.get("NANOSECONDS", "1")
63 
64 if NANOSECONDS == "1":
65     dft_col1_width = dft_col1_width + 7
66 
67 SHOW_ELAPSED_TIME = os.environ.get("SHOW_ELAPSED_TIME", "1")
68 
69 if 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.
76 start_time = time.time()
77 # sprint_time_last_seconds is used to calculate elapsed seconds.
78 sprint_time_last_seconds = [start_time, start_time]
79 # Define global index for the sprint_time_last_seconds list.
80 last_seconds_ix = 0
81 
82 
83 def 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].
99 def 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 
106 def 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.
114 gen_print_debug = int(os.environ.get("GEN_PRINT_DEBUG", 0))
115 
116 
117 def 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 
142 def 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 
158 def 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.
168 def 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 
494 def 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 
576 def 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 
584 def 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.
609 def digit_length_in_bits():
610     r"""
611     Return the digit length in bits.
612     """
613 
614     return 4
615 
616 
617 def word_length_in_digits():
618     r"""
619     Return the word length in digits.
620     """
621 
622     return 8
623 
624 
625 def 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 
653 def 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 
704 def 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.
720 def dict_type():
721     return 1
722 
723 
724 def ordered_dict_type():
725     return 2
726 
727 
728 def dot_dict_type():
729     return 3
730 
731 
732 def normalized_dict_type():
733     return 4
734 
735 
736 def proxy_dict_type():
737     return 5
738 
739 
740 def 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 
777 def 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 
792 def 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 
807 def 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 
827 def 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):
856 exec(create_fmt_definition())
857 
858 
859 def 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 
872 def 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 
889 def 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 
995 def 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 
1265 def 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 
1284 def 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 
1308 def 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 
1327 def 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 
1342 func_line_style_std = None
1343 func_line_style_short = 1
1344 
1345 
1346 def 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(
1379             stack_frame[0]
1380         )
1381 
1382         args_list = []
1383         for arg_name in filter(None, args + [varargs, keywords]):
1384             # Get the arg value from frame locals.
1385             arg_value = locals[arg_name]
1386             if arg_name == "self":
1387                 if style == func_line_style_short:
1388                     continue
1389                 # Manipulations to improve output for class methods.
1390                 func_name = arg_value.__class__.__name__ + "." + func_name
1391                 args_list.append(arg_name + " = <self>")
1392             elif (
1393                 style == func_line_style_short
1394                 and arg_name == "args"
1395                 and type(arg_value) in (list, tuple)
1396             ):
1397                 if len(arg_value) == 0:
1398                     continue
1399                 args_list.append(repr(", ".join(arg_value)))
1400             elif (
1401                 style == func_line_style_short
1402                 and arg_name == "kwargs"
1403                 and type(arg_value) is dict
1404             ):
1405                 for key, value in arg_value.items():
1406                     args_list.append(key + "=" + repr(value))
1407             else:
1408                 args_list.append(arg_name + " = " + repr(arg_value))
1409         args_str = "(" + ", ".join(map(str, args_list)) + ")"
1410 
1411         # Now we need to print this in a nicely-wrapped way.
1412         func_and_args = func_name + args_str
1413 
1414     if len(func_and_args) > max_width:
1415         func_and_args = func_and_args[0:max_width] + "..."
1416     return func_and_args
1417 
1418 
1419 def sprint_call_stack(indent=0, stack_frame_ix=0, style=None):
1420     r"""
1421     Return a call stack report for the given point in the program with line numbers, function names and
1422     function parameters and arguments.
1423 
1424     Sample output:
1425 
1426     -------------------------------------------------------------------------
1427     Python function call stack
1428 
1429     Line # Function name and arguments
1430     ------ ------------------------------------------------------------------
1431        424 sprint_call_stack()
1432          4 print_call_stack()
1433         31 func1(last_name = 'walsh', first_name = 'mikey')
1434         59 /tmp/scr5.py
1435     -------------------------------------------------------------------------
1436 
1437     Description of argument(s):
1438     indent                          The number of characters to indent each line of output.
1439     stack_frame_ix                  The index of the first stack frame which is to be returned.
1440     style                           See the sprint_line_func prolog above for details.
1441     """
1442 
1443     buffer = ""
1444     buffer += sprint_dashes(indent)
1445     buffer += sindent("Python function call stack\n\n", indent)
1446     buffer += sindent("Line # Function name and arguments\n", indent)
1447     buffer += sprint_dashes(indent, 6, 0) + " " + sprint_dashes(0, 73)
1448 
1449     # Grab the current program stack.
1450     work_around_inspect_stack_cwd_failure()
1451     current_stack = inspect.stack()
1452 
1453     # Process each frame in turn.
1454     format_string = "%6s %s\n"
1455     ix = 0
1456     for stack_frame in current_stack:
1457         if ix < stack_frame_ix:
1458             ix += 1
1459             continue
1460         # Make the line number shown to be the line where one finds the line shown.
1461         try:
1462             line_num = str(current_stack[ix + 1][2])
1463         except IndexError:
1464             line_num = ""
1465         func_and_args = sprint_func_line(stack_frame, style=style)
1466 
1467         buffer += sindent(format_string % (line_num, func_and_args), indent)
1468         ix += 1
1469 
1470     buffer += sprint_dashes(indent)
1471 
1472     return buffer
1473 
1474 
1475 def sprint_executing(stack_frame_ix=None, style=None, max_width=None):
1476     r"""
1477     Print a line indicating what function is executing and with what parameter values.  This is useful for
1478     debugging.
1479 
1480     Sample output:
1481 
1482     #(CDT) 2016/08/25 17:54:27 - Executing: func1(x = 1)
1483 
1484     Description of argument(s):
1485     stack_frame_ix                  The index of the stack frame whose function info should be returned.  If
1486                                     the caller does not specify a value, this function will set the value to
1487                                     1 which is the index of the caller's stack frame.  If the caller is the
1488                                     wrapper function "print_executing", this function will bump it up by 1.
1489     style                           See the sprint_line_func prolog above for details.
1490     max_width                       See the sprint_line_func prolog above for details.
1491     """
1492 
1493     # If user wants default stack_frame_ix.
1494     if stack_frame_ix is None:
1495         func_name = sys._getframe().f_code.co_name
1496         caller_func_name = sys._getframe(1).f_code.co_name
1497         if caller_func_name.endswith(func_name[1:]):
1498             stack_frame_ix = 2
1499         else:
1500             stack_frame_ix = 1
1501 
1502     work_around_inspect_stack_cwd_failure()
1503     stack_frame = inspect.stack()[stack_frame_ix]
1504 
1505     if max_width is None:
1506         max_width = 160 - (dft_col1_width + 11)
1507     func_and_args = sprint_func_line(stack_frame, style, max_width=max_width)
1508 
1509     return sprint_time() + "Executing: " + func_and_args + "\n"
1510 
1511 
1512 def sprint_pgm_header(indent=0, linefeed=1):
1513     r"""
1514     Return a standardized header that programs should print at the beginning of the run.  It includes useful
1515     information like command line, pid, userid, program parameters, etc.
1516 
1517     Description of argument(s):
1518     indent                          The number of characters to indent each line of output.
1519     linefeed                        Indicates whether a line feed be included at the beginning and end of the
1520                                     report.
1521     """
1522 
1523     col1_width = dft_col1_width + indent
1524 
1525     buffer = ""
1526     if linefeed:
1527         buffer = "\n"
1528 
1529     if robot_env:
1530         suite_name = BuiltIn().get_variable_value("${suite_name}")
1531         buffer += sindent(
1532             sprint_time('Running test suite "' + suite_name + '".\n'), indent
1533         )
1534 
1535     buffer += sindent(sprint_time() + "Running " + pgm_name + ".\n", indent)
1536     buffer += sindent(
1537         sprint_time() + "Program parameter values, etc.:\n\n", indent
1538     )
1539     buffer += sprint_varx(
1540         "command_line", " ".join(sys.argv), 0, indent, col1_width
1541     )
1542     # We want the output to show a customized name for the pid and pgid but we want it to look like a valid
1543     # variable name.  Therefore, we'll use pgm_name_var_name which was set when this module was imported.
1544     buffer += sprint_varx(
1545         pgm_name_var_name + "_pid", os.getpid(), 0, indent, col1_width
1546     )
1547     buffer += sprint_varx(
1548         pgm_name_var_name + "_pgid", os.getpgrp(), 0, indent, col1_width
1549     )
1550     userid_num = str(os.geteuid())
1551     try:
1552         username = os.getlogin()
1553     except OSError:
1554         if userid_num == "0":
1555             username = "root"
1556         else:
1557             username = "?"
1558     buffer += sprint_varx(
1559         "uid", userid_num + " (" + username + ")", 0, indent, col1_width
1560     )
1561     buffer += sprint_varx(
1562         "gid",
1563         str(os.getgid()) + " (" + str(grp.getgrgid(os.getgid()).gr_name) + ")",
1564         0,
1565         indent,
1566         col1_width,
1567     )
1568     buffer += sprint_varx(
1569         "host_name", socket.gethostname(), 0, indent, col1_width
1570     )
1571     try:
1572         DISPLAY = os.environ["DISPLAY"]
1573     except KeyError:
1574         DISPLAY = ""
1575     buffer += sprint_var(DISPLAY, 0, indent, col1_width)
1576     PYTHON_VERSION = os.environ.get("PYTHON_VERSION", None)
1577     if PYTHON_VERSION is not None:
1578         buffer += sprint_var(PYTHON_VERSION, 0, indent, col1_width)
1579     PYTHON_PGM_PATH = os.environ.get("PYTHON_PGM_PATH", None)
1580     if PYTHON_PGM_PATH is not None:
1581         buffer += sprint_var(PYTHON_PGM_PATH, 0, indent, col1_width)
1582     python_version = sys.version.replace("\n", "")
1583     buffer += sprint_var(python_version, 0, indent, col1_width)
1584     ROBOT_VERSION = os.environ.get("ROBOT_VERSION", None)
1585     if ROBOT_VERSION is not None:
1586         buffer += sprint_var(ROBOT_VERSION, 0, indent, col1_width)
1587     ROBOT_PGM_PATH = os.environ.get("ROBOT_PGM_PATH", None)
1588     if ROBOT_PGM_PATH is not None:
1589         buffer += sprint_var(ROBOT_PGM_PATH, 0, indent, col1_width)
1590 
1591     # __builtin__.arg_obj is created by the get_arg module function, gen_get_options.
1592     try:
1593         buffer += ga.sprint_args(__builtin__.arg_obj, indent)
1594     except AttributeError:
1595         pass
1596 
1597     if robot_env:
1598         # Get value of global parm_list.
1599         parm_list = BuiltIn().get_variable_value("${parm_list}")
1600 
1601         for parm in parm_list:
1602             parm_value = BuiltIn().get_variable_value("${" + parm + "}")
1603             buffer += sprint_varx(parm, parm_value, 0, indent, col1_width)
1604 
1605         # Setting global program_pid.
1606         BuiltIn().set_global_variable("${program_pid}", os.getpid())
1607 
1608     if linefeed:
1609         buffer += "\n"
1610 
1611     return buffer
1612 
1613 
1614 def sprint_error_report(
1615     error_text="\n", indent=2, format=None, stack_frame_ix=None
1616 ):
1617     r"""
1618     Return a string with a standardized report which includes the caller's error text, the call stack and the
1619     program header.
1620 
1621     Description of argument(s):
1622     error_text                      The error text to be included in the report.  The caller should include
1623                                     any needed linefeeds.
1624     indent                          The number of characters to indent each line of output.
1625     format                          Long or short format.  Long includes extras like lines of dashes, call
1626                                     stack, etc.
1627     stack_frame_ix                  The index of the first stack frame which is to be shown in the
1628                                     print_call_stack portion of the error report.
1629     """
1630 
1631     # Process input.
1632     indent = int(indent)
1633     if format is None:
1634         if robot_env:
1635             format = "short"
1636         else:
1637             format = "long"
1638     error_text = error_text.rstrip("\n") + "\n"
1639 
1640     if format == "short":
1641         return sprint_error(error_text)
1642 
1643     buffer = ""
1644     buffer += sprint_dashes(width=120, char="=")
1645     buffer += sprint_error(error_text)
1646     buffer += "\n"
1647     if not stack_frame_ix:
1648         # Calling sprint_call_stack with stack_frame_ix of 0 causes it to show itself and this function in
1649         # the call stack.  This is not helpful to a debugger and is therefore clutter.  We will adjust the
1650         # stack_frame_ix to hide that information.
1651         stack_frame_ix = 1
1652         caller_func_name = sprint_func_name(1)
1653         if caller_func_name.endswith("print_error_report"):
1654             stack_frame_ix += 1
1655         caller_func_name = sprint_func_name(2)
1656         if caller_func_name.endswith("print_error_report"):
1657             stack_frame_ix += 1
1658     buffer += sprint_call_stack(indent, stack_frame_ix)
1659     buffer += sprint_pgm_header(indent)
1660     buffer += sprint_dashes(width=120, char="=")
1661 
1662     return buffer
1663 
1664 
1665 def sprint_issuing(cmd_buf, test_mode=0):
1666     r"""
1667     Return a line indicating a command that the program is about to execute.
1668 
1669     Sample output for a cmd_buf of "ls"
1670 
1671     #(CDT) 2016/08/25 17:57:36 - Issuing: ls
1672 
1673     Description of argument(s):
1674     cmd_buf                         The command to be executed by caller.
1675     test_mode                       With test_mode set, the output will look like this:
1676 
1677     #(CDT) 2016/08/25 17:57:36 - (test_mode) Issuing: ls
1678 
1679     """
1680 
1681     buffer = sprint_time()
1682     if test_mode:
1683         buffer += "(test_mode) "
1684     if type(cmd_buf) is list:
1685         # Assume this is a robot command in the form of a list.
1686         cmd_buf = "  ".join([str(element) for element in cmd_buf])
1687 
1688     try:
1689         passwd_value = os.environ.get(
1690             "OPENBMC_PASSWORD", ""
1691         ) or BuiltIn().get_variable_value("${OPENBMC_PASSWORD}", default="")
1692     except Exception as e:
1693         passwd_value = ""
1694         pass
1695 
1696     # adds overhead checking for password masking.
1697     if passwd_value != "" and passwd_value in cmd_buf:
1698         buffer += (
1699             "Issuing: " + cmd_buf.replace(passwd_value, "**********") + "\n"
1700         )
1701     else:
1702         buffer += "Issuing: " + cmd_buf + "\n"
1703 
1704     return buffer
1705 
1706 
1707 def sprint_pgm_footer():
1708     r"""
1709     Return a standardized footer that programs should print at the end of the program run.  It includes
1710     useful information like total run time, etc.
1711     """
1712 
1713     buffer = "\n" + sprint_time() + "Finished running " + pgm_name + ".\n\n"
1714 
1715     total_time = time.time() - start_time
1716     total_time_string = "%0.6f" % total_time
1717 
1718     buffer += sprint_varx(pgm_name_var_name + "_runtime", total_time_string)
1719     buffer += "\n"
1720 
1721     return buffer
1722 
1723 
1724 def sprint_file(file_path):
1725     r"""
1726     Return the file data as a string.
1727 
1728     Description of argument(s):
1729     file_path                       The path to a file (e.g. "/tmp/file1").
1730     """
1731 
1732     with open(file_path, "r") as file:
1733         buffer = file.read()
1734     return buffer
1735 
1736 
1737 def sprint(buffer=""):
1738     r"""
1739     Simply return the user's buffer.  This function is used by the qprint and dprint functions defined
1740     dynamically below, i.e. it would not normally be called for general use.
1741 
1742     Description of argument(s).
1743     buffer                          This will be returned to the caller.
1744     """
1745 
1746     try:
1747         return str(buffer)
1748     except UnicodeEncodeError:
1749         return buffer
1750 
1751 
1752 def sprintn(buffer=""):
1753     r"""
1754     Simply return the user's buffer with a line feed.  This function is used by the qprint and dprint
1755     functions defined dynamically below, i.e. it would not normally be called for general use.
1756 
1757     Description of argument(s).
1758     buffer                          This will be returned to the caller.
1759     """
1760 
1761     try:
1762         buffer = str(buffer) + "\n"
1763     except UnicodeEncodeError:
1764         buffer = buffer + "\n"
1765 
1766     return buffer
1767 
1768 
1769 def gp_print(buffer, stream="stdout"):
1770     r"""
1771     Print the buffer using either sys.stdout.write or BuiltIn().log_to_console depending on whether we are
1772     running in a robot environment.
1773 
1774     This function is intended for use only by other functions in this module.
1775 
1776     Description of argument(s):
1777     buffer                          The string to be printed.
1778     stream                          Either "stdout" or "stderr".
1779     """
1780 
1781     if robot_env:
1782         BuiltIn().log_to_console(buffer, stream=stream, no_newline=True)
1783     else:
1784         if stream == "stdout":
1785             sys.stdout.write(buffer)
1786             sys.stdout.flush()
1787         else:
1788             sys.stderr.write(buffer)
1789             sys.stderr.flush()
1790 
1791 
1792 def gp_log(buffer):
1793     r"""
1794     Log the buffer using either python logging or BuiltIn().log depending on whether we are running in a
1795     robot environment.
1796 
1797     This function is intended for use only by other functions in this module.
1798 
1799     Description of argument(s):
1800     buffer                          The string to be logged.
1801     """
1802 
1803     if robot_env:
1804         BuiltIn().log(buffer)
1805     else:
1806         logging.warning(buffer)
1807 
1808 
1809 def gp_debug_print(buffer):
1810     r"""
1811     Print with gp_print only if gen_print_debug is set.
1812 
1813     This function is intended for use only by other functions in this module.
1814 
1815     Description of argument(s):
1816     buffer                          The string to be printed.
1817     """
1818 
1819     if not gen_print_debug:
1820         return
1821 
1822     gp_print(buffer)
1823 
1824 
1825 def get_var_value(var_value=None, default=1, var_name=None):
1826     r"""
1827     Return either var_value, the corresponding global value or default.
1828 
1829     If var_value is not None, it will simply be returned.
1830 
1831     If var_value is None, this function will return the corresponding global value of the variable in
1832     question.
1833 
1834     Note: For global values, if we are in a robot environment, get_variable_value will be used.  Otherwise,
1835     the __builtin__ version of the variable is returned (which are set by gen_arg.py functions).
1836 
1837     If there is no global value associated with the variable, default is returned.
1838 
1839     This function is useful for other functions in setting default values for parameters.
1840 
1841     Example use:
1842 
1843     def my_func(quiet=None):
1844 
1845       quiet = int(get_var_value(quiet, 0))
1846 
1847     Example calls to my_func():
1848 
1849     In the following example, the caller is explicitly asking to have quiet be set to 1.
1850 
1851     my_func(quiet=1)
1852 
1853     In the following example, quiet will be set to the global value of quiet, if defined, or to 0 (the
1854     default).
1855 
1856     my_func()
1857 
1858     Description of argument(s):
1859     var_value                       The value to be returned (if not equal to None).
1860     default                         The value that is returned if var_value is None and there is no
1861                                     corresponding global value defined.
1862     var_name                        The name of the variable whose value is to be returned.  Under most
1863                                     circumstances, this value need not be provided.  This function can figure
1864                                     out the name of the variable passed as var_value.  One exception to this
1865                                     would be if this function is called directly from a .robot file.
1866     """
1867 
1868     if var_value is not None:
1869         return var_value
1870 
1871     if var_name is None:
1872         var_name = get_arg_name(None, 1, 2)
1873 
1874     if robot_env:
1875         var_value = BuiltIn().get_variable_value(
1876             "${" + var_name + "}", default
1877         )
1878     else:
1879         var_value = getattr(__builtin__, var_name, default)
1880 
1881     return var_value
1882 
1883 
1884 def get_stack_var(var_name, default="", init_stack_ix=2):
1885     r"""
1886     Starting with the caller's stack level, search upward in the call stack for a variable named var_name and
1887     return its value.  If the variable cannot be found in the stack, attempt to get the global value.  If the
1888     variable still cannot be found, return default.
1889 
1890     Example code:
1891 
1892     def func12():
1893         my_loc_var1 = get_stack_var('my_var1', "default value")
1894 
1895     def func11():
1896         my_var1 = 11
1897         func12()
1898 
1899     In this example, get_stack_var will find the value of my_var1 in func11's stack and will therefore return
1900     the value 11.  Therefore, my_loc_var1 would get set to 11.
1901 
1902     Description of argument(s):
1903     var_name                        The name of the variable to be searched for.
1904     default                         The value to return if the the variable cannot be found.
1905     init_stack_ix                   The initial stack index from which to begin the search.  0 would be the
1906                                     index of this func1tion ("get_stack_var"), 1 would be the index of the
1907                                     function calling this function, etc.
1908     """
1909 
1910     work_around_inspect_stack_cwd_failure()
1911     default = get_var_value(var_name=var_name, default=default)
1912     return next(
1913         (
1914             frame[0].f_locals[var_name]
1915             for frame in inspect.stack()[init_stack_ix:]
1916             if var_name in frame[0].f_locals
1917         ),
1918         default,
1919     )
1920 
1921 
1922 # hidden_text is a list of passwords which are to be replaced with asterisks by print functions defined in
1923 # this module.
1924 hidden_text = []
1925 # password_regex is created based on the contents of hidden_text.
1926 password_regex = ""
1927 
1928 
1929 def register_passwords(*args):
1930     r"""
1931     Register one or more passwords which are to be hidden in output produced by the print functions in this
1932     module.
1933 
1934     Note:  Blank password values are NOT registered.  They are simply ignored.
1935 
1936     Description of argument(s):
1937     args                            One or more password values.  If a given password value is already
1938                                     registered, this function will simply do nothing.
1939     """
1940 
1941     global hidden_text
1942     global password_regex
1943 
1944     for password in args:
1945         if password == "":
1946             break
1947         if password in hidden_text:
1948             break
1949 
1950         # Place the password into the hidden_text list.
1951         hidden_text.append(password)
1952         # Create a corresponding password regular expression.  Escape regex special characters too.
1953         password_regex = (
1954             "(" + "|".join([re.escape(x) for x in hidden_text]) + ")"
1955         )
1956 
1957 
1958 def replace_passwords(buffer):
1959     r"""
1960     Return the buffer but with all registered passwords replaced by a string of asterisks.
1961 
1962 
1963     Description of argument(s):
1964     buffer                          The string to be returned but with passwords replaced.
1965     """
1966 
1967     global password_regex
1968 
1969     if int(os.environ.get("DEBUG_SHOW_PASSWORDS", "0")):
1970         return buffer
1971 
1972     if password_regex == "":
1973         # No passwords to replace.
1974         return buffer
1975 
1976     return re.sub(password_regex, "********", buffer)
1977 
1978 
1979 def create_print_wrapper_funcs(
1980     func_names, stderr_func_names, replace_dict, func_prefix=""
1981 ):
1982     r"""
1983     Generate code for print wrapper functions and return the generated code as a string.
1984 
1985     To illustrate, suppose there is a "print_foo_bar" function in the func_names list.
1986     This function will...
1987     - Expect that there is an sprint_foo_bar function already in existence.
1988     - Create a print_foo_bar function which calls sprint_foo_bar and prints the result.
1989     - Create a qprint_foo_bar function which calls upon sprint_foo_bar only if global value quiet is 0.
1990     - Create a dprint_foo_bar function which calls upon sprint_foo_bar only if global value debug is 1.
1991 
1992     Also, code will be generated to define aliases for each function as well.  Each alias will be created by
1993     replacing "print_" in the function name with "p"  For example, the alias for print_foo_bar will be
1994     pfoo_bar.
1995 
1996     Description of argument(s):
1997     func_names                      A list of functions for which print wrapper function code is to be
1998                                     generated.
1999     stderr_func_names               A list of functions whose generated code should print to stderr rather
2000                                     than to stdout.
2001     replace_dict                    Please see the create_func_def_string function in wrap_utils.py for
2002                                     details on this parameter.  This parameter will be passed directly to
2003                                     create_func_def_string.
2004     func_prefix                     Prefix to be prepended to the generated function name.
2005     """
2006 
2007     buffer = ""
2008 
2009     for func_name in func_names:
2010         if func_name in stderr_func_names:
2011             replace_dict["output_stream"] = "stderr"
2012         else:
2013             replace_dict["output_stream"] = "stdout"
2014 
2015         s_func_name = "s" + func_name
2016         q_func_name = "q" + func_name
2017         d_func_name = "d" + func_name
2018 
2019         # We don't want to try to redefine the "print" function, thus the following if statement.
2020         if func_name != "print":
2021             func_def = create_func_def_string(
2022                 s_func_name,
2023                 func_prefix + func_name,
2024                 print_func_template,
2025                 replace_dict,
2026             )
2027             buffer += func_def
2028 
2029         func_def = create_func_def_string(
2030             s_func_name,
2031             func_prefix + "q" + func_name,
2032             qprint_func_template,
2033             replace_dict,
2034         )
2035         buffer += func_def
2036 
2037         func_def = create_func_def_string(
2038             s_func_name,
2039             func_prefix + "d" + func_name,
2040             dprint_func_template,
2041             replace_dict,
2042         )
2043         buffer += func_def
2044 
2045         func_def = create_func_def_string(
2046             s_func_name,
2047             func_prefix + "l" + func_name,
2048             lprint_func_template,
2049             replace_dict,
2050         )
2051         buffer += func_def
2052 
2053         # Create abbreviated aliases (e.g. spvar is an alias for sprint_var).
2054         alias = re.sub("print_", "p", func_name)
2055         alias = re.sub("print", "p", alias)
2056         prefixes = [
2057             func_prefix + "",
2058             "s",
2059             func_prefix + "q",
2060             func_prefix + "d",
2061             func_prefix + "l",
2062         ]
2063         for prefix in prefixes:
2064             if alias == "p":
2065                 continue
2066             func_def = prefix + alias + " = " + prefix + func_name
2067             buffer += func_def + "\n"
2068 
2069     return buffer
2070 
2071 
2072 # In the following section of code, we will dynamically create print versions for each of the sprint
2073 # functions defined above.  So, for example, where we have an sprint_time() function defined above that
2074 # returns the time to the caller in a string, we will create a corresponding print_time() function that will
2075 # print that string directly to stdout.
2076 
2077 # It can be complicated to follow what's being created below.  Here is an example of the print_time()
2078 # function that will be created:
2079 
2080 # def print_time(buffer=''):
2081 #     gp_print(replace_passwords(sprint_time(buffer=buffer)), stream='stdout')
2082 
2083 # For each print function defined below, there will also be a qprint, a dprint and an lprint version defined
2084 # (e.g. qprint_time, dprint_time, lprint_time).
2085 
2086 # The q version of each print function will only print if the quiet variable is 0.
2087 # The d version of each print function will only print if the debug variable is 1.
2088 # The l version of each print function will print the contents as log data.  For conventional programs, this
2089 # means use of the logging module.  For robot programs it means use of the BuiltIn().log() function.
2090 
2091 # Templates for the various print wrapper functions.
2092 print_func_template = [
2093     "    <mod_qualifier>gp_print(<mod_qualifier>replace_passwords("
2094     + "<call_line>), stream='<output_stream>')"
2095 ]
2096 
2097 qprint_func_template = [
2098     '    quiet = <mod_qualifier>get_stack_var("quiet", 0)',
2099     "    if int(quiet): return",
2100 ] + print_func_template
2101 
2102 dprint_func_template = [
2103     '    debug = <mod_qualifier>get_stack_var("debug", 0)',
2104     "    if not int(debug): return",
2105 ] + print_func_template
2106 
2107 lprint_func_template = [
2108     "    <mod_qualifier>set_last_seconds_ix(<mod_qualifier>"
2109     + "lprint_last_seconds_ix())",
2110     "    <mod_qualifier>gp_log(<mod_qualifier>replace_passwords"
2111     + "(<call_line>))",
2112     "    <mod_qualifier>set_last_seconds_ix(<mod_qualifier>"
2113     + "standard_print_last_seconds_ix())",
2114 ]
2115 
2116 replace_dict = {"output_stream": "stdout", "mod_qualifier": ""}
2117 
2118 gp_debug_print("robot_env: " + str(robot_env) + "\n")
2119 
2120 # func_names contains a list of all print functions which should be created from their sprint counterparts.
2121 func_names = [
2122     "print_time",
2123     "print_timen",
2124     "print_error",
2125     "print_varx",
2126     "print_var",
2127     "print_vars",
2128     "print_dashes",
2129     "indent",
2130     "print_call_stack",
2131     "print_func_name",
2132     "print_executing",
2133     "print_pgm_header",
2134     "print_issuing",
2135     "print_pgm_footer",
2136     "print_file",
2137     "print_error_report",
2138     "print",
2139     "printn",
2140 ]
2141 
2142 # stderr_func_names is a list of functions whose output should go to stderr rather than stdout.
2143 stderr_func_names = ["print_error", "print_error_report"]
2144 
2145 func_defs = create_print_wrapper_funcs(
2146     func_names, stderr_func_names, replace_dict
2147 )
2148 gp_debug_print(func_defs)
2149 exec(func_defs)
2150