Fixes and new support functions.

lib/gen_print.py:
  sprint_varx:  Fixed error in handling hex parm.

lib/gen_robot_print.py
  - I added support for GEN_ROBOT_PRINT_DEBUG environment variable.
  - I added the following new functions:
  - set_quiet_default
  - sprvars
  - sprint_robot_pgm_header
  - sprint_robot_error_report
  - sissuing_keyword
  - sprint_auto_vars

lib/gen_robot_valid.py:  New file.

Change-Id: I78fc30fe430323267bfd2ffaede102c0aa8dfc82
Signed-off-by: Michael Walsh <micwalsh@us.ibm.com>
diff --git a/lib/gen_print.py b/lib/gen_print.py
index e7efb16..996a289 100755
--- a/lib/gen_print.py
+++ b/lib/gen_print.py
@@ -500,7 +500,7 @@
         loc_col1_width = loc_col1_width - loc_col1_indent
         # See if the user wants the output in hex format.
         if hex:
-            if type(var_value) in (str, unicode):
+            if type(var_value) not in (int, long):
                 value_format = "%s"
                 if var_value is "":
                     var_value = "<blank>"
@@ -597,6 +597,79 @@
 
 
 ###############################################################################
+def sprint_vars(*args):
+
+    r"""
+    Sprint the values of one or more variables.
+
+    Description of args:
+    args:
+        If the first argument is an integer, it will be interpreted to be the
+        "indent" value.
+        If the second argument is an integer, it will be interpreted to be the
+        "col1_width" value.
+        If the third argument is an integer, it will be interpreted to be the
+        "hex" value.
+        All remaining parms are considered variable names which are to be
+        sprinted.
+    """
+
+    if len(args) == 0:
+        return
+
+    # Get the name of the first variable passed to this function.
+    stack_frame = 2
+    caller_func_name = sprint_func_name(2)
+    if caller_func_name.endswith("print_vars"):
+        stack_frame += 1
+
+    parm_num = 1
+
+    # Create list from args (which is a tuple) so that it can be modified.
+    args_list = list(args)
+
+    var_name = get_arg_name(None, parm_num, stack_frame)
+    # See if parm 1 is to be interpreted as "indent".
+    try:
+        if type(int(var_name)) is int:
+            indent = int(var_name)
+            args_list.pop(0)
+            parm_num += 1
+    except ValueError:
+        indent = 0
+
+    var_name = get_arg_name(None, parm_num, stack_frame)
+    # See if parm 1 is to be interpreted as "col1_width".
+    try:
+        if type(int(var_name)) is int:
+            loc_col1_width = int(var_name)
+            args_list.pop(0)
+            parm_num += 1
+    except ValueError:
+        loc_col1_width = col1_width
+
+    var_name = get_arg_name(None, parm_num, stack_frame)
+    # See if parm 1 is to be interpreted as "hex".
+    try:
+        if type(int(var_name)) is int:
+            hex = int(var_name)
+            args_list.pop(0)
+            parm_num += 1
+    except ValueError:
+        hex = 0
+
+    buffer = ""
+    for var_value in args_list:
+        var_name = get_arg_name(None, parm_num, stack_frame)
+        buffer += sprint_varx(var_name, var_value, hex, indent, loc_col1_width)
+        parm_num += 1
+
+    return buffer
+
+###############################################################################
+
+
+###############################################################################
 def lprint_varx(var_name,
                 var_value,
                 hex=0,
@@ -927,8 +1000,8 @@
 
 
 ###############################################################################
-def sissuing(cmd_buf,
-             test_mode=0):
+def sprint_issuing(cmd_buf,
+                   test_mode=0):
 
     r"""
     Return a line indicating a command that the program is about to execute.
@@ -1038,10 +1111,10 @@
 # func_names contains a list of all print functions which should be created
 # from their sprint counterparts.
 func_names = ['print_time', 'print_timen', 'print_error', 'print_varx',
-              'print_var', 'print_dashes', 'indent', 'print_call_stack',
-              'print_func_name', 'print_executing', 'print_pgm_header',
-              'issuing', 'print_pgm_footer', 'print_error_report', 'print',
-              'printn']
+              'print_var', 'print_vars', 'print_dashes', 'indent',
+              'print_call_stack', 'print_func_name', 'print_executing',
+              'print_pgm_header', 'print_issuing', 'print_pgm_footer',
+              'print_error_report', 'print', 'printn']
 
 for func_name in func_names:
     if func_name == "print":
diff --git a/lib/gen_robot_print.py b/lib/gen_robot_print.py
index 331690b..8fdd7d0 100755
--- a/lib/gen_robot_print.py
+++ b/lib/gen_robot_print.py
@@ -6,6 +6,7 @@
 
 import sys
 import re
+import os
 
 import gen_print as gp
 
@@ -13,54 +14,40 @@
 from robot.api import logger
 
 
+try:
+    # The user can set environment variable "GEN_ROBOT_PRINT_DEBUG" to get
+    # debug output from this module.
+    gen_robot_print_debug = os.environ['GEN_ROBOT_PRINT_DEBUG']
+except KeyError:
+    gen_robot_print_debug = 0
+
+
 ###############################################################################
-# In the following section of code, we will dynamically create robot versions
-# of print functions for each of the sprint functions defined in the
-# gen_print.py module.  So, for example, where we have an sprint_time()
-# function defined above that returns the time to the caller in a string, we
-# will create a corresponding rprint_time() function that will print that
-# string directly to stdout.
+def set_quiet_default(quiet=None,
+                      default=0):
 
-# It can be complicated to follow what's being creaed by the exec statement
-# below.  Here is an example of the rprint_time() function that will be
-# created (as of the time of this writing):
+    r"""
+    Return a default value for the quiet variable based on its current value,
+    the value of global ${QUIET} and default.
 
-# def rprint_time(*args):
-#   s_func = getattr(gp, "sprint_time")
-#   BuiltIn().log_to_console(s_func(*args),
-#                            stream='STDIN',
-#                            no_newline=True)
+    Description of Arguments:
+    quiet                           If this is set already, no default value
+                                    is chosen.  Otherwise, it will be set to
+                                    either the global ${QUIET} robot variable
+                                    or to default (below).
+    default                         The default value to be used if global
+                                    ${QUIET} does not exist.
+    """
 
-# Here are comments describing the lines in the body of the created function.
-# Put a reference to the "s" version of this function in s_func.
-# Call the "s" version of this function passing it all of our arguments.
-# Write the result to stdout.
+    if quiet is None:
+        # Set default quiet value.
+        try:
+            quiet = int(BuiltIn().get_variable_value("${quiet}"))
+        except TypeError:
+            quiet = int(default)
+    quiet = int(quiet)
 
-robot_prefix = "r"
-for func_name in gp.func_names:
-    # The print_var function's job is to figure out the name of arg 1 and
-    # then call print_varx.  This is not currently supported for robot
-    # programs.  Though it IS supported for python modules.
-    if func_name == "print_error":
-        output_stream = "STDERR"
-    else:
-        output_stream = "STDIN"
-    func_def = \
-        [
-            "def " + robot_prefix + func_name + "(*args):",
-            "  s_func = getattr(gp, \"s" + func_name + "\")",
-            "  BuiltIn().log_to_console(s_func(*args),"
-            " stream='" + output_stream + "',"
-            " no_newline=True)"
-        ]
-
-    pgm_definition_string = '\n'.join(func_def)
-    exec(pgm_definition_string)
-
-    # Create abbreviated aliases (e.g. rpvarx is an alias for rprint_varx).
-    alias = re.sub("print_", "p", func_name)
-    exec(robot_prefix + alias + " = " + robot_prefix + func_name)
-
+    return quiet
 
 ###############################################################################
 
@@ -73,14 +60,13 @@
     rprint stands for "Robot Print".  This keyword will print the user's
     buffer to the console.  This keyword does not write a linefeed.  It is the
     responsibility of the caller to include a line feed if desired.  This
-    keyword is essentially an alias for "Log to Console  <string>
-    <stream>".
+    keyword is essentially an alias for "Log to Console  <string>  <stream>".
 
     Description of arguments:
     buffer                          The value that is to written to stdout.
     """
 
-    BuiltIn().log_to_console(buffer, no_newline=True, stream=stream)
+    BuiltIn().log_to_console(str(buffer), no_newline=True, stream=stream)
 
 ###############################################################################
 
@@ -104,64 +90,303 @@
 
 
 ###############################################################################
-def rprint_auto_vars(headers=0):
+def sprint_vars(*args):
 
     r"""
-    This keyword will print all of the Automatic Variables described in the
-    Robot User's Guide using rpvars.
+    sprint_vars stands for "String Print Vars".  This is a robot redefinition
+    of the sprint_vars function in gen_print.py.  Given a list of variable
+    names, this keyword will string print each variable name and value such
+    that the value lines up in the same column as messages printed with rptime.
+
+    Description of arguments:
+    args:
+        If the first argument is an integer, it will be interpreted to be the
+        "indent" value.
+        If the second argument is an integer, it will be interpreted to be the
+        "col1_width" value.
+        If the third argument is an integer, it will be interpreted to be the
+        "hex" value.
+        All remaining parms are considered variable names which are to be
+        sprinted.
+    """
+
+    if len(args) == 0:
+        return
+
+    # Create list from args (which is a tuple) so that it can be modified.
+    args_list = list(args)
+
+    # See if parm 1 is to be interpreted as "indent".
+    try:
+        if type(int(args_list[0])) is int:
+            indent = int(args_list[0])
+            args_list.pop(0)
+    except ValueError:
+        indent = 0
+
+    # See if parm 2 is to be interpreted as "col1_width".
+    try:
+        if type(int(args_list[0])) is int:
+            loc_col1_width = int(args_list[0])
+            args_list.pop(0)
+    except ValueError:
+        loc_col1_width = gp.col1_width
+
+    # See if parm 2 is to be interpreted as "hex".
+    try:
+        if type(int(args_list[0])) is int:
+            hex = int(args_list[0])
+            args_list.pop(0)
+    except ValueError:
+        hex = 0
+
+    buffer = ""
+    for var_name in args_list:
+        var_value = BuiltIn().get_variable_value("${" + var_name + "}")
+        buffer += gp.sprint_varx(var_name, var_value, hex, indent,
+                                 loc_col1_width)
+
+    return buffer
+
+###############################################################################
+
+
+###############################################################################
+def sprint_pgm_header(indent=0):
+
+    r"""
+    Sprint a standardized header that robot programs should print at the
+    beginning of the run.  The header includes useful information like command
+    line, pid, userid, program parameters, etc.  Callers need to have declared
+    a global @{parm_list} variable which contains the names of all program
+    parameters.
+    """
+
+    loc_col1_width = gp.col1_width + indent
+
+    linefeed = 0
+    rprintn()
+    suite_name = BuiltIn().get_variable_value("${suite_name}")
+
+    buffer = "\n"
+    buffer += gp.sindent(gp.sprint_time("Running test suite \"" +
+                                        suite_name + "\".\n"),
+                         indent)
+    buffer += gp.sprint_pgm_header(indent, linefeed)
+
+    # Get value of global parm_list.
+    parm_list = BuiltIn().get_variable_value("${parm_list}")
+
+    buffer += sprint_vars(str(indent), str(loc_col1_width), *parm_list)
+    buffer += "\n"
+
+    return buffer
+
+###############################################################################
+
+
+###############################################################################
+def sprint_error_report(error_text="\n"):
+
+    r"""
+    Print a standardized error report that robot programs should print on
+    failure.  The report includes useful information like error text, command
+    line, pid, userid, program parameters, etc.  Callers must have declared a
+    @{parm_list} variable which contains the names of all program parameters.
+    """
+
+    buffer = ""
+    buffer += gp.sprint_dashes(width=120, char="=")
+    buffer += gp.sprint_error(error_text)
+
+    indent = 2
+    linefeed = 0
+
+    buffer += sprint_pgm_header(indent)
+
+    buffer += gp.sprint_dashes(width=120, char="=")
+
+    return buffer
+
+###############################################################################
+
+
+###############################################################################
+def sprint_issuing_keyword(cmd_buf,
+                           test_mode=0):
+
+    r"""
+    Return a line indicating a robot command (i.e. keyword + args) that the
+    program is about to execute.
+
+    For example, for the following robot code...
+
+    @{cmd_buf}=  Set Variable  Set Environment Variable  VAR1  1
+    rdprint_issuing_keyword
+
+    The output would look something like this:
+
+    #(CDT) 2016/10/27 12:04:21 - Issuing: Set Environment Variable  VAR1  1
+
+    Description of args:
+    cmd_buf                         A list containing the keyword and
+                                    arguments to be run.
+    """
+
+    buffer = ""
+    cmd_buf_str = '  '.join([str(element) for element in cmd_buf])
+    buffer += gp.sprint_issuing(cmd_buf_str, int(test_mode))
+
+    return buffer
+
+###############################################################################
+
+
+###############################################################################
+def sprint_auto_vars(headers=0):
+
+    r"""
+    This keyword will string print all of the Automatic Variables described in
+    the Robot User's Guide using rprint_vars.
 
     NOTE: Not all automatic variables are guaranteed to exist.
 
     Description of arguments:
     headers                         This indicates that a header and footer
-                                    will be printed.
+                                    should be printed.
     """
 
+    buffer = ""
     if int(headers) == 1:
-        BuiltIn().log_to_console(gp.sprint_dashes(), no_newline=True)
-        BuiltIn().log_to_console("Automatic Variables:", no_newline=False)
+        buffer += gp.sprint_dashes()
+        buffer += "Automatic Variables:"
 
-    rpvars("TEST_NAME", "TEST_TAGS", "TEST_DOCUMENTATION", "TEST_STATUS",
-           "TEST_DOCUMENTATION", "TEST_STATUS", "TEST_MESSAGE",
-           "PREV_TEST_NAME", "PREV_TEST_STATUS", "PREV_TEST_MESSAGE",
-           "SUITE_NAME", "SUITE_SOURCE", "SUITE_DOCUMENTATION",
-           "SUITE_METADATA", "SUITE_STATUS", "SUITE_MESSAGE", "KEYWORD_STATUS",
-           "KEYWORD_MESSAGE", "LOG_LEVEL", "OUTPUT_FILE", "LOG_FILE",
-           "REPORT_FILE", "DEBUG_FILE", "OUTPUT_DIR")
+    buffer += \
+        sprint_vars(
+            "TEST_NAME", "TEST_TAGS", "TEST_DOCUMENTATION", "TEST_STATUS",
+            "TEST_DOCUMENTATION", "TEST_STATUS", "TEST_MESSAGE",
+            "PREV_TEST_NAME", "PREV_TEST_STATUS", "PREV_TEST_MESSAGE",
+            "SUITE_NAME", "SUITE_SOURCE", "SUITE_DOCUMENTATION",
+            "SUITE_METADATA", "SUITE_STATUS", "SUITE_MESSAGE",
+            "KEYWORD_STATUS", "KEYWORD_MESSAGE", "LOG_LEVEL", "OUTPUT_FILE",
+            "LOG_FILE", "REPORT_FILE", "DEBUG_FILE", "OUTPUT_DIR")
 
     if int(headers) == 1:
-        BuiltIn().log_to_console(gp.sprint_dashes(), no_newline=True)
+        buffer += gp.sprint_dashes()
+
+    return buffer
 
 ###############################################################################
 
 
 ###############################################################################
-def rpvars(*var_names):
+# In the following section of code, we will dynamically create robot versions
+# of print functions for each of the sprint functions defined in the
+# gen_print.py module.  So, for example, where we have an sprint_time()
+# function defined above that returns the time to the caller in a string, we
+# will create a corresponding rprint_time() function that will print that
+# string directly to stdout.
 
-    r"""
-    rpvars stands for "Robot Print Vars".  Given a list of variable names,
-    this keyword will print each variable name and value such that the value
-    lines up in the same column as messages printed with rptime.
+# It can be complicated to follow what's being created by the exec statement
+# below.  Here is an example of the rprint_time() function that will be
+# created (as of the time of this writing):
 
-    NOTE: This function should NOT be called for local variables.  It is
-    incapable of obtaining their values.
+# def rprint_time(*args):
+#   s_func = getattr(gp, "sprint_time")
+#   BuiltIn().log_to_console(s_func(*args),
+#                            stream='STDIN',
+#                            no_newline=True)
 
-    NOTE: I intend to add code to allow the last several parms to be
-    recognized as hex, indent, etc. and passed on to rpvarx function.  See the
-    sprint_var() function in gen_print.py for details.
+# Here are comments describing the lines in the body of the created function.
+# Put a reference to the "s" version of this function in s_func.
+# Call the "s" version of this function passing it all of our arguments.  Log
+# the result to the console.
 
-    Description of arguments:
-    var_names                       A list of the names of variables to be
-                                    printed.
-    """
+robot_prefix = "r"
+robot_func_names =\
+    [
+        'print_error_report', 'print_pgm_header',
+        'print_issuing_keyword', 'print_vars', 'print_auto_vars'
+    ]
+func_names = gp.func_names + robot_func_names
+for func_name in func_names:
+    # The print_var function's job is to figure out the name of arg 1 and
+    # then call print_varx.  This is not currently supported for robot
+    # programs.  Though it IS supported for python modules.
+    if func_name == "print_error" or func_name == "print_error_report":
+        output_stream = "STDERR"
+    else:
+        output_stream = "STDIN"
+    if func_name in robot_func_names:
+        object_name = "__import__(__name__)"
+    else:
+        object_name = "gp"
+    func_def = \
+        [
+            "def " + robot_prefix + func_name + "(*args):",
+            "  s_func = getattr(" + object_name + ", \"s" + func_name + "\")",
+            "  BuiltIn().log_to_console(s_func(*args),"
+            " stream='" + output_stream + "',"
+            " no_newline=True)"
+        ]
 
-    for var_name in var_names:
-        var_value = BuiltIn().get_variable_value("${" + var_name + "}")
-        rpvarx(var_name, var_value)
+    pgm_definition_string = '\n'.join(func_def)
+    if gen_robot_print_debug:
+        rprintn(pgm_definition_string)
+    exec(pgm_definition_string)
+
+    # Now define "q" versions of each print function.  The q functions only
+    # print if global robot var "quiet" is 0.  If the global var quiet is not
+    # defined, it will be treated as though it were "1", i.e. no printing will
+    # be done.
+    func_def = \
+        [
+            "def rq" + func_name + "(*args):",
+            "  try:",
+            "    quiet = int(BuiltIn().get_variable_value(\"${quiet}\"))",
+            "  except TypeError:",
+            "    quiet = 1",
+            "  if quiet:",
+            "    return",
+            "  r" + func_name + "(*args)"
+        ]
+
+    pgm_definition_string = '\n'.join(func_def)
+    if gen_robot_print_debug:
+        rprintn(pgm_definition_string)
+    exec(pgm_definition_string)
+
+    # Now define "d" versions of each print function.  The d functions only
+    # print if global robot var "debug" is 1.
+    func_def = \
+        [
+            "def rd" + func_name + "(*args):",
+            "  try:",
+            "    debug = int(BuiltIn().get_variable_value(\"${debug}\"))",
+            "  except TypeError:",
+            "    debug = 0",
+            "  if not debug:",
+            "    return",
+            "  r" + func_name + "(*args)"
+        ]
+
+    pgm_definition_string = '\n'.join(func_def)
+    if gen_robot_print_debug:
+        rprintn(pgm_definition_string)
+    exec(pgm_definition_string)
+
+    # Create abbreviated aliases (e.g. rpvarx is an alias for rprint_varx).
+    alias = re.sub("print_", "p", func_name)
+    cmd_buf = robot_prefix + alias + " = " + robot_prefix + func_name
+    if gen_robot_print_debug:
+        rprintn(cmd_buf)
+    exec(cmd_buf)
+
+# Define an alias.  rpvar is just a special case of rpvars where the args
+# list contains only one element.
+cmd_buf = "rpvar = rpvars"
+if gen_robot_print_debug:
+    rprintn(cmd_buf)
+exec(cmd_buf)
 
 ###############################################################################
-
-
-# Define an alias.  rpvar is just a special case of rpvars where the
-# var_names list contains only one element.
-rpvar = rpvars
diff --git a/lib/gen_robot_valid.py b/lib/gen_robot_valid.py
new file mode 100755
index 0000000..8a58d8c
--- /dev/null
+++ b/lib/gen_robot_valid.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+
+r"""
+This file contains functions useful for validating variables in robot.
+"""
+
+import gen_robot_print as grp
+import gen_valid as gv
+
+from robot.libraries.BuiltIn import BuiltIn
+from robot.api import logger
+
+
+###############################################################################
+def rvalid_value(var_name,
+                 invalid_values=[],
+                 valid_values=[]):
+
+    r"""
+    rprint stands for "Robot Valid Value".  This function is the robot wrapper
+    for gen_robot_print.svalid_value.
+
+    Description of arguments:
+    var_name                        The name of the variable whose value is to
+                                    be validated.
+    invalid_values                  A list of invalid values.  If var_value is
+                                    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.
+    valid_values                    A list of invalid values.  var_value must
+                                    be equal to one of these values to be
+                                    considered valid.
+
+    Examples of robot calls and corresponding output:
+
+    Robot code...
+    rvalid_value                    MY_PARM
+
+    Output...
+    #(CDT) 2016/11/02 10:04:20 - **ERROR** Variable "MY_PARM" not found (i.e.
+    #it's undefined).
+
+    or if it is defined but blank:
+
+    Output...
+    #(CDT) 2016/11/02 10:14:24 - **ERROR** The following variable has an
+    #invalid value:
+    MY_PARM:
+
+    It must NOT be one of the following values:
+    invalid_values:
+      invalid_values[0]:         <blank>
+
+    Robot code...
+    ${invalid_values}=  Create List  one  two  three
+    ${MY_PARM}=  Set Variable  one
+    rvalid_value                    MY_PARM  invalid_values=${invalid_values}
+
+    Output...
+    #(CDT) 2016/11/02 10:20:05 - **ERROR** The following variable has an
+    #invalid value:
+    MY_PARM:                     one
+
+    It must NOT be one of the following values:
+    invalid_values:
+        invalid_values[0]:       one
+        invalid_values[1]:       two
+        invalid_values[2]:       three
+
+    """
+
+    # Note: get_variable_value() seems to have no trouble with local variables.
+    var_value = BuiltIn().get_variable_value("${" + var_name + "}")
+
+    if var_value is None:
+        var_value = ""
+        error_message = "Variable \"" + var_name +\
+                        "\" not found (i.e. it's undefined).\n"
+    else:
+        error_message = gv.svalid_value(var_value, invalid_values,
+                                        valid_values, var_name)
+    if not error_message == "":
+        error_message = grp.sprint_robot_error_report(error_message)
+        BuiltIn().fail(error_message)
+
+###############################################################################
+
+
+###############################################################################
+def rvalid_integer(var_name):
+
+    r"""
+    rprint stands for "Robot Valid Integer".  This function is the robot
+    wrapper for gen_robot_print.svalid_integer.
+
+    Description of arguments:
+    var_name                        The name of the variable whose value is to
+                                    be validated.
+
+    Examples of robot calls and corresponding output:
+
+    Robot code...
+    Rvalid Integer  MY_PARM
+
+    Output...
+    #(CDT) 2016/11/02 10:44:43 - **ERROR** Variable "MY_PARM" not found (i.e.
+    #it's undefined).
+
+    or if it is defined but blank:
+
+    Output...
+    #(CDT) 2016/11/02 10:45:37 - **ERROR** Invalid integer value:
+    MY_PARM:                     <blank>
+
+    Robot code...
+    ${MY_PARM}=  Set Variable  HELLO
+    Rvalid Integer  MY_PARM
+
+    Output...
+    #(CDT) 2016/11/02 10:46:18 - **ERROR** Invalid integer value:
+    MY_PARM:                     HELLO
+
+    """
+
+    # Note: get_variable_value() seems to have no trouble with local variables.
+    var_value = BuiltIn().get_variable_value("${" + var_name + "}")
+
+    if var_value is None:
+        var_value = ""
+        error_message = "Variable \"" + var_name +\
+                        "\" not found (i.e. it's undefined).\n"
+    else:
+        error_message = gv.svalid_integer(var_value, var_name)
+    if not error_message == "":
+        error_message = grp.sprint_robot_error_report(error_message)
+        BuiltIn().fail(error_message)
+
+###############################################################################