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