blob: 3f1a1fd9616c4a1f698e141eaa1319736f445678 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Michael Walshde791732016-09-06 14:25:24 -05002
3r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05004This module provides many print functions such as sprint_var, sprint_time, sprint_error, sprint_call_stack.
Michael Walshde791732016-09-06 14:25:24 -05005"""
6
Michael Walshde791732016-09-06 14:25:24 -05007import argparse
Michael Walsh4dbb6002019-05-17 15:51:15 -05008import copy
Patrick Williams20f38712022-12-08 06:18:26 -06009import grp
10import inspect
11import os
12import re
13import socket
14import sys
15import time
16
George Keishing3b7115a2018-08-02 10:48:17 -050017try:
18 import __builtin__
19except ImportError:
20 import builtins as __builtin__
Patrick Williams20f38712022-12-08 06:18:26 -060021
George Keishinge635ddc2022-12-08 07:38:02 -060022import collections
Patrick Williams20f38712022-12-08 06:18:26 -060023import logging
24
Michael Walshfd2733c2017-11-13 11:36:20 -060025from wrap_utils import *
Michael Walshbec416d2016-11-10 08:54:52 -060026
Michael Walshbec416d2016-11-10 08:54:52 -060027try:
Michael Walsh2ee77cd2017-03-08 11:50:17 -060028 robot_env = 1
Michael Walsh2ee77cd2017-03-08 11:50:17 -060029 from robot.libraries.BuiltIn import BuiltIn
Patrick Williams20f38712022-12-08 06:18:26 -060030 from robot.utils import DotDict, NormalizedDict
31
Michael Walsh46fcecb2019-10-16 17:11:59 -050032 # 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.
Michael Walshb1500152017-04-12 15:42:43 -050034 try:
35 var_value = BuiltIn().get_variable_value("${SUITE_NAME}", "")
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -050036 except BaseException:
Michael Walshb1500152017-04-12 15:42:43 -050037 robot_env = 0
Michael Walshbec416d2016-11-10 08:54:52 -060038except ImportError:
Michael Walsh2ee77cd2017-03-08 11:50:17 -060039 robot_env = 0
Michael Walsh7423c012016-10-04 10:27:21 -050040
41import gen_arg as ga
Michael Walshde791732016-09-06 14:25:24 -050042
Michael Walsh46fcecb2019-10-16 17:11:59 -050043# Setting these variables for use both inside this module and by programs importing this module.
Michael Walshbf605652017-09-01 12:33:26 -050044pgm_file_path = sys.argv[0]
45pgm_name = os.path.basename(pgm_file_path)
Patrick Williams20f38712022-12-08 06:18:26 -060046pgm_dir_path = (
47 os.path.normpath(re.sub("/" + pgm_name, "", pgm_file_path)) + os.path.sep
48)
Michael Walsh7423c012016-10-04 10:27:21 -050049
Michael Walshde791732016-09-06 14:25:24 -050050
Michael Walsh46fcecb2019-10-16 17:11:59 -050051# 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.
Michael Walshde791732016-09-06 14:25:24 -050053pgm_name_var_name = pgm_name.replace(".", "_")
54
55# Initialize global values used as defaults by print_time, print_var, etc.
Michael Walsh4dbb6002019-05-17 15:51:15 -050056dft_indent = 0
Michael Walshde791732016-09-06 14:25:24 -050057
Michael Walsh46fcecb2019-10-16 17:11:59 -050058# 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.
Michael Walsh4dbb6002019-05-17 15:51:15 -050060dft_col1_width = 29
Michael Walshb1500152017-04-12 15:42:43 -050061
Patrick Williams20f38712022-12-08 06:18:26 -060062NANOSECONDS = os.environ.get("NANOSECONDS", "1")
Michael Walshb1500152017-04-12 15:42:43 -050063
Michael Walshde791732016-09-06 14:25:24 -050064if NANOSECONDS == "1":
Michael Walsh4dbb6002019-05-17 15:51:15 -050065 dft_col1_width = dft_col1_width + 7
Michael Walshde791732016-09-06 14:25:24 -050066
Patrick Williams20f38712022-12-08 06:18:26 -060067SHOW_ELAPSED_TIME = os.environ.get("SHOW_ELAPSED_TIME", "1")
Michael Walshde791732016-09-06 14:25:24 -050068
69if SHOW_ELAPSED_TIME == "1":
70 if NANOSECONDS == "1":
Michael Walsh4dbb6002019-05-17 15:51:15 -050071 dft_col1_width = dft_col1_width + 14
Michael Walshde791732016-09-06 14:25:24 -050072 else:
Michael Walsh4dbb6002019-05-17 15:51:15 -050073 dft_col1_width = dft_col1_width + 7
Michael Walshde791732016-09-06 14:25:24 -050074
75# Initialize some time variables used in module functions.
76start_time = time.time()
Michael Walsh4fea2cf2018-08-22 17:48:18 -050077# sprint_time_last_seconds is used to calculate elapsed seconds.
Michael Walsh61c12982019-03-28 12:38:01 -050078sprint_time_last_seconds = [start_time, start_time]
Michael Walsh4fea2cf2018-08-22 17:48:18 -050079# Define global index for the sprint_time_last_seconds list.
80last_seconds_ix = 0
81
82
Michael Walsh61c12982019-03-28 12:38:01 -050083def 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):
Michael Walsh46fcecb2019-10-16 17:11:59 -050088 ix The index value to be set into the module global last_seconds_ix variable.
Michael Walsh61c12982019-03-28 12:38:01 -050089 """
90 global last_seconds_ix
91 last_seconds_ix = ix
92
93
Michael Walsh46fcecb2019-10-16 17:11:59 -050094# 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
Michael Walsh4fea2cf2018-08-22 17:48:18 -050098# sprint_time_last_seconds[1].
Michael Walsh61c12982019-03-28 12:38:01 -050099def 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
Michael Walsh4fea2cf2018-08-22 17:48:18 -0500106def lprint_last_seconds_ix():
107 r"""
108 Return lprint last_seconds index value to the caller.
109 """
110 return 1
111
Michael Walshde791732016-09-06 14:25:24 -0500112
Michael Walsh46fcecb2019-10-16 17:11:59 -0500113# The user can set environment variable "GEN_PRINT_DEBUG" to get debug output from this module.
Patrick Williams20f38712022-12-08 06:18:26 -0600114gen_print_debug = int(os.environ.get("GEN_PRINT_DEBUG", 0))
Michael Walsh7423c012016-10-04 10:27:21 -0500115
Michael Walshde791732016-09-06 14:25:24 -0500116
Michael Walshde791732016-09-06 14:25:24 -0500117def sprint_func_name(stack_frame_ix=None):
Michael Walshde791732016-09-06 14:25:24 -0500118 r"""
119 Return the function name associated with the indicated stack frame.
120
Michael Walsh4dbb6002019-05-17 15:51:15 -0500121 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -0500122 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.
Michael Walshde791732016-09-06 14:25:24 -0500126 """
127
Michael Walsh46fcecb2019-10-16 17:11:59 -0500128 # If user specified no stack_frame_ix, we'll set it to a proper default value.
Michael Walshde791732016-09-06 14:25:24 -0500129 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
Michael Walshde791732016-09-06 14:25:24 -0500141
Michael Walsh6f0362c2019-03-25 14:05:14 -0500142def work_around_inspect_stack_cwd_failure():
143 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -0500144 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
Michael Walsh6f0362c2019-03-25 14:05:14 -0500150 current working directory.
Michael Walsh6f0362c2019-03-25 14:05:14 -0500151 """
152 try:
153 os.getcwd()
154 except OSError:
155 os.chdir("/tmp")
156
157
Michael Walsh1173a522018-05-21 17:24:51 -0500158def get_line_indent(line):
159 r"""
160 Return the number of spaces at the beginning of the line.
161 """
162
Patrick Williams20f38712022-12-08 06:18:26 -0600163 return len(line) - len(line.lstrip(" "))
Michael Walsh1173a522018-05-21 17:24:51 -0500164
165
Michael Walsh46fcecb2019-10-16 17:11:59 -0500166# 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.
Patrick Williams20f38712022-12-08 06:18:26 -0600168def get_arg_name(var, arg_num=1, stack_frame_ix=1):
Michael Walshde791732016-09-06 14:25:24 -0500169 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -0500170 Return the "name" of an argument passed to a function. This could be a literal or a variable name.
Michael Walshde791732016-09-06 14:25:24 -0500171
Michael Walsh4dbb6002019-05-17 15:51:15 -0500172 Description of argument(s):
173 var The variable whose name is to be returned.
Michael Walsh46fcecb2019-10-16 17:11:59 -0500174 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.
Michael Walshde791732016-09-06 14:25:24 -0500194
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):
Michael Walsh46fcecb2019-10-16 17:11:59 -0500205 # 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
Michael Walsh4dbb6002019-05-17 15:51:15 -0500207 # matters.
Michael Walshde791732016-09-06 14:25:24 -0500208 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
Michael Walsh46fcecb2019-10-16 17:11:59 -0500222 # Note: To avoid infinite recursion, avoid calling any function that calls this function (e.g.
223 # sprint_var, valid_value, etc.).
Michael Walshde791732016-09-06 14:25:24 -0500224
Michael Walsh46fcecb2019-10-16 17:11:59 -0500225 # The user can set environment variable "GET_ARG_NAME_DEBUG" to get debug output from this function.
Patrick Williams20f38712022-12-08 06:18:26 -0600226 local_debug = int(os.environ.get("GET_ARG_NAME_DEBUG", 0))
Michael Walsh46fcecb2019-10-16 17:11:59 -0500227 # 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.
Michael Walsh23e7f492017-01-10 11:34:47 -0600229 local_debug_show_source = int(
Patrick Williams20f38712022-12-08 06:18:26 -0600230 os.environ.get("GET_ARG_NAME_SHOW_SOURCE", 0)
231 )
Michael Walshde791732016-09-06 14:25:24 -0500232
Michael Walshde791732016-09-06 14:25:24 -0500233 if stack_frame_ix < 1:
Patrick Williams20f38712022-12-08 06:18:26 -0600234 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 )
Michael Walshde791732016-09-06 14:25:24 -0500242 return
243
244 if local_debug:
245 debug_indent = 2
Michael Walsh23e7f492017-01-10 11:34:47 -0600246 print("")
247 print_dashes(0, 120)
Michael Walshde791732016-09-06 14:25:24 -0500248 print(sprint_func_name() + "() parms:")
Michael Walsh4dbb6002019-05-17 15:51:15 -0500249 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)
Michael Walsh23e7f492017-01-10 11:34:47 -0600252 print("")
253 print_call_stack(debug_indent, 2)
Michael Walshde791732016-09-06 14:25:24 -0500254
Michael Walsh6f0362c2019-03-25 14:05:14 -0500255 work_around_inspect_stack_cwd_failure()
Michael Walsh23e7f492017-01-10 11:34:47 -0600256 for count in range(0, 2):
257 try:
Patrick Williams20f38712022-12-08 06:18:26 -0600258 (
259 frame,
260 filename,
261 cur_line_no,
262 function_name,
263 lines,
264 index,
265 ) = inspect.stack()[stack_frame_ix]
Michael Walsh23e7f492017-01-10 11:34:47 -0600266 except IndexError:
Patrick Williams20f38712022-12-08 06:18:26 -0600267 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 )
Michael Walsh23e7f492017-01-10 11:34:47 -0600277 return
278 if filename != "<string>":
279 break
Michael Walsh46fcecb2019-10-16 17:11:59 -0500280 # 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.
Michael Walsh23e7f492017-01-10 11:34:47 -0600283 stack_frame_ix += 1
284 if local_debug:
285 print("Adjusted stack_frame_ix...")
Michael Walsh4dbb6002019-05-17 15:51:15 -0500286 print_varx("stack_frame_ix", stack_frame_ix, indent=debug_indent)
Michael Walshde791732016-09-06 14:25:24 -0500287
Michael Walsh1173a522018-05-21 17:24:51 -0500288 real_called_func_name = sprint_func_name(stack_frame_ix)
Michael Walsh23e7f492017-01-10 11:34:47 -0600289
290 module = inspect.getmodule(frame)
291
Michael Walsh46fcecb2019-10-16 17:11:59 -0500292 # 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).
Michael Walsh23e7f492017-01-10 11:34:47 -0600294 if function_name == "<module>":
Patrick Williams20f38712022-12-08 06:18:26 -0600295 source_lines, source_line_num = inspect.getsourcelines(module)
Michael Walsh23e7f492017-01-10 11:34:47 -0600296 line_ix = cur_line_no - source_line_num - 1
297 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600298 source_lines, source_line_num = inspect.getsourcelines(frame)
Michael Walsh23e7f492017-01-10 11:34:47 -0600299 line_ix = cur_line_no - source_line_num
300
301 if local_debug:
302 print("\n Variables retrieved from inspect.stack() function:")
Michael Walsh4dbb6002019-05-17 15:51:15 -0500303 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)
Michael Walsh23e7f492017-01-10 11:34:47 -0600311 if local_debug_show_source:
Michael Walsh4dbb6002019-05-17 15:51:15 -0500312 print_varx("source_lines", source_lines, indent=debug_indent)
Patrick Williams20f38712022-12-08 06:18:26 -0600313 print_varx(
314 "real_called_func_name", real_called_func_name, indent=debug_indent
315 )
Michael Walsh23e7f492017-01-10 11:34:47 -0600316
Michael Walsh46fcecb2019-10-16 17:11:59 -0500317 # 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.
Michael Walsh23e7f492017-01-10 11:34:47 -0600320 all_functions = inspect.getmembers(module, inspect.isfunction)
321
Michael Walsh46fcecb2019-10-16 17:11:59 -0500322 # Get called_func_id by searching for our function in the list of all functions.
Michael Walsh23e7f492017-01-10 11:34:47 -0600323 called_func_id = None
324 for func_name, function in all_functions:
Michael Walsh1173a522018-05-21 17:24:51 -0500325 if func_name == real_called_func_name:
Michael Walsh23e7f492017-01-10 11:34:47 -0600326 called_func_id = id(function)
327 break
Michael Walsh46fcecb2019-10-16 17:11:59 -0500328 # 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.
Michael Walsh23e7f492017-01-10 11:34:47 -0600330
331 # Look for other functions in module with matching id.
Michael Walsh1173a522018-05-21 17:24:51 -0500332 aliases = set([real_called_func_name])
Michael Walsh23e7f492017-01-10 11:34:47 -0600333 for func_name, function in all_functions:
Michael Walsh1173a522018-05-21 17:24:51 -0500334 if func_name == real_called_func_name:
Michael Walsh23e7f492017-01-10 11:34:47 -0600335 continue
336 func_id = id(function)
337 if func_id == called_func_id:
338 aliases.add(func_name)
339
Michael Walsh46fcecb2019-10-16 17:11:59 -0500340 # 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.
Michael Walsh23e7f492017-01-10 11:34:47 -0600343 # pvar is an alias for print_var.
Michael Walsh1173a522018-05-21 17:24:51 -0500344 aliases.add(re.sub("print_var", "pvar", real_called_func_name))
Michael Walsh23e7f492017-01-10 11:34:47 -0600345
Michael Walsh46fcecb2019-10-16 17:11:59 -0500346 # The call to the function could be encased in a recast (e.g. int(func_name())).
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500347 recast_regex = "([^ ]+\\([ ]*)?"
348 import_name_regex = "([a-zA-Z0-9_]+\\.)?"
Patrick Williams20f38712022-12-08 06:18:26 -0600349 func_name_regex = (
350 recast_regex + import_name_regex + "(" + "|".join(aliases) + ")"
351 )
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500352 pre_args_regex = ".*" + func_name_regex + "[ ]*\\("
Michael Walsh23e7f492017-01-10 11:34:47 -0600353
Michael Walsh46fcecb2019-10-16 17:11:59 -0500354 # Search backward through source lines looking for the calling function name.
Michael Walsh23e7f492017-01-10 11:34:47 -0600355 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
Michael Walsh1173a522018-05-21 17:24:51 -0500360 if re.match(pre_args_regex, source_lines[start_line_ix]):
Michael Walsh23e7f492017-01-10 11:34:47 -0600361 found = True
362 break
363 if not found:
Patrick Williams20f38712022-12-08 06:18:26 -0600364 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 )
Michael Walsh23e7f492017-01-10 11:34:47 -0600370 return
371
Michael Walsh46fcecb2019-10-16 17:11:59 -0500372 # 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.
Michael Walsh1173a522018-05-21 17:24:51 -0500374 start_indent = get_line_indent(source_lines[start_line_ix])
Michael Walsh37cd29d2018-05-24 13:19:18 -0500375 end_line_ix = line_ix
Michael Walsh23e7f492017-01-10 11:34:47 -0600376 for end_line_ix in range(line_ix + 1, len(source_lines)):
377 if source_lines[end_line_ix].strip() == "":
378 continue
Michael Walsh1173a522018-05-21 17:24:51 -0500379 line_indent = get_line_indent(source_lines[end_line_ix])
Michael Walsh82acf002017-05-04 14:33:05 -0500380 if line_indent <= start_indent:
Michael Walsh23e7f492017-01-10 11:34:47 -0600381 end_line_ix -= 1
382 break
Michael Walsh1173a522018-05-21 17:24:51 -0500383 if start_line_ix != 0:
Michael Walsh46fcecb2019-10-16 17:11:59 -0500384 # Check to see whether the start line is a continuation of the prior line.
Michael Walsha52e9eb2018-09-10 13:56:01 -0500385 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)
Patrick Williams20f38712022-12-08 06:18:26 -0600388 if (
389 prior_line != prior_line_stripped
390 and prior_line_indent < start_indent
391 ):
Michael Walsh1173a522018-05-21 17:24:51 -0500392 start_line_ix -= 1
Michael Walsha52e9eb2018-09-10 13:56:01 -0500393 # Remove the backslash (continuation char) from prior line.
394 source_lines[start_line_ix] = prior_line_stripped
Michael Walsh23e7f492017-01-10 11:34:47 -0600395
396 # Join the start line through the end line into a composite line.
Patrick Williams20f38712022-12-08 06:18:26 -0600397 composite_line = "".join(
398 map(str.strip, source_lines[start_line_ix : end_line_ix + 1])
399 )
Michael Walsh1173a522018-05-21 17:24:51 -0500400 # Insert one space after first "=" if there isn't one already.
401 composite_line = re.sub("=[ ]*([^ ])", "= \\1", composite_line, 1)
Michael Walsh7423c012016-10-04 10:27:21 -0500402
Michael Walsh3f248272018-06-01 13:59:35 -0500403 lvalue_regex = "[ ]*=[ ]+" + func_name_regex + ".*"
Michael Walsh1173a522018-05-21 17:24:51 -0500404 lvalue_string = re.sub(lvalue_regex, "", composite_line)
Michael Walsh3f248272018-06-01 13:59:35 -0500405 if lvalue_string == composite_line:
406 # i.e. the regex did not match so there are no lvalues.
407 lvalue_string = ""
Michael Walsh37762f92018-08-07 14:59:18 -0500408 lvalues_list = list(filter(None, map(str.strip, lvalue_string.split(","))))
Michael Walsh3f248272018-06-01 13:59:35 -0500409 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 = {}
Michael Walsh1173a522018-05-21 17:24:51 -0500414 ix = len(lvalues_list) * -1
415 for lvalue in lvalues_list:
416 lvalues[ix] = lvalue
417 ix += 1
Michael Walsh3f248272018-06-01 13:59:35 -0500418 lvalue_prefix_regex = "(.*=[ ]+)?"
Patrick Williams20f38712022-12-08 06:18:26 -0600419 called_func_name_regex = (
420 lvalue_prefix_regex + func_name_regex + "[ ]*\\(.*"
421 )
Michael Walsh3f248272018-06-01 13:59:35 -0500422 called_func_name = re.sub(called_func_name_regex, "\\4", composite_line)
Michael Walsh1173a522018-05-21 17:24:51 -0500423 arg_list_etc = "(" + re.sub(pre_args_regex, "", composite_line)
Michael Walshde791732016-09-06 14:25:24 -0500424 if local_debug:
Michael Walsh4dbb6002019-05-17 15:51:15 -0500425 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)
Patrick Williams20f38712022-12-08 06:18:26 -0600435 print_varx(
436 "called_func_name_regex",
437 called_func_name_regex,
438 indent=debug_indent,
439 )
Michael Walsh4dbb6002019-05-17 15:51:15 -0500440 print_varx("called_func_name", called_func_name, indent=debug_indent)
441 print_varx("arg_list_etc", arg_list_etc, indent=debug_indent)
Michael Walshde791732016-09-06 14:25:24 -0500442
443 # Parse arg list...
444 # Initialize...
445 nest_level = -1
446 arg_ix = 0
Michael Walsh7423c012016-10-04 10:27:21 -0500447 args_list = [""]
Michael Walshde791732016-09-06 14:25:24 -0500448 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
Michael Walsh46fcecb2019-10-16 17:11:59 -0500460 # 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.
Michael Walshde791732016-09-06 14:25:24 -0500462 if char == "," and nest_level == 0:
463 arg_ix += 1
Michael Walsh7423c012016-10-04 10:27:21 -0500464 args_list.append("")
Michael Walshde791732016-09-06 14:25:24 -0500465 continue
466
Michael Walsh46fcecb2019-10-16 17:11:59 -0500467 # For any other character, we append it it to the current arg list entry.
Michael Walsh7423c012016-10-04 10:27:21 -0500468 args_list[arg_ix] += char
Michael Walshde791732016-09-06 14:25:24 -0500469
470 # Trim whitespace from each list entry.
Michael Walsh7423c012016-10-04 10:27:21 -0500471 args_list = [arg.strip() for arg in args_list]
Michael Walshde791732016-09-06 14:25:24 -0500472
Michael Walsh1173a522018-05-21 17:24:51 -0500473 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
Michael Walsh2750b442018-05-18 14:49:11 -0500480 else:
Michael Walsh1173a522018-05-21 17:24:51 -0500481 if arg_num > len(args_list):
482 argument = args_list
483 else:
484 argument = args_list[arg_num - 1]
Michael Walshde791732016-09-06 14:25:24 -0500485
486 if local_debug:
Michael Walsh4dbb6002019-05-17 15:51:15 -0500487 print_varx("args_list", args_list, indent=debug_indent)
488 print_varx("argument", argument, indent=debug_indent)
Michael Walsh23e7f492017-01-10 11:34:47 -0600489 print_dashes(0, 120)
Michael Walshde791732016-09-06 14:25:24 -0500490
491 return argument
492
Michael Walshde791732016-09-06 14:25:24 -0500493
Michael Walshde791732016-09-06 14:25:24 -0500494def sprint_time(buffer=""):
Michael Walshde791732016-09-06 14:25:24 -0500495 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
Michael Walsh46fcecb2019-10-16 17:11:59 -0500519 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.
Michael Walshde791732016-09-06 14:25:24 -0500524
525 #(CDT) 2016/08/03 17:16:25.510469 - Hi.
526
Michael Walsh46fcecb2019-10-16 17:11:59 -0500527 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.
Michael Walshde791732016-09-06 14:25:24 -0500532
533 #(CDT) 2016/08/03 17:17:40 - 0 - Hi.
534
Michael Walsh46fcecb2019-10-16 17:11:59 -0500535 Example of the output when environment variable NANOSECONDS=1 and SHOW_ELAPSED_TIME=1.
Michael Walshde791732016-09-06 14:25:24 -0500536
537 #(CDT) 2016/08/03 17:18:47.317339 - 0.000046 - Hi.
538
Michael Walsh4dbb6002019-05-17 15:51:15 -0500539 Description of argument(s).
Michael Walsh46fcecb2019-10-16 17:11:59 -0500540 buffer This will be appended to the formatted time string.
Michael Walshde791732016-09-06 14:25:24 -0500541 """
542
543 global NANOSECONDS
544 global SHOW_ELAPSED_TIME
545 global sprint_time_last_seconds
Michael Walsh4fea2cf2018-08-22 17:48:18 -0500546 global last_seconds_ix
Michael Walshde791732016-09-06 14:25:24 -0500547
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
Patrick Williams20f38712022-12-08 06:18:26 -0600560 math_string = (
561 "%9.9f" % cur_time_seconds
562 + " - "
563 + "%9.9f" % sprint_time_last_seconds[last_seconds_ix]
564 )
Michael Walshde791732016-09-06 14:25:24 -0500565 elapsed_seconds = eval(math_string)
566 if NANOSECONDS == "1":
567 elapsed_seconds = "%11.6f" % elapsed_seconds
568 else:
569 elapsed_seconds = "%4i" % elapsed_seconds
Michael Walsh4fea2cf2018-08-22 17:48:18 -0500570 sprint_time_last_seconds[last_seconds_ix] = cur_time_seconds
Michael Walshde791732016-09-06 14:25:24 -0500571 time_string = time_string + " - " + elapsed_seconds
572
573 return time_string + " - " + buffer
574
Michael Walshde791732016-09-06 14:25:24 -0500575
Michael Walshde791732016-09-06 14:25:24 -0500576def sprint_timen(buffer=""):
Michael Walshde791732016-09-06 14:25:24 -0500577 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -0500578 Append a line feed to the buffer, pass it to sprint_time and return the result.
Michael Walshde791732016-09-06 14:25:24 -0500579 """
580
581 return sprint_time(buffer + "\n")
582
Michael Walshde791732016-09-06 14:25:24 -0500583
Michael Walshde791732016-09-06 14:25:24 -0500584def sprint_error(buffer=""):
Michael Walshde791732016-09-06 14:25:24 -0500585 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
Michael Walsh4dbb6002019-05-17 15:51:15 -0500601 Description of argument(s).
Michael Walsh46fcecb2019-10-16 17:11:59 -0500602 buffer This will be appended to the formatted error string.
Michael Walshde791732016-09-06 14:25:24 -0500603 """
604
605 return sprint_time() + "**ERROR** " + buffer
606
Michael Walshde791732016-09-06 14:25:24 -0500607
Michael Walsh3f248272018-06-01 13:59:35 -0500608# 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:
Michael Walsh46fcecb2019-10-16 17:11:59 -0500634 # 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.
Michael Walsh3f248272018-06-01 13:59:35 -0500642 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"""
Michael Walsh46fcecb2019-10-16 17:11:59 -0500655 Return the required number of hex digits required to display the given number.
Michael Walsh3f248272018-06-01 13:59:35 -0500656
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:
Michael Walsh46fcecb2019-10-16 17:11:59 -0500664 # 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.
Michael Walsh3f248272018-06-01 13:59:35 -0500672 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)
Patrick Williams20f38712022-12-08 06:18:26 -0600681 num_hex_digits, remainder = divmod(
682 num_length_in_bits, digit_length_in_bits()
683 )
Michael Walsh3f248272018-06-01 13:59:35 -0500684 if remainder > 0:
Michael Walsh46fcecb2019-10-16 17:11:59 -0500685 # 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.
Michael Walsh3f248272018-06-01 13:59:35 -0500687 num_hex_digits += 1
688
Michael Walsh46fcecb2019-10-16 17:11:59 -0500689 # Check to see whether the negative bit is set. This is the left-most bit in the highest order digit.
Michael Walsh3f248272018-06-01 13:59:35 -0500690 negative_mask = 2 ** (num_hex_digits * 4 - 1)
691 if working_number & negative_mask:
Michael Walsh46fcecb2019-10-16 17:11:59 -0500692 # 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.
Michael Walsh3f248272018-06-01 13:59:35 -0500694 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"""
Michael Walsh46fcecb2019-10-16 17:11:59 -0500706 Return the default number of hex digits to be used to represent a hex number in print.
Michael Walsh3f248272018-06-01 13:59:35 -0500707
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
Michael Walsh4dbb6002019-05-17 15:51:15 -0500719# 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
Michael Walsh91fc8822019-05-29 17:34:17 -0500736def proxy_dict_type():
737 return 5
738
739
Michael Walsh8646d962019-01-21 14:36:13 -0600740def is_dict(var_value):
741 r"""
Michael Walsh4dbb6002019-05-17 15:51:15 -0500742 Return non-zero if var_value is a type of dictionary and 0 if it is not.
743
Michael Walsh46fcecb2019-10-16 17:11:59 -0500744 The specific non-zero value returned will indicate what type of dictionary var_value is (see constant
745 functions above).
Michael Walsh4dbb6002019-05-17 15:51:15 -0500746
747 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -0500748 var_value The object to be analyzed to determine whether it is a dictionary and if
749 so, what type of dictionary.
Michael Walsh8646d962019-01-21 14:36:13 -0600750 """
751
Michael Walsh8646d962019-01-21 14:36:13 -0600752 if isinstance(var_value, dict):
Michael Walsh91fc8822019-05-29 17:34:17 -0500753 return dict_type()
Michael Walsh8646d962019-01-21 14:36:13 -0600754 try:
755 if isinstance(var_value, collections.OrderedDict):
Michael Walsh91fc8822019-05-29 17:34:17 -0500756 return ordered_dict_type()
Michael Walsh8646d962019-01-21 14:36:13 -0600757 except AttributeError:
758 pass
759 try:
760 if isinstance(var_value, DotDict):
Michael Walsh91fc8822019-05-29 17:34:17 -0500761 return dot_dict_type()
Michael Walsh8646d962019-01-21 14:36:13 -0600762 except NameError:
763 pass
764 try:
765 if isinstance(var_value, NormalizedDict):
Michael Walsh91fc8822019-05-29 17:34:17 -0500766 return normalized_dict_type()
Michael Walsh8646d962019-01-21 14:36:13 -0600767 except NameError:
768 pass
Michael Walsh91fc8822019-05-29 17:34:17 -0500769 try:
770 if str(type(var_value)).split("'")[1] == "dictproxy":
771 return proxy_dict_type()
772 except NameError:
773 pass
774 return 0
Michael Walsh8646d962019-01-21 14:36:13 -0600775
776
Michael Walsh4dbb6002019-05-17 15:51:15 -0500777def get_int_types():
778 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -0500779 Return a tuple consisting of the valid integer data types for the system and version of python being run.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500780
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"""
Michael Walsh46fcecb2019-10-16 17:11:59 -0500794 Return a tuple consisting of the valid string data types for the system and version of python being run.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500795
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"""
Michael Walsh46fcecb2019-10-16 17:11:59 -0500809 Return a list of the valid formats that can be specified for the fmt argument of the sprint_varx function
810 (defined below).
Michael Walsh4dbb6002019-05-17 15:51:15 -0500811 """
812
813 return [
Patrick Williams20f38712022-12-08 06:18:26 -0600814 "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 ]
Michael Walsh4dbb6002019-05-17 15:51:15 -0500825
826
827def create_fmt_definition():
828 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -0500829 Create a string consisting of function-definition code that can be executed to create constant fmt
830 definition functions.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500831
Michael Walsh46fcecb2019-10-16 17:11:59 -0500832 These functions can be used by callers of sprint_var/sprint_varx to set the fmt argument correctly.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500833
Michael Walsh46fcecb2019-10-16 17:11:59 -0500834 Likewise, the sprint_varx function will use these generated functions to correctly interpret the fmt
835 argument.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500836
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
Michael Walsh46fcecb2019-10-16 17:11:59 -0500855# Dynamically create fmt definitions (for use with the fmt argument of sprint_varx function):
Michael Walsh4dbb6002019-05-17 15:51:15 -0500856exec(create_fmt_definition())
857
858
Michael Walsh6bed4d32019-07-10 14:11:30 -0500859def terse():
860 r"""
861 Constant function to return fmt value of 0.
862
Michael Walsh46fcecb2019-10-16 17:11:59 -0500863 Now that sprint_varx defaults to printing in terse format, the terse option is deprecated. This function
864 is here for backward compatibility.
Michael Walsh6bed4d32019-07-10 14:11:30 -0500865
Michael Walsh46fcecb2019-10-16 17:11:59 -0500866 Once the repo has been purged of the use of terse, this function can be removed.
Michael Walsh6bed4d32019-07-10 14:11:30 -0500867 """
868
869 return 0
870
871
Michael Walsh4dbb6002019-05-17 15:51:15 -0500872def list_pop(a_list, index=0, default=None):
873 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -0500874 Pop the list entry indicated by the index and return the entry. If no such entry exists, return default.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500875
876 Note that the list passed to this function will be modified.
877
878 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -0500879 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.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500882 """
883 try:
884 return a_list.pop(index)
885 except IndexError:
886 return default
887
888
889def parse_fmt(fmt):
890 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -0500891 Parse the fmt argument and return a tuple consisting of a format and a child format.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500892
Michael Walsh46fcecb2019-10-16 17:11:59 -0500893 This function was written for use by the sprint_varx function defined in this module.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500894
Michael Walsh46fcecb2019-10-16 17:11:59 -0500895 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:
Michael Walsh4dbb6002019-05-17 15:51:15 -0500899
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:
Michael Walsh6bed4d32019-07-10 14:11:30 -0500915 [one]: 1
916 [two]: 2
917 [sub]:
918 [three]: 3
919 [four]: 4
Michael Walsh4dbb6002019-05-17 15:51:15 -0500920
Michael Walsh46fcecb2019-10-16 17:11:59 -0500921 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).
Michael Walsh4dbb6002019-05-17 15:51:15 -0500924
Michael Walsh46fcecb2019-10-16 17:11:59 -0500925 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.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500927
Michael Walsh6bed4d32019-07-10 14:11:30 -0500928 print_var(ord_dict, fmt=verbose())
Michael Walsh4dbb6002019-05-17 15:51:15 -0500929
930 The output would look like this:
931
932 ord_dict:
Michael Walsh6bed4d32019-07-10 14:11:30 -0500933 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
Michael Walsh4dbb6002019-05-17 15:51:15 -0500938
Michael Walsh46fcecb2019-10-16 17:11:59 -0500939 Note the verbose format where the name of the object ("ord_dict") is repeated on every line.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500940
Michael Walsh46fcecb2019-10-16 17:11:59 -0500941 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.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500944
Michael Walsh46fcecb2019-10-16 17:11:59 -0500945 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:
Michael Walsh4dbb6002019-05-17 15:51:15 -0500947
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):
Michael Walsh46fcecb2019-10-16 17:11:59 -0500976 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.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500979 """
980
Michael Walsh46fcecb2019-10-16 17:11:59 -0500981 # Make a deep copy of the fmt argument in order to avoid modifying the caller's fmt value when it is a
982 # list.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500983 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)
Michael Walsh46fcecb2019-10-16 17:11:59 -0500987 # 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.
Michael Walsh4dbb6002019-05-17 15:51:15 -0500989 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
Patrick Williams20f38712022-12-08 06:18:26 -0600995def 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):
Michael Walshde791732016-09-06 14:25:24 -05001005 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001006 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.
Michael Walshde791732016-09-06 14:25:24 -05001008
Michael Walsh46fcecb2019-10-16 17:11:59 -05001009 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.
Michael Walshde791732016-09-06 14:25:24 -05001012
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
Michael Walsh46fcecb2019-10-16 17:11:59 -05001026 This function recognizes several complex types of data such as dict, list or tuple.
Michael Walshde791732016-09-06 14:25:24 -05001027
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
Michael Walsh4dbb6002019-05-17 15:51:15 -05001040 Description of argument(s).
Michael Walshde791732016-09-06 14:25:24 -05001041 var_name The name of the variable to be printed.
1042 var_value The value of the variable to be printed.
Michael Walsh46fcecb2019-10-16 17:11:59 -05001043 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.
Michael Walsh4dbb6002019-05-17 15:51:15 -05001054 octal Print all integer values in octal format.
1055 binary Print all integer values in binary format.
Michael Walsh46fcecb2019-10-16 17:11:59 -05001056 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
Michael Walsh4dbb6002019-05-17 15:51:15 -05001061 my_dict[key1].
Michael Walsh46fcecb2019-10-16 17:11:59 -05001062 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.
Michael Walsh4dbb6002019-05-17 15:51:15 -05001070 indent The number of spaces to indent the output.
Michael Walsh46fcecb2019-10-16 17:11:59 -05001071 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.
Michael Walsh7423c012016-10-04 10:27:21 -05001087 """
Michael Walshde791732016-09-06 14:25:24 -05001088
Michael Walsh4dbb6002019-05-17 15:51:15 -05001089 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:
Michael Walsh46fcecb2019-10-16 17:11:59 -05001099 # The data type is simple in the sense that it has no subordinate parts.
Michael Walsh4dbb6002019-05-17 15:51:15 -05001100 # 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():
Patrick Williams20f38712022-12-08 06:18:26 -06001108 num_hex_digits = max(
1109 dft_num_hex_digits(), get_req_num_hex_digits(var_value)
1110 )
Michael Walsh46fcecb2019-10-16 17:11:59 -05001111 # 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
Michael Walsh3f248272018-06-01 13:59:35 -05001113 # "0xffffffffffffffff".
1114 var_value = var_value & (2 ** (num_hex_digits * 4) - 1)
1115 value_format = "0x%0" + str(num_hex_digits) + "x"
Michael Walsh4dbb6002019-05-17 15:51:15 -05001116 elif fmt & octal():
1117 value_format = "0o%016o"
1118 elif fmt & binary():
Patrick Williams20f38712022-12-08 06:18:26 -06001119 num_digits, remainder = divmod(
1120 max(bit_length(var_value), 1), 8
1121 )
Michael Walsh4dbb6002019-05-17 15:51:15 -05001122 num_digits *= 8
1123 if remainder:
1124 num_digits += 8
1125 num_digits += 2
Patrick Williams20f38712022-12-08 06:18:26 -06001126 value_format = "#0" + str(num_digits) + "b"
Michael Walsh4dbb6002019-05-17 15:51:15 -05001127 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]
Patrick Williams20f38712022-12-08 06:18:26 -06001136 format_string = (
1137 "%" + str(indent) + "s%-" + str(col1_width) + "s" + value_format
1138 )
Michael Walsh4dbb6002019-05-17 15:51:15 -05001139 if fmt & show_type():
1140 if var_value != "":
1141 format_string += " "
1142 format_string += type_str
1143 format_string += trailing_char
Michael Walsh46fcecb2019-10-16 17:11:59 -05001144 if fmt & quote_values():
1145 var_value = "'" + var_value + "'"
Michael Walsh6bed4d32019-07-10 14:11:30 -05001146 if not (fmt & verbose()):
Michael Walsh4dbb6002019-05-17 15:51:15 -05001147 # Strip everything leading up to the first left square brace.
1148 var_name = re.sub(r".*\[", "[", var_name)
Patrick Williams20f38712022-12-08 06:18:26 -06001149 if fmt & strip_brackets():
Michael Walshc6acf742019-08-06 11:48:51 -05001150 var_name = re.sub(r"[\[\]]", "", var_name)
Michael Walsh3383e652017-09-01 17:10:59 -05001151 if value_format == "0x%08x":
Patrick Williams20f38712022-12-08 06:18:26 -06001152 return format_string % (
1153 "",
1154 str(var_name) + delim,
1155 var_value & 0xFFFFFFFF,
1156 )
Michael Walsh3383e652017-09-01 17:10:59 -05001157 else:
Michael Walsh4dbb6002019-05-17 15:51:15 -05001158 return format_string % ("", str(var_name) + delim, var_value)
Michael Walshde791732016-09-06 14:25:24 -05001159 else:
1160 # The data type is complex in the sense that it has subordinate parts.
Patrick Williams20f38712022-12-08 06:18:26 -06001161 if fmt & no_header():
Michael Walshc6acf742019-08-06 11:48:51 -05001162 buffer = ""
Michael Walsh4dbb6002019-05-17 15:51:15 -05001163 else:
Michael Walshc6acf742019-08-06 11:48:51 -05001164 # 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
Patrick Williams20f38712022-12-08 06:18:26 -06001170 if fmt & strip_brackets():
Michael Walshadf4ab22019-09-06 16:23:41 -05001171 loc_var_name = re.sub(r"[\[\]]", "", loc_var_name)
Michael Walshc6acf742019-08-06 11:48:51 -05001172 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
Michael Walsh7423c012016-10-04 10:27:21 -05001177 try:
1178 length = len(var_value)
1179 except TypeError:
Michael Walsh23e7f492017-01-10 11:34:47 -06001180 length = 0
Michael Walsh7423c012016-10-04 10:27:21 -05001181 ix = 0
1182 loc_trailing_char = "\n"
Michael Walsh8646d962019-01-21 14:36:13 -06001183 if is_dict(var_value):
Michael Walsh4dbb6002019-05-17 15:51:15 -05001184 if type(child_fmt) is list:
Patrick Williams20f38712022-12-08 06:18:26 -06001185 child_quote_keys = child_fmt[0] & quote_keys()
Michael Walsh4dbb6002019-05-17 15:51:15 -05001186 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001187 child_quote_keys = child_fmt & quote_keys()
Michael Walsh37762f92018-08-07 14:59:18 -05001188 for key, value in var_value.items():
Michael Walshd2869032018-03-22 16:12:11 -05001189 if key_list is not None:
1190 key_list_regex = "^" + "|".join(key_list) + "$"
1191 if not re.match(key_list_regex, key):
1192 continue
Michael Walsh7423c012016-10-04 10:27:21 -05001193 ix += 1
1194 if ix == length:
1195 loc_trailing_char = trailing_char
Michael Walsh4dbb6002019-05-17 15:51:15 -05001196 if child_quote_keys:
1197 key = "'" + key + "'"
1198 key = "[" + str(key) + "]"
Patrick Williams20f38712022-12-08 06:18:26 -06001199 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 )
Michael Walsh7423c012016-10-04 10:27:21 -05001209 elif type(var_value) in (list, tuple, set):
Michael Walshde791732016-09-06 14:25:24 -05001210 for key, value in enumerate(var_value):
Michael Walsh7423c012016-10-04 10:27:21 -05001211 ix += 1
1212 if ix == length:
1213 loc_trailing_char = trailing_char
Michael Walsh4dbb6002019-05-17 15:51:15 -05001214 key = "[" + str(key) + "]"
Patrick Williams20f38712022-12-08 06:18:26 -06001215 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 )
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -05001225 elif isinstance(var_value, argparse.Namespace):
Michael Walshde791732016-09-06 14:25:24 -05001226 for key in var_value.__dict__:
Michael Walsh7423c012016-10-04 10:27:21 -05001227 ix += 1
1228 if ix == length:
1229 loc_trailing_char = trailing_char
Patrick Williams20f38712022-12-08 06:18:26 -06001230 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 )
Michael Walshde791732016-09-06 14:25:24 -05001238 exec(cmd_buf)
1239 else:
1240 var_type = type(var_value).__name__
1241 func_name = sys._getframe().f_code.co_name
Patrick Williams20f38712022-12-08 06:18:26 -06001242 var_value = (
1243 "<" + var_type + " type not supported by " + func_name + "()>"
1244 )
Michael Walshde791732016-09-06 14:25:24 -05001245 value_format = "%s"
Michael Walsh4dbb6002019-05-17 15:51:15 -05001246 indent -= 2
1247 # Adjust col1_width.
1248 col1_width = col1_width - indent
Patrick Williams20f38712022-12-08 06:18:26 -06001249 format_string = (
1250 "%"
1251 + str(indent)
1252 + "s%-"
1253 + str(col1_width)
1254 + "s"
1255 + value_format
1256 + trailing_char
1257 )
Michael Walsh0f2ea5f2017-02-20 15:55:00 -06001258 return format_string % ("", str(var_name) + ":", var_value)
Michael Walsh23e7f492017-01-10 11:34:47 -06001259
Michael Walshde791732016-09-06 14:25:24 -05001260 return buffer
1261
1262 return ""
1263
Michael Walshde791732016-09-06 14:25:24 -05001264
Michael Walsh4dbb6002019-05-17 15:51:15 -05001265def sprint_var(*args, **kwargs):
Michael Walshde791732016-09-06 14:25:24 -05001266 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001267 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:
Michael Walshde791732016-09-06 14:25:24 -05001269 sprint_varx("var1", var1)
1270 sprint_var(var1)
Michael Walsh4dbb6002019-05-17 15:51:15 -05001271
1272 See sprint_varx for description of arguments.
Michael Walshde791732016-09-06 14:25:24 -05001273 """
1274
Michael Walshde791732016-09-06 14:25:24 -05001275 stack_frame = 2
Michael Walsh7423c012016-10-04 10:27:21 -05001276 caller_func_name = sprint_func_name(2)
1277 if caller_func_name.endswith("print_var"):
Michael Walshde791732016-09-06 14:25:24 -05001278 stack_frame += 1
Michael Walsh4dbb6002019-05-17 15:51:15 -05001279 # Get the name of the first variable passed to this function.
Michael Walshde791732016-09-06 14:25:24 -05001280 var_name = get_arg_name(None, 1, stack_frame)
Michael Walsh4dbb6002019-05-17 15:51:15 -05001281 return sprint_varx(var_name, *args, **kwargs)
Michael Walshde791732016-09-06 14:25:24 -05001282
1283
Michael Walsh4dbb6002019-05-17 15:51:15 -05001284def sprint_vars(*args, **kwargs):
Michael Walsh18176322016-11-15 15:11:21 -06001285 r"""
1286 Sprint the values of one or more variables.
1287
Michael Walsh4dbb6002019-05-17 15:51:15 -05001288 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001289 args The variable values which are to be printed.
1290 kwargs See sprint_varx (above) for description of additional arguments.
Michael Walsh18176322016-11-15 15:11:21 -06001291 """
1292
Michael Walsh18176322016-11-15 15:11:21 -06001293 stack_frame = 2
1294 caller_func_name = sprint_func_name(2)
1295 if caller_func_name.endswith("print_vars"):
1296 stack_frame += 1
1297
Michael Walsh18176322016-11-15 15:11:21 -06001298 buffer = ""
Michael Walsh4dbb6002019-05-17 15:51:15 -05001299 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
Michael Walsh18176322016-11-15 15:11:21 -06001304
1305 return buffer
1306
Michael Walsh18176322016-11-15 15:11:21 -06001307
Patrick Williams20f38712022-12-08 06:18:26 -06001308def sprint_dashes(indent=dft_indent, width=80, line_feed=1, char="-"):
Michael Walshde791732016-09-06 14:25:24 -05001309 r"""
1310 Return a string of dashes to the caller.
1311
Michael Walsh4dbb6002019-05-17 15:51:15 -05001312 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001313 indent The number of characters to indent the output.
Michael Walshde791732016-09-06 14:25:24 -05001314 width The width of the string of dashes.
Michael Walsh46fcecb2019-10-16 17:11:59 -05001315 line_feed Indicates whether the output should end with a line feed.
1316 char The character to be repeated in the output string.
Michael Walshde791732016-09-06 14:25:24 -05001317 """
1318
Michael Walsh7423c012016-10-04 10:27:21 -05001319 width = int(width)
Michael Walsh23e7f492017-01-10 11:34:47 -06001320 buffer = " " * int(indent) + char * width
Michael Walshde791732016-09-06 14:25:24 -05001321 if line_feed:
1322 buffer += "\n"
1323
1324 return buffer
1325
Michael Walshde791732016-09-06 14:25:24 -05001326
Patrick Williams20f38712022-12-08 06:18:26 -06001327def sindent(text="", indent=0):
Michael Walsh7423c012016-10-04 10:27:21 -05001328 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001329 Pre-pend the specified number of characters to the text string (i.e. indent it) and return it.
Michael Walsh7423c012016-10-04 10:27:21 -05001330
Michael Walsh4dbb6002019-05-17 15:51:15 -05001331 Description of argument(s):
Michael Walsh7423c012016-10-04 10:27:21 -05001332 text The string to be indented.
Michael Walsh46fcecb2019-10-16 17:11:59 -05001333 indent The number of characters to indent the string.
Michael Walsh7423c012016-10-04 10:27:21 -05001334 """
1335
1336 format_string = "%" + str(indent) + "s%s"
1337 buffer = format_string % ("", text)
1338
1339 return buffer
1340
Michael Walsh7423c012016-10-04 10:27:21 -05001341
Michael Walsh662e13b2019-03-01 15:54:08 -06001342func_line_style_std = None
1343func_line_style_short = 1
1344
1345
Michael Walshb26d2c72019-11-04 10:48:04 -06001346def sprint_func_line(stack_frame, style=None, max_width=160):
Michael Walsh47aa2a42018-12-10 15:06:02 -06001347 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001348 For the given stack_frame, return a formatted string containing the function name and all its arguments.
Michael Walsh47aa2a42018-12-10 15:06:02 -06001349
1350 Example:
1351
1352 func1(last_name = 'walsh', first_name = 'mikey')
1353
1354 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001355 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.
Michael Walshd40115e2020-02-19 14:22:35 -06001358 max_width The max width of the result. If it exceeds this length, it will be
1359 truncated on the right.
Michael Walsh662e13b2019-03-01 15:54:08 -06001360
1361 Description of styles:
1362 func_line_style_std The standard formatting.
Michael Walsh46fcecb2019-10-16 17:11:59 -05001363 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.
Michael Walsh47aa2a42018-12-10 15:06:02 -06001366 """
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>":
Michael Walsh46fcecb2019-10-16 17:11:59 -05001374 # If the func_name is the "main" program, we simply get the command line call string.
Patrick Williams20f38712022-12-08 06:18:26 -06001375 func_and_args = " ".join(sys.argv)
Michael Walsh47aa2a42018-12-10 15:06:02 -06001376 else:
1377 # Get the program arguments.
Patrick Williams20f38712022-12-08 06:18:26 -06001378 (args, varargs, keywords, locals) = inspect.getargvalues(
1379 stack_frame[0]
1380 )
Michael Walsh47aa2a42018-12-10 15:06:02 -06001381
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]
Patrick Williams20f38712022-12-08 06:18:26 -06001386 if arg_name == "self":
Michael Walsh662e13b2019-03-01 15:54:08 -06001387 if style == func_line_style_short:
1388 continue
Michael Walsh47aa2a42018-12-10 15:06:02 -06001389 # Manipulations to improve output for class methods.
1390 func_name = arg_value.__class__.__name__ + "." + func_name
1391 args_list.append(arg_name + " = <self>")
Patrick Williams20f38712022-12-08 06:18:26 -06001392 elif (
1393 style == func_line_style_short
1394 and arg_name == "args"
1395 and type(arg_value) in (list, tuple)
1396 ):
Michael Walsh662e13b2019-03-01 15:54:08 -06001397 if len(arg_value) == 0:
1398 continue
Patrick Williams20f38712022-12-08 06:18:26 -06001399 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 ):
Michael Walsh662e13b2019-03-01 15:54:08 -06001405 for key, value in arg_value.items():
1406 args_list.append(key + "=" + repr(value))
Michael Walsh47aa2a42018-12-10 15:06:02 -06001407 else:
1408 args_list.append(arg_name + " = " + repr(arg_value))
Patrick Williams20f38712022-12-08 06:18:26 -06001409 args_str = "(" + ", ".join(map(str, args_list)) + ")"
Michael Walsh47aa2a42018-12-10 15:06:02 -06001410
1411 # Now we need to print this in a nicely-wrapped way.
1412 func_and_args = func_name + args_str
1413
Michael Walshb26d2c72019-11-04 10:48:04 -06001414 if len(func_and_args) > max_width:
1415 func_and_args = func_and_args[0:max_width] + "..."
Michael Walsh47aa2a42018-12-10 15:06:02 -06001416 return func_and_args
1417
1418
Patrick Williams20f38712022-12-08 06:18:26 -06001419def sprint_call_stack(indent=0, stack_frame_ix=0, style=None):
Michael Walshde791732016-09-06 14:25:24 -05001420 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001421 Return a call stack report for the given point in the program with line numbers, function names and
1422 function parameters and arguments.
Michael Walshde791732016-09-06 14:25:24 -05001423
1424 Sample output:
1425
1426 -------------------------------------------------------------------------
1427 Python function call stack
1428
1429 Line # Function name and arguments
1430 ------ ------------------------------------------------------------------
Michael Walsh47aa2a42018-12-10 15:06:02 -06001431 424 sprint_call_stack()
1432 4 print_call_stack()
1433 31 func1(last_name = 'walsh', first_name = 'mikey')
Michael Walshde791732016-09-06 14:25:24 -05001434 59 /tmp/scr5.py
1435 -------------------------------------------------------------------------
1436
Michael Walsh4dbb6002019-05-17 15:51:15 -05001437 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001438 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.
Michael Walshde791732016-09-06 14:25:24 -05001441 """
1442
1443 buffer = ""
Michael Walsh7423c012016-10-04 10:27:21 -05001444 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)
Michael Walshde791732016-09-06 14:25:24 -05001448
1449 # Grab the current program stack.
Michael Walsh6f0362c2019-03-25 14:05:14 -05001450 work_around_inspect_stack_cwd_failure()
Michael Walshde791732016-09-06 14:25:24 -05001451 current_stack = inspect.stack()
1452
1453 # Process each frame in turn.
1454 format_string = "%6s %s\n"
Michael Walsh7423c012016-10-04 10:27:21 -05001455 ix = 0
Michael Walshde791732016-09-06 14:25:24 -05001456 for stack_frame in current_stack:
Michael Walsh7423c012016-10-04 10:27:21 -05001457 if ix < stack_frame_ix:
1458 ix += 1
1459 continue
Michael Walsh46fcecb2019-10-16 17:11:59 -05001460 # Make the line number shown to be the line where one finds the line shown.
Michael Walsh23e7f492017-01-10 11:34:47 -06001461 try:
1462 line_num = str(current_stack[ix + 1][2])
1463 except IndexError:
1464 line_num = ""
Michael Walsh662e13b2019-03-01 15:54:08 -06001465 func_and_args = sprint_func_line(stack_frame, style=style)
Michael Walshde791732016-09-06 14:25:24 -05001466
Michael Walsh23e7f492017-01-10 11:34:47 -06001467 buffer += sindent(format_string % (line_num, func_and_args), indent)
Michael Walsh7423c012016-10-04 10:27:21 -05001468 ix += 1
Michael Walshde791732016-09-06 14:25:24 -05001469
Michael Walsh7423c012016-10-04 10:27:21 -05001470 buffer += sprint_dashes(indent)
Michael Walshde791732016-09-06 14:25:24 -05001471
1472 return buffer
1473
Michael Walshde791732016-09-06 14:25:24 -05001474
Michael Walshd40115e2020-02-19 14:22:35 -06001475def sprint_executing(stack_frame_ix=None, style=None, max_width=None):
Michael Walshde791732016-09-06 14:25:24 -05001476 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001477 Print a line indicating what function is executing and with what parameter values. This is useful for
1478 debugging.
Michael Walshde791732016-09-06 14:25:24 -05001479
1480 Sample output:
1481
Michael Walsh47aa2a42018-12-10 15:06:02 -06001482 #(CDT) 2016/08/25 17:54:27 - Executing: func1(x = 1)
Michael Walshde791732016-09-06 14:25:24 -05001483
Michael Walsh4dbb6002019-05-17 15:51:15 -05001484 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001485 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.
Michael Walshd40115e2020-02-19 14:22:35 -06001490 max_width See the sprint_line_func prolog above for details.
Michael Walshde791732016-09-06 14:25:24 -05001491 """
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
Michael Walsh7423c012016-10-04 10:27:21 -05001497 if caller_func_name.endswith(func_name[1:]):
Michael Walshde791732016-09-06 14:25:24 -05001498 stack_frame_ix = 2
1499 else:
1500 stack_frame_ix = 1
1501
Michael Walsh6f0362c2019-03-25 14:05:14 -05001502 work_around_inspect_stack_cwd_failure()
Michael Walshde791732016-09-06 14:25:24 -05001503 stack_frame = inspect.stack()[stack_frame_ix]
1504
Michael Walshd40115e2020-02-19 14:22:35 -06001505 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)
Michael Walshde791732016-09-06 14:25:24 -05001508
1509 return sprint_time() + "Executing: " + func_and_args + "\n"
1510
Michael Walshde791732016-09-06 14:25:24 -05001511
Patrick Williams20f38712022-12-08 06:18:26 -06001512def sprint_pgm_header(indent=0, linefeed=1):
Michael Walshde791732016-09-06 14:25:24 -05001513 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001514 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.
Michael Walshde791732016-09-06 14:25:24 -05001516
Michael Walsh4dbb6002019-05-17 15:51:15 -05001517 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001518 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.
Michael Walshde791732016-09-06 14:25:24 -05001521 """
1522
Michael Walsh4dbb6002019-05-17 15:51:15 -05001523 col1_width = dft_col1_width + indent
Michael Walshbec416d2016-11-10 08:54:52 -06001524
1525 buffer = ""
1526 if linefeed:
1527 buffer = "\n"
Michael Walsh7423c012016-10-04 10:27:21 -05001528
Michael Walshdb6e68a2017-05-23 17:55:31 -05001529 if robot_env:
1530 suite_name = BuiltIn().get_variable_value("${suite_name}")
Patrick Williams20f38712022-12-08 06:18:26 -06001531 buffer += sindent(
1532 sprint_time('Running test suite "' + suite_name + '".\n'), indent
1533 )
Michael Walshdb6e68a2017-05-23 17:55:31 -05001534
Michael Walsh7423c012016-10-04 10:27:21 -05001535 buffer += sindent(sprint_time() + "Running " + pgm_name + ".\n", indent)
Patrick Williams20f38712022-12-08 06:18:26 -06001536 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 )
Michael Walsh46fcecb2019-10-16 17:11:59 -05001542 # 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.
Patrick Williams20f38712022-12-08 06:18:26 -06001544 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 )
Michael Walsh86de0d22016-12-05 10:13:15 -06001550 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 = "?"
Patrick Williams20f38712022-12-08 06:18:26 -06001558 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 )
Michael Walsh86de0d22016-12-05 10:13:15 -06001571 try:
Patrick Williams20f38712022-12-08 06:18:26 -06001572 DISPLAY = os.environ["DISPLAY"]
Michael Walsh86de0d22016-12-05 10:13:15 -06001573 except KeyError:
1574 DISPLAY = ""
Michael Walsh91fc8822019-05-29 17:34:17 -05001575 buffer += sprint_var(DISPLAY, 0, indent, col1_width)
Patrick Williams20f38712022-12-08 06:18:26 -06001576 PYTHON_VERSION = os.environ.get("PYTHON_VERSION", None)
Michael Walsh91fc8822019-05-29 17:34:17 -05001577 if PYTHON_VERSION is not None:
Michael Walshc9a7ee72019-08-28 16:45:50 -05001578 buffer += sprint_var(PYTHON_VERSION, 0, indent, col1_width)
Patrick Williams20f38712022-12-08 06:18:26 -06001579 PYTHON_PGM_PATH = os.environ.get("PYTHON_PGM_PATH", None)
Michael Walsh91fc8822019-05-29 17:34:17 -05001580 if PYTHON_PGM_PATH is not None:
Michael Walshc9a7ee72019-08-28 16:45:50 -05001581 buffer += sprint_var(PYTHON_PGM_PATH, 0, indent, col1_width)
Michael Walsh91fc8822019-05-29 17:34:17 -05001582 python_version = sys.version.replace("\n", "")
Michael Walshc9a7ee72019-08-28 16:45:50 -05001583 buffer += sprint_var(python_version, 0, indent, col1_width)
Patrick Williams20f38712022-12-08 06:18:26 -06001584 ROBOT_VERSION = os.environ.get("ROBOT_VERSION", None)
Michael Walsh91fc8822019-05-29 17:34:17 -05001585 if ROBOT_VERSION is not None:
Michael Walshc9a7ee72019-08-28 16:45:50 -05001586 buffer += sprint_var(ROBOT_VERSION, 0, indent, col1_width)
Patrick Williams20f38712022-12-08 06:18:26 -06001587 ROBOT_PGM_PATH = os.environ.get("ROBOT_PGM_PATH", None)
Michael Walsh91fc8822019-05-29 17:34:17 -05001588 if ROBOT_PGM_PATH is not None:
Michael Walshc9a7ee72019-08-28 16:45:50 -05001589 buffer += sprint_var(ROBOT_PGM_PATH, 0, indent, col1_width)
Michael Walsh91fc8822019-05-29 17:34:17 -05001590
Michael Walsh46fcecb2019-10-16 17:11:59 -05001591 # __builtin__.arg_obj is created by the get_arg module function, gen_get_options.
Michael Walsh7423c012016-10-04 10:27:21 -05001592 try:
1593 buffer += ga.sprint_args(__builtin__.arg_obj, indent)
1594 except AttributeError:
1595 pass
1596
Michael Walshdb6e68a2017-05-23 17:55:31 -05001597 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 + "}")
Michael Walsh4dbb6002019-05-17 15:51:15 -05001603 buffer += sprint_varx(parm, parm_value, 0, indent, col1_width)
Michael Walshdb6e68a2017-05-23 17:55:31 -05001604
1605 # Setting global program_pid.
1606 BuiltIn().set_global_variable("${program_pid}", os.getpid())
1607
Michael Walshbec416d2016-11-10 08:54:52 -06001608 if linefeed:
1609 buffer += "\n"
Michael Walshde791732016-09-06 14:25:24 -05001610
1611 return buffer
1612
Michael Walshde791732016-09-06 14:25:24 -05001613
Patrick Williams20f38712022-12-08 06:18:26 -06001614def sprint_error_report(
1615 error_text="\n", indent=2, format=None, stack_frame_ix=None
1616):
Michael Walsh7423c012016-10-04 10:27:21 -05001617 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001618 Return a string with a standardized report which includes the caller's error text, the call stack and the
1619 program header.
Michael Walsh7423c012016-10-04 10:27:21 -05001620
Michael Walsh4dbb6002019-05-17 15:51:15 -05001621 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001622 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.
Michael Walsh7423c012016-10-04 10:27:21 -05001629 """
1630
Michael Walshdb6e68a2017-05-23 17:55:31 -05001631 # Process input.
1632 indent = int(indent)
1633 if format is None:
1634 if robot_env:
Patrick Williams20f38712022-12-08 06:18:26 -06001635 format = "short"
Michael Walshdb6e68a2017-05-23 17:55:31 -05001636 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001637 format = "long"
1638 error_text = error_text.rstrip("\n") + "\n"
Michael Walshdb6e68a2017-05-23 17:55:31 -05001639
Patrick Williams20f38712022-12-08 06:18:26 -06001640 if format == "short":
Michael Walshdb6e68a2017-05-23 17:55:31 -05001641 return sprint_error(error_text)
1642
Michael Walsh7423c012016-10-04 10:27:21 -05001643 buffer = ""
1644 buffer += sprint_dashes(width=120, char="=")
1645 buffer += sprint_error(error_text)
1646 buffer += "\n"
Michael Walshe7ca2b02019-08-01 11:09:45 -05001647 if not stack_frame_ix:
Michael Walsh46fcecb2019-10-16 17:11:59 -05001648 # 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
Michael Walshe7ca2b02019-08-01 11:09:45 -05001650 # 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
Michael Walsh7bfa9ab2018-11-16 15:24:26 -06001658 buffer += sprint_call_stack(indent, stack_frame_ix)
Michael Walsh7423c012016-10-04 10:27:21 -05001659 buffer += sprint_pgm_header(indent)
1660 buffer += sprint_dashes(width=120, char="=")
1661
1662 return buffer
1663
Michael Walsh7423c012016-10-04 10:27:21 -05001664
Patrick Williams20f38712022-12-08 06:18:26 -06001665def sprint_issuing(cmd_buf, test_mode=0):
Michael Walshde791732016-09-06 14:25:24 -05001666 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
Michael Walshbec416d2016-11-10 08:54:52 -06001672
Michael Walsh4dbb6002019-05-17 15:51:15 -05001673 Description of argument(s):
Michael Walshde791732016-09-06 14:25:24 -05001674 cmd_buf The command to be executed by caller.
Michael Walsh46fcecb2019-10-16 17:11:59 -05001675 test_mode With test_mode set, the output will look like this:
Michael Walshbec416d2016-11-10 08:54:52 -06001676
1677 #(CDT) 2016/08/25 17:57:36 - (test_mode) Issuing: ls
1678
Michael Walshde791732016-09-06 14:25:24 -05001679 """
1680
Michael Walshbec416d2016-11-10 08:54:52 -06001681 buffer = sprint_time()
1682 if test_mode:
1683 buffer += "(test_mode) "
Michael Walsh61c12982019-03-28 12:38:01 -05001684 if type(cmd_buf) is list:
1685 # Assume this is a robot command in the form of a list.
Patrick Williams20f38712022-12-08 06:18:26 -06001686 cmd_buf = " ".join([str(element) for element in cmd_buf])
Michael Walshbec416d2016-11-10 08:54:52 -06001687 buffer += "Issuing: " + cmd_buf + "\n"
Michael Walshde791732016-09-06 14:25:24 -05001688
1689 return buffer
1690
Michael Walshde791732016-09-06 14:25:24 -05001691
Michael Walshde791732016-09-06 14:25:24 -05001692def sprint_pgm_footer():
Michael Walshde791732016-09-06 14:25:24 -05001693 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001694 Return a standardized footer that programs should print at the end of the program run. It includes
1695 useful information like total run time, etc.
Michael Walshde791732016-09-06 14:25:24 -05001696 """
1697
1698 buffer = "\n" + sprint_time() + "Finished running " + pgm_name + ".\n\n"
1699
1700 total_time = time.time() - start_time
1701 total_time_string = "%0.6f" % total_time
1702
Michael Walsh7423c012016-10-04 10:27:21 -05001703 buffer += sprint_varx(pgm_name_var_name + "_runtime", total_time_string)
Michael Walshbec416d2016-11-10 08:54:52 -06001704 buffer += "\n"
Michael Walsh7423c012016-10-04 10:27:21 -05001705
1706 return buffer
1707
Michael Walsh7423c012016-10-04 10:27:21 -05001708
Michael Walsh45ee00b2019-12-05 16:27:04 -06001709def sprint_file(file_path):
1710 r"""
1711 Return the file data as a string.
1712
1713 Description of argument(s):
1714 file_path The path to a file (e.g. "/tmp/file1").
1715 """
1716
Patrick Williams20f38712022-12-08 06:18:26 -06001717 with open(file_path, "r") as file:
Michael Walsh45ee00b2019-12-05 16:27:04 -06001718 buffer = file.read()
1719 return buffer
1720
1721
Michael Walsh7423c012016-10-04 10:27:21 -05001722def sprint(buffer=""):
Michael Walsh7423c012016-10-04 10:27:21 -05001723 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001724 Simply return the user's buffer. This function is used by the qprint and dprint functions defined
1725 dynamically below, i.e. it would not normally be called for general use.
Michael Walsh7423c012016-10-04 10:27:21 -05001726
Michael Walsh4dbb6002019-05-17 15:51:15 -05001727 Description of argument(s).
Michael Walsh7423c012016-10-04 10:27:21 -05001728 buffer This will be returned to the caller.
1729 """
Michael Walshde791732016-09-06 14:25:24 -05001730
Michael Walsh95e45102018-02-09 12:44:43 -06001731 try:
1732 return str(buffer)
1733 except UnicodeEncodeError:
1734 return buffer
Michael Walshbec416d2016-11-10 08:54:52 -06001735
Michael Walshbec416d2016-11-10 08:54:52 -06001736
Michael Walshbec416d2016-11-10 08:54:52 -06001737def sprintn(buffer=""):
Michael Walshbec416d2016-11-10 08:54:52 -06001738 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001739 Simply return the user's buffer with a line feed. This function is used by the qprint and dprint
1740 functions defined dynamically below, i.e. it would not normally be called for general use.
Michael Walshbec416d2016-11-10 08:54:52 -06001741
Michael Walsh4dbb6002019-05-17 15:51:15 -05001742 Description of argument(s).
Michael Walshbec416d2016-11-10 08:54:52 -06001743 buffer This will be returned to the caller.
1744 """
1745
Michael Walsh95e45102018-02-09 12:44:43 -06001746 try:
1747 buffer = str(buffer) + "\n"
1748 except UnicodeEncodeError:
1749 buffer = buffer + "\n"
Michael Walshbec416d2016-11-10 08:54:52 -06001750
Michael Walshde791732016-09-06 14:25:24 -05001751 return buffer
1752
Michael Walsh168eb0f2017-12-01 15:35:32 -06001753
Patrick Williams20f38712022-12-08 06:18:26 -06001754def gp_print(buffer, stream="stdout"):
Michael Walshfd2733c2017-11-13 11:36:20 -06001755 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001756 Print the buffer using either sys.stdout.write or BuiltIn().log_to_console depending on whether we are
1757 running in a robot environment.
Michael Walshfd2733c2017-11-13 11:36:20 -06001758
1759 This function is intended for use only by other functions in this module.
1760
Michael Walsh4dbb6002019-05-17 15:51:15 -05001761 Description of argument(s):
Michael Walshfd2733c2017-11-13 11:36:20 -06001762 buffer The string to be printed.
1763 stream Either "stdout" or "stderr".
1764 """
1765
1766 if robot_env:
1767 BuiltIn().log_to_console(buffer, stream=stream, no_newline=True)
1768 else:
1769 if stream == "stdout":
1770 sys.stdout.write(buffer)
1771 sys.stdout.flush()
1772 else:
1773 sys.stderr.write(buffer)
1774 sys.stderr.flush()
Michael Walshde791732016-09-06 14:25:24 -05001775
1776
Michael Walsh168eb0f2017-12-01 15:35:32 -06001777def gp_log(buffer):
Michael Walsh168eb0f2017-12-01 15:35:32 -06001778 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001779 Log the buffer using either python logging or BuiltIn().log depending on whether we are running in a
1780 robot environment.
Michael Walsh168eb0f2017-12-01 15:35:32 -06001781
1782 This function is intended for use only by other functions in this module.
1783
Michael Walsh4dbb6002019-05-17 15:51:15 -05001784 Description of argument(s):
Michael Walsh168eb0f2017-12-01 15:35:32 -06001785 buffer The string to be logged.
1786 """
1787
1788 if robot_env:
1789 BuiltIn().log(buffer)
1790 else:
1791 logging.warning(buffer)
1792
1793
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001794def gp_debug_print(buffer):
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001795 r"""
Michael Walshfd2733c2017-11-13 11:36:20 -06001796 Print with gp_print only if gen_print_debug is set.
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001797
1798 This function is intended for use only by other functions in this module.
1799
Michael Walsh4dbb6002019-05-17 15:51:15 -05001800 Description of argument(s):
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001801 buffer The string to be printed.
1802 """
1803
1804 if not gen_print_debug:
1805 return
1806
Michael Walshfd2733c2017-11-13 11:36:20 -06001807 gp_print(buffer)
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001808
1809
Patrick Williams20f38712022-12-08 06:18:26 -06001810def get_var_value(var_value=None, default=1, var_name=None):
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001811 r"""
Michael Walshb1500152017-04-12 15:42:43 -05001812 Return either var_value, the corresponding global value or default.
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001813
Michael Walshb1500152017-04-12 15:42:43 -05001814 If var_value is not None, it will simply be returned.
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001815
Michael Walsh46fcecb2019-10-16 17:11:59 -05001816 If var_value is None, this function will return the corresponding global value of the variable in
1817 question.
Michael Walshb1500152017-04-12 15:42:43 -05001818
Michael Walsh46fcecb2019-10-16 17:11:59 -05001819 Note: For global values, if we are in a robot environment, get_variable_value will be used. Otherwise,
1820 the __builtin__ version of the variable is returned (which are set by gen_arg.py functions).
Michael Walshb1500152017-04-12 15:42:43 -05001821
Michael Walsh46fcecb2019-10-16 17:11:59 -05001822 If there is no global value associated with the variable, default is returned.
Michael Walshb1500152017-04-12 15:42:43 -05001823
Michael Walsh46fcecb2019-10-16 17:11:59 -05001824 This function is useful for other functions in setting default values for parameters.
Michael Walshb1500152017-04-12 15:42:43 -05001825
1826 Example use:
1827
1828 def my_func(quiet=None):
1829
1830 quiet = int(get_var_value(quiet, 0))
1831
1832 Example calls to my_func():
1833
Michael Walsh46fcecb2019-10-16 17:11:59 -05001834 In the following example, the caller is explicitly asking to have quiet be set to 1.
Michael Walshb1500152017-04-12 15:42:43 -05001835
1836 my_func(quiet=1)
1837
Michael Walsh46fcecb2019-10-16 17:11:59 -05001838 In the following example, quiet will be set to the global value of quiet, if defined, or to 0 (the
1839 default).
Michael Walshb1500152017-04-12 15:42:43 -05001840
1841 my_func()
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001842
Michael Walsh4dbb6002019-05-17 15:51:15 -05001843 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001844 var_value The value to be returned (if not equal to None).
1845 default The value that is returned if var_value is None and there is no
1846 corresponding global value defined.
1847 var_name The name of the variable whose value is to be returned. Under most
1848 circumstances, this value need not be provided. This function can figure
1849 out the name of the variable passed as var_value. One exception to this
1850 would be if this function is called directly from a .robot file.
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001851 """
1852
Michael Walshb1500152017-04-12 15:42:43 -05001853 if var_value is not None:
1854 return var_value
1855
1856 if var_name is None:
1857 var_name = get_arg_name(None, 1, 2)
1858
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001859 if robot_env:
Patrick Williams20f38712022-12-08 06:18:26 -06001860 var_value = BuiltIn().get_variable_value(
1861 "${" + var_name + "}", default
1862 )
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001863 else:
1864 var_value = getattr(__builtin__, var_name, default)
1865
1866 return var_value
1867
Michael Walsh2ee77cd2017-03-08 11:50:17 -06001868
Patrick Williams20f38712022-12-08 06:18:26 -06001869def get_stack_var(var_name, default="", init_stack_ix=2):
Michael Walsh052ff812018-05-18 16:09:09 -05001870 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001871 Starting with the caller's stack level, search upward in the call stack for a variable named var_name and
1872 return its value. If the variable cannot be found in the stack, attempt to get the global value. If the
Michael Walshc9a7ee72019-08-28 16:45:50 -05001873 variable still cannot be found, return default.
Michael Walsh052ff812018-05-18 16:09:09 -05001874
1875 Example code:
1876
1877 def func12():
1878 my_loc_var1 = get_stack_var('my_var1', "default value")
1879
1880 def func11():
1881 my_var1 = 11
1882 func12()
1883
Michael Walsh46fcecb2019-10-16 17:11:59 -05001884 In this example, get_stack_var will find the value of my_var1 in func11's stack and will therefore return
1885 the value 11. Therefore, my_loc_var1 would get set to 11.
Michael Walsh052ff812018-05-18 16:09:09 -05001886
1887 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001888 var_name The name of the variable to be searched for.
1889 default The value to return if the the variable cannot be found.
1890 init_stack_ix The initial stack index from which to begin the search. 0 would be the
1891 index of this func1tion ("get_stack_var"), 1 would be the index of the
1892 function calling this function, etc.
Michael Walsh052ff812018-05-18 16:09:09 -05001893 """
1894
Michael Walsh6f0362c2019-03-25 14:05:14 -05001895 work_around_inspect_stack_cwd_failure()
Michael Walshc9a7ee72019-08-28 16:45:50 -05001896 default = get_var_value(var_name=var_name, default=default)
Patrick Williams20f38712022-12-08 06:18:26 -06001897 return next(
1898 (
1899 frame[0].f_locals[var_name]
1900 for frame in inspect.stack()[init_stack_ix:]
1901 if var_name in frame[0].f_locals
1902 ),
1903 default,
1904 )
Michael Walsh052ff812018-05-18 16:09:09 -05001905
1906
Michael Walsh46fcecb2019-10-16 17:11:59 -05001907# hidden_text is a list of passwords which are to be replaced with asterisks by print functions defined in
1908# this module.
Michael Walsh82acf002017-05-04 14:33:05 -05001909hidden_text = []
1910# password_regex is created based on the contents of hidden_text.
1911password_regex = ""
1912
1913
Michael Walsh82acf002017-05-04 14:33:05 -05001914def register_passwords(*args):
Michael Walsh82acf002017-05-04 14:33:05 -05001915 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001916 Register one or more passwords which are to be hidden in output produced by the print functions in this
1917 module.
Michael Walsh82acf002017-05-04 14:33:05 -05001918
1919 Note: Blank password values are NOT registered. They are simply ignored.
1920
1921 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001922 args One or more password values. If a given password value is already
1923 registered, this function will simply do nothing.
Michael Walsh82acf002017-05-04 14:33:05 -05001924 """
1925
1926 global hidden_text
1927 global password_regex
1928
1929 for password in args:
1930 if password == "":
1931 break
1932 if password in hidden_text:
1933 break
1934
1935 # Place the password into the hidden_text list.
1936 hidden_text.append(password)
Michael Walsh46fcecb2019-10-16 17:11:59 -05001937 # Create a corresponding password regular expression. Escape regex special characters too.
Patrick Williams20f38712022-12-08 06:18:26 -06001938 password_regex = (
1939 "(" + "|".join([re.escape(x) for x in hidden_text]) + ")"
1940 )
Michael Walsh82acf002017-05-04 14:33:05 -05001941
Michael Walsh82acf002017-05-04 14:33:05 -05001942
Michael Walsh82acf002017-05-04 14:33:05 -05001943def replace_passwords(buffer):
Michael Walsh82acf002017-05-04 14:33:05 -05001944 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001945 Return the buffer but with all registered passwords replaced by a string of asterisks.
Michael Walsh82acf002017-05-04 14:33:05 -05001946
1947
1948 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001949 buffer The string to be returned but with passwords replaced.
Michael Walsh82acf002017-05-04 14:33:05 -05001950 """
1951
1952 global password_regex
1953
1954 if int(os.environ.get("DEBUG_SHOW_PASSWORDS", "0")):
1955 return buffer
1956
1957 if password_regex == "":
1958 # No passwords to replace.
1959 return buffer
1960
1961 return re.sub(password_regex, "********", buffer)
1962
Michael Walshfd2733c2017-11-13 11:36:20 -06001963
Patrick Williams20f38712022-12-08 06:18:26 -06001964def create_print_wrapper_funcs(
1965 func_names, stderr_func_names, replace_dict, func_prefix=""
1966):
Michael Walshfd2733c2017-11-13 11:36:20 -06001967 r"""
Michael Walsh46fcecb2019-10-16 17:11:59 -05001968 Generate code for print wrapper functions and return the generated code as a string.
Michael Walshfd2733c2017-11-13 11:36:20 -06001969
Michael Walsh46fcecb2019-10-16 17:11:59 -05001970 To illustrate, suppose there is a "print_foo_bar" function in the func_names list.
Michael Walshfd2733c2017-11-13 11:36:20 -06001971 This function will...
1972 - Expect that there is an sprint_foo_bar function already in existence.
Michael Walsh46fcecb2019-10-16 17:11:59 -05001973 - Create a print_foo_bar function which calls sprint_foo_bar and prints the result.
1974 - Create a qprint_foo_bar function which calls upon sprint_foo_bar only if global value quiet is 0.
1975 - Create a dprint_foo_bar function which calls upon sprint_foo_bar only if global value debug is 1.
Michael Walshfd2733c2017-11-13 11:36:20 -06001976
Michael Walsh46fcecb2019-10-16 17:11:59 -05001977 Also, code will be generated to define aliases for each function as well. Each alias will be created by
1978 replacing "print_" in the function name with "p" For example, the alias for print_foo_bar will be
1979 pfoo_bar.
Michael Walshfd2733c2017-11-13 11:36:20 -06001980
1981 Description of argument(s):
Michael Walsh46fcecb2019-10-16 17:11:59 -05001982 func_names A list of functions for which print wrapper function code is to be
1983 generated.
1984 stderr_func_names A list of functions whose generated code should print to stderr rather
1985 than to stdout.
1986 replace_dict Please see the create_func_def_string function in wrap_utils.py for
1987 details on this parameter. This parameter will be passed directly to
1988 create_func_def_string.
George Keishinge16f1582022-12-15 07:32:21 -06001989 func_prefix Prefix to be prepended to the generated function name.
Michael Walshfd2733c2017-11-13 11:36:20 -06001990 """
1991
1992 buffer = ""
1993
1994 for func_name in func_names:
1995 if func_name in stderr_func_names:
Patrick Williams20f38712022-12-08 06:18:26 -06001996 replace_dict["output_stream"] = "stderr"
Michael Walshfd2733c2017-11-13 11:36:20 -06001997 else:
Patrick Williams20f38712022-12-08 06:18:26 -06001998 replace_dict["output_stream"] = "stdout"
Michael Walshfd2733c2017-11-13 11:36:20 -06001999
2000 s_func_name = "s" + func_name
2001 q_func_name = "q" + func_name
2002 d_func_name = "d" + func_name
2003
Michael Walsh46fcecb2019-10-16 17:11:59 -05002004 # We don't want to try to redefine the "print" function, thus the following if statement.
Michael Walshfd2733c2017-11-13 11:36:20 -06002005 if func_name != "print":
Patrick Williams20f38712022-12-08 06:18:26 -06002006 func_def = create_func_def_string(
2007 s_func_name,
2008 func_prefix + func_name,
2009 print_func_template,
2010 replace_dict,
2011 )
Michael Walshfd2733c2017-11-13 11:36:20 -06002012 buffer += func_def
2013
Patrick Williams20f38712022-12-08 06:18:26 -06002014 func_def = create_func_def_string(
2015 s_func_name,
2016 func_prefix + "q" + func_name,
2017 qprint_func_template,
2018 replace_dict,
2019 )
Michael Walshfd2733c2017-11-13 11:36:20 -06002020 buffer += func_def
2021
Patrick Williams20f38712022-12-08 06:18:26 -06002022 func_def = create_func_def_string(
2023 s_func_name,
2024 func_prefix + "d" + func_name,
2025 dprint_func_template,
2026 replace_dict,
2027 )
Michael Walshfd2733c2017-11-13 11:36:20 -06002028 buffer += func_def
2029
Patrick Williams20f38712022-12-08 06:18:26 -06002030 func_def = create_func_def_string(
2031 s_func_name,
2032 func_prefix + "l" + func_name,
2033 lprint_func_template,
2034 replace_dict,
2035 )
Michael Walsh168eb0f2017-12-01 15:35:32 -06002036 buffer += func_def
2037
Michael Walshfd2733c2017-11-13 11:36:20 -06002038 # Create abbreviated aliases (e.g. spvar is an alias for sprint_var).
2039 alias = re.sub("print_", "p", func_name)
2040 alias = re.sub("print", "p", alias)
Patrick Williams20f38712022-12-08 06:18:26 -06002041 prefixes = [
2042 func_prefix + "",
2043 "s",
2044 func_prefix + "q",
2045 func_prefix + "d",
2046 func_prefix + "l",
2047 ]
Michael Walshfd2733c2017-11-13 11:36:20 -06002048 for prefix in prefixes:
2049 if alias == "p":
2050 continue
2051 func_def = prefix + alias + " = " + prefix + func_name
2052 buffer += func_def + "\n"
2053
2054 return buffer
Michael Walsh82acf002017-05-04 14:33:05 -05002055
2056
Michael Walsh46fcecb2019-10-16 17:11:59 -05002057# In the following section of code, we will dynamically create print versions for each of the sprint
2058# functions defined above. So, for example, where we have an sprint_time() function defined above that
2059# returns the time to the caller in a string, we will create a corresponding print_time() function that will
2060# print that string directly to stdout.
Michael Walshde791732016-09-06 14:25:24 -05002061
Michael Walsh46fcecb2019-10-16 17:11:59 -05002062# It can be complicated to follow what's being created below. Here is an example of the print_time()
2063# function that will be created:
Michael Walshde791732016-09-06 14:25:24 -05002064
Michael Walshfd2733c2017-11-13 11:36:20 -06002065# def print_time(buffer=''):
Michael Walsh61c12982019-03-28 12:38:01 -05002066# gp_print(replace_passwords(sprint_time(buffer=buffer)), stream='stdout')
2067
Michael Walsh46fcecb2019-10-16 17:11:59 -05002068# For each print function defined below, there will also be a qprint, a dprint and an lprint version defined
2069# (e.g. qprint_time, dprint_time, lprint_time).
Michael Walsh61c12982019-03-28 12:38:01 -05002070
Michael Walsh46fcecb2019-10-16 17:11:59 -05002071# The q version of each print function will only print if the quiet variable is 0.
2072# The d version of each print function will only print if the debug variable is 1.
2073# The l version of each print function will print the contents as log data. For conventional programs, this
2074# means use of the logging module. For robot programs it means use of the BuiltIn().log() function.
Michael Walshde791732016-09-06 14:25:24 -05002075
Michael Walshfd2733c2017-11-13 11:36:20 -06002076# Templates for the various print wrapper functions.
Patrick Williams20f38712022-12-08 06:18:26 -06002077print_func_template = [
2078 " <mod_qualifier>gp_print(<mod_qualifier>replace_passwords("
2079 + "<call_line>), stream='<output_stream>')"
2080]
Michael Walshfd2733c2017-11-13 11:36:20 -06002081
Patrick Williams20f38712022-12-08 06:18:26 -06002082qprint_func_template = [
2083 ' quiet = <mod_qualifier>get_stack_var("quiet", 0)',
2084 " if int(quiet): return",
2085] + print_func_template
Michael Walshfd2733c2017-11-13 11:36:20 -06002086
Patrick Williams20f38712022-12-08 06:18:26 -06002087dprint_func_template = [
2088 ' debug = <mod_qualifier>get_stack_var("debug", 0)',
2089 " if not int(debug): return",
2090] + print_func_template
Michael Walshfd2733c2017-11-13 11:36:20 -06002091
Patrick Williams20f38712022-12-08 06:18:26 -06002092lprint_func_template = [
2093 " <mod_qualifier>set_last_seconds_ix(<mod_qualifier>"
2094 + "lprint_last_seconds_ix())",
2095 " <mod_qualifier>gp_log(<mod_qualifier>replace_passwords"
2096 + "(<call_line>))",
2097 " <mod_qualifier>set_last_seconds_ix(<mod_qualifier>"
2098 + "standard_print_last_seconds_ix())",
2099]
Michael Walsh168eb0f2017-12-01 15:35:32 -06002100
Patrick Williams20f38712022-12-08 06:18:26 -06002101replace_dict = {"output_stream": "stdout", "mod_qualifier": ""}
Michael Walshfd2733c2017-11-13 11:36:20 -06002102
Michael Walsh61c12982019-03-28 12:38:01 -05002103gp_debug_print("robot_env: " + str(robot_env) + "\n")
Michael Walshde791732016-09-06 14:25:24 -05002104
Michael Walsh46fcecb2019-10-16 17:11:59 -05002105# func_names contains a list of all print functions which should be created from their sprint counterparts.
Patrick Williams20f38712022-12-08 06:18:26 -06002106func_names = [
2107 "print_time",
2108 "print_timen",
2109 "print_error",
2110 "print_varx",
2111 "print_var",
2112 "print_vars",
2113 "print_dashes",
2114 "indent",
2115 "print_call_stack",
2116 "print_func_name",
2117 "print_executing",
2118 "print_pgm_header",
2119 "print_issuing",
2120 "print_pgm_footer",
2121 "print_file",
2122 "print_error_report",
2123 "print",
2124 "printn",
2125]
Michael Walshde791732016-09-06 14:25:24 -05002126
Michael Walsh46fcecb2019-10-16 17:11:59 -05002127# stderr_func_names is a list of functions whose output should go to stderr rather than stdout.
Patrick Williams20f38712022-12-08 06:18:26 -06002128stderr_func_names = ["print_error", "print_error_report"]
Michael Walshde791732016-09-06 14:25:24 -05002129
Patrick Williams20f38712022-12-08 06:18:26 -06002130func_defs = create_print_wrapper_funcs(
2131 func_names, stderr_func_names, replace_dict
2132)
Michael Walshfd2733c2017-11-13 11:36:20 -06002133gp_debug_print(func_defs)
2134exec(func_defs)