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 19 20try: 21 robot_env = 1 22 from robot.utils import DotDict 23 from robot.utils import NormalizedDict 24 from robot.libraries.BuiltIn import BuiltIn 25except ImportError: 26 robot_env = 0 27 28import gen_arg as ga 29 30# Setting these variables for use both inside this module and by programs 31# importing this module. 32pgm_dir_path = sys.argv[0] 33pgm_name = os.path.basename(pgm_dir_path) 34pgm_dir_name = re.sub("/" + pgm_name, "", pgm_dir_path) + "/" 35 36 37# Some functions (e.g. sprint_pgm_header) have need of a program name value 38# that looks more like a valid variable name. Therefore, we'll swap odd 39# characters like "." out for underscores. 40pgm_name_var_name = pgm_name.replace(".", "_") 41 42# Initialize global values used as defaults by print_time, print_var, etc. 43col1_indent = 0 44 45# Calculate default column width for print_var functions based on environment 46# variable settings. The objective is to make the variable values line up 47# nicely with the time stamps. 48col1_width = 29 49if 'NANOSECONDS' in os.environ: 50 NANOSECONDS = os.environ['NANOSECONDS'] 51else: 52 NANOSECONDS = 0 53 54if NANOSECONDS == "1": 55 col1_width = col1_width + 7 56 57if 'SHOW_ELAPSED_TIME' in os.environ: 58 SHOW_ELAPSED_TIME = os.environ['SHOW_ELAPSED_TIME'] 59else: 60 SHOW_ELAPSED_TIME = 0 61 62if SHOW_ELAPSED_TIME == "1": 63 if NANOSECONDS == "1": 64 col1_width = col1_width + 14 65 else: 66 col1_width = col1_width + 7 67 68# Initialize some time variables used in module functions. 69start_time = time.time() 70sprint_time_last_seconds = start_time 71 72# The user can set environment variable "GEN_PRINT_DEBUG" to get debug output 73# from this module. 74gen_print_debug = int(os.environ.get('GEN_PRINT_DEBUG', 0)) 75 76 77############################################################################### 78def sprint_func_name(stack_frame_ix=None): 79 80 r""" 81 Return the function name associated with the indicated stack frame. 82 83 Description of arguments: 84 stack_frame_ix The index of the stack frame whose 85 function name should be returned. If the 86 caller does not specify a value, this 87 function will set the value to 1 which is 88 the index of the caller's stack frame. If 89 the caller is the wrapper function 90 "print_func_name", this function will bump 91 it up by 1. 92 """ 93 94 # If user specified no stack_frame_ix, we'll set it to a proper default 95 # value. 96 if stack_frame_ix is None: 97 func_name = sys._getframe().f_code.co_name 98 caller_func_name = sys._getframe(1).f_code.co_name 99 if func_name[1:] == caller_func_name: 100 stack_frame_ix = 2 101 else: 102 stack_frame_ix = 1 103 104 func_name = sys._getframe(stack_frame_ix).f_code.co_name 105 106 return func_name 107 108############################################################################### 109 110 111# get_arg_name is not a print function per se. I have included it in this 112# module because it is used by sprint_var which is found in this module. 113############################################################################### 114def get_arg_name(var, 115 arg_num=1, 116 stack_frame_ix=1): 117 118 r""" 119 Return the "name" of an argument passed to a function. This could be a 120 literal or a variable name. 121 122 Description of arguments: 123 var The variable whose name you want returned. 124 arg_num The arg number (1 through n) whose name 125 you wish to have returned. This value 126 should not exceed the number of arguments 127 allowed by the target function. 128 stack_frame_ix The stack frame index of the target 129 function. This value must be 1 or 130 greater. 1 would indicate get_arg_name's 131 stack frame. 2 would be the caller of 132 get_arg_name's stack frame, etc. 133 134 Example 1: 135 136 my_var = "mike" 137 var_name = get_arg_name(my_var) 138 139 In this example, var_name will receive the value "my_var". 140 141 Example 2: 142 143 def test1(var): 144 # Getting the var name of the first arg to this function, test1. 145 # Note, in this case, it doesn't matter what you pass as the first arg 146 # to get_arg_name since it is the caller's variable name that matters. 147 dummy = 1 148 arg_num = 1 149 stack_frame = 2 150 var_name = get_arg_name(dummy, arg_num, stack_frame) 151 152 # Mainline... 153 154 another_var = "whatever" 155 test1(another_var) 156 157 In this example, var_name will be set to "another_var". 158 159 """ 160 161 # Note: I wish to avoid recursion so I refrain from calling any function 162 # that calls this function (i.e. sprint_var, valid_value, etc.). 163 164 # The user can set environment variable "GET_ARG_NAME_DEBUG" to get debug 165 # output from this function. 166 local_debug = int(os.environ.get('GET_ARG_NAME_DEBUG', 0)) 167 # In addition to GET_ARG_NAME_DEBUG, the user can set environment 168 # variable "GET_ARG_NAME_SHOW_SOURCE" to have this function include source 169 # code in the debug output. 170 local_debug_show_source = int( 171 os.environ.get('GET_ARG_NAME_SHOW_SOURCE', 0)) 172 173 if arg_num < 1: 174 print_error("Programmer error - Variable \"arg_num\" has an invalid" + 175 " value of \"" + str(arg_num) + "\". The value must be" + 176 " an integer that is greater than 0.\n") 177 # What is the best way to handle errors? Raise exception? I'll 178 # revisit later. 179 return 180 if stack_frame_ix < 1: 181 print_error("Programmer error - Variable \"stack_frame_ix\" has an" + 182 " invalid value of \"" + str(stack_frame_ix) + "\". The" + 183 " value must be an integer that is greater than or equal" + 184 " to 1.\n") 185 return 186 187 if local_debug: 188 debug_indent = 2 189 print("") 190 print_dashes(0, 120) 191 print(sprint_func_name() + "() parms:") 192 print_varx("var", var, 0, debug_indent) 193 print_varx("arg_num", arg_num, 0, debug_indent) 194 print_varx("stack_frame_ix", stack_frame_ix, 0, debug_indent) 195 print("") 196 print_call_stack(debug_indent, 2) 197 198 for count in range(0, 2): 199 try: 200 frame, filename, cur_line_no, function_name, lines, index = \ 201 inspect.stack()[stack_frame_ix] 202 except IndexError: 203 print_error("Programmer error - The caller has asked for" + 204 " information about the stack frame at index \"" + 205 str(stack_frame_ix) + "\". However, the stack" + 206 " only contains " + str(len(inspect.stack())) + 207 " entries. Therefore the stack frame index is out" + 208 " of range.\n") 209 return 210 if filename != "<string>": 211 break 212 # filename of "<string>" may mean that the function in question was 213 # defined dynamically and therefore its code stack is inaccessible. 214 # This may happen with functions like "rqprint_var". In this case, 215 # we'll increment the stack_frame_ix and try again. 216 stack_frame_ix += 1 217 if local_debug: 218 print("Adjusted stack_frame_ix...") 219 print_varx("stack_frame_ix", stack_frame_ix, 0, debug_indent) 220 221 called_func_name = sprint_func_name(stack_frame_ix) 222 223 module = inspect.getmodule(frame) 224 225 # Though I would expect inspect.getsourcelines(frame) to get all module 226 # source lines if the frame is "<module>", it doesn't do that. Therefore, 227 # for this special case, I will do inspect.getsourcelines(module). 228 if function_name == "<module>": 229 source_lines, source_line_num =\ 230 inspect.getsourcelines(module) 231 line_ix = cur_line_no - source_line_num - 1 232 else: 233 source_lines, source_line_num =\ 234 inspect.getsourcelines(frame) 235 line_ix = cur_line_no - source_line_num 236 237 if local_debug: 238 print("\n Variables retrieved from inspect.stack() function:") 239 print_varx("frame", frame, 0, debug_indent + 2) 240 print_varx("filename", filename, 0, debug_indent + 2) 241 print_varx("cur_line_no", cur_line_no, 0, debug_indent + 2) 242 print_varx("function_name", function_name, 0, debug_indent + 2) 243 print_varx("lines", lines, 0, debug_indent + 2) 244 print_varx("index", index, 0, debug_indent + 2) 245 print_varx("source_line_num", source_line_num, 0, debug_indent) 246 print_varx("line_ix", line_ix, 0, debug_indent) 247 if local_debug_show_source: 248 print_varx("source_lines", source_lines, 0, debug_indent) 249 print_varx("called_func_name", called_func_name, 0, debug_indent) 250 251 # Get a list of all functions defined for the module. Note that this 252 # doesn't work consistently when _run_exitfuncs is at the top of the stack 253 # (i.e. if we're running an exit function). I've coded a work-around 254 # below for this deficiency. 255 all_functions = inspect.getmembers(module, inspect.isfunction) 256 257 # Get called_func_id by searching for our function in the list of all 258 # functions. 259 called_func_id = None 260 for func_name, function in all_functions: 261 if func_name == called_func_name: 262 called_func_id = id(function) 263 break 264 # NOTE: The only time I've found that called_func_id can't be found is 265 # when we're running from an exit function. 266 267 # Look for other functions in module with matching id. 268 aliases = set([called_func_name]) 269 for func_name, function in all_functions: 270 if func_name == called_func_name: 271 continue 272 func_id = id(function) 273 if func_id == called_func_id: 274 aliases.add(func_name) 275 276 # In most cases, my general purpose code above will find all aliases. 277 # However, for the odd case (i.e. running from exit function), I've added 278 # code to handle pvar, qpvar, dpvar, etc. aliases explicitly since they 279 # are defined in this module and used frequently. 280 # pvar is an alias for print_var. 281 aliases.add(re.sub("print_var", "pvar", called_func_name)) 282 283 func_regex = ".*(" + '|'.join(aliases) + ")[ ]*\(" 284 285 # Search backward through source lines looking for the calling function 286 # name. 287 found = False 288 for start_line_ix in range(line_ix, 0, -1): 289 # Skip comment lines. 290 if re.match(r"[ ]*#", source_lines[start_line_ix]): 291 continue 292 if re.match(func_regex, source_lines[start_line_ix]): 293 found = True 294 break 295 if not found: 296 print_error("Programmer error - Could not find the source line with" + 297 " a reference to function \"" + called_func_name + "\".\n") 298 return 299 300 # Search forward through the source lines looking for a line with the 301 # same indentation as the start time. The end of our composite line 302 # should be the line preceding that line. 303 start_indent = len(source_lines[start_line_ix]) -\ 304 len(source_lines[start_line_ix].lstrip(' ')) 305 end_line_ix = line_ix 306 for end_line_ix in range(line_ix + 1, len(source_lines)): 307 if source_lines[end_line_ix].strip() == "": 308 continue 309 line_indent = len(source_lines[end_line_ix]) -\ 310 len(source_lines[end_line_ix].lstrip(' ')) 311 if line_indent == start_indent: 312 end_line_ix -= 1 313 break 314 315 # Join the start line through the end line into a composite line. 316 composite_line = ''.join(map(str.strip, 317 source_lines[start_line_ix:end_line_ix + 1])) 318 319 # arg_list_etc = re.sub(".*" + called_func_name, "", composite_line) 320 arg_list_etc = "(" + re.sub(func_regex, "", composite_line) 321 if local_debug: 322 print_varx("aliases", aliases, 0, debug_indent) 323 print_varx("func_regex", func_regex, 0, debug_indent) 324 print_varx("start_line_ix", start_line_ix, 0, debug_indent) 325 print_varx("end_line_ix", end_line_ix, 0, debug_indent) 326 print_varx("composite_line", composite_line, 0, debug_indent) 327 print_varx("arg_list_etc", arg_list_etc, 0, debug_indent) 328 329 # Parse arg list... 330 # Initialize... 331 nest_level = -1 332 arg_ix = 0 333 args_list = [""] 334 for ix in range(0, len(arg_list_etc)): 335 char = arg_list_etc[ix] 336 # Set the nest_level based on whether we've encounted a parenthesis. 337 if char == "(": 338 nest_level += 1 339 if nest_level == 0: 340 continue 341 elif char == ")": 342 nest_level -= 1 343 if nest_level < 0: 344 break 345 346 # If we reach a comma at base nest level, we are done processing an 347 # argument so we increment arg_ix and initialize a new args_list entry. 348 if char == "," and nest_level == 0: 349 arg_ix += 1 350 args_list.append("") 351 continue 352 353 # For any other character, we append it it to the current arg list 354 # entry. 355 args_list[arg_ix] += char 356 357 # Trim whitespace from each list entry. 358 args_list = [arg.strip() for arg in args_list] 359 360 if arg_num > len(args_list): 361 print_error("Programmer error - The caller has asked for the name of" + 362 " argument number \"" + str(arg_num) + "\" but there " + 363 "were only \"" + str(len(args_list)) + "\" args used:\n" + 364 sprint_varx("args_list", args_list)) 365 return 366 367 argument = args_list[arg_num - 1] 368 369 if local_debug: 370 print_varx("args_list", args_list, 0, debug_indent) 371 print_varx("argument", argument, 0, debug_indent) 372 print_dashes(0, 120) 373 374 return argument 375 376############################################################################### 377 378 379############################################################################### 380def sprint_time(buffer=""): 381 382 r""" 383 Return the time in the following format. 384 385 Example: 386 387 The following python code... 388 389 sys.stdout.write(sprint_time()) 390 sys.stdout.write("Hi.\n") 391 392 Will result in the following type of output: 393 394 #(CDT) 2016/07/08 15:25:35 - Hi. 395 396 Example: 397 398 The following python code... 399 400 sys.stdout.write(sprint_time("Hi.\n")) 401 402 Will result in the following type of output: 403 404 #(CDT) 2016/08/03 17:12:05 - Hi. 405 406 The following environment variables will affect the formatting as 407 described: 408 NANOSECONDS This will cause the time stamps to be 409 precise to the microsecond (Yes, it 410 probably should have been named 411 MICROSECONDS but the convention was set 412 long ago so we're sticking with it). 413 Example of the output when environment 414 variable NANOSECONDS=1. 415 416 #(CDT) 2016/08/03 17:16:25.510469 - Hi. 417 418 SHOW_ELAPSED_TIME This will cause the elapsed time to be 419 included in the output. This is the 420 amount of time that has elapsed since the 421 last time this function was called. The 422 precision of the elapsed time field is 423 also affected by the value of the 424 NANOSECONDS environment variable. Example 425 of the output when environment variable 426 NANOSECONDS=0 and SHOW_ELAPSED_TIME=1. 427 428 #(CDT) 2016/08/03 17:17:40 - 0 - Hi. 429 430 Example of the output when environment variable NANOSECONDS=1 and 431 SHOW_ELAPSED_TIME=1. 432 433 #(CDT) 2016/08/03 17:18:47.317339 - 0.000046 - Hi. 434 435 Description of arguments. 436 buffer This will be appended to the formatted 437 time string. 438 """ 439 440 global NANOSECONDS 441 global SHOW_ELAPSED_TIME 442 global sprint_time_last_seconds 443 444 seconds = time.time() 445 loc_time = time.localtime(seconds) 446 nanoseconds = "%0.6f" % seconds 447 pos = nanoseconds.find(".") 448 nanoseconds = nanoseconds[pos:] 449 450 time_string = time.strftime("#(%Z) %Y/%m/%d %H:%M:%S", loc_time) 451 if NANOSECONDS == "1": 452 time_string = time_string + nanoseconds 453 454 if SHOW_ELAPSED_TIME == "1": 455 cur_time_seconds = seconds 456 math_string = "%9.9f" % cur_time_seconds + " - " + "%9.9f" % \ 457 sprint_time_last_seconds 458 elapsed_seconds = eval(math_string) 459 if NANOSECONDS == "1": 460 elapsed_seconds = "%11.6f" % elapsed_seconds 461 else: 462 elapsed_seconds = "%4i" % elapsed_seconds 463 sprint_time_last_seconds = cur_time_seconds 464 time_string = time_string + " - " + elapsed_seconds 465 466 return time_string + " - " + buffer 467 468############################################################################### 469 470 471############################################################################### 472def sprint_timen(buffer=""): 473 474 r""" 475 Append a line feed to the buffer, pass it to sprint_time and return the 476 result. 477 """ 478 479 return sprint_time(buffer + "\n") 480 481############################################################################### 482 483 484############################################################################### 485def sprint_error(buffer=""): 486 487 r""" 488 Return a standardized error string. This includes: 489 - A time stamp 490 - The "**ERROR**" string 491 - The caller's buffer string. 492 493 Example: 494 495 The following python code... 496 497 print(sprint_error("Oops.\n")) 498 499 Will result in the following type of output: 500 501 #(CDT) 2016/08/03 17:12:05 - **ERROR** Oops. 502 503 Description of arguments. 504 buffer This will be appended to the formatted 505 error string. 506 """ 507 508 return sprint_time() + "**ERROR** " + buffer 509 510############################################################################### 511 512 513############################################################################### 514def sprint_varx(var_name, 515 var_value, 516 hex=0, 517 loc_col1_indent=col1_indent, 518 loc_col1_width=col1_width, 519 trailing_char="\n"): 520 521 r""" 522 Print the var name/value passed to it. If the caller lets loc_col1_width 523 default, the printing lines up nicely with output generated by the 524 print_time functions. 525 526 Note that the sprint_var function (defined below) can be used to call this 527 function so that the programmer does not need to pass the var_name. 528 sprint_var will figure out the var_name. The sprint_var function is the 529 one that would normally be used by the general user. 530 531 For example, the following python code: 532 533 first_name = "Mike" 534 print_time("Doing this...\n") 535 print_varx("first_name", first_name) 536 print_time("Doing that...\n") 537 538 Will generate output like this: 539 540 #(CDT) 2016/08/10 17:34:42.847374 - 0.001285 - Doing this... 541 first_name: Mike 542 #(CDT) 2016/08/10 17:34:42.847510 - 0.000136 - Doing that... 543 544 This function recognizes several complex types of data such as dict, list 545 or tuple. 546 547 For example, the following python code: 548 549 my_dict = dict(one=1, two=2, three=3) 550 print_var(my_dict) 551 552 Will generate the following output: 553 554 my_dict: 555 my_dict[three]: 3 556 my_dict[two]: 2 557 my_dict[one]: 1 558 559 Description of arguments. 560 var_name The name of the variable to be printed. 561 var_value The value of the variable to be printed. 562 hex This indicates that the value should be 563 printed in hex format. It is the user's 564 responsibility to ensure that a var_value 565 contains a valid hex number. For string 566 var_values, this will be interpreted as 567 show_blanks which means that blank values 568 will be printed as "<blank>". For dict 569 var_values, this will be interpreted as 570 terse format where keys are not repeated 571 in the output. 572 loc_col1_indent The number of spaces to indent the output. 573 loc_col1_width The width of the output column containing 574 the variable name. The default value of 575 this is adjusted so that the var_value 576 lines up with text printed via the 577 print_time function. 578 trailing_char The character to be used at the end of the 579 returned string. The default value is a 580 line feed. 581 """ 582 583 # Determine the type 584 if type(var_value) in (int, float, bool, str, unicode) \ 585 or var_value is None: 586 # The data type is simple in the sense that it has no subordinate 587 # parts. 588 # Adjust loc_col1_width. 589 loc_col1_width = loc_col1_width - loc_col1_indent 590 # See if the user wants the output in hex format. 591 if hex: 592 if type(var_value) not in (int, long): 593 value_format = "%s" 594 if var_value == "": 595 var_value = "<blank>" 596 else: 597 value_format = "0x%08x" 598 else: 599 value_format = "%s" 600 format_string = "%" + str(loc_col1_indent) + "s%-" \ 601 + str(loc_col1_width) + "s" + value_format + trailing_char 602 return format_string % ("", str(var_name) + ":", var_value) 603 else: 604 # The data type is complex in the sense that it has subordinate parts. 605 format_string = "%" + str(loc_col1_indent) + "s%s\n" 606 buffer = format_string % ("", var_name + ":") 607 loc_col1_indent += 2 608 try: 609 length = len(var_value) 610 except TypeError: 611 length = 0 612 ix = 0 613 loc_trailing_char = "\n" 614 type_is_dict = 0 615 if type(var_value) is dict: 616 type_is_dict = 1 617 try: 618 if type(var_value) is collections.OrderedDict: 619 type_is_dict = 1 620 except AttributeError: 621 pass 622 try: 623 if type(var_value) is DotDict: 624 type_is_dict = 1 625 except NameError: 626 pass 627 try: 628 if type(var_value) is NormalizedDict: 629 type_is_dict = 1 630 except NameError: 631 pass 632 if type_is_dict: 633 for key, value in var_value.iteritems(): 634 ix += 1 635 if ix == length: 636 loc_trailing_char = trailing_char 637 if hex: 638 # Since hex is being used as a format type, we want it 639 # turned off when processing integer dictionary values so 640 # it is not interpreted as a hex indicator. 641 loc_hex = not (type(value) is int) 642 buffer += sprint_varx(key, value, 643 loc_hex, loc_col1_indent, 644 loc_col1_width, 645 loc_trailing_char) 646 else: 647 buffer += sprint_varx(var_name + "[" + key + "]", value, 648 hex, loc_col1_indent, loc_col1_width, 649 loc_trailing_char) 650 elif type(var_value) in (list, tuple, set): 651 for key, value in enumerate(var_value): 652 ix += 1 653 if ix == length: 654 loc_trailing_char = trailing_char 655 buffer += sprint_varx(var_name + "[" + str(key) + "]", value, 656 hex, loc_col1_indent, loc_col1_width, 657 loc_trailing_char) 658 elif type(var_value) is argparse.Namespace: 659 for key in var_value.__dict__: 660 ix += 1 661 if ix == length: 662 loc_trailing_char = trailing_char 663 cmd_buf = "buffer += sprint_varx(var_name + \".\" + str(key)" \ 664 + ", var_value." + key + ", hex, loc_col1_indent," \ 665 + " loc_col1_width, loc_trailing_char)" 666 exec(cmd_buf) 667 else: 668 var_type = type(var_value).__name__ 669 func_name = sys._getframe().f_code.co_name 670 var_value = "<" + var_type + " type not supported by " + \ 671 func_name + "()>" 672 value_format = "%s" 673 loc_col1_indent -= 2 674 # Adjust loc_col1_width. 675 loc_col1_width = loc_col1_width - loc_col1_indent 676 format_string = "%" + str(loc_col1_indent) + "s%-" \ 677 + str(loc_col1_width) + "s" + value_format + trailing_char 678 return format_string % ("", str(var_name) + ":", var_value) 679 680 return buffer 681 682 return "" 683 684############################################################################### 685 686 687############################################################################### 688def sprint_var(*args): 689 690 r""" 691 Figure out the name of the first argument for you and then call 692 sprint_varx with it. Therefore, the following 2 calls are equivalent: 693 sprint_varx("var1", var1) 694 sprint_var(var1) 695 """ 696 697 # Get the name of the first variable passed to this function. 698 stack_frame = 2 699 caller_func_name = sprint_func_name(2) 700 if caller_func_name.endswith("print_var"): 701 stack_frame += 1 702 var_name = get_arg_name(None, 1, stack_frame) 703 return sprint_varx(var_name, *args) 704 705############################################################################### 706 707 708############################################################################### 709def sprint_vars(*args): 710 711 r""" 712 Sprint the values of one or more variables. 713 714 Description of args: 715 args: 716 If the first argument is an integer, it will be interpreted to be the 717 "indent" value. 718 If the second argument is an integer, it will be interpreted to be the 719 "col1_width" value. 720 If the third argument is an integer, it will be interpreted to be the 721 "hex" value. 722 All remaining parms are considered variable names which are to be 723 sprinted. 724 """ 725 726 if len(args) == 0: 727 return 728 729 # Get the name of the first variable passed to this function. 730 stack_frame = 2 731 caller_func_name = sprint_func_name(2) 732 if caller_func_name.endswith("print_vars"): 733 stack_frame += 1 734 735 parm_num = 1 736 737 # Create list from args (which is a tuple) so that it can be modified. 738 args_list = list(args) 739 740 var_name = get_arg_name(None, parm_num, stack_frame) 741 # See if parm 1 is to be interpreted as "indent". 742 try: 743 if type(int(var_name)) is int: 744 indent = int(var_name) 745 args_list.pop(0) 746 parm_num += 1 747 except ValueError: 748 indent = 0 749 750 var_name = get_arg_name(None, parm_num, stack_frame) 751 # See if parm 1 is to be interpreted as "col1_width". 752 try: 753 if type(int(var_name)) is int: 754 loc_col1_width = int(var_name) 755 args_list.pop(0) 756 parm_num += 1 757 except ValueError: 758 loc_col1_width = col1_width 759 760 var_name = get_arg_name(None, parm_num, stack_frame) 761 # See if parm 1 is to be interpreted as "hex". 762 try: 763 if type(int(var_name)) is int: 764 hex = int(var_name) 765 args_list.pop(0) 766 parm_num += 1 767 except ValueError: 768 hex = 0 769 770 buffer = "" 771 for var_value in args_list: 772 var_name = get_arg_name(None, parm_num, stack_frame) 773 buffer += sprint_varx(var_name, var_value, hex, indent, loc_col1_width) 774 parm_num += 1 775 776 return buffer 777 778############################################################################### 779 780 781############################################################################### 782def lprint_varx(var_name, 783 var_value, 784 hex=0, 785 loc_col1_indent=col1_indent, 786 loc_col1_width=col1_width, 787 log_level=getattr(logging, 'INFO')): 788 789 r""" 790 Send sprint_varx output to logging. 791 """ 792 793 logging.log(log_level, sprint_varx(var_name, var_value, hex, 794 loc_col1_indent, loc_col1_width, "")) 795 796############################################################################### 797 798 799############################################################################### 800def lprint_var(*args): 801 802 r""" 803 Figure out the name of the first argument for you and then call 804 lprint_varx with it. Therefore, the following 2 calls are equivalent: 805 lprint_varx("var1", var1) 806 lprint_var(var1) 807 """ 808 809 # Get the name of the first variable passed to this function. 810 stack_frame = 2 811 caller_func_name = sprint_func_name(2) 812 if caller_func_name.endswith("print_var"): 813 stack_frame += 1 814 var_name = get_arg_name(None, 1, stack_frame) 815 lprint_varx(var_name, *args) 816 817############################################################################### 818 819 820############################################################################### 821def sprint_dashes(indent=col1_indent, 822 width=80, 823 line_feed=1, 824 char="-"): 825 826 r""" 827 Return a string of dashes to the caller. 828 829 Description of arguments: 830 indent The number of characters to indent the 831 output. 832 width The width of the string of dashes. 833 line_feed Indicates whether the output should end 834 with a line feed. 835 char The character to be repeated in the output 836 string. 837 """ 838 839 width = int(width) 840 buffer = " " * int(indent) + char * width 841 if line_feed: 842 buffer += "\n" 843 844 return buffer 845 846############################################################################### 847 848 849############################################################################### 850def sindent(text="", 851 indent=0): 852 853 r""" 854 Pre-pend the specified number of characters to the text string (i.e. 855 indent it) and return it. 856 857 Description of arguments: 858 text The string to be indented. 859 indent The number of characters to indent the 860 string. 861 """ 862 863 format_string = "%" + str(indent) + "s%s" 864 buffer = format_string % ("", text) 865 866 return buffer 867 868############################################################################### 869 870 871############################################################################### 872def sprint_call_stack(indent=0, 873 stack_frame_ix=0): 874 875 r""" 876 Return a call stack report for the given point in the program with line 877 numbers, function names and function parameters and arguments. 878 879 Sample output: 880 881 ------------------------------------------------------------------------- 882 Python function call stack 883 884 Line # Function name and arguments 885 ------ ------------------------------------------------------------------ 886 424 sprint_call_stack () 887 4 print_call_stack () 888 31 func1 (last_name = 'walsh', first_name = 'mikey') 889 59 /tmp/scr5.py 890 ------------------------------------------------------------------------- 891 892 Description of arguments: 893 indent The number of characters to indent each 894 line of output. 895 stack_frame_ix The index of the first stack frame which 896 is to be returned. 897 """ 898 899 buffer = "" 900 buffer += sprint_dashes(indent) 901 buffer += sindent("Python function call stack\n\n", indent) 902 buffer += sindent("Line # Function name and arguments\n", indent) 903 buffer += sprint_dashes(indent, 6, 0) + " " + sprint_dashes(0, 73) 904 905 # Grab the current program stack. 906 current_stack = inspect.stack() 907 908 # Process each frame in turn. 909 format_string = "%6s %s\n" 910 ix = 0 911 for stack_frame in current_stack: 912 if ix < stack_frame_ix: 913 ix += 1 914 continue 915 # I want the line number shown to be the line where you find the line 916 # shown. 917 try: 918 line_num = str(current_stack[ix + 1][2]) 919 except IndexError: 920 line_num = "" 921 func_name = str(stack_frame[3]) 922 if func_name == "?": 923 # "?" is the name used when code is not in a function. 924 func_name = "(none)" 925 926 if func_name == "<module>": 927 # If the func_name is the "main" program, we simply get the 928 # command line call string. 929 func_and_args = ' '.join(sys.argv) 930 else: 931 # Get the program arguments. 932 arg_vals = inspect.getargvalues(stack_frame[0]) 933 function_parms = arg_vals[0] 934 frame_locals = arg_vals[3] 935 936 args_list = [] 937 for arg_name in function_parms: 938 # Get the arg value from frame locals. 939 arg_value = frame_locals[arg_name] 940 args_list.append(arg_name + " = " + repr(arg_value)) 941 args_str = "(" + ', '.join(map(str, args_list)) + ")" 942 943 # Now we need to print this in a nicely-wrapped way. 944 func_and_args = func_name + " " + args_str 945 946 buffer += sindent(format_string % (line_num, func_and_args), indent) 947 ix += 1 948 949 buffer += sprint_dashes(indent) 950 951 return buffer 952 953############################################################################### 954 955 956############################################################################### 957def sprint_executing(stack_frame_ix=None): 958 959 r""" 960 Print a line indicating what function is executing and with what parameter 961 values. This is useful for debugging. 962 963 Sample output: 964 965 #(CDT) 2016/08/25 17:54:27 - Executing: func1 (x = 1) 966 967 Description of arguments: 968 stack_frame_ix The index of the stack frame whose 969 function info should be returned. If the 970 caller does not specify a value, this 971 function will set the value to 1 which is 972 the index of the caller's stack frame. If 973 the caller is the wrapper function 974 "print_executing", this function will bump 975 it up by 1. 976 """ 977 978 # If user wants default stack_frame_ix. 979 if stack_frame_ix is None: 980 func_name = sys._getframe().f_code.co_name 981 caller_func_name = sys._getframe(1).f_code.co_name 982 if caller_func_name.endswith(func_name[1:]): 983 stack_frame_ix = 2 984 else: 985 stack_frame_ix = 1 986 987 stack_frame = inspect.stack()[stack_frame_ix] 988 989 func_name = str(stack_frame[3]) 990 if func_name == "?": 991 # "?" is the name used when code is not in a function. 992 func_name = "(none)" 993 994 if func_name == "<module>": 995 # If the func_name is the "main" program, we simply get the command 996 # line call string. 997 func_and_args = ' '.join(sys.argv) 998 else: 999 # Get the program arguments. 1000 arg_vals = inspect.getargvalues(stack_frame[0]) 1001 function_parms = arg_vals[0] 1002 frame_locals = arg_vals[3] 1003 1004 args_list = [] 1005 for arg_name in function_parms: 1006 # Get the arg value from frame locals. 1007 arg_value = frame_locals[arg_name] 1008 args_list.append(arg_name + " = " + repr(arg_value)) 1009 args_str = "(" + ', '.join(map(str, args_list)) + ")" 1010 1011 # Now we need to print this in a nicely-wrapped way. 1012 func_and_args = func_name + " " + args_str 1013 1014 return sprint_time() + "Executing: " + func_and_args + "\n" 1015 1016############################################################################### 1017 1018 1019############################################################################### 1020def sprint_pgm_header(indent=0, 1021 linefeed=1): 1022 1023 r""" 1024 Return a standardized header that programs should print at the beginning 1025 of the run. It includes useful information like command line, pid, 1026 userid, program parameters, etc. 1027 1028 Description of arguments: 1029 indent The number of characters to indent each 1030 line of output. 1031 linefeed Indicates whether a line feed be included 1032 at the beginning and end of the report. 1033 """ 1034 1035 loc_col1_width = col1_width + indent 1036 1037 buffer = "" 1038 if linefeed: 1039 buffer = "\n" 1040 1041 buffer += sindent(sprint_time() + "Running " + pgm_name + ".\n", indent) 1042 buffer += sindent(sprint_time() + "Program parameter values, etc.:\n\n", 1043 indent) 1044 buffer += sprint_varx("command_line", ' '.join(sys.argv), 0, indent, 1045 loc_col1_width) 1046 # We want the output to show a customized name for the pid and pgid but 1047 # we want it to look like a valid variable name. Therefore, we'll use 1048 # pgm_name_var_name which was set when this module was imported. 1049 buffer += sprint_varx(pgm_name_var_name + "_pid", os.getpid(), 0, indent, 1050 loc_col1_width) 1051 buffer += sprint_varx(pgm_name_var_name + "_pgid", os.getpgrp(), 0, indent, 1052 loc_col1_width) 1053 userid_num = str(os.geteuid()) 1054 try: 1055 username = os.getlogin() 1056 except OSError: 1057 if userid_num == "0": 1058 username = "root" 1059 else: 1060 username = "?" 1061 buffer += sprint_varx("uid", userid_num + " (" + username + 1062 ")", 0, indent, loc_col1_width) 1063 buffer += sprint_varx("gid", str(os.getgid()) + " (" + 1064 str(grp.getgrgid(os.getgid()).gr_name) + ")", 0, 1065 indent, loc_col1_width) 1066 buffer += sprint_varx("host_name", socket.gethostname(), 0, indent, 1067 loc_col1_width) 1068 try: 1069 DISPLAY = os.environ['DISPLAY'] 1070 except KeyError: 1071 DISPLAY = "" 1072 buffer += sprint_varx("DISPLAY", DISPLAY, 0, indent, 1073 loc_col1_width) 1074 # I want to add code to print caller's parms. 1075 1076 # __builtin__.arg_obj is created by the get_arg module function, 1077 # gen_get_options. 1078 try: 1079 buffer += ga.sprint_args(__builtin__.arg_obj, indent) 1080 except AttributeError: 1081 pass 1082 1083 if linefeed: 1084 buffer += "\n" 1085 1086 return buffer 1087 1088############################################################################### 1089 1090 1091############################################################################### 1092def sprint_error_report(error_text="\n", 1093 indent=2): 1094 1095 r""" 1096 Return a string with a standardized report which includes the caller's 1097 error text, the call stack and the program header. 1098 1099 Description of args: 1100 error_text The error text to be included in the 1101 report. The caller should include any 1102 needed linefeeds. 1103 indent The number of characters to indent each 1104 line of output. 1105 """ 1106 1107 buffer = "" 1108 buffer += sprint_dashes(width=120, char="=") 1109 buffer += sprint_error(error_text) 1110 buffer += "\n" 1111 # Calling sprint_call_stack with stack_frame_ix of 0 causes it to show 1112 # itself and this function in the call stack. This is not helpful to a 1113 # debugger and is therefore clutter. We will adjust the stack_frame_ix to 1114 # hide that information. 1115 stack_frame_ix = 2 1116 caller_func_name = sprint_func_name(2) 1117 if caller_func_name.endswith("print_error_report"): 1118 stack_frame_ix += 1 1119 buffer += sprint_call_stack(indent, stack_frame_ix) 1120 buffer += sprint_pgm_header(indent) 1121 buffer += sprint_dashes(width=120, char="=") 1122 1123 return buffer 1124 1125############################################################################### 1126 1127 1128############################################################################### 1129def sprint_issuing(cmd_buf, 1130 test_mode=0): 1131 1132 r""" 1133 Return a line indicating a command that the program is about to execute. 1134 1135 Sample output for a cmd_buf of "ls" 1136 1137 #(CDT) 2016/08/25 17:57:36 - Issuing: ls 1138 1139 Description of args: 1140 cmd_buf The command to be executed by caller. 1141 test_mode With test_mode set, your output will look 1142 like this: 1143 1144 #(CDT) 2016/08/25 17:57:36 - (test_mode) Issuing: ls 1145 1146 """ 1147 1148 buffer = sprint_time() 1149 if test_mode: 1150 buffer += "(test_mode) " 1151 buffer += "Issuing: " + cmd_buf + "\n" 1152 1153 return buffer 1154 1155############################################################################### 1156 1157 1158############################################################################### 1159def sprint_pgm_footer(): 1160 1161 r""" 1162 Return a standardized footer that programs should print at the end of the 1163 program run. It includes useful information like total run time, etc. 1164 """ 1165 1166 buffer = "\n" + sprint_time() + "Finished running " + pgm_name + ".\n\n" 1167 1168 total_time = time.time() - start_time 1169 total_time_string = "%0.6f" % total_time 1170 1171 buffer += sprint_varx(pgm_name_var_name + "_runtime", total_time_string) 1172 buffer += "\n" 1173 1174 return buffer 1175 1176############################################################################### 1177 1178 1179############################################################################### 1180def sprint(buffer=""): 1181 1182 r""" 1183 Simply return the user's buffer. This function is used by the qprint and 1184 dprint functions defined dynamically below, i.e. it would not normally be 1185 called for general use. 1186 1187 Description of arguments. 1188 buffer This will be returned to the caller. 1189 """ 1190 1191 return str(buffer) 1192 1193############################################################################### 1194 1195 1196############################################################################### 1197def sprintn(buffer=""): 1198 1199 r""" 1200 Simply return the user's buffer with a line feed. This function is used 1201 by the qprint and dprint functions defined dynamically below, i.e. it 1202 would not normally be called for general use. 1203 1204 Description of arguments. 1205 buffer This will be returned to the caller. 1206 """ 1207 1208 buffer = str(buffer) + "\n" 1209 1210 return buffer 1211 1212############################################################################### 1213 1214 1215############################################################################### 1216def gp_debug_print(buffer): 1217 1218 r""" 1219 Print buffer to stdout only if gen_print_debug is set. 1220 1221 This function is intended for use only by other functions in this module. 1222 1223 Description of arguments: 1224 buffer The string to be printed. 1225 """ 1226 1227 if not gen_print_debug: 1228 return 1229 1230 if robot_env: 1231 BuiltIn().log_to_console(buffer) 1232 else: 1233 print(buffer) 1234 1235############################################################################### 1236 1237 1238############################################################################### 1239def gp_get_var_value(var_name, 1240 default=1): 1241 1242 r""" 1243 Get the value of the named variable and return it. If the variable is not 1244 defined, the default value is returned. 1245 1246 If we are in a robot environment, get_variable_value will be used. 1247 Otherwise, the __builtin__ version of the variable is returned. 1248 1249 This function is intended for use only by other functions in this module. 1250 1251 Description of arguments: 1252 var_name The name of the variable whose value is to 1253 be returned. 1254 default The value that is returned if var_name is 1255 not defined. 1256 """ 1257 1258 if robot_env: 1259 var_value = int(BuiltIn().get_variable_value("${" + var_name + "}", 1260 default)) 1261 else: 1262 var_value = getattr(__builtin__, var_name, default) 1263 1264 return var_value 1265 1266############################################################################### 1267 1268 1269############################################################################### 1270# In the following section of code, we will dynamically create print versions 1271# for each of the sprint functions defined above. So, for example, where we 1272# have an sprint_time() function defined above that returns the time to the 1273# caller in a string, we will create a corresponding print_time() function 1274# that will print that string directly to stdout. 1275 1276# It can be complicated to follow what's being created by the exec statements 1277# below. Here is an example of the print_time() function that will be created: 1278 1279# def print_time(*args): 1280# s_func = getattr(sys.modules[__name__], "sprint_time") 1281# sys.stdout.write(s_func(*args)) 1282# sys.stdout.flush() 1283 1284# Here are comments describing the 3 lines in the body of the created function. 1285# Create a reference to the "s" version of the given function in s_func (e.g. 1286# if this function name is print_time, we want s_funcname to be "sprint_time"). 1287# Call the "s" version of this function passing it all of our arguments. 1288# Write the result to stdout. 1289 1290# func_names contains a list of all print functions which should be created 1291# from their sprint counterparts. 1292func_names = ['print_time', 'print_timen', 'print_error', 'print_varx', 1293 'print_var', 'print_vars', 'print_dashes', 'indent', 1294 'print_call_stack', 'print_func_name', 'print_executing', 1295 'print_pgm_header', 'print_issuing', 'print_pgm_footer', 1296 'print_error_report', 'print', 'printn'] 1297 1298# stderr_func_names is a list of functions whose output should go to stderr 1299# rather than stdout. 1300stderr_func_names = ['print_error', 'print_error_report'] 1301 1302gp_debug_print("robot_env: " + str(robot_env)) 1303for func_name in func_names: 1304 gp_debug_print("func_name: " + func_name) 1305 if func_name in stderr_func_names: 1306 output_stream = "stderr" 1307 else: 1308 output_stream = "stdout" 1309 1310 func_def_line = "def " + func_name + "(*args):" 1311 s_func_line = " s_func = getattr(sys.modules[__name__], \"s" +\ 1312 func_name + "\")" 1313 # Generate the code to do the printing. 1314 if robot_env: 1315 func_print_lines = \ 1316 [ 1317 " BuiltIn().log_to_console(s_func(*args)," 1318 " stream='" + output_stream + "'," 1319 " no_newline=True)" 1320 ] 1321 else: 1322 func_print_lines = \ 1323 [ 1324 " sys." + output_stream + ".write(s_func(*args))", 1325 " sys." + output_stream + ".flush()" 1326 ] 1327 1328 # Create an array containing the lines of the function we wish to create. 1329 func_def = [func_def_line, s_func_line] + func_print_lines 1330 # We don't want to try to redefine the "print" function, thus the if 1331 # statement. 1332 if func_name != "print": 1333 pgm_definition_string = '\n'.join(func_def) 1334 gp_debug_print(pgm_definition_string) 1335 exec(pgm_definition_string) 1336 1337 # Insert a blank line which will be overwritten by the next several 1338 # definitions. 1339 func_def.insert(1, "") 1340 1341 # Define the "q" (i.e. quiet) version of the given print function. 1342 func_def[0] = "def q" + func_name + "(*args):" 1343 func_def[1] = " if gp_get_var_value(\"quiet\", 0): return" 1344 pgm_definition_string = '\n'.join(func_def) 1345 gp_debug_print(pgm_definition_string) 1346 exec(pgm_definition_string) 1347 1348 # Define the "d" (i.e. debug) version of the given print function. 1349 func_def[0] = "def d" + func_name + "(*args):" 1350 func_def[1] = " if not gp_get_var_value(\"debug\", 0): return" 1351 pgm_definition_string = '\n'.join(func_def) 1352 gp_debug_print(pgm_definition_string) 1353 exec(pgm_definition_string) 1354 1355 # Define the "l" (i.e. log) version of the given print function. 1356 func_def_line = "def l" + func_name + "(*args):" 1357 func_print_lines = \ 1358 [ 1359 " logging.log(getattr(logging, 'INFO'), s_func(*args))" 1360 ] 1361 1362 func_def = [func_def_line, s_func_line] + func_print_lines 1363 if func_name != "print_varx" and func_name != "print_var": 1364 pgm_definition_string = '\n'.join(func_def) 1365 gp_debug_print(pgm_definition_string) 1366 exec(pgm_definition_string) 1367 1368 if func_name == "print" or func_name == "printn": 1369 gp_debug_print("") 1370 continue 1371 1372 # Create abbreviated aliases (e.g. spvar is an alias for sprint_var). 1373 alias = re.sub("print_", "p", func_name) 1374 prefixes = ["", "s", "q", "d", "l"] 1375 for prefix in prefixes: 1376 pgm_definition_string = prefix + alias + " = " + prefix + func_name 1377 gp_debug_print(pgm_definition_string) 1378 exec(pgm_definition_string) 1379 1380 gp_debug_print("") 1381 1382############################################################################### 1383