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 BaseException: 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 "[ ]*\\(.*" 380 called_func_name = re.sub(called_func_name_regex, "\\4", composite_line) 381 arg_list_etc = "(" + re.sub(pre_args_regex, "", composite_line) 382 if local_debug: 383 print_varx("aliases", aliases, 0, debug_indent) 384 print_varx("import_name_regex", import_name_regex, 0, debug_indent) 385 print_varx("func_name_regex", func_name_regex, 0, debug_indent) 386 print_varx("pre_args_regex", pre_args_regex, 0, debug_indent) 387 print_varx("start_line_ix", start_line_ix, 0, debug_indent) 388 print_varx("end_line_ix", end_line_ix, 0, debug_indent) 389 print_varx("composite_line", composite_line, 0, debug_indent) 390 print_varx("lvalue_regex", lvalue_regex, 0, debug_indent) 391 print_varx("lvalue_string", lvalue_string, 0, debug_indent) 392 print_varx("lvalues", lvalues, 0, debug_indent) 393 print_varx("called_func_name_regex", called_func_name_regex, 0, 394 debug_indent) 395 print_varx("called_func_name", called_func_name, 0, debug_indent) 396 print_varx("arg_list_etc", arg_list_etc, 0, debug_indent) 397 398 # Parse arg list... 399 # Initialize... 400 nest_level = -1 401 arg_ix = 0 402 args_list = [""] 403 for ix in range(0, len(arg_list_etc)): 404 char = arg_list_etc[ix] 405 # Set the nest_level based on whether we've encounted a parenthesis. 406 if char == "(": 407 nest_level += 1 408 if nest_level == 0: 409 continue 410 elif char == ")": 411 nest_level -= 1 412 if nest_level < 0: 413 break 414 415 # If we reach a comma at base nest level, we are done processing an 416 # argument so we increment arg_ix and initialize a new args_list entry. 417 if char == "," and nest_level == 0: 418 arg_ix += 1 419 args_list.append("") 420 continue 421 422 # For any other character, we append it it to the current arg list 423 # entry. 424 args_list[arg_ix] += char 425 426 # Trim whitespace from each list entry. 427 args_list = [arg.strip() for arg in args_list] 428 429 if arg_num < 0: 430 if abs(arg_num) > len(lvalues): 431 argument = lvalues 432 else: 433 argument = lvalues[arg_num] 434 elif arg_num == 0: 435 argument = called_func_name 436 else: 437 if arg_num > len(args_list): 438 argument = args_list 439 else: 440 argument = args_list[arg_num - 1] 441 442 if local_debug: 443 print_varx("args_list", args_list, 0, debug_indent) 444 print_varx("argument", argument, 0, debug_indent) 445 print_dashes(0, 120) 446 447 return argument 448 449 450def sprint_time(buffer=""): 451 r""" 452 Return the time in the following format. 453 454 Example: 455 456 The following python code... 457 458 sys.stdout.write(sprint_time()) 459 sys.stdout.write("Hi.\n") 460 461 Will result in the following type of output: 462 463 #(CDT) 2016/07/08 15:25:35 - Hi. 464 465 Example: 466 467 The following python code... 468 469 sys.stdout.write(sprint_time("Hi.\n")) 470 471 Will result in the following type of output: 472 473 #(CDT) 2016/08/03 17:12:05 - Hi. 474 475 The following environment variables will affect the formatting as 476 described: 477 NANOSECONDS This will cause the time stamps to be 478 precise to the microsecond (Yes, it 479 probably should have been named 480 MICROSECONDS but the convention was set 481 long ago so we're sticking with it). 482 Example of the output when environment 483 variable NANOSECONDS=1. 484 485 #(CDT) 2016/08/03 17:16:25.510469 - Hi. 486 487 SHOW_ELAPSED_TIME This will cause the elapsed time to be 488 included in the output. This is the 489 amount of time that has elapsed since the 490 last time this function was called. The 491 precision of the elapsed time field is 492 also affected by the value of the 493 NANOSECONDS environment variable. Example 494 of the output when environment variable 495 NANOSECONDS=0 and SHOW_ELAPSED_TIME=1. 496 497 #(CDT) 2016/08/03 17:17:40 - 0 - Hi. 498 499 Example of the output when environment variable NANOSECONDS=1 and 500 SHOW_ELAPSED_TIME=1. 501 502 #(CDT) 2016/08/03 17:18:47.317339 - 0.000046 - Hi. 503 504 Description of arguments. 505 buffer This will be appended to the formatted 506 time string. 507 """ 508 509 global NANOSECONDS 510 global SHOW_ELAPSED_TIME 511 global sprint_time_last_seconds 512 513 seconds = time.time() 514 loc_time = time.localtime(seconds) 515 nanoseconds = "%0.6f" % seconds 516 pos = nanoseconds.find(".") 517 nanoseconds = nanoseconds[pos:] 518 519 time_string = time.strftime("#(%Z) %Y/%m/%d %H:%M:%S", loc_time) 520 if NANOSECONDS == "1": 521 time_string = time_string + nanoseconds 522 523 if SHOW_ELAPSED_TIME == "1": 524 cur_time_seconds = seconds 525 math_string = "%9.9f" % cur_time_seconds + " - " + "%9.9f" % \ 526 sprint_time_last_seconds 527 elapsed_seconds = eval(math_string) 528 if NANOSECONDS == "1": 529 elapsed_seconds = "%11.6f" % elapsed_seconds 530 else: 531 elapsed_seconds = "%4i" % elapsed_seconds 532 sprint_time_last_seconds = cur_time_seconds 533 time_string = time_string + " - " + elapsed_seconds 534 535 return time_string + " - " + buffer 536 537 538def sprint_timen(buffer=""): 539 r""" 540 Append a line feed to the buffer, pass it to sprint_time and return the 541 result. 542 """ 543 544 return sprint_time(buffer + "\n") 545 546 547def sprint_error(buffer=""): 548 r""" 549 Return a standardized error string. This includes: 550 - A time stamp 551 - The "**ERROR**" string 552 - The caller's buffer string. 553 554 Example: 555 556 The following python code... 557 558 print(sprint_error("Oops.\n")) 559 560 Will result in the following type of output: 561 562 #(CDT) 2016/08/03 17:12:05 - **ERROR** Oops. 563 564 Description of arguments. 565 buffer This will be appended to the formatted 566 error string. 567 """ 568 569 return sprint_time() + "**ERROR** " + buffer 570 571 572# Implement "constants" with functions. 573def digit_length_in_bits(): 574 r""" 575 Return the digit length in bits. 576 """ 577 578 return 4 579 580 581def word_length_in_digits(): 582 r""" 583 Return the word length in digits. 584 """ 585 586 return 8 587 588 589def bit_length(number): 590 r""" 591 Return the bit length of the number. 592 593 Description of argument(s): 594 number The number to be analyzed. 595 """ 596 597 if number < 0: 598 # Convert negative numbers to positive and subtract one. The 599 # following example illustrates the reason for this: 600 # Consider a single nibble whose signed values can range from -8 to 7 601 # (0x8 to 0x7). A value of 0x7 equals 0b0111. Therefore, its length 602 # in bits is 3. Since the negative bit (i.e. 0b1000) is not set, the 603 # value 7 clearly will fit in one nibble. With -8 = 0x8 = 0b1000, you 604 # have the smallest negative value that will fit. Note that it 605 # requires 3 bits of 0. So by converting a number value of -8 to a 606 # working_number of 7, this function can accurately calculate the 607 # number of bits and therefore nibbles required to represent the 608 # number in print. 609 working_number = abs(number) - 1 610 else: 611 working_number = number 612 613 # Handle the special case of the number 0. 614 if working_number == 0: 615 return 0 616 617 return len(bin(working_number)) - 2 618 619 620def get_req_num_hex_digits(number): 621 r""" 622 Return the required number of hex digits required to display the given 623 number. 624 625 The returned value will always be rounded up to the nearest multiple of 8. 626 627 Description of argument(s): 628 number The number to be analyzed. 629 """ 630 631 if number < 0: 632 # Convert negative numbers to positive and subtract one. The 633 # following example illustrates the reason for this: 634 # Consider a single nibble whose signed values can range from -8 to 7 635 # (0x8 to 0x7). A value of 0x7 equals 0b0111. Therefore, its length 636 # in bits is 3. Since the negative bit (i.e. 0b1000) is not set, the 637 # value 7 clearly will fit in one nibble. With -8 = 0x8 = 0b1000, you 638 # have the smallest negative value that will fit. Note that it 639 # requires 3 bits of 0. So by converting a number value of -8 to a 640 # working_number of 7, this function can accurately calculate the 641 # number of bits and therefore nibbles required to represent the 642 # number in print. 643 working_number = abs(number) - 1 644 else: 645 working_number = number 646 647 # Handle the special case of the number 0. 648 if working_number == 0: 649 return word_length_in_digits() 650 651 num_length_in_bits = bit_length(working_number) 652 num_hex_digits, remainder = divmod(num_length_in_bits, 653 digit_length_in_bits()) 654 if remainder > 0: 655 # Example: the number 7 requires 3 bits. The divmod above produces, 656 # 0 with remainder of 3. So because we have a remainder, we increment 657 # num_hex_digits from 0 to 1. 658 num_hex_digits += 1 659 660 # Check to see whether the negative bit is set. This is the left-most 661 # bit in the highest order digit. 662 negative_mask = 2 ** (num_hex_digits * 4 - 1) 663 if working_number & negative_mask: 664 # If a number that is intended to be positive has its negative bit 665 # on, an additional digit will be required to represent it correctly 666 # in print. 667 num_hex_digits += 1 668 669 num_words, remainder = divmod(num_hex_digits, word_length_in_digits()) 670 if remainder > 0 or num_words == 0: 671 num_words += 1 672 673 # Round up to the next word length in digits. 674 return num_words * word_length_in_digits() 675 676 677def dft_num_hex_digits(): 678 r""" 679 Return the default number of hex digits to be used to represent a hex 680 number in print. 681 682 The value returned is a function of sys.maxsize. 683 """ 684 685 global _gen_print_dft_num_hex_digits_ 686 try: 687 return _gen_print_dft_num_hex_digits_ 688 except NameError: 689 _gen_print_dft_num_hex_digits_ = get_req_num_hex_digits(sys.maxsize) 690 return _gen_print_dft_num_hex_digits_ 691 692 693def sprint_varx(var_name, 694 var_value, 695 hex=0, 696 loc_col1_indent=col1_indent, 697 loc_col1_width=col1_width, 698 trailing_char="\n", 699 key_list=None): 700 r""" 701 Print the var name/value passed to it. If the caller lets loc_col1_width 702 default, the printing lines up nicely with output generated by the 703 print_time functions. 704 705 Note that the sprint_var function (defined below) can be used to call this 706 function so that the programmer does not need to pass the var_name. 707 sprint_var will figure out the var_name. The sprint_var function is the 708 one that would normally be used by the general user. 709 710 For example, the following python code: 711 712 first_name = "Mike" 713 print_time("Doing this...\n") 714 print_varx("first_name", first_name) 715 print_time("Doing that...\n") 716 717 Will generate output like this: 718 719 #(CDT) 2016/08/10 17:34:42.847374 - 0.001285 - Doing this... 720 first_name: Mike 721 #(CDT) 2016/08/10 17:34:42.847510 - 0.000136 - Doing that... 722 723 This function recognizes several complex types of data such as dict, list 724 or tuple. 725 726 For example, the following python code: 727 728 my_dict = dict(one=1, two=2, three=3) 729 print_var(my_dict) 730 731 Will generate the following output: 732 733 my_dict: 734 my_dict[three]: 3 735 my_dict[two]: 2 736 my_dict[one]: 1 737 738 Description of arguments. 739 var_name The name of the variable to be printed. 740 var_value The value of the variable to be printed. 741 hex This indicates that the value should be 742 printed in hex format. It is the user's 743 responsibility to ensure that a var_value 744 contains a valid hex number. For string 745 var_values, this will be interpreted as 746 show_blanks which means that blank values 747 will be printed as "<blank>". For dict 748 var_values, this will be interpreted as 749 terse format where keys are not repeated 750 in the output. 751 loc_col1_indent The number of spaces to indent the output. 752 loc_col1_width The width of the output column containing 753 the variable name. The default value of 754 this is adjusted so that the var_value 755 lines up with text printed via the 756 print_time function. 757 trailing_char The character to be used at the end of the 758 returned string. The default value is a 759 line feed. 760 key_list A list of which dictionary keys should be 761 printed. All others keys will be skipped. 762 Each value in key_list will be regarded 763 as a regular expression and it will be 764 regarded as anchored to the beginning and 765 ends of the dictionary key being 766 referenced. For example if key_list is 767 ["one", "two"], the resulting regex used 768 will be "^one|two$", i.e. only keys "one" 769 and "two" from the var_value dictionary 770 will be printed. As another example, if 771 the caller were to specify a key_list of 772 ["one.*"], then only dictionary keys whose 773 names begin with "one" will be printed. 774 Note: This argument pertains only to 775 var_values which are dictionaries. 776 """ 777 778 # Determine the type 779 if type(var_value) in (int, long, float, bool, str, unicode) \ 780 or var_value is None: 781 # The data type is simple in the sense that it has no subordinate 782 # parts. 783 # Adjust loc_col1_width. 784 loc_col1_width = loc_col1_width - loc_col1_indent 785 # See if the user wants the output in hex format. 786 if hex: 787 if type(var_value) not in (int, long): 788 value_format = "%s" 789 if var_value == "": 790 var_value = "<blank>" 791 else: 792 num_hex_digits = max(dft_num_hex_digits(), 793 get_req_num_hex_digits(var_value)) 794 # Convert a negative number to its positive twos complement 795 # for proper printing. For example, instead of printing -1 as 796 # "0x-000000000000001" it will be printed as 797 # "0xffffffffffffffff". 798 var_value = var_value & (2 ** (num_hex_digits * 4) - 1) 799 value_format = "0x%0" + str(num_hex_digits) + "x" 800 else: 801 value_format = "%s" 802 format_string = "%" + str(loc_col1_indent) + "s%-" \ 803 + str(loc_col1_width) + "s" + value_format + trailing_char 804 if value_format == "0x%08x": 805 return format_string % ("", str(var_name) + ":", 806 var_value & 0xffffffff) 807 else: 808 return format_string % ("", str(var_name) + ":", var_value) 809 elif isinstance(var_value, type): 810 return sprint_varx(var_name, str(var_value).split("'")[1], hex, 811 loc_col1_indent, loc_col1_width, trailing_char, 812 key_list) 813 else: 814 # The data type is complex in the sense that it has subordinate parts. 815 format_string = "%" + str(loc_col1_indent) + "s%s\n" 816 buffer = format_string % ("", var_name + ":") 817 loc_col1_indent += 2 818 try: 819 length = len(var_value) 820 except TypeError: 821 length = 0 822 ix = 0 823 loc_trailing_char = "\n" 824 type_is_dict = 0 825 if isinstance(var_value, dict): 826 type_is_dict = 1 827 try: 828 if isinstance(var_value, collections.OrderedDict): 829 type_is_dict = 1 830 except AttributeError: 831 pass 832 try: 833 if isinstance(var_value, DotDict): 834 type_is_dict = 1 835 except NameError: 836 pass 837 try: 838 if isinstance(var_value, NormalizedDict): 839 type_is_dict = 1 840 except NameError: 841 pass 842 if type_is_dict: 843 for key, value in var_value.iteritems(): 844 if key_list is not None: 845 key_list_regex = "^" + "|".join(key_list) + "$" 846 if not re.match(key_list_regex, key): 847 continue 848 ix += 1 849 if ix == length: 850 loc_trailing_char = trailing_char 851 if hex: 852 # Since hex is being used as a format type, we want it 853 # turned off when processing integer dictionary values so 854 # it is not interpreted as a hex indicator. 855 loc_hex = not (isinstance(value, int)) 856 buffer += sprint_varx("[" + key + "]", value, 857 loc_hex, loc_col1_indent, 858 loc_col1_width, 859 loc_trailing_char, 860 key_list) 861 else: 862 buffer += sprint_varx(var_name + "[" + str(key) + "]", 863 value, hex, loc_col1_indent, 864 loc_col1_width, loc_trailing_char, 865 key_list) 866 elif type(var_value) in (list, tuple, set): 867 for key, value in enumerate(var_value): 868 ix += 1 869 if ix == length: 870 loc_trailing_char = trailing_char 871 buffer += sprint_varx(var_name + "[" + str(key) + "]", value, 872 hex, loc_col1_indent, loc_col1_width, 873 loc_trailing_char, key_list) 874 elif isinstance(var_value, argparse.Namespace): 875 for key in var_value.__dict__: 876 ix += 1 877 if ix == length: 878 loc_trailing_char = trailing_char 879 cmd_buf = "buffer += sprint_varx(var_name + \".\" + str(key)" \ 880 + ", var_value." + key + ", hex, loc_col1_indent," \ 881 + " loc_col1_width, loc_trailing_char, key_list)" 882 exec(cmd_buf) 883 else: 884 var_type = type(var_value).__name__ 885 func_name = sys._getframe().f_code.co_name 886 var_value = "<" + var_type + " type not supported by " + \ 887 func_name + "()>" 888 value_format = "%s" 889 loc_col1_indent -= 2 890 # Adjust loc_col1_width. 891 loc_col1_width = loc_col1_width - loc_col1_indent 892 format_string = "%" + str(loc_col1_indent) + "s%-" \ 893 + str(loc_col1_width) + "s" + value_format + trailing_char 894 return format_string % ("", str(var_name) + ":", var_value) 895 896 return buffer 897 898 return "" 899 900 901def sprint_var(var_value, 902 hex=0, 903 loc_col1_indent=col1_indent, 904 loc_col1_width=col1_width, 905 trailing_char="\n", 906 key_list=None): 907 r""" 908 Figure out the name of the first argument for you and then call 909 sprint_varx with it. Therefore, the following 2 calls are equivalent: 910 sprint_varx("var1", var1) 911 sprint_var(var1) 912 """ 913 914 # Get the name of the first variable passed to this function. 915 stack_frame = 2 916 caller_func_name = sprint_func_name(2) 917 if caller_func_name.endswith("print_var"): 918 stack_frame += 1 919 var_name = get_arg_name(None, 1, stack_frame) 920 return sprint_varx(var_name, var_value=var_value, hex=hex, 921 loc_col1_indent=loc_col1_indent, 922 loc_col1_width=loc_col1_width, 923 trailing_char=trailing_char, 924 key_list=key_list) 925 926 927def sprint_vars(*args): 928 r""" 929 Sprint the values of one or more variables. 930 931 Description of args: 932 args: 933 If the first argument is an integer, it will be interpreted to be the 934 "indent" value. 935 If the second argument is an integer, it will be interpreted to be the 936 "col1_width" value. 937 If the third argument is an integer, it will be interpreted to be the 938 "hex" value. 939 All remaining parms are considered variable names which are to be 940 sprinted. 941 """ 942 943 if len(args) == 0: 944 return 945 946 # Get the name of the first variable passed to this function. 947 stack_frame = 2 948 caller_func_name = sprint_func_name(2) 949 if caller_func_name.endswith("print_vars"): 950 stack_frame += 1 951 952 parm_num = 1 953 954 # Create list from args (which is a tuple) so that it can be modified. 955 args_list = list(args) 956 957 var_name = get_arg_name(None, parm_num, stack_frame) 958 # See if parm 1 is to be interpreted as "indent". 959 try: 960 if isinstance(int(var_name), int): 961 indent = int(var_name) 962 args_list.pop(0) 963 parm_num += 1 964 except ValueError: 965 indent = 0 966 967 var_name = get_arg_name(None, parm_num, stack_frame) 968 # See if parm 1 is to be interpreted as "col1_width". 969 try: 970 if isinstance(int(var_name), int): 971 loc_col1_width = int(var_name) 972 args_list.pop(0) 973 parm_num += 1 974 except ValueError: 975 loc_col1_width = col1_width 976 977 var_name = get_arg_name(None, parm_num, stack_frame) 978 # See if parm 1 is to be interpreted as "hex". 979 try: 980 if isinstance(int(var_name), int): 981 hex = int(var_name) 982 args_list.pop(0) 983 parm_num += 1 984 except ValueError: 985 hex = 0 986 987 buffer = "" 988 for var_value in args_list: 989 var_name = get_arg_name(None, parm_num, stack_frame) 990 buffer += sprint_varx(var_name, var_value, hex, indent, loc_col1_width) 991 parm_num += 1 992 993 return buffer 994 995 996def sprint_dashes(indent=col1_indent, 997 width=80, 998 line_feed=1, 999 char="-"): 1000 r""" 1001 Return a string of dashes to the caller. 1002 1003 Description of arguments: 1004 indent The number of characters to indent the 1005 output. 1006 width The width of the string of dashes. 1007 line_feed Indicates whether the output should end 1008 with a line feed. 1009 char The character to be repeated in the output 1010 string. 1011 """ 1012 1013 width = int(width) 1014 buffer = " " * int(indent) + char * width 1015 if line_feed: 1016 buffer += "\n" 1017 1018 return buffer 1019 1020 1021def sindent(text="", 1022 indent=0): 1023 r""" 1024 Pre-pend the specified number of characters to the text string (i.e. 1025 indent it) and return it. 1026 1027 Description of arguments: 1028 text The string to be indented. 1029 indent The number of characters to indent the 1030 string. 1031 """ 1032 1033 format_string = "%" + str(indent) + "s%s" 1034 buffer = format_string % ("", text) 1035 1036 return buffer 1037 1038 1039def sprint_call_stack(indent=0, 1040 stack_frame_ix=0): 1041 r""" 1042 Return a call stack report for the given point in the program with line 1043 numbers, function names and function parameters and arguments. 1044 1045 Sample output: 1046 1047 ------------------------------------------------------------------------- 1048 Python function call stack 1049 1050 Line # Function name and arguments 1051 ------ ------------------------------------------------------------------ 1052 424 sprint_call_stack () 1053 4 print_call_stack () 1054 31 func1 (last_name = 'walsh', first_name = 'mikey') 1055 59 /tmp/scr5.py 1056 ------------------------------------------------------------------------- 1057 1058 Description of arguments: 1059 indent The number of characters to indent each 1060 line of output. 1061 stack_frame_ix The index of the first stack frame which 1062 is to be returned. 1063 """ 1064 1065 buffer = "" 1066 buffer += sprint_dashes(indent) 1067 buffer += sindent("Python function call stack\n\n", indent) 1068 buffer += sindent("Line # Function name and arguments\n", indent) 1069 buffer += sprint_dashes(indent, 6, 0) + " " + sprint_dashes(0, 73) 1070 1071 # Grab the current program stack. 1072 current_stack = inspect.stack() 1073 1074 # Process each frame in turn. 1075 format_string = "%6s %s\n" 1076 ix = 0 1077 for stack_frame in current_stack: 1078 if ix < stack_frame_ix: 1079 ix += 1 1080 continue 1081 # I want the line number shown to be the line where you find the line 1082 # shown. 1083 try: 1084 line_num = str(current_stack[ix + 1][2]) 1085 except IndexError: 1086 line_num = "" 1087 func_name = str(stack_frame[3]) 1088 if func_name == "?": 1089 # "?" is the name used when code is not in a function. 1090 func_name = "(none)" 1091 1092 if func_name == "<module>": 1093 # If the func_name is the "main" program, we simply get the 1094 # command line call string. 1095 func_and_args = ' '.join(sys.argv) 1096 else: 1097 # Get the program arguments. 1098 arg_vals = inspect.getargvalues(stack_frame[0]) 1099 function_parms = arg_vals[0] 1100 frame_locals = arg_vals[3] 1101 1102 args_list = [] 1103 for arg_name in function_parms: 1104 # Get the arg value from frame locals. 1105 arg_value = frame_locals[arg_name] 1106 args_list.append(arg_name + " = " + repr(arg_value)) 1107 args_str = "(" + ', '.join(map(str, args_list)) + ")" 1108 1109 # Now we need to print this in a nicely-wrapped way. 1110 func_and_args = func_name + " " + args_str 1111 1112 buffer += sindent(format_string % (line_num, func_and_args), indent) 1113 ix += 1 1114 1115 buffer += sprint_dashes(indent) 1116 1117 return buffer 1118 1119 1120def sprint_executing(stack_frame_ix=None): 1121 r""" 1122 Print a line indicating what function is executing and with what parameter 1123 values. This is useful for debugging. 1124 1125 Sample output: 1126 1127 #(CDT) 2016/08/25 17:54:27 - Executing: func1 (x = 1) 1128 1129 Description of arguments: 1130 stack_frame_ix The index of the stack frame whose 1131 function info should be returned. If the 1132 caller does not specify a value, this 1133 function will set the value to 1 which is 1134 the index of the caller's stack frame. If 1135 the caller is the wrapper function 1136 "print_executing", this function will bump 1137 it up by 1. 1138 """ 1139 1140 # If user wants default stack_frame_ix. 1141 if stack_frame_ix is None: 1142 func_name = sys._getframe().f_code.co_name 1143 caller_func_name = sys._getframe(1).f_code.co_name 1144 if caller_func_name.endswith(func_name[1:]): 1145 stack_frame_ix = 2 1146 else: 1147 stack_frame_ix = 1 1148 1149 stack_frame = inspect.stack()[stack_frame_ix] 1150 1151 func_name = str(stack_frame[3]) 1152 if func_name == "?": 1153 # "?" is the name used when code is not in a function. 1154 func_name = "(none)" 1155 1156 if func_name == "<module>": 1157 # If the func_name is the "main" program, we simply get the command 1158 # line call string. 1159 func_and_args = ' '.join(sys.argv) 1160 else: 1161 # Get the program arguments. 1162 arg_vals = inspect.getargvalues(stack_frame[0]) 1163 function_parms = arg_vals[0] 1164 frame_locals = arg_vals[3] 1165 1166 args_list = [] 1167 for arg_name in function_parms: 1168 # Get the arg value from frame locals. 1169 arg_value = frame_locals[arg_name] 1170 args_list.append(arg_name + " = " + repr(arg_value)) 1171 args_str = "(" + ', '.join(map(str, args_list)) + ")" 1172 1173 # Now we need to print this in a nicely-wrapped way. 1174 func_and_args = func_name + " " + args_str 1175 1176 return sprint_time() + "Executing: " + func_and_args + "\n" 1177 1178 1179def sprint_pgm_header(indent=0, 1180 linefeed=1): 1181 r""" 1182 Return a standardized header that programs should print at the beginning 1183 of the run. It includes useful information like command line, pid, 1184 userid, program parameters, etc. 1185 1186 Description of arguments: 1187 indent The number of characters to indent each 1188 line of output. 1189 linefeed Indicates whether a line feed be included 1190 at the beginning and end of the report. 1191 """ 1192 1193 loc_col1_width = col1_width + indent 1194 1195 buffer = "" 1196 if linefeed: 1197 buffer = "\n" 1198 1199 if robot_env: 1200 suite_name = BuiltIn().get_variable_value("${suite_name}") 1201 buffer += sindent(sprint_time("Running test suite \"" + suite_name 1202 + "\".\n"), indent) 1203 1204 buffer += sindent(sprint_time() + "Running " + pgm_name + ".\n", indent) 1205 buffer += sindent(sprint_time() + "Program parameter values, etc.:\n\n", 1206 indent) 1207 buffer += sprint_varx("command_line", ' '.join(sys.argv), 0, indent, 1208 loc_col1_width) 1209 # We want the output to show a customized name for the pid and pgid but 1210 # we want it to look like a valid variable name. Therefore, we'll use 1211 # pgm_name_var_name which was set when this module was imported. 1212 buffer += sprint_varx(pgm_name_var_name + "_pid", os.getpid(), 0, indent, 1213 loc_col1_width) 1214 buffer += sprint_varx(pgm_name_var_name + "_pgid", os.getpgrp(), 0, indent, 1215 loc_col1_width) 1216 userid_num = str(os.geteuid()) 1217 try: 1218 username = os.getlogin() 1219 except OSError: 1220 if userid_num == "0": 1221 username = "root" 1222 else: 1223 username = "?" 1224 buffer += sprint_varx("uid", userid_num + " (" + username 1225 + ")", 0, indent, loc_col1_width) 1226 buffer += sprint_varx("gid", str(os.getgid()) + " (" 1227 + str(grp.getgrgid(os.getgid()).gr_name) + ")", 0, 1228 indent, loc_col1_width) 1229 buffer += sprint_varx("host_name", socket.gethostname(), 0, indent, 1230 loc_col1_width) 1231 try: 1232 DISPLAY = os.environ['DISPLAY'] 1233 except KeyError: 1234 DISPLAY = "" 1235 buffer += sprint_varx("DISPLAY", DISPLAY, 0, indent, 1236 loc_col1_width) 1237 # I want to add code to print caller's parms. 1238 1239 # __builtin__.arg_obj is created by the get_arg module function, 1240 # gen_get_options. 1241 try: 1242 buffer += ga.sprint_args(__builtin__.arg_obj, indent) 1243 except AttributeError: 1244 pass 1245 1246 if robot_env: 1247 # Get value of global parm_list. 1248 parm_list = BuiltIn().get_variable_value("${parm_list}") 1249 1250 for parm in parm_list: 1251 parm_value = BuiltIn().get_variable_value("${" + parm + "}") 1252 buffer += sprint_varx(parm, parm_value, 0, indent, loc_col1_width) 1253 1254 # Setting global program_pid. 1255 BuiltIn().set_global_variable("${program_pid}", os.getpid()) 1256 1257 if linefeed: 1258 buffer += "\n" 1259 1260 return buffer 1261 1262 1263def sprint_error_report(error_text="\n", 1264 indent=2, 1265 format=None): 1266 r""" 1267 Return a string with a standardized report which includes the caller's 1268 error text, the call stack and the program header. 1269 1270 Description of args: 1271 error_text The error text to be included in the 1272 report. The caller should include any 1273 needed linefeeds. 1274 indent The number of characters to indent each 1275 line of output. 1276 format Long or short format. Long includes 1277 extras like lines of dashes, call stack, 1278 etc. 1279 """ 1280 1281 # Process input. 1282 indent = int(indent) 1283 if format is None: 1284 if robot_env: 1285 format = 'short' 1286 else: 1287 format = 'long' 1288 error_text = error_text.rstrip('\n') + '\n' 1289 1290 if format == 'short': 1291 return sprint_error(error_text) 1292 1293 buffer = "" 1294 buffer += sprint_dashes(width=120, char="=") 1295 buffer += sprint_error(error_text) 1296 buffer += "\n" 1297 # Calling sprint_call_stack with stack_frame_ix of 0 causes it to show 1298 # itself and this function in the call stack. This is not helpful to a 1299 # debugger and is therefore clutter. We will adjust the stack_frame_ix to 1300 # hide that information. 1301 stack_frame_ix = 1 1302 caller_func_name = sprint_func_name(2) 1303 if caller_func_name.endswith("print_error_report"): 1304 stack_frame_ix += 1 1305 if not robot_env: 1306 buffer += sprint_call_stack(indent, stack_frame_ix) 1307 buffer += sprint_pgm_header(indent) 1308 buffer += sprint_dashes(width=120, char="=") 1309 1310 return buffer 1311 1312 1313def sprint_issuing(cmd_buf, 1314 test_mode=0): 1315 r""" 1316 Return a line indicating a command that the program is about to execute. 1317 1318 Sample output for a cmd_buf of "ls" 1319 1320 #(CDT) 2016/08/25 17:57:36 - Issuing: ls 1321 1322 Description of args: 1323 cmd_buf The command to be executed by caller. 1324 test_mode With test_mode set, your output will look 1325 like this: 1326 1327 #(CDT) 2016/08/25 17:57:36 - (test_mode) Issuing: ls 1328 1329 """ 1330 1331 buffer = sprint_time() 1332 if test_mode: 1333 buffer += "(test_mode) " 1334 buffer += "Issuing: " + cmd_buf + "\n" 1335 1336 return buffer 1337 1338 1339def sprint_pgm_footer(): 1340 r""" 1341 Return a standardized footer that programs should print at the end of the 1342 program run. It includes useful information like total run time, etc. 1343 """ 1344 1345 buffer = "\n" + sprint_time() + "Finished running " + pgm_name + ".\n\n" 1346 1347 total_time = time.time() - start_time 1348 total_time_string = "%0.6f" % total_time 1349 1350 buffer += sprint_varx(pgm_name_var_name + "_runtime", total_time_string) 1351 buffer += "\n" 1352 1353 return buffer 1354 1355 1356def sprint(buffer=""): 1357 r""" 1358 Simply return the user's buffer. This function is used by the qprint and 1359 dprint functions defined dynamically below, i.e. it would not normally be 1360 called for general use. 1361 1362 Description of arguments. 1363 buffer This will be returned to the caller. 1364 """ 1365 1366 try: 1367 return str(buffer) 1368 except UnicodeEncodeError: 1369 return buffer 1370 1371 1372def sprintn(buffer=""): 1373 r""" 1374 Simply return the user's buffer with a line feed. This function is used 1375 by the qprint and dprint functions defined dynamically below, i.e. it 1376 would not normally be called for general use. 1377 1378 Description of arguments. 1379 buffer This will be returned to the caller. 1380 """ 1381 1382 try: 1383 buffer = str(buffer) + "\n" 1384 except UnicodeEncodeError: 1385 buffer = buffer + "\n" 1386 1387 return buffer 1388 1389 1390def gp_print(buffer, 1391 stream='stdout'): 1392 r""" 1393 Print the buffer using either sys.stdout.write or BuiltIn().log_to_console 1394 depending on whether we are running in a robot environment. 1395 1396 This function is intended for use only by other functions in this module. 1397 1398 Description of arguments: 1399 buffer The string to be printed. 1400 stream Either "stdout" or "stderr". 1401 """ 1402 1403 if robot_env: 1404 BuiltIn().log_to_console(buffer, stream=stream, no_newline=True) 1405 else: 1406 if stream == "stdout": 1407 sys.stdout.write(buffer) 1408 sys.stdout.flush() 1409 else: 1410 sys.stderr.write(buffer) 1411 sys.stderr.flush() 1412 1413 1414def gp_log(buffer): 1415 r""" 1416 Log the buffer using either python logging or BuiltIn().log depending on 1417 whether we are running in a robot environment. 1418 1419 This function is intended for use only by other functions in this module. 1420 1421 Description of arguments: 1422 buffer The string to be logged. 1423 """ 1424 1425 if robot_env: 1426 BuiltIn().log(buffer) 1427 else: 1428 logging.warning(buffer) 1429 1430 1431def gp_debug_print(buffer): 1432 r""" 1433 Print with gp_print only if gen_print_debug is set. 1434 1435 This function is intended for use only by other functions in this module. 1436 1437 Description of arguments: 1438 buffer The string to be printed. 1439 """ 1440 1441 if not gen_print_debug: 1442 return 1443 1444 gp_print(buffer) 1445 1446 1447def get_var_value(var_value=None, 1448 default=1, 1449 var_name=None): 1450 r""" 1451 Return either var_value, the corresponding global value or default. 1452 1453 If var_value is not None, it will simply be returned. 1454 1455 If var_value is None, this function will return the corresponding global 1456 value of the variable in question. 1457 1458 Note: For global values, if we are in a robot environment, 1459 get_variable_value will be used. Otherwise, the __builtin__ version of 1460 the variable is returned (which are set by gen_arg.py functions). 1461 1462 If there is no global value associated with the variable, default is 1463 returned. 1464 1465 This function is useful for other functions in setting default values for 1466 parameters. 1467 1468 Example use: 1469 1470 def my_func(quiet=None): 1471 1472 quiet = int(get_var_value(quiet, 0)) 1473 1474 Example calls to my_func(): 1475 1476 In the following example, the caller is explicitly asking to have quiet be 1477 set to 1. 1478 1479 my_func(quiet=1) 1480 1481 In the following example, quiet will be set to the global value of quiet, 1482 if defined, or to 0 (the default). 1483 1484 my_func() 1485 1486 Description of arguments: 1487 var_value The value to be returned (if not equal to 1488 None). 1489 default The value that is returned if var_value is 1490 None and there is no corresponding global 1491 value defined. 1492 var_name The name of the variable whose value is to 1493 be returned. Under most circumstances, 1494 this value need not be provided. This 1495 function can figure out the name of the 1496 variable passed as var_value. One 1497 exception to this would be if this 1498 function is called directly from a .robot 1499 file. 1500 """ 1501 1502 if var_value is not None: 1503 return var_value 1504 1505 if var_name is None: 1506 var_name = get_arg_name(None, 1, 2) 1507 1508 if robot_env: 1509 var_value = BuiltIn().get_variable_value("${" + var_name + "}", 1510 default) 1511 else: 1512 var_value = getattr(__builtin__, var_name, default) 1513 1514 return var_value 1515 1516 1517def get_stack_var(var_name, 1518 default="", 1519 init_stack_ix=2): 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