17423c01aSMichael Walsh#!/usr/bin/env python
27423c01aSMichael Walsh
37423c01aSMichael Walshr"""
4da20f845SMichael WalshThis module provides validation functions like valid_value(), valid_integer(), etc.
57423c01aSMichael Walsh"""
67423c01aSMichael Walsh
778bdfdd6SMichael Walshimport os
87423c01aSMichael Walshimport gen_print as gp
9be3a8150SMichael Walshimport gen_cmd as gc
10018e25fbSMichael Walshimport func_args as fa
118333a18dSMichael Walshimport datetime
127423c01aSMichael Walsh
13e23b5ad3SMichael Walshexit_on_error = False
14e23b5ad3SMichael Walsh
15e23b5ad3SMichael Walsh
16e23b5ad3SMichael Walshdef set_exit_on_error(value):
17e23b5ad3SMichael Walsh    r"""
18e23b5ad3SMichael Walsh    Set the exit_on_error value to either True or False.
19e23b5ad3SMichael Walsh
20da20f845SMichael Walsh    If exit_on_error is set, validation functions like valid_value() will exit the program on error instead
21da20f845SMichael Walsh    of returning False.
22e23b5ad3SMichael Walsh
23e23b5ad3SMichael Walsh    Description of argument(s):
24e23b5ad3SMichael Walsh    value                           Value to set global exit_on_error to.
25e23b5ad3SMichael Walsh    """
26e23b5ad3SMichael Walsh
27e23b5ad3SMichael Walsh    global exit_on_error
28e23b5ad3SMichael Walsh    exit_on_error = value
29e23b5ad3SMichael Walsh
30e23b5ad3SMichael Walsh
3189eff544SMichael Walshdef get_var_name(var_name):
32e23b5ad3SMichael Walsh    r"""
33da20f845SMichael Walsh    If var_name is not None, simply return its value.  Otherwise, get the variable name of the first argument
34da20f845SMichael Walsh    used to call the validation function (e.g. valid, valid_integer, etc.) and return it.
35e23b5ad3SMichael Walsh
36e23b5ad3SMichael Walsh    This function is designed solely for use by other functions in this file.
37e23b5ad3SMichael Walsh
38e23b5ad3SMichael Walsh    Example:
39e23b5ad3SMichael Walsh
40e23b5ad3SMichael Walsh    A programmer codes this:
41e23b5ad3SMichael Walsh
42e23b5ad3SMichael Walsh    valid_value(last_name)
43e23b5ad3SMichael Walsh
44e23b5ad3SMichael Walsh    Which results in the following call stack:
45e23b5ad3SMichael Walsh
46e23b5ad3SMichael Walsh    valid_value(last_name)
47e23b5ad3SMichael Walsh      -> get_var_name(var_name)
48e23b5ad3SMichael Walsh
49e23b5ad3SMichael Walsh    In this example, this function will return "last_name".
50e23b5ad3SMichael Walsh
51e23b5ad3SMichael Walsh    Example:
52e23b5ad3SMichael Walsh
53018e25fbSMichael Walsh    err_msg = valid_value(last_name, var_name="some_other_name")
54e23b5ad3SMichael Walsh
55e23b5ad3SMichael Walsh    Which results in the following call stack:
56e23b5ad3SMichael Walsh
57018e25fbSMichael Walsh    valid_value(var_value, var_name="some_other_name")
58e23b5ad3SMichael Walsh      -> get_var_name(var_name)
59e23b5ad3SMichael Walsh
60e23b5ad3SMichael Walsh    In this example, this function will return "some_other_name".
61e23b5ad3SMichael Walsh
62e23b5ad3SMichael Walsh    Description of argument(s):
63e23b5ad3SMichael Walsh    var_name                        The name of the variable.
64e23b5ad3SMichael Walsh    """
65e23b5ad3SMichael Walsh
6689eff544SMichael Walsh    return var_name or gp.get_arg_name(0, 1, stack_frame_ix=3)
67e23b5ad3SMichael Walsh
68e23b5ad3SMichael Walsh
69e23b5ad3SMichael Walshdef process_error_message(error_message):
70e23b5ad3SMichael Walsh    r"""
71018e25fbSMichael Walsh    Process the error_message in the manner described below.
72e23b5ad3SMichael Walsh
73018e25fbSMichael Walsh    This function is designed solely for use by other functions in this file.
74018e25fbSMichael Walsh
75018e25fbSMichael Walsh    NOTE: A blank error_message means that there is no error.
76018e25fbSMichael Walsh
77da20f845SMichael Walsh    For the following explanations, assume the caller of this function is a function with the following
78da20f845SMichael Walsh    definition:
7989eff544SMichael Walsh    valid_value(var_value, valid_values=[], invalid_values=[], var_name=None):
80018e25fbSMichael Walsh
81da20f845SMichael Walsh    If the user of valid_value() is assigning the valid_value() return value to a variable,
82da20f845SMichael Walsh    process_error_message() will simply return the error_message.  This mode of usage is illustrated by the
83da20f845SMichael Walsh    following example:
84018e25fbSMichael Walsh
85018e25fbSMichael Walsh    error_message = valid_value(var1)
86018e25fbSMichael Walsh
87da20f845SMichael Walsh    This mode is useful for callers who wish to validate a variable and then decide for themselves what to do
88da20f845SMichael Walsh    with the error_message (e.g. raise(error_message), BuiltIn().fail(error_message), etc.).
89018e25fbSMichael Walsh
90da20f845SMichael Walsh    If the user of valid_value() is NOT assigning the valid_value() return value to a variable,
91da20f845SMichael Walsh    process_error_message() will behave as follows.
92018e25fbSMichael Walsh
93da20f845SMichael Walsh    First, if error_message is non-blank, it will be printed to stderr via a call to
94da20f845SMichael Walsh    gp.print_error_report(error_message).
95018e25fbSMichael Walsh
96018e25fbSMichael Walsh    If exit_on_error is set:
97018e25fbSMichael Walsh    - If the error_message is blank, simply return.
98da20f845SMichael Walsh    - If the error_message is non-blank, exit the program with a return code of 1.
99018e25fbSMichael Walsh
100018e25fbSMichael Walsh    If exit_on_error is NOT set:
101018e25fbSMichael Walsh    - If the error_message is blank, return True.
102018e25fbSMichael Walsh    - If the error_message is non-blank, return False.
103e23b5ad3SMichael Walsh
104e23b5ad3SMichael Walsh    Description of argument(s):
105e23b5ad3SMichael Walsh    error_message                   An error message.
106e23b5ad3SMichael Walsh    """
107e23b5ad3SMichael Walsh
108da20f845SMichael Walsh    # Determine whether the caller's caller is assigning the result to a variable.
109018e25fbSMichael Walsh    l_value = gp.get_arg_name(None, -1, stack_frame_ix=3)
110018e25fbSMichael Walsh    if l_value:
111018e25fbSMichael Walsh        return error_message
112018e25fbSMichael Walsh
113e23b5ad3SMichael Walsh    if error_message == "":
114018e25fbSMichael Walsh        if exit_on_error:
115018e25fbSMichael Walsh            return
116e23b5ad3SMichael Walsh        return True
117e23b5ad3SMichael Walsh
118018e25fbSMichael Walsh    gp.print_error_report(error_message, stack_frame_ix=4)
119e23b5ad3SMichael Walsh    if exit_on_error:
12010a4d98bSMichael Walsh        exit(1)
121e23b5ad3SMichael Walsh    return False
122e23b5ad3SMichael Walsh
1237423c01aSMichael Walsh
124da20f845SMichael Walsh# Note to programmers:  All of the validation functions in this module should follow the same basic template:
12589eff544SMichael Walsh# def valid_value(var_value, var1, var2, varn, var_name=None):
126018e25fbSMichael Walsh#
127018e25fbSMichael Walsh#     error_message = ""
128018e25fbSMichael Walsh#     if not valid:
12989eff544SMichael Walsh#         var_name = get_var_name(var_name)
130018e25fbSMichael Walsh#         error_message += "The following variable is invalid because...:\n"
131018e25fbSMichael Walsh#         error_message += gp.sprint_varx(var_name, var_value, gp.blank())
132018e25fbSMichael Walsh#
133018e25fbSMichael Walsh#     return process_error_message(error_message)
1347423c01aSMichael Walsh
135018e25fbSMichael Walsh
136da20f845SMichael Walsh# The docstring header and footer will be added to each validation function's existing docstring.
137018e25fbSMichael Walshdocstring_header = \
138018e25fbSMichael Walsh    r"""
139018e25fbSMichael Walsh    Determine whether var_value is valid, construct an error_message and call
140018e25fbSMichael Walsh    process_error_message(error_message).
141018e25fbSMichael Walsh
142da20f845SMichael Walsh    See the process_error_message() function defined in this module for a description of how error messages
143da20f845SMichael Walsh    are processed.
1447423c01aSMichael Walsh    """
1457423c01aSMichael Walsh
146018e25fbSMichael Walshadditional_args_docstring_footer = \
147018e25fbSMichael Walsh    r"""
148da20f845SMichael Walsh    var_name                        The name of the variable whose value is passed in var_value.  For the
149da20f845SMichael Walsh                                    general case, this argument is unnecessary as this function can figure
150da20f845SMichael Walsh                                    out the var_name.  This is provided for Robot callers in which case, this
151da20f845SMichael Walsh                                    function lacks the ability to determine the variable name.
152018e25fbSMichael Walsh    """
153018e25fbSMichael Walsh
154018e25fbSMichael Walsh
15589eff544SMichael Walshdef valid_type(var_value, required_type, var_name=None):
156018e25fbSMichael Walsh    r"""
157018e25fbSMichael Walsh    The variable value is valid if it is of the required type.
158018e25fbSMichael Walsh
159018e25fbSMichael Walsh    Examples:
160018e25fbSMichael Walsh
161018e25fbSMichael Walsh    valid_type(var1, int)
162018e25fbSMichael Walsh
163018e25fbSMichael Walsh    valid_type(var1, (list, dict))
164018e25fbSMichael Walsh
165018e25fbSMichael Walsh    Description of argument(s):
166018e25fbSMichael Walsh    var_value                       The value being validated.
167da20f845SMichael Walsh    required_type                   A type or a tuple of types (e.g. str, int, etc.).
168018e25fbSMichael Walsh    """
169018e25fbSMichael Walsh
170018e25fbSMichael Walsh    error_message = ""
171018e25fbSMichael Walsh    if type(required_type) is tuple:
172018e25fbSMichael Walsh        if type(var_value) in required_type:
173018e25fbSMichael Walsh            return process_error_message(error_message)
174018e25fbSMichael Walsh    else:
175018e25fbSMichael Walsh        if type(var_value) is required_type:
176018e25fbSMichael Walsh            return process_error_message(error_message)
177018e25fbSMichael Walsh
178018e25fbSMichael Walsh    # If we get to this point, the validation has failed.
17989eff544SMichael Walsh    var_name = get_var_name(var_name)
180018e25fbSMichael Walsh    error_message += "Invalid variable type:\n"
181018e25fbSMichael Walsh    error_message += gp.sprint_varx(var_name, var_value,
182018e25fbSMichael Walsh                                    gp.blank() | gp.show_type())
183018e25fbSMichael Walsh    error_message += "\n"
184018e25fbSMichael Walsh    error_message += gp.sprint_var(required_type)
185018e25fbSMichael Walsh
186018e25fbSMichael Walsh    return process_error_message(error_message)
187018e25fbSMichael Walsh
188018e25fbSMichael Walsh
18989eff544SMichael Walshdef valid_value(var_value, valid_values=[], invalid_values=[], var_name=None):
190018e25fbSMichael Walsh
191018e25fbSMichael Walsh    r"""
192da20f845SMichael Walsh    The variable value is valid if it is either contained in the valid_values list or if it is NOT contained
193da20f845SMichael Walsh    in the invalid_values list.  If the caller specifies nothing for either of these 2 arguments,
194da20f845SMichael Walsh    invalid_values will be initialized to ['', None].  This is a good way to fail on variables which contain
195da20f845SMichael Walsh    blank values.
196018e25fbSMichael Walsh
197018e25fbSMichael Walsh    It is illegal to specify both valid_values and invalid values.
198018e25fbSMichael Walsh
199018e25fbSMichael Walsh    Example:
200018e25fbSMichael Walsh
201018e25fbSMichael Walsh    var1 = ''
202018e25fbSMichael Walsh    valid_value(var1)
203018e25fbSMichael Walsh
204da20f845SMichael Walsh    This code would fail because var1 is blank and the default value for invalid_values is ['', None].
205018e25fbSMichael Walsh
206018e25fbSMichael Walsh    Example:
207018e25fbSMichael Walsh    var1 = 'yes'
208018e25fbSMichael Walsh    valid_value(var1, valid_values=['yes', 'true'])
209018e25fbSMichael Walsh
210018e25fbSMichael Walsh    This code would pass.
211018e25fbSMichael Walsh
212018e25fbSMichael Walsh    Description of argument(s):
213018e25fbSMichael Walsh    var_value                       The value being validated.
214da20f845SMichael Walsh    valid_values                    A list of valid values.  The variable value must be equal to one of these
215da20f845SMichael Walsh                                    values to be considered valid.
216da20f845SMichael Walsh    invalid_values                  A list of invalid values.  If the variable value is equal to any of
217da20f845SMichael Walsh                                    these, it is considered invalid.
218018e25fbSMichael Walsh    """
219018e25fbSMichael Walsh
220bec416ddSMichael Walsh    error_message = ""
221bec416ddSMichael Walsh
222e23b5ad3SMichael Walsh    # Validate this function's arguments.
2237423c01aSMichael Walsh    len_valid_values = len(valid_values)
2247423c01aSMichael Walsh    len_invalid_values = len(invalid_values)
2257423c01aSMichael Walsh    if len_valid_values > 0 and len_invalid_values > 0:
226018e25fbSMichael Walsh        error_message += "Programmer error - You must provide either an"
227018e25fbSMichael Walsh        error_message += " invalid_values list or a valid_values"
228018e25fbSMichael Walsh        error_message += " list but NOT both:\n"
229018e25fbSMichael Walsh        error_message += gp.sprint_var(invalid_values)
230018e25fbSMichael Walsh        error_message += gp.sprint_var(valid_values)
231018e25fbSMichael Walsh        return process_error_message(error_message)
2327423c01aSMichael Walsh
2330f5fa532SMichael Walsh    error_message = valid_type(valid_values, list, var_name='valid_values')
2340f5fa532SMichael Walsh    if error_message:
2350f5fa532SMichael Walsh        return process_error_message(error_message)
2360f5fa532SMichael Walsh
2370f5fa532SMichael Walsh    error_message = valid_type(invalid_values, list, var_name='invalid_values')
2380f5fa532SMichael Walsh    if error_message:
2390f5fa532SMichael Walsh        return process_error_message(error_message)
2400f5fa532SMichael Walsh
2417423c01aSMichael Walsh    if len_valid_values > 0:
2427423c01aSMichael Walsh        # Processing the valid_values list.
2437423c01aSMichael Walsh        if var_value in valid_values:
244018e25fbSMichael Walsh            return process_error_message(error_message)
24589eff544SMichael Walsh        var_name = get_var_name(var_name)
246018e25fbSMichael Walsh        error_message += "Invalid variable value:\n"
247018e25fbSMichael Walsh        error_message += gp.sprint_varx(var_name, var_value,
248018e25fbSMichael Walsh                                        gp.blank() | gp.verbose()
249018e25fbSMichael Walsh                                        | gp.show_type())
250018e25fbSMichael Walsh        error_message += "\n"
251018e25fbSMichael Walsh        error_message += "It must be one of the following values:\n"
252018e25fbSMichael Walsh        error_message += "\n"
253018e25fbSMichael Walsh        error_message += gp.sprint_var(valid_values,
254018e25fbSMichael Walsh                                       gp.blank() | gp.show_type())
255018e25fbSMichael Walsh        return process_error_message(error_message)
2567423c01aSMichael Walsh
2577423c01aSMichael Walsh    if len_invalid_values == 0:
258bec416ddSMichael Walsh        # Assign default value.
259018e25fbSMichael Walsh        invalid_values = ["", None]
2607423c01aSMichael Walsh
2617423c01aSMichael Walsh    # Assertion: We have an invalid_values list.  Processing it now.
2627423c01aSMichael Walsh    if var_value not in invalid_values:
263018e25fbSMichael Walsh        return process_error_message(error_message)
264bec416ddSMichael Walsh
26589eff544SMichael Walsh    var_name = get_var_name(var_name)
266018e25fbSMichael Walsh    error_message += "Invalid variable value:\n"
267018e25fbSMichael Walsh    error_message += gp.sprint_varx(var_name, var_value,
268018e25fbSMichael Walsh                                    gp.blank() | gp.verbose()
269018e25fbSMichael Walsh                                    | gp.show_type())
270018e25fbSMichael Walsh    error_message += "\n"
271*53902dd1SMichael Walsh    error_message += "It must NOT be any of the following values:\n"
272018e25fbSMichael Walsh    error_message += "\n"
273018e25fbSMichael Walsh    error_message += gp.sprint_var(invalid_values,
274018e25fbSMichael Walsh                                   gp.blank() | gp.show_type())
275e23b5ad3SMichael Walsh    return process_error_message(error_message)
2767423c01aSMichael Walsh
277bec416ddSMichael Walsh
27889eff544SMichael Walshdef valid_range(var_value, lower=None, upper=None, var_name=None):
279bec416ddSMichael Walsh    r"""
280018e25fbSMichael Walsh    The variable value is valid if it is within the specified range.
281bec416ddSMichael Walsh
282da20f845SMichael Walsh    This function can be used with any type of operands where they can have a greater than/less than
283da20f845SMichael Walsh    relationship to each other (e.g. int, float, str).
284018e25fbSMichael Walsh
285018e25fbSMichael Walsh    Description of argument(s):
286bec416ddSMichael Walsh    var_value                       The value being validated.
287da20f845SMichael Walsh    lower                           The lower end of the range.  If not None, the var_value must be greater
288da20f845SMichael Walsh                                    than or equal to lower.
289da20f845SMichael Walsh    upper                           The upper end of the range.  If not None, the var_value must be less than
290da20f845SMichael Walsh                                    or equal to upper.
291bec416ddSMichael Walsh    """
292bec416ddSMichael Walsh
293bec416ddSMichael Walsh    error_message = ""
294b9d8dfd2SMichael Walsh    if lower is None and upper is None:
295018e25fbSMichael Walsh        return process_error_message(error_message)
296b9d8dfd2SMichael Walsh    if lower is None and var_value <= upper:
297018e25fbSMichael Walsh        return process_error_message(error_message)
298b9d8dfd2SMichael Walsh    if upper is None and var_value >= lower:
299018e25fbSMichael Walsh        return process_error_message(error_message)
300018e25fbSMichael Walsh    if lower and upper:
301018e25fbSMichael Walsh        if lower > upper:
30289eff544SMichael Walsh            var_name = get_var_name(var_name)
303018e25fbSMichael Walsh            error_message += "Programmer error - the lower value is greater"
304018e25fbSMichael Walsh            error_message += " than the upper value:\n"
305018e25fbSMichael Walsh            error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type())
306018e25fbSMichael Walsh            return process_error_message(error_message)
307018e25fbSMichael Walsh        if lower <= var_value <= upper:
308018e25fbSMichael Walsh            return process_error_message(error_message)
309bec416ddSMichael Walsh
31089eff544SMichael Walsh    var_name = get_var_name(var_name)
311018e25fbSMichael Walsh    error_message += "The following variable is not within the expected"
312018e25fbSMichael Walsh    error_message += " range:\n"
313018e25fbSMichael Walsh    error_message += gp.sprint_varx(var_name, var_value, gp.show_type())
314018e25fbSMichael Walsh    error_message += "\n"
315018e25fbSMichael Walsh    error_message += "range:\n"
316018e25fbSMichael Walsh    error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type(), indent=2)
317e23b5ad3SMichael Walsh    return process_error_message(error_message)
3187423c01aSMichael Walsh
31978bdfdd6SMichael Walsh
32089eff544SMichael Walshdef valid_integer(var_value, lower=None, upper=None, var_name=None):
32178bdfdd6SMichael Walsh    r"""
322da20f845SMichael Walsh    The variable value is valid if it is an integer or can be interpreted as an integer (e.g. 7, "7", etc.).
32378bdfdd6SMichael Walsh
324da20f845SMichael Walsh    This function also calls valid_range to make sure the integer value is within the specified range (if
325da20f845SMichael Walsh    any).
326018e25fbSMichael Walsh
327018e25fbSMichael Walsh    Description of argument(s):
32878bdfdd6SMichael Walsh    var_value                       The value being validated.
329da20f845SMichael Walsh    lower                           The lower end of the range.  If not None, the var_value must be greater
330da20f845SMichael Walsh                                    than or equal to lower.
331da20f845SMichael Walsh    upper                           The upper end of the range.  If not None, the var_value must be less than
332da20f845SMichael Walsh                                    or equal to upper.
333018e25fbSMichael Walsh    """
334018e25fbSMichael Walsh
335018e25fbSMichael Walsh    error_message = ""
33689eff544SMichael Walsh    var_name = get_var_name(var_name)
337018e25fbSMichael Walsh    try:
338018e25fbSMichael Walsh        var_value = int(str(var_value), 0)
339018e25fbSMichael Walsh    except ValueError:
340018e25fbSMichael Walsh        error_message += "Invalid integer value:\n"
341018e25fbSMichael Walsh        error_message += gp.sprint_varx(var_name, var_value,
342018e25fbSMichael Walsh                                        gp.blank() | gp.show_type())
343018e25fbSMichael Walsh        return process_error_message(error_message)
344018e25fbSMichael Walsh
345018e25fbSMichael Walsh    # Check the range (if any).
346018e25fbSMichael Walsh    if lower:
347018e25fbSMichael Walsh        lower = int(str(lower), 0)
348018e25fbSMichael Walsh    if upper:
349018e25fbSMichael Walsh        upper = int(str(upper), 0)
350018e25fbSMichael Walsh    error_message = valid_range(var_value, lower, upper, var_name=var_name)
351018e25fbSMichael Walsh
352018e25fbSMichael Walsh    return process_error_message(error_message)
353018e25fbSMichael Walsh
354018e25fbSMichael Walsh
3558333a18dSMichael Walshdef valid_float(var_value, lower=None, upper=None, var_name=None):
3568333a18dSMichael Walsh    r"""
3578333a18dSMichael Walsh    The variable value is valid if it is a floating point value or can be interpreted as a floating point
3588333a18dSMichael Walsh    value (e.g. 7.5, "7.5", etc.).
3598333a18dSMichael Walsh
3608333a18dSMichael Walsh    This function also calls valid_range to make sure the float value is within the specified range (if any).
3618333a18dSMichael Walsh
3628333a18dSMichael Walsh    Description of argument(s):
3638333a18dSMichael Walsh    var_value                       The value being validated.
3648333a18dSMichael Walsh    lower                           The lower end of the range.  If not None, the var_value must be greater
3658333a18dSMichael Walsh                                    than or equal to lower.
3668333a18dSMichael Walsh    upper                           The upper end of the range.  If not None, the var_value must be less than
3678333a18dSMichael Walsh                                    or equal to upper.
3688333a18dSMichael Walsh    """
3698333a18dSMichael Walsh
3708333a18dSMichael Walsh    error_message = ""
3718333a18dSMichael Walsh    var_name = get_var_name(var_name)
3728333a18dSMichael Walsh    try:
3738333a18dSMichael Walsh        var_value = float(str(var_value))
3748333a18dSMichael Walsh    except ValueError:
3758333a18dSMichael Walsh        error_message += "Invalid float value:\n"
3768333a18dSMichael Walsh        error_message += gp.sprint_varx(var_name, var_value,
3778333a18dSMichael Walsh                                        gp.blank() | gp.show_type())
3788333a18dSMichael Walsh        return process_error_message(error_message)
3798333a18dSMichael Walsh
3808333a18dSMichael Walsh    # Check the range (if any).
3818333a18dSMichael Walsh    if lower:
3828333a18dSMichael Walsh        lower = float(str(lower))
3838333a18dSMichael Walsh    if upper:
3848333a18dSMichael Walsh        upper = float(str(upper))
3858333a18dSMichael Walsh    error_message = valid_range(var_value, lower, upper, var_name=var_name)
3868333a18dSMichael Walsh
3878333a18dSMichael Walsh    return process_error_message(error_message)
3888333a18dSMichael Walsh
3898333a18dSMichael Walsh
3908333a18dSMichael Walshdef valid_date_time(var_value, var_name=None):
3918333a18dSMichael Walsh    r"""
3928333a18dSMichael Walsh    The variable value is valid if it can be interpreted as a date/time (e.g. "14:49:49.981", "tomorrow",
3938333a18dSMichael Walsh    etc.) by the linux date command.
3948333a18dSMichael Walsh
3958333a18dSMichael Walsh    Description of argument(s):
3968333a18dSMichael Walsh    var_value                       The value being validated.
3978333a18dSMichael Walsh    """
3988333a18dSMichael Walsh
3998333a18dSMichael Walsh    error_message = ""
4008333a18dSMichael Walsh    rc, out_buf = gc.shell_cmd("date -d '" + str(var_value) + "'", quiet=1, show_err=0, ignore_err=1)
4018333a18dSMichael Walsh    if rc:
4028333a18dSMichael Walsh        var_name = get_var_name(var_name)
4038333a18dSMichael Walsh        error_message += "Invalid date/time value:\n"
4048333a18dSMichael Walsh        error_message += gp.sprint_varx(var_name, var_value,
4058333a18dSMichael Walsh                                        gp.blank() | gp.show_type())
4068333a18dSMichael Walsh        return process_error_message(error_message)
4078333a18dSMichael Walsh
4088333a18dSMichael Walsh    return process_error_message(error_message)
4098333a18dSMichael Walsh
4108333a18dSMichael Walsh
41189eff544SMichael Walshdef valid_dir_path(var_value, var_name=None):
412018e25fbSMichael Walsh    r"""
413da20f845SMichael Walsh    The variable value is valid if it contains the path of an existing directory.
414018e25fbSMichael Walsh
415018e25fbSMichael Walsh    Description of argument(s):
416018e25fbSMichael Walsh    var_value                       The value being validated.
41778bdfdd6SMichael Walsh    """
41878bdfdd6SMichael Walsh
41978bdfdd6SMichael Walsh    error_message = ""
42078bdfdd6SMichael Walsh    if not os.path.isdir(str(var_value)):
42189eff544SMichael Walsh        var_name = get_var_name(var_name)
422018e25fbSMichael Walsh        error_message += "The following directory does not exist:\n"
42300244345SMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
42478bdfdd6SMichael Walsh
425e23b5ad3SMichael Walsh    return process_error_message(error_message)
42678bdfdd6SMichael Walsh
42778bdfdd6SMichael Walsh
42889eff544SMichael Walshdef valid_file_path(var_value, var_name=None):
42978bdfdd6SMichael Walsh    r"""
430018e25fbSMichael Walsh    The variable value is valid if it contains the path of an existing file.
43178bdfdd6SMichael Walsh
432018e25fbSMichael Walsh    Description of argument(s):
43378bdfdd6SMichael Walsh    var_value                       The value being validated.
43478bdfdd6SMichael Walsh    """
43578bdfdd6SMichael Walsh
43678bdfdd6SMichael Walsh    error_message = ""
43778bdfdd6SMichael Walsh    if not os.path.isfile(str(var_value)):
43889eff544SMichael Walsh        var_name = get_var_name(var_name)
439018e25fbSMichael Walsh        error_message += "The following file does not exist:\n"
44000244345SMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
44178bdfdd6SMichael Walsh
442e23b5ad3SMichael Walsh    return process_error_message(error_message)
44378bdfdd6SMichael Walsh
44478bdfdd6SMichael Walsh
44589eff544SMichael Walshdef valid_path(var_value, var_name=None):
446e23b5ad3SMichael Walsh    r"""
447da20f845SMichael Walsh    The variable value is valid if it contains the path of an existing file or directory.
448e23b5ad3SMichael Walsh
449018e25fbSMichael Walsh    Description of argument(s):
450e23b5ad3SMichael Walsh    var_value                       The value being validated.
451e23b5ad3SMichael Walsh    """
452e23b5ad3SMichael Walsh
453e23b5ad3SMichael Walsh    error_message = ""
454e23b5ad3SMichael Walsh    if not (os.path.isfile(str(var_value)) or os.path.isdir(str(var_value))):
45589eff544SMichael Walsh        var_name = get_var_name(var_name)
456018e25fbSMichael Walsh        error_message += "Invalid path (file or directory does not exist):\n"
45700244345SMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
458e23b5ad3SMichael Walsh
459e23b5ad3SMichael Walsh    return process_error_message(error_message)
4602c687e98SMichael Walsh
4612c687e98SMichael Walsh
46235026be5SMichael Walshdef valid_list(var_value, valid_values=[], invalid_values=[],
46389eff544SMichael Walsh               required_values=[], fail_on_empty=False, var_name=None):
4642c687e98SMichael Walsh    r"""
465da20f845SMichael Walsh    The variable value is valid if it is a list where each entry can be found in the valid_values list or if
466da20f845SMichael Walsh    none of its values can be found in the invalid_values list or if all of the values in the required_values
467da20f845SMichael Walsh    list can be found in var_value.
46835026be5SMichael Walsh
469da20f845SMichael Walsh    The caller may only specify one of these 3 arguments: valid_values, invalid_values, required_values.
4702c687e98SMichael Walsh
471018e25fbSMichael Walsh    Description of argument(s):
472018e25fbSMichael Walsh    var_value                       The value being validated.
473da20f845SMichael Walsh    valid_values                    A list of valid values.  Each element in the var_value list must be equal
474da20f845SMichael Walsh                                    to one of these values to be considered valid.
475da20f845SMichael Walsh    invalid_values                  A list of invalid values.  If any element in var_value is equal to any of
476da20f845SMichael Walsh                                    the values in this argument, var_value is considered invalid.
477da20f845SMichael Walsh    required_values                 Every value in required_values must be found in var_value.  Otherwise,
478da20f845SMichael Walsh                                    var_value is considered invalid.
479da20f845SMichael Walsh    fail_on_empty                   Indicates that an empty list for the variable value should be considered
480da20f845SMichael Walsh                                    an error.
481ca193993SMichael Walsh    """
482ca193993SMichael Walsh
483ca193993SMichael Walsh    error_message = ""
484018e25fbSMichael Walsh
48535026be5SMichael Walsh    # Validate this function's arguments.
486da20f845SMichael Walsh    if not (bool(len(valid_values)) ^ bool(len(invalid_values)) ^ bool(len(required_values))):
48735026be5SMichael Walsh        error_message += "Programmer error - You must provide only one of the"
48835026be5SMichael Walsh        error_message += " following: valid_values, invalid_values,"
48935026be5SMichael Walsh        error_message += " required_values.\n"
490da20f845SMichael Walsh        error_message += gp.sprint_var(invalid_values, gp.show_type())
491da20f845SMichael Walsh        error_message += gp.sprint_var(valid_values, gp.show_type())
492da20f845SMichael Walsh        error_message += gp.sprint_var(required_values, gp.show_type())
49335026be5SMichael Walsh        return process_error_message(error_message)
49435026be5SMichael Walsh
495018e25fbSMichael Walsh    if type(var_value) is not list:
49689eff544SMichael Walsh        var_name = get_var_name(var_name)
497018e25fbSMichael Walsh        error_message = valid_type(var_value, list, var_name=var_name)
498018e25fbSMichael Walsh        if error_message:
499018e25fbSMichael Walsh            return process_error_message(error_message)
500018e25fbSMichael Walsh
501018e25fbSMichael Walsh    if fail_on_empty and len(var_value) == 0:
50289eff544SMichael Walsh        var_name = get_var_name(var_name)
503018e25fbSMichael Walsh        error_message += "Invalid empty list:\n"
504018e25fbSMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.show_type())
505018e25fbSMichael Walsh        return process_error_message(error_message)
506ca193993SMichael Walsh
50735026be5SMichael Walsh    if len(required_values):
50835026be5SMichael Walsh        found_error = 0
50935026be5SMichael Walsh        display_required_values = list(required_values)
51035026be5SMichael Walsh        for ix in range(0, len(required_values)):
51135026be5SMichael Walsh            if required_values[ix] not in var_value:
51235026be5SMichael Walsh                found_error = 1
51335026be5SMichael Walsh                display_required_values[ix] = \
51435026be5SMichael Walsh                    str(display_required_values[ix]) + "*"
51535026be5SMichael Walsh        if found_error:
51689eff544SMichael Walsh            var_name = get_var_name(var_name)
51735026be5SMichael Walsh            error_message += "The following list is invalid:\n"
51835026be5SMichael Walsh            error_message += gp.sprint_varx(var_name, var_value,
51935026be5SMichael Walsh                                            gp.blank() | gp.show_type())
52035026be5SMichael Walsh            error_message += "\n"
52135026be5SMichael Walsh            error_message += "Because some of the values in the "
52235026be5SMichael Walsh            error_message += "required_values list are not present (see"
52335026be5SMichael Walsh            error_message += " entries marked with \"*\"):\n"
52435026be5SMichael Walsh            error_message += "\n"
52535026be5SMichael Walsh            error_message += gp.sprint_varx('required_values',
52635026be5SMichael Walsh                                            display_required_values,
52735026be5SMichael Walsh                                            gp.blank() | gp.show_type())
52835026be5SMichael Walsh            error_message += "\n"
52935026be5SMichael Walsh
53035026be5SMichael Walsh        return process_error_message(error_message)
53135026be5SMichael Walsh
53235026be5SMichael Walsh    if len(invalid_values):
53335026be5SMichael Walsh        found_error = 0
53435026be5SMichael Walsh        display_var_value = list(var_value)
53535026be5SMichael Walsh        for ix in range(0, len(var_value)):
53635026be5SMichael Walsh            if var_value[ix] in invalid_values:
53735026be5SMichael Walsh                found_error = 1
53835026be5SMichael Walsh                display_var_value[ix] = str(var_value[ix]) + "*"
53935026be5SMichael Walsh
54035026be5SMichael Walsh        if found_error:
54189eff544SMichael Walsh            var_name = get_var_name(var_name)
54235026be5SMichael Walsh            error_message += "The following list is invalid (see entries"
54335026be5SMichael Walsh            error_message += " marked with \"*\"):\n"
54435026be5SMichael Walsh            error_message += gp.sprint_varx(var_name, display_var_value,
54535026be5SMichael Walsh                                            gp.blank() | gp.show_type())
54635026be5SMichael Walsh            error_message += "\n"
54735026be5SMichael Walsh            error_message += gp.sprint_var(invalid_values, gp.show_type())
54835026be5SMichael Walsh        return process_error_message(error_message)
54935026be5SMichael Walsh
550ca193993SMichael Walsh    found_error = 0
551ca193993SMichael Walsh    display_var_value = list(var_value)
552ca193993SMichael Walsh    for ix in range(0, len(var_value)):
553ca193993SMichael Walsh        if var_value[ix] not in valid_values:
554ca193993SMichael Walsh            found_error = 1
55535026be5SMichael Walsh            display_var_value[ix] = str(var_value[ix]) + "*"
556ca193993SMichael Walsh
557ca193993SMichael Walsh    if found_error:
55889eff544SMichael Walsh        var_name = get_var_name(var_name)
559018e25fbSMichael Walsh        error_message += "The following list is invalid (see entries marked"
560018e25fbSMichael Walsh        error_message += " with \"*\"):\n"
561018e25fbSMichael Walsh        error_message += gp.sprint_varx(var_name, display_var_value,
562018e25fbSMichael Walsh                                        gp.blank() | gp.show_type())
563018e25fbSMichael Walsh        error_message += "\n"
56435026be5SMichael Walsh        error_message += gp.sprint_var(valid_values, gp.show_type())
565018e25fbSMichael Walsh        return process_error_message(error_message)
566ca193993SMichael Walsh
567ca193993SMichael Walsh    return process_error_message(error_message)
5687ac5fd81SMichael Walsh
5697ac5fd81SMichael Walsh
570*53902dd1SMichael Walshdef valid_dict(var_value, required_keys=[], valid_values={}, invalid_values={}, var_name=None):
5717ac5fd81SMichael Walsh    r"""
572*53902dd1SMichael Walsh    The dictionary variable value is valid if it contains all required keys and each entry passes the
573*53902dd1SMichael Walsh    valid_value() call.
574*53902dd1SMichael Walsh
575*53902dd1SMichael Walsh    Examples:
576*53902dd1SMichael Walsh    person_record = {'last_name': 'Jones', 'first_name': 'John'}
577*53902dd1SMichael Walsh    valid_values = {'last_name': ['Doe', 'Jones', 'Johnson'], 'first_name': ['John', 'Mary']}
578*53902dd1SMichael Walsh    invalid_values = {'last_name': ['Manson', 'Hitler', 'Presley'], 'first_name': ['Mickey', 'Goofy']}
579*53902dd1SMichael Walsh
580*53902dd1SMichael Walsh    valid_dict(person_record, valid_values=valid_values)
581*53902dd1SMichael Walsh    valid_dict(person_record, invalid_values=invalid_values)
5827ac5fd81SMichael Walsh
583018e25fbSMichael Walsh    Description of argument(s):
584018e25fbSMichael Walsh    var_value                       The value being validated.
585da20f845SMichael Walsh    required_keys                   A list of keys which must be found in the dictionary for it to be
586da20f845SMichael Walsh                                    considered valid.
587*53902dd1SMichael Walsh    valid_values                    A dictionary whose entries correspond to the entries in var_value.  Each
588*53902dd1SMichael Walsh                                    value in valid_values is itself a valid_values list for the correponding
589*53902dd1SMichael Walsh                                    value in var_value.  For any var_value[key] to be considered valid, its
590*53902dd1SMichael Walsh                                    value must be found in valid_values[key].
591*53902dd1SMichael Walsh
592*53902dd1SMichael Walsh    invalid_values                  A dictionary whose entries correspond to the entries in var_value.  Each
593*53902dd1SMichael Walsh                                    value in invalid_values is itself an invalid_values list for the
594*53902dd1SMichael Walsh                                    correponding value in var_value.  For any var_value[key] to be considered
595*53902dd1SMichael Walsh                                    valid, its value must NOT be found in invalid_values[key].
5967ac5fd81SMichael Walsh    """
5977ac5fd81SMichael Walsh
5987ac5fd81SMichael Walsh    error_message = ""
599018e25fbSMichael Walsh    missing_keys = list(set(required_keys) - set(var_value.keys()))
600018e25fbSMichael Walsh    if len(missing_keys) > 0:
60189eff544SMichael Walsh        var_name = get_var_name(var_name)
602018e25fbSMichael Walsh        error_message += "The following dictionary is invalid because it is"
603018e25fbSMichael Walsh        error_message += " missing required keys:\n"
604*53902dd1SMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.blank() | gp.show_type())
605018e25fbSMichael Walsh        error_message += "\n"
60635026be5SMichael Walsh        error_message += gp.sprint_var(missing_keys, gp.show_type())
6077ac5fd81SMichael Walsh        return process_error_message(error_message)
608018e25fbSMichael Walsh
609*53902dd1SMichael Walsh    var_name = get_var_name(var_name)
610*53902dd1SMichael Walsh    if len(valid_values):
611*53902dd1SMichael Walsh        keys = valid_values.keys()
612*53902dd1SMichael Walsh        error_message = valid_dict(var_value, required_keys=keys, var_name=var_name)
613*53902dd1SMichael Walsh        if error_message:
614*53902dd1SMichael Walsh            return process_error_message(error_message)
615*53902dd1SMichael Walsh    for key, value in valid_values.items():
616*53902dd1SMichael Walsh        key_name = "  [" + key + "]"
617*53902dd1SMichael Walsh        sub_error_message = valid_value(var_value[key], valid_values=value, var_name=key_name)
618*53902dd1SMichael Walsh        if sub_error_message:
619*53902dd1SMichael Walsh            error_message += "The following dictionary is invalid because one of its entries is invalid:\n"
620*53902dd1SMichael Walsh            error_message += gp.sprint_varx(var_name, var_value, gp.blank() | gp.show_type())
621*53902dd1SMichael Walsh            error_message += "\n"
622*53902dd1SMichael Walsh            error_message += sub_error_message
623*53902dd1SMichael Walsh            return process_error_message(error_message)
624*53902dd1SMichael Walsh
625*53902dd1SMichael Walsh    for key, value in invalid_values.items():
626*53902dd1SMichael Walsh        if key not in var_value:
627*53902dd1SMichael Walsh            continue
628*53902dd1SMichael Walsh        key_name = "  [" + key + "]"
629*53902dd1SMichael Walsh        sub_error_message = valid_value(var_value[key], invalid_values=value, var_name=key_name)
630*53902dd1SMichael Walsh        if sub_error_message:
631*53902dd1SMichael Walsh            error_message += "The following dictionary is invalid because one of its entries is invalid:\n"
632*53902dd1SMichael Walsh            error_message += gp.sprint_varx(var_name, var_value, gp.blank() | gp.show_type())
633*53902dd1SMichael Walsh            error_message += "\n"
634*53902dd1SMichael Walsh            error_message += sub_error_message
635*53902dd1SMichael Walsh            return process_error_message(error_message)
636*53902dd1SMichael Walsh
637*53902dd1SMichael Walsh    return process_error_message(error_message)
638*53902dd1SMichael Walsh
639018e25fbSMichael Walsh
64089eff544SMichael Walshdef valid_program(var_value, var_name=None):
641be3a8150SMichael Walsh    r"""
642da20f845SMichael Walsh    The variable value is valid if it contains the name of a program which can be located using the "which"
643da20f845SMichael Walsh    command.
644be3a8150SMichael Walsh
645be3a8150SMichael Walsh    Description of argument(s):
646be3a8150SMichael Walsh    var_value                       The value being validated.
647be3a8150SMichael Walsh    """
648be3a8150SMichael Walsh
649be3a8150SMichael Walsh    error_message = ""
650be3a8150SMichael Walsh    rc, out_buf = gc.shell_cmd("which " + var_value, quiet=1, show_err=0,
651be3a8150SMichael Walsh                               ignore_err=1)
652be3a8150SMichael Walsh    if rc:
65389eff544SMichael Walsh        var_name = get_var_name(var_name)
654be3a8150SMichael Walsh        error_message += "The following required program could not be found"
655be3a8150SMichael Walsh        error_message += " using the $PATH environment variable:\n"
65600244345SMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
657be3a8150SMichael Walsh        PATH = os.environ.get("PATH", "").split(":")
658be3a8150SMichael Walsh        error_message += "\n"
659be3a8150SMichael Walsh        error_message += gp.sprint_var(PATH)
660be3a8150SMichael Walsh    return process_error_message(error_message)
661be3a8150SMichael Walsh
662be3a8150SMichael Walsh
66389eff544SMichael Walshdef valid_length(var_value, min_length=None, max_length=None, var_name=None):
664b9d8dfd2SMichael Walsh    r"""
665da20f845SMichael Walsh    The variable value is valid if it is an object (e.g. list, dictionary) whose length is within the
666da20f845SMichael Walsh    specified range.
667b9d8dfd2SMichael Walsh
668b9d8dfd2SMichael Walsh    Description of argument(s):
669b9d8dfd2SMichael Walsh    var_value                       The value being validated.
670da20f845SMichael Walsh    min_length                      The minimum length of the object.  If not None, the length of var_value
671da20f845SMichael Walsh                                    must be greater than or equal to min_length.
672da20f845SMichael Walsh    max_length                      The maximum length of the object.  If not None, the length of var_value
673da20f845SMichael Walsh                                    must be less than or equal to min_length.
674b9d8dfd2SMichael Walsh    """
675b9d8dfd2SMichael Walsh
676b9d8dfd2SMichael Walsh    error_message = ""
677b9d8dfd2SMichael Walsh    length = len(var_value)
678b9d8dfd2SMichael Walsh    error_message = valid_range(length, min_length, max_length)
679b9d8dfd2SMichael Walsh    if error_message:
68089eff544SMichael Walsh        var_name = get_var_name(var_name)
681b9d8dfd2SMichael Walsh        error_message = "The length of the following object is not within the"
682b9d8dfd2SMichael Walsh        error_message += " expected range:\n"
683da20f845SMichael Walsh        error_message += gp.sprint_vars(min_length, max_length)
684b9d8dfd2SMichael Walsh        error_message += gp.sprint_var(length)
685b9d8dfd2SMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
686b9d8dfd2SMichael Walsh        error_message += "\n"
687b9d8dfd2SMichael Walsh        return process_error_message(error_message)
688b9d8dfd2SMichael Walsh
689b9d8dfd2SMichael Walsh    return process_error_message(error_message)
690b9d8dfd2SMichael Walsh
691b9d8dfd2SMichael Walsh
692018e25fbSMichael Walsh# Modify selected function docstrings by adding headers/footers.
693018e25fbSMichael Walsh
694018e25fbSMichael Walshfunc_names = [
695018e25fbSMichael Walsh    "valid_type", "valid_value", "valid_range", "valid_integer",
696018e25fbSMichael Walsh    "valid_dir_path", "valid_file_path", "valid_path", "valid_list",
6978333a18dSMichael Walsh    "valid_dict", "valid_program", "valid_length", "valid_float",
6988333a18dSMichael Walsh    "valid_date_time"
699018e25fbSMichael Walsh]
700018e25fbSMichael Walsh
701018e25fbSMichael Walshraw_doc_strings = {}
702018e25fbSMichael Walsh
703018e25fbSMichael Walshfor func_name in func_names:
704018e25fbSMichael Walsh    cmd_buf = "raw_doc_strings['" + func_name + "'] = " + func_name
705018e25fbSMichael Walsh    cmd_buf += ".__doc__"
706018e25fbSMichael Walsh    exec(cmd_buf)
707018e25fbSMichael Walsh    cmd_buf = func_name + ".__doc__ = docstring_header + " + func_name
708018e25fbSMichael Walsh    cmd_buf += ".__doc__.rstrip(\" \\n\") + additional_args_docstring_footer"
709018e25fbSMichael Walsh    exec(cmd_buf)
710