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