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