Changes to gen_print.py.

  - get_arg_name:  Improved debug output.
      - Streamline getting GET_ARG_NAME_DEBUG.
      - Added code to recognize GET_ARG_NAME_SHOW_SOURCE.
    - I added code to process calls from functions which were
      dynamically created (and therefore have no source code
      available).
  - sprint_varx:  Corrected some failures to handle dictionaries.
  - Made some pep8 corrections.

Change-Id: I6aab0707a1789458ee5920559fd88ad80b0b0d31
Signed-off-by: Michael Walsh <micwalsh@us.ibm.com>
diff --git a/lib/gen_print.py b/lib/gen_print.py
index 79e70d6..e08c4e7 100755
--- a/lib/gen_print.py
+++ b/lib/gen_print.py
@@ -161,12 +161,14 @@
     # Note: I wish to avoid recursion so I refrain from calling any function
     # that calls this function (i.e. sprint_var, valid_value, etc.).
 
-    try:
-        # The user can set environment variable "GET_ARG_NAME_DEBUG" to get
-        # debug output from this function.
-        local_debug = os.environ['GET_ARG_NAME_DEBUG']
-    except KeyError:
-        local_debug = 0
+    # The user can set environment variable "GET_ARG_NAME_DEBUG" to get debug
+    # output from this function.
+    local_debug = int(os.environ.get('GET_ARG_NAME_DEBUG', 0))
+    # In addition to GET_ARG_NAME_DEBUG, the user can set environment
+    # variable "GET_ARG_NAME_SHOW_SOURCE" to have this function include source
+    # code in the debug output.
+    local_debug_show_source = int(
+        os.environ.get('GET_ARG_NAME_SHOW_SOURCE', 0))
 
     if arg_num < 1:
         print_error("Programmer error - Variable \"arg_num\" has an invalid" +
@@ -184,59 +186,143 @@
 
     if local_debug:
         debug_indent = 2
+        print("")
+        print_dashes(0, 120)
         print(sprint_func_name() + "() parms:")
         print_varx("var", var, 0, debug_indent)
         print_varx("arg_num", arg_num, 0, debug_indent)
         print_varx("stack_frame_ix", stack_frame_ix, 0, debug_indent)
+        print("")
+        print_call_stack(debug_indent, 2)
 
-    try:
-        frame, filename, cur_line_no, function_name, lines, index = \
-            inspect.stack()[stack_frame_ix]
-    except IndexError:
-        print_error("Programmer error - The caller has asked for information" +
-                    " about the stack frame at index \"" +
-                    str(stack_frame_ix) + "\".  However, the stack only" +
-                    " contains " + str(len(inspect.stack())) + " entries." +
-                    "  Therefore the stack frame index is out of range.\n")
-        return
-
-    if local_debug:
-        print("\nVariables retrieved from inspect.stack() function:")
-        print_varx("frame", frame, 0, debug_indent)
-        print_varx("filename", filename, 0, debug_indent)
-        print_varx("cur_line_no", cur_line_no, 0, debug_indent)
-        print_varx("function_name", function_name, 0, debug_indent)
-        print_varx("lines", lines, 0, debug_indent)
-        print_varx("index", index, 0, debug_indent)
-
-    composite_line = lines[0].strip()
+    for count in range(0, 2):
+        try:
+            frame, filename, cur_line_no, function_name, lines, index = \
+                inspect.stack()[stack_frame_ix]
+        except IndexError:
+            print_error("Programmer error - The caller has asked for" +
+                        " information about the stack frame at index \"" +
+                        str(stack_frame_ix) + "\".  However, the stack" +
+                        " only contains " + str(len(inspect.stack())) +
+                        " entries.  Therefore the stack frame index is out" +
+                        " of range.\n")
+            return
+        if filename != "<string>":
+            break
+        # filename of "<string>" may mean that the function in question was
+        # defined dynamically and therefore its code stack is inaccessible.
+        # This may happen with functions like "rqprint_var".  In this case,
+        # we'll increment the stack_frame_ix and try again.
+        stack_frame_ix += 1
+        if local_debug:
+            print("Adjusted stack_frame_ix...")
+            print_varx("stack_frame_ix", stack_frame_ix, 0, debug_indent)
 
     called_func_name = sprint_func_name(stack_frame_ix)
-    # Needed to add a right anchor to func_regex for cases like this where
-    # there is an arg whose name is a substring of the function name.  So the
-    # function name needs to be bounded on the right by zero or more spaces
-    # and a left parenthesis.
-    # if not valid_value(whatever, valid_values=["one", "two"]):
-    func_regex = ".*" + called_func_name + "[ ]*\("
-    # if not re.match(r".*" + called_func_name, composite_line):
-    if not re.match(func_regex, composite_line):
-        # The called function name was not found in the composite line.  The
-        # caller may be using a function alias.
-        # I added code to handle pvar, qpvar, dpvar, etc. aliases.
-        # pvar is an alias for print_var.  However, when it is used,
-        # sprint_func_name() returns the non-alias version, i.e. "print_var".
-        # Adjusting for that here.
-        alias = re.sub("print_var", "pvar", called_func_name)
-        if local_debug:
-            print_varx("alias", alias, 0, debug_indent)
-        called_func_name = alias
-        func_regex = ".*" + called_func_name + "[ ]*\("
+
+    module = inspect.getmodule(frame)
+
+    # Though I would expect inspect.getsourcelines(frame) to get all module
+    # source lines if the frame is "<module>", it doesn't do that.  Therefore,
+    # for this special case, I will do inspect.getsourcelines(module).
+    if function_name == "<module>":
+        source_lines, source_line_num =\
+            inspect.getsourcelines(module)
+        line_ix = cur_line_no - source_line_num - 1
+    else:
+        source_lines, source_line_num =\
+            inspect.getsourcelines(frame)
+        line_ix = cur_line_no - source_line_num
+
+    if local_debug:
+        print("\n  Variables retrieved from inspect.stack() function:")
+        print_varx("frame", frame, 0, debug_indent + 2)
+        print_varx("filename", filename, 0, debug_indent + 2)
+        print_varx("cur_line_no", cur_line_no, 0, debug_indent + 2)
+        print_varx("function_name", function_name, 0, debug_indent + 2)
+        print_varx("lines", lines, 0, debug_indent + 2)
+        print_varx("index", index, 0, debug_indent + 2)
+        print_varx("source_line_num", source_line_num, 0, debug_indent)
+        print_varx("line_ix", line_ix, 0, debug_indent)
+        if local_debug_show_source:
+            print_varx("source_lines", source_lines, 0, debug_indent)
+        print_varx("called_func_name", called_func_name, 0, debug_indent)
+
+    # Get a list of all functions defined for the module.  Note that this
+    # doesn't work consistently when _run_exitfuncs is at the top of the stack
+    # (i.e. if we're running an exit function).  I've coded a work-around
+    # below for this deficiency.
+    all_functions = inspect.getmembers(module, inspect.isfunction)
+
+    # Get called_func_id by searching for our function in the list of all
+    # functions.
+    called_func_id = None
+    for func_name, function in all_functions:
+        if func_name == called_func_name:
+            called_func_id = id(function)
+            break
+    # NOTE: The only time I've found that called_func_id can't be found is
+    # when we're running from an exit function.
+
+    # Look for other functions in module with matching id.
+    aliases = set([called_func_name])
+    for func_name, function in all_functions:
+        if func_name == called_func_name:
+            continue
+        func_id = id(function)
+        if func_id == called_func_id:
+            aliases.add(func_name)
+
+    # In most cases, my general purpose code above will find all aliases.
+    # However, for the odd case (i.e. running from exit function), I've added
+    # code to handle pvar, qpvar, dpvar, etc. aliases explicitly since they
+    # are defined in this module and used frequently.
+    # pvar is an alias for print_var.
+    aliases.add(re.sub("print_var", "pvar", called_func_name))
+
+    func_regex = ".*(" + '|'.join(aliases) + ")[ ]*\("
+
+    # Search backward through source lines looking for the calling function
+    # name.
+    found = False
+    for start_line_ix in range(line_ix, 0, -1):
+        # Skip comment lines.
+        if re.match(r"[ ]*#", source_lines[start_line_ix]):
+            continue
+        if re.match(func_regex, source_lines[start_line_ix]):
+            found = True
+            break
+    if not found:
+        print_error("Programmer error - Could not find the source line with" +
+                    " a reference to function \"" + called_func_name + "\".\n")
+        return
+
+    # Search forward through the source lines looking for a line with the
+    # same indentation as the start time.  The end of our composite line
+    # should be the line preceding that line.
+    start_indent = len(source_lines[start_line_ix]) -\
+        len(source_lines[start_line_ix].lstrip(' '))
+    end_line_ix = line_ix
+    for end_line_ix in range(line_ix + 1, len(source_lines)):
+        if source_lines[end_line_ix].strip() == "":
+            continue
+        line_indent = len(source_lines[end_line_ix]) -\
+            len(source_lines[end_line_ix].lstrip(' '))
+        if line_indent == start_indent:
+            end_line_ix -= 1
+            break
+
+    # Join the start line through the end line into a composite line.
+    composite_line = ''.join(map(str.strip,
+                             source_lines[start_line_ix:end_line_ix + 1]))
 
     # arg_list_etc = re.sub(".*" + called_func_name, "", composite_line)
     arg_list_etc = "(" + re.sub(func_regex, "", composite_line)
     if local_debug:
+        print_varx("aliases", aliases, 0, debug_indent)
         print_varx("func_regex", func_regex, 0, debug_indent)
-        print_varx("called_func_name", called_func_name, 0, debug_indent)
+        print_varx("start_line_ix", start_line_ix, 0, debug_indent)
+        print_varx("end_line_ix", end_line_ix, 0, debug_indent)
         print_varx("composite_line", composite_line, 0, debug_indent)
         print_varx("arg_list_etc", arg_list_etc, 0, debug_indent)
 
@@ -283,6 +369,7 @@
     if local_debug:
         print_varx("args_list", args_list, 0, debug_indent)
         print_varx("argument", argument, 0, debug_indent)
+        print_dashes(0, 120)
 
     return argument
 
@@ -518,15 +605,18 @@
         try:
             length = len(var_value)
         except TypeError:
-            pass
+            length = 0
         ix = 0
         loc_trailing_char = "\n"
         type_is_dict = 0
-        try:
-            if type(var_value) in (dict, collections.OrderedDict):
-                type_is_dict = 1
-        except AttributeError:
-            pass
+        if type(var_value) is dict:
+            type_is_dict = 1
+        if not type_is_dict:
+            try:
+                if type(var_value) is collections.OrderedDict:
+                    type_is_dict = 1
+            except AttributeError:
+                pass
         if not type_is_dict:
             try:
                 if type(var_value) is DotDict:
@@ -570,6 +660,7 @@
             format_string = "%" + str(loc_col1_indent) + "s%-" \
                 + str(loc_col1_width) + "s" + value_format + trailing_char
             return format_string % ("", var_name + ":", var_value)
+
         return buffer
 
     return ""
@@ -730,7 +821,7 @@
     """
 
     width = int(width)
-    buffer = " "*int(indent) + char*width
+    buffer = " " * int(indent) + char * width
     if line_feed:
         buffer += "\n"
 
@@ -805,7 +896,12 @@
         if ix < stack_frame_ix:
             ix += 1
             continue
-        lineno = str(stack_frame[2])
+        # I want the line number shown to be the line where you find the line
+        # shown.
+        try:
+            line_num = str(current_stack[ix + 1][2])
+        except IndexError:
+            line_num = ""
         func_name = str(stack_frame[3])
         if func_name == "?":
             # "?" is the name used when code is not in a function.
@@ -831,7 +927,7 @@
             # Now we need to print this in a nicely-wrapped way.
             func_and_args = func_name + " " + args_str
 
-        buffer += sindent(format_string % (lineno, func_and_args), indent)
+        buffer += sindent(format_string % (line_num, func_and_args), indent)
         ix += 1
 
     buffer += sprint_dashes(indent)