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