New exit_on_error mode for gen_valid.py

In 'exit_on_error' mode, the validation functions will exit the program
on error instead of returning False.

- Also general cleanup and improvements including:
  - New functions:
    - get_var_name
    - process_error_message
    - svalid_path
    - valid_path

Change-Id: If69243f794cf880b3677336d3b5bedd2539a5de9
Signed-off-by: Michael Walsh <micwalsh@us.ibm.com>
diff --git a/lib/gen_valid.py b/lib/gen_valid.py
index 9db81e6..e18e847 100755
--- a/lib/gen_valid.py
+++ b/lib/gen_valid.py
@@ -5,11 +5,110 @@
 gen_get_options and sprint_args.
 """
 
-import sys
 import os
-
 import gen_print as gp
 
+exit_on_error = False
+
+
+def set_exit_on_error(value):
+    r"""
+    Set the exit_on_error value to either True or False.
+
+    If exit_on_error is set, validation functions like valid_value will exit
+    the program on error instead of returning False.
+
+    Description of argument(s):
+    value                           Value to set global exit_on_error to.
+    """
+
+    global exit_on_error
+    exit_on_error = value
+
+
+def get_var_name(var_name):
+    r"""
+    If var_name has a value, simply return it.  Otherwise, get the variable
+    name of the first arguement used to call the validation function (e.g.
+    valid_value, valid_integer, etc.) and return it.
+
+    This function is designed solely for use by other functions in this file.
+
+    Example:
+
+    A programmer codes this:
+
+    valid_value(last_name)
+
+    Which results in the following call stack:
+
+    valid_value(last_name)
+      -> svalid_value(var_value...)
+        -> get_var_name(var_name)
+
+    In this example, this function will return "last_name".
+
+    Example:
+
+    err_msg = svalid_value(last_name, var_name="some_other_name")
+
+    Which results in the following call stack:
+
+    svalid_value(var_value, var_name="some_other_name")
+      -> get_var_name(var_name)
+
+    In this example, this function will return "some_other_name".
+
+    Description of argument(s):
+    var_name                        The name of the variable.
+    """
+
+    if var_name != "":
+        return var_name
+    # Calculate stack_frame_ix.  The validation functions in this file come
+    # in pairs.  There is an "s" version of each validation function (e.g.
+    # svalid_value) whose job is to return an error message string.  Then
+    # there is a wrapper function (e.g. valid_value) that will call the "s"
+    # version and print the result if there is an error.  See examples 1 and 2
+    # above for illustration.  This function must be cognizant of both
+    # scenarios to accurately determine the name of the variable being
+    # validated.  Where the "s" function is being called directly, the
+    # stack_frame_ix should be set to 3.  Where the wrapper function is being
+    # called, the stack_frame_ix should be incremented to 4.
+    stack_frame_ix = 3
+    parent_func_name = gp.sprint_func_name(2)
+    grandparent_func_name = gp.sprint_func_name(3)
+    if parent_func_name == "s" + grandparent_func_name:
+        stack_frame_ix += 1
+    var_name = gp.get_arg_name(0, 1, stack_frame_ix)
+    return var_name
+
+
+def process_error_message(error_message):
+    r"""
+    Process the error_message as follows:
+    - If the error_message is blank, return True.
+    - If the error_message contains a value:
+        - Print the error_message as part of a full error report.
+        - If global exit_on_error is set, then exit the program with a return
+        code of 1.
+        - If exit_on_error is not set, return False.
+
+    This function is designed solely for use by wrapper functions in this file
+    (e.g. "valid_value").
+
+    Description of argument(s):
+    error_message                   An error message.
+    """
+
+    if error_message == "":
+        return True
+
+    gp.print_error_report(error_message)
+    if exit_on_error:
+        exit(0)
+    return False
+
 
 def svalid_value(var_value,
                  invalid_values=[],
@@ -25,7 +124,10 @@
                                     equal to any of these, it is invalid.
                                     Note that if you specify anything for
                                     invalid_values (below), the valid_values
-                                    list is not even processed.
+                                    list is not even processed.  If you
+                                    specify nothing for both invalid_values
+                                    and valid_values, invalid_values will be
+                                    set to a default value of [""].
     valid_values                    A list of invalid values.  var_value must
                                     be equal to one of these values to be
                                     considered valid.
@@ -40,8 +142,8 @@
 
     success_message = ""
     error_message = ""
-    stack_frame_ix = 3
 
+    # Validate this function's arguments.
     len_valid_values = len(valid_values)
     len_invalid_values = len(invalid_values)
     if len_valid_values > 0 and len_invalid_values > 0:
@@ -57,11 +159,10 @@
         # Processing the valid_values list.
         if var_value in valid_values:
             return success_message
-        if var_name == "":
-            var_name = gp.get_arg_name(0, 1, stack_frame_ix)
         error_message += "The following variable has an invalid" +\
                          " value:\n" +\
-                         gp.sprint_varx(var_name, var_value, show_blanks) +\
+                         gp.sprint_varx(get_var_name(var_name), var_value,
+                                        show_blanks) +\
                          "\nIt must be one of the following values:\n" +\
                          gp.sprint_varx("valid_values", valid_values,
                                         show_blanks)
@@ -75,10 +176,9 @@
     if var_value not in invalid_values:
         return success_message
 
-    if var_name == "":
-        var_name = gp.get_arg_name(0, 1, stack_frame_ix)
     error_message += "The following variable has an invalid value:\n" +\
-                     gp.sprint_varx(var_name, var_value, show_blanks) +\
+                     gp.sprint_varx(get_var_name(var_name), var_value,
+                                    show_blanks) +\
                      "\nIt must NOT be one of the following values:\n" +\
                      gp.sprint_varx("invalid_values", invalid_values,
                                     show_blanks)
@@ -90,8 +190,8 @@
                 valid_values=[],
                 var_name=""):
     r"""
-    Return True if var_value is a valid value.  Otherwise, return False and
-    print an error message to stderr.
+    Return True if var_value is valid.  Otherwise, print an error message and
+    either return False or exit(1) depending on the value of exit_on_error.
 
     Description of arguments:
     (See description of arguments for svalid_value (above)).
@@ -99,11 +199,7 @@
 
     error_message = svalid_value(var_value, invalid_values, valid_values,
                                  var_name)
-
-    if not error_message == "":
-        gp.print_error_report(error_message)
-        return False
-    return True
+    return process_error_message(error_message)
 
 
 def svalid_integer(var_value,
@@ -132,13 +228,10 @@
         pass
 
     # If we get to this point, the validation has failed.
-    if var_name is "":
-        stack_index = 3
-        var_name = gp.get_arg_name(0, 1, stack_index)
-
     show_blanks = 1
-    error_message += "Invalid integer value:\n" +\
-                     gp.sprint_varx(var_name, var_value, show_blanks)
+    error_message +=\
+        "Invalid integer value:\n" +\
+        gp.sprint_varx(get_var_name(var_name), var_value, show_blanks)
 
     return error_message
 
@@ -146,19 +239,16 @@
 def valid_integer(var_value,
                   var_name=""):
     r"""
-    Return True if var_value is a valid integer.  Otherwise, return False and
-    print an error message to stderr.
+    Return True if var_value is a valid integer.  Otherwise, print an error
+    message and either return False or exit(1) depending on the value of
+    exit_on_error.
 
     Description of arguments:
-    (See description of arguments for svalid_value (above)).
+    (See description of arguments for svalid_integer (above)).
     """
 
     error_message = svalid_integer(var_value, var_name)
-
-    if not error_message == "":
-        gp.print_error_report(error_message)
-        return False
-    return True
+    return process_error_message(error_message)
 
 
 def svalid_dir_path(var_value,
@@ -180,11 +270,8 @@
 
     error_message = ""
     if not os.path.isdir(str(var_value)):
-        if var_name is "":
-            stack_index = 3
-            var_name = gp.get_arg_name(0, 1, stack_index)
         error_message += "The following directory does not exist:\n" +\
-                         gp.sprint_varx(var_name, var_value)
+            gp.sprint_varx(get_var_name(var_name), var_value)
 
     return error_message
 
@@ -192,20 +279,18 @@
 def valid_dir_path(var_value,
                    var_name=""):
     r"""
-    Return True if var_value is a valid integer.  Otherwise, return False and
-    print an error message to stderr.
+    Return True if var_value is a valid directory path.  Otherwise, print an
+    error message and either return False or exit(1) depending on the value of
+    exit_on_error.
+
+    Valid means that the directory path exists.
 
     Description of arguments:
-    (See description of arguments for svalid_value (above)).
+    (See description of arguments for svalid_dir_path (above)).
     """
 
     error_message = svalid_dir_path(var_value, var_name)
-
-    if not error_message == "":
-        gp.print_error_report(error_message)
-        return False
-
-    return True
+    return process_error_message(error_message)
 
 
 def svalid_file_path(var_value,
@@ -227,11 +312,8 @@
 
     error_message = ""
     if not os.path.isfile(str(var_value)):
-        if var_name is "":
-            stack_index = 3
-            var_name = gp.get_arg_name(0, 1, stack_index)
         error_message += "Invalid file (does not exist):\n" +\
-                         gp.sprint_varx(var_name, var_value)
+            gp.sprint_varx(get_var_name(var_name), var_value)
 
     return error_message
 
@@ -239,24 +321,64 @@
 def valid_file_path(var_value,
                     var_name=""):
     r"""
-    Return True if var_value is a valid integer.  Otherwise, return False and
-    print an error message to stderr.
+    Return True if var_value is a valid file path.  Otherwise, print an error
+    message and either return False or exit(1) depending on the value of
+    exit_on_error.
+
+    Valid means that the file exists.
 
     Description of arguments:
-    (See description of arguments for svalid_value (above)).
+    (See description of arguments for svalid_file_path (above)).
     """
 
     error_message = svalid_file_path(var_value, var_name)
+    return process_error_message(error_message)
 
-    if not error_message == "":
-        gp.print_error_report(error_message)
-        return False
 
-    return True
+def svalid_path(var_value,
+                var_name=""):
+    r"""
+    Return an empty string if var_value is either a valid file path or
+    directory path.  Otherwise, return an error string.
+
+    Description of arguments:
+    var_value                       The value being validated.
+    var_name                        The name of the variable whose value is
+                                    passed in var_value.  This parameter is
+                                    normally unnecessary as this function can
+                                    figure out the var_name.  This is provided
+                                    for Robot callers.  In this scenario, we
+                                    are unable to get the variable name
+                                    ourselves.
+    """
+
+    error_message = ""
+    if not (os.path.isfile(str(var_value)) or os.path.isdir(str(var_value))):
+        error_message = "Invalid path (file or directory does not exist):\n" +\
+            gp.sprint_varx(get_var_name(var_name), var_value)
+
+    return error_message
+
+
+def valid_path(var_value,
+               var_name=""):
+    r"""
+    Return True if var_value is a valid file path.  Otherwise, print an error
+    message and either return False or exit(1) depending on the value of
+    exit_on_error.
+
+    Valid means that the file exists.
+
+    Description of arguments:
+    (See description of arguments for svalid_path (above)).
+    """
+
+    error_message = svalid_path(var_value, var_name)
+    return process_error_message(error_message)
 
 
 def svalid_range(var_value,
-                 range=[],
+                 valid_range=[],
                  var_name=""):
     r"""
     Return an empty string if var_value is within the range.  Otherwise,
@@ -265,7 +387,7 @@
     Description of arguments:
     var_value                       The value being validated.  This value
                                     must be an integer.
-    range                           A list comprised of one or two elements
+    valid_range                     A list comprised of one or two elements
                                     which are the lower and upper ends of a
                                     range.  These values must be integers
                                     except where noted.  Valid specifications
@@ -281,8 +403,6 @@
     """
 
     error_message = ""
-    if var_name == "":
-        var_name = gp.get_arg_name(0, 1, stack_frame_ix=3)
 
     # Validate this function's parms:
     # First, ensure that the value is an integer.
@@ -291,75 +411,80 @@
         return error_message
     var_value = int(var_value)
 
-    len_range = len(range)
-    if len_range > 2:
-        error_message += "Programmer error - For the range parameter, you" +\
-                         " must provide a list consisting of one or two" +\
+    len_valid_range = len(valid_range)
+    if len_valid_range == 0 or len_valid_range > 2:
+        error_message += "Programmer error - For the valid_range parameter," +\
+                         " you must provide a list consisting of one or two" +\
                          " elements.\n" +\
-                         gp.sprint_var(range)
+                         gp.sprint_var(valid_range)
         return error_message
 
-    if len_range == 1 or range[0] is not None:
-        # Make sure lower range value is an integer.
-        error_message = svalid_integer(range[0], "range[0]")
+    if len_valid_range == 1 or valid_range[0] is not None:
+        # Make sure lower valid_range value is an integer.
+        error_message = svalid_integer(valid_range[0], "valid_range[0]")
         if not error_message == "":
             error_message = "Programmer error:\n" + error_message
             return error_message
-    if range[0] is not None:
-        range[0] = int(range[0])
-    if len_range == 2:
-        # Make sure upper range value is an integer.
-        error_message = svalid_integer(range[1], "range[1]")
+    if valid_range[0] is not None:
+        valid_range[0] = int(valid_range[0])
+    if len_valid_range == 2:
+        # Make sure upper valid_range value is an integer.
+        error_message = svalid_integer(valid_range[1], "valid_range[1]")
         if not error_message == "":
             error_message = "Programmer error:\n" + error_message
             return error_message
-        range[1] = int(range[1])
-
-    if len_range == 1:
-        if var_value < range[0]:
-            error_message += "The following variable is not within the" +\
-                             " expected range:\n" +\
-                             gp.sprint_varx(var_name, var_value) +\
-                             gp.sprint_varx("valid_range",
-                                            str(range[0]) + "..")
+        valid_range[1] = int(valid_range[1])
+        if valid_range[0] is not None and valid_range[0] > valid_range[1]:
+            error_message = "Programmer error - In the following range, the" +\
+                            " lower limit is greater than the upper" +\
+                            " limit:\n" + gp.sprint_varx("valid_range",
+                                                         valid_range)
             return error_message
 
-    if range[0] is None:
-        if var_value > range[1]:
+    if len_valid_range == 1:
+        if var_value < valid_range[0]:
             error_message += "The following variable is not within the" +\
                              " expected range:\n" +\
-                             gp.sprint_varx(var_name, var_value) +\
+                             gp.sprint_varx(get_var_name(var_name),
+                                            var_value) +\
                              gp.sprint_varx("valid_range",
-                                            ".." + str(range[1]))
+                                            str(valid_range[0]) + "..")
+            return error_message
+        return error_message
+
+    if valid_range[0] is None:
+        if var_value > valid_range[1]:
+            error_message += "The following variable is not within the" +\
+                             " expected range:\n" +\
+                             gp.sprint_varx(get_var_name(var_name),
+                                            var_value) +\
+                             gp.sprint_varx("valid_range",
+                                            ".." + str(valid_range[1]))
             return error_message
 
-    if var_value < range[0] or var_value > range[1]:
+    if var_value < valid_range[0] or var_value > valid_range[1]:
         error_message += "The following variable is not within the expected" +\
                          " range:\n" +\
-                         gp.sprint_varx(var_name, var_value) +\
+                         gp.sprint_varx(get_var_name(var_name), var_value) +\
                          gp.sprint_varx("valid_range",
-                                        str(range[0]) + ".." +
-                                        str(range[1]))
+                                        str(valid_range[0]) + ".." +
+                                        str(valid_range[1]))
         return error_message
 
     return error_message
 
 
 def valid_range(var_value,
-                range=[],
+                valid_range=[],
                 var_name=""):
     r"""
-    Return True if var_value is within the range.  Otherwise, return False and
-    print an error message to stderr.
+    Return True if var_value is within range.  Otherwise, print an error
+    message and either return False or exit(1) depending on the value of
+    exit_on_error.
 
     Description of arguments:
     (See description of arguments for svalid_range (above)).
     """
 
-    error_message = svalid_range(var_value, range, var_name)
-
-    if not error_message == "":
-        gp.print_error_report(error_message)
-        return False
-
-    return True
+    error_message = svalid_range(var_value, valid_range, var_name)
+    return process_error_message(error_message)