1e7e9171eSGeorge Keishing#!/usr/bin/env python3
27423c01aSMichael Walsh
37423c01aSMichael Walshr"""
4da20f845SMichael WalshThis module provides validation functions like valid_value(), valid_integer(), etc.
57423c01aSMichael Walsh"""
67423c01aSMichael Walsh
7e635ddc0SGeorge Keishingimport datetime
8*20f38712SPatrick Williamsimport os
9*20f38712SPatrick Williams
10*20f38712SPatrick Williamsimport func_args as fa
11*20f38712SPatrick Williamsimport gen_cmd as gc
12*20f38712SPatrick Williamsimport gen_print as gp
137423c01aSMichael Walsh
14e23b5ad3SMichael Walshexit_on_error = False
15e23b5ad3SMichael Walsh
16e23b5ad3SMichael Walsh
17e23b5ad3SMichael Walshdef set_exit_on_error(value):
18e23b5ad3SMichael Walsh    r"""
19e23b5ad3SMichael Walsh    Set the exit_on_error value to either True or False.
20e23b5ad3SMichael Walsh
21da20f845SMichael Walsh    If exit_on_error is set, validation functions like valid_value() will exit the program on error instead
22da20f845SMichael Walsh    of returning False.
23e23b5ad3SMichael Walsh
24e23b5ad3SMichael Walsh    Description of argument(s):
25e23b5ad3SMichael Walsh    value                           Value to set global exit_on_error to.
26e23b5ad3SMichael Walsh    """
27e23b5ad3SMichael Walsh
28e23b5ad3SMichael Walsh    global exit_on_error
29e23b5ad3SMichael Walsh    exit_on_error = value
30e23b5ad3SMichael Walsh
31e23b5ad3SMichael Walsh
3289eff544SMichael Walshdef get_var_name(var_name):
33e23b5ad3SMichael Walsh    r"""
34da20f845SMichael Walsh    If var_name is not None, simply return its value.  Otherwise, get the variable name of the first argument
35da20f845SMichael Walsh    used to call the validation function (e.g. valid, valid_integer, etc.) and return it.
36e23b5ad3SMichael Walsh
37e23b5ad3SMichael Walsh    This function is designed solely for use by other functions in this file.
38e23b5ad3SMichael Walsh
39e23b5ad3SMichael Walsh    Example:
40e23b5ad3SMichael Walsh
41e23b5ad3SMichael Walsh    A programmer codes this:
42e23b5ad3SMichael Walsh
43e23b5ad3SMichael Walsh    valid_value(last_name)
44e23b5ad3SMichael Walsh
45e23b5ad3SMichael Walsh    Which results in the following call stack:
46e23b5ad3SMichael Walsh
47e23b5ad3SMichael Walsh    valid_value(last_name)
48e23b5ad3SMichael Walsh      -> get_var_name(var_name)
49e23b5ad3SMichael Walsh
50e23b5ad3SMichael Walsh    In this example, this function will return "last_name".
51e23b5ad3SMichael Walsh
52e23b5ad3SMichael Walsh    Example:
53e23b5ad3SMichael Walsh
54018e25fbSMichael Walsh    err_msg = valid_value(last_name, var_name="some_other_name")
55e23b5ad3SMichael Walsh
56e23b5ad3SMichael Walsh    Which results in the following call stack:
57e23b5ad3SMichael Walsh
58018e25fbSMichael Walsh    valid_value(var_value, var_name="some_other_name")
59e23b5ad3SMichael Walsh      -> get_var_name(var_name)
60e23b5ad3SMichael Walsh
61e23b5ad3SMichael Walsh    In this example, this function will return "some_other_name".
62e23b5ad3SMichael Walsh
63e23b5ad3SMichael Walsh    Description of argument(s):
64e23b5ad3SMichael Walsh    var_name                        The name of the variable.
65e23b5ad3SMichael Walsh    """
66e23b5ad3SMichael Walsh
6789eff544SMichael Walsh    return var_name or gp.get_arg_name(0, 1, stack_frame_ix=3)
68e23b5ad3SMichael Walsh
69e23b5ad3SMichael Walsh
70e23b5ad3SMichael Walshdef process_error_message(error_message):
71e23b5ad3SMichael Walsh    r"""
72018e25fbSMichael Walsh    Process the error_message in the manner described below.
73e23b5ad3SMichael Walsh
74018e25fbSMichael Walsh    This function is designed solely for use by other functions in this file.
75018e25fbSMichael Walsh
76018e25fbSMichael Walsh    NOTE: A blank error_message means that there is no error.
77018e25fbSMichael Walsh
78da20f845SMichael Walsh    For the following explanations, assume the caller of this function is a function with the following
79da20f845SMichael Walsh    definition:
8089eff544SMichael Walsh    valid_value(var_value, valid_values=[], invalid_values=[], var_name=None):
81018e25fbSMichael Walsh
82da20f845SMichael Walsh    If the user of valid_value() is assigning the valid_value() return value to a variable,
83da20f845SMichael Walsh    process_error_message() will simply return the error_message.  This mode of usage is illustrated by the
84da20f845SMichael Walsh    following example:
85018e25fbSMichael Walsh
86018e25fbSMichael Walsh    error_message = valid_value(var1)
87018e25fbSMichael Walsh
88da20f845SMichael Walsh    This mode is useful for callers who wish to validate a variable and then decide for themselves what to do
89da20f845SMichael Walsh    with the error_message (e.g. raise(error_message), BuiltIn().fail(error_message), etc.).
90018e25fbSMichael Walsh
91da20f845SMichael Walsh    If the user of valid_value() is NOT assigning the valid_value() return value to a variable,
92da20f845SMichael Walsh    process_error_message() will behave as follows.
93018e25fbSMichael Walsh
94da20f845SMichael Walsh    First, if error_message is non-blank, it will be printed to stderr via a call to
95da20f845SMichael Walsh    gp.print_error_report(error_message).
96018e25fbSMichael Walsh
97018e25fbSMichael Walsh    If exit_on_error is set:
98018e25fbSMichael Walsh    - If the error_message is blank, simply return.
99da20f845SMichael Walsh    - If the error_message is non-blank, exit the program with a return code of 1.
100018e25fbSMichael Walsh
101018e25fbSMichael Walsh    If exit_on_error is NOT set:
102018e25fbSMichael Walsh    - If the error_message is blank, return True.
103018e25fbSMichael Walsh    - If the error_message is non-blank, return False.
104e23b5ad3SMichael Walsh
105e23b5ad3SMichael Walsh    Description of argument(s):
106e23b5ad3SMichael Walsh    error_message                   An error message.
107e23b5ad3SMichael Walsh    """
108e23b5ad3SMichael Walsh
109da20f845SMichael Walsh    # Determine whether the caller's caller is assigning the result to a variable.
110018e25fbSMichael Walsh    l_value = gp.get_arg_name(None, -1, stack_frame_ix=3)
111018e25fbSMichael Walsh    if l_value:
112018e25fbSMichael Walsh        return error_message
113018e25fbSMichael Walsh
114e23b5ad3SMichael Walsh    if error_message == "":
115018e25fbSMichael Walsh        if exit_on_error:
116018e25fbSMichael Walsh            return
117e23b5ad3SMichael Walsh        return True
118e23b5ad3SMichael Walsh
119018e25fbSMichael Walsh    gp.print_error_report(error_message, stack_frame_ix=4)
120e23b5ad3SMichael Walsh    if exit_on_error:
12110a4d98bSMichael Walsh        exit(1)
122e23b5ad3SMichael Walsh    return False
123e23b5ad3SMichael Walsh
1247423c01aSMichael Walsh
125da20f845SMichael Walsh# Note to programmers:  All of the validation functions in this module should follow the same basic template:
12689eff544SMichael Walsh# def valid_value(var_value, var1, var2, varn, var_name=None):
127018e25fbSMichael Walsh#
128018e25fbSMichael Walsh#     error_message = ""
129018e25fbSMichael Walsh#     if not valid:
13089eff544SMichael Walsh#         var_name = get_var_name(var_name)
131018e25fbSMichael Walsh#         error_message += "The following variable is invalid because...:\n"
132018e25fbSMichael Walsh#         error_message += gp.sprint_varx(var_name, var_value, gp.blank())
133018e25fbSMichael Walsh#
134018e25fbSMichael Walsh#     return process_error_message(error_message)
1357423c01aSMichael Walsh
136018e25fbSMichael Walsh
137da20f845SMichael Walsh# The docstring header and footer will be added to each validation function's existing docstring.
138*20f38712SPatrick Williamsdocstring_header = 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
146*20f38712SPatrick Williamsadditional_args_docstring_footer = r"""
147da20f845SMichael Walsh    var_name                        The name of the variable whose value is passed in var_value.  For the
148da20f845SMichael Walsh                                    general case, this argument is unnecessary as this function can figure
149da20f845SMichael Walsh                                    out the var_name.  This is provided for Robot callers in which case, this
150da20f845SMichael Walsh                                    function lacks the ability to determine the variable name.
151018e25fbSMichael Walsh    """
152018e25fbSMichael Walsh
153018e25fbSMichael Walsh
15489eff544SMichael Walshdef valid_type(var_value, required_type, var_name=None):
155018e25fbSMichael Walsh    r"""
156018e25fbSMichael Walsh    The variable value is valid if it is of the required type.
157018e25fbSMichael Walsh
158018e25fbSMichael Walsh    Examples:
159018e25fbSMichael Walsh
160018e25fbSMichael Walsh    valid_type(var1, int)
161018e25fbSMichael Walsh
162018e25fbSMichael Walsh    valid_type(var1, (list, dict))
163018e25fbSMichael Walsh
164018e25fbSMichael Walsh    Description of argument(s):
165018e25fbSMichael Walsh    var_value                       The value being validated.
166da20f845SMichael Walsh    required_type                   A type or a tuple of types (e.g. str, int, etc.).
167018e25fbSMichael Walsh    """
168018e25fbSMichael Walsh
169018e25fbSMichael Walsh    error_message = ""
170018e25fbSMichael Walsh    if type(required_type) is tuple:
171018e25fbSMichael Walsh        if type(var_value) in required_type:
172018e25fbSMichael Walsh            return process_error_message(error_message)
173018e25fbSMichael Walsh    else:
174018e25fbSMichael Walsh        if type(var_value) is required_type:
175018e25fbSMichael Walsh            return process_error_message(error_message)
176018e25fbSMichael Walsh
177018e25fbSMichael Walsh    # If we get to this point, the validation has failed.
17889eff544SMichael Walsh    var_name = get_var_name(var_name)
179018e25fbSMichael Walsh    error_message += "Invalid variable type:\n"
180*20f38712SPatrick Williams    error_message += gp.sprint_varx(
181*20f38712SPatrick Williams        var_name, var_value, gp.blank() | gp.show_type()
182*20f38712SPatrick Williams    )
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    r"""
191da20f845SMichael Walsh    The variable value is valid if it is either contained in the valid_values list or if it is NOT contained
192da20f845SMichael Walsh    in the invalid_values list.  If the caller specifies nothing for either of these 2 arguments,
193da20f845SMichael Walsh    invalid_values will be initialized to ['', None].  This is a good way to fail on variables which contain
194da20f845SMichael Walsh    blank values.
195018e25fbSMichael Walsh
196018e25fbSMichael Walsh    It is illegal to specify both valid_values and invalid values.
197018e25fbSMichael Walsh
198018e25fbSMichael Walsh    Example:
199018e25fbSMichael Walsh
200018e25fbSMichael Walsh    var1 = ''
201018e25fbSMichael Walsh    valid_value(var1)
202018e25fbSMichael Walsh
203da20f845SMichael Walsh    This code would fail because var1 is blank and the default value for invalid_values is ['', None].
204018e25fbSMichael Walsh
205018e25fbSMichael Walsh    Example:
206018e25fbSMichael Walsh    var1 = 'yes'
207018e25fbSMichael Walsh    valid_value(var1, valid_values=['yes', 'true'])
208018e25fbSMichael Walsh
209018e25fbSMichael Walsh    This code would pass.
210018e25fbSMichael Walsh
211018e25fbSMichael Walsh    Description of argument(s):
212018e25fbSMichael Walsh    var_value                       The value being validated.
213da20f845SMichael Walsh    valid_values                    A list of valid values.  The variable value must be equal to one of these
214da20f845SMichael Walsh                                    values to be considered valid.
215da20f845SMichael Walsh    invalid_values                  A list of invalid values.  If the variable value is equal to any of
216da20f845SMichael Walsh                                    these, it is considered invalid.
217018e25fbSMichael Walsh    """
218018e25fbSMichael Walsh
219bec416ddSMichael Walsh    error_message = ""
220bec416ddSMichael Walsh
221e23b5ad3SMichael Walsh    # Validate this function's arguments.
2227423c01aSMichael Walsh    len_valid_values = len(valid_values)
2237423c01aSMichael Walsh    len_invalid_values = len(invalid_values)
2247423c01aSMichael Walsh    if len_valid_values > 0 and len_invalid_values > 0:
225018e25fbSMichael Walsh        error_message += "Programmer error - You must provide either an"
226018e25fbSMichael Walsh        error_message += " invalid_values list or a valid_values"
227018e25fbSMichael Walsh        error_message += " list but NOT both:\n"
228018e25fbSMichael Walsh        error_message += gp.sprint_var(invalid_values)
229018e25fbSMichael Walsh        error_message += gp.sprint_var(valid_values)
230018e25fbSMichael Walsh        return process_error_message(error_message)
2317423c01aSMichael Walsh
232*20f38712SPatrick Williams    error_message = valid_type(valid_values, list, var_name="valid_values")
2330f5fa532SMichael Walsh    if error_message:
2340f5fa532SMichael Walsh        return process_error_message(error_message)
2350f5fa532SMichael Walsh
236*20f38712SPatrick Williams    error_message = valid_type(invalid_values, list, var_name="invalid_values")
2370f5fa532SMichael Walsh    if error_message:
2380f5fa532SMichael Walsh        return process_error_message(error_message)
2390f5fa532SMichael Walsh
2407423c01aSMichael Walsh    if len_valid_values > 0:
2417423c01aSMichael Walsh        # Processing the valid_values list.
2427423c01aSMichael Walsh        if var_value in valid_values:
243018e25fbSMichael Walsh            return process_error_message(error_message)
24489eff544SMichael Walsh        var_name = get_var_name(var_name)
245018e25fbSMichael Walsh        error_message += "Invalid variable value:\n"
246*20f38712SPatrick Williams        error_message += gp.sprint_varx(
247*20f38712SPatrick Williams            var_name, var_value, gp.blank() | gp.verbose() | gp.show_type()
248*20f38712SPatrick Williams        )
249018e25fbSMichael Walsh        error_message += "\n"
250018e25fbSMichael Walsh        error_message += "It must be one of the following values:\n"
251018e25fbSMichael Walsh        error_message += "\n"
252*20f38712SPatrick Williams        error_message += gp.sprint_var(
253*20f38712SPatrick Williams            valid_values, gp.blank() | gp.show_type()
254*20f38712SPatrick Williams        )
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"
267*20f38712SPatrick Williams    error_message += gp.sprint_varx(
268*20f38712SPatrick Williams        var_name, var_value, gp.blank() | gp.verbose() | gp.show_type()
269*20f38712SPatrick Williams    )
270018e25fbSMichael Walsh    error_message += "\n"
27153902dd1SMichael Walsh    error_message += "It must NOT be any of the following values:\n"
272018e25fbSMichael Walsh    error_message += "\n"
273*20f38712SPatrick Williams    error_message += gp.sprint_var(invalid_values, gp.blank() | gp.show_type())
274e23b5ad3SMichael Walsh    return process_error_message(error_message)
2757423c01aSMichael Walsh
276bec416ddSMichael Walsh
27789eff544SMichael Walshdef valid_range(var_value, lower=None, upper=None, var_name=None):
278bec416ddSMichael Walsh    r"""
279018e25fbSMichael Walsh    The variable value is valid if it is within the specified range.
280bec416ddSMichael Walsh
281da20f845SMichael Walsh    This function can be used with any type of operands where they can have a greater than/less than
282da20f845SMichael Walsh    relationship to each other (e.g. int, float, str).
283018e25fbSMichael Walsh
284018e25fbSMichael Walsh    Description of argument(s):
285bec416ddSMichael Walsh    var_value                       The value being validated.
286da20f845SMichael Walsh    lower                           The lower end of the range.  If not None, the var_value must be greater
287da20f845SMichael Walsh                                    than or equal to lower.
288da20f845SMichael Walsh    upper                           The upper end of the range.  If not None, the var_value must be less than
289da20f845SMichael Walsh                                    or equal to upper.
290bec416ddSMichael Walsh    """
291bec416ddSMichael Walsh
292bec416ddSMichael Walsh    error_message = ""
293b9d8dfd2SMichael Walsh    if lower is None and upper is None:
294018e25fbSMichael Walsh        return process_error_message(error_message)
295b9d8dfd2SMichael Walsh    if lower is None and var_value <= upper:
296018e25fbSMichael Walsh        return process_error_message(error_message)
297b9d8dfd2SMichael Walsh    if upper is None and var_value >= lower:
298018e25fbSMichael Walsh        return process_error_message(error_message)
2992f88a81fSTony Lee    if lower is not None and upper is not None:
300018e25fbSMichael Walsh        if lower > upper:
30189eff544SMichael Walsh            var_name = get_var_name(var_name)
302018e25fbSMichael Walsh            error_message += "Programmer error - the lower value is greater"
303018e25fbSMichael Walsh            error_message += " than the upper value:\n"
304018e25fbSMichael Walsh            error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type())
305018e25fbSMichael Walsh            return process_error_message(error_message)
306018e25fbSMichael Walsh        if lower <= var_value <= upper:
307018e25fbSMichael Walsh            return process_error_message(error_message)
308bec416ddSMichael Walsh
30989eff544SMichael Walsh    var_name = get_var_name(var_name)
310018e25fbSMichael Walsh    error_message += "The following variable is not within the expected"
311018e25fbSMichael Walsh    error_message += " range:\n"
312018e25fbSMichael Walsh    error_message += gp.sprint_varx(var_name, var_value, gp.show_type())
313018e25fbSMichael Walsh    error_message += "\n"
314018e25fbSMichael Walsh    error_message += "range:\n"
315018e25fbSMichael Walsh    error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type(), indent=2)
316e23b5ad3SMichael Walsh    return process_error_message(error_message)
3177423c01aSMichael Walsh
31878bdfdd6SMichael Walsh
31989eff544SMichael Walshdef valid_integer(var_value, lower=None, upper=None, var_name=None):
32078bdfdd6SMichael Walsh    r"""
321da20f845SMichael Walsh    The variable value is valid if it is an integer or can be interpreted as an integer (e.g. 7, "7", etc.).
32278bdfdd6SMichael Walsh
323da20f845SMichael Walsh    This function also calls valid_range to make sure the integer value is within the specified range (if
324da20f845SMichael Walsh    any).
325018e25fbSMichael Walsh
326018e25fbSMichael Walsh    Description of argument(s):
32778bdfdd6SMichael Walsh    var_value                       The value being validated.
328da20f845SMichael Walsh    lower                           The lower end of the range.  If not None, the var_value must be greater
329da20f845SMichael Walsh                                    than or equal to lower.
330da20f845SMichael Walsh    upper                           The upper end of the range.  If not None, the var_value must be less than
331da20f845SMichael Walsh                                    or equal to upper.
332018e25fbSMichael Walsh    """
333018e25fbSMichael Walsh
334018e25fbSMichael Walsh    error_message = ""
33589eff544SMichael Walsh    var_name = get_var_name(var_name)
336018e25fbSMichael Walsh    try:
337018e25fbSMichael Walsh        var_value = int(str(var_value), 0)
338018e25fbSMichael Walsh    except ValueError:
339018e25fbSMichael Walsh        error_message += "Invalid integer value:\n"
340*20f38712SPatrick Williams        error_message += gp.sprint_varx(
341*20f38712SPatrick Williams            var_name, var_value, gp.blank() | gp.show_type()
342*20f38712SPatrick Williams        )
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"
376*20f38712SPatrick Williams        error_message += gp.sprint_varx(
377*20f38712SPatrick Williams            var_name, var_value, gp.blank() | gp.show_type()
378*20f38712SPatrick Williams        )
3798333a18dSMichael Walsh        return process_error_message(error_message)
3808333a18dSMichael Walsh
3818333a18dSMichael Walsh    # Check the range (if any).
3828333a18dSMichael Walsh    if lower:
3838333a18dSMichael Walsh        lower = float(str(lower))
3848333a18dSMichael Walsh    if upper:
3858333a18dSMichael Walsh        upper = float(str(upper))
3868333a18dSMichael Walsh    error_message = valid_range(var_value, lower, upper, var_name=var_name)
3878333a18dSMichael Walsh
3888333a18dSMichael Walsh    return process_error_message(error_message)
3898333a18dSMichael Walsh
3908333a18dSMichael Walsh
3918333a18dSMichael Walshdef valid_date_time(var_value, var_name=None):
3928333a18dSMichael Walsh    r"""
3938333a18dSMichael Walsh    The variable value is valid if it can be interpreted as a date/time (e.g. "14:49:49.981", "tomorrow",
3948333a18dSMichael Walsh    etc.) by the linux date command.
3958333a18dSMichael Walsh
3968333a18dSMichael Walsh    Description of argument(s):
3978333a18dSMichael Walsh    var_value                       The value being validated.
3988333a18dSMichael Walsh    """
3998333a18dSMichael Walsh
4008333a18dSMichael Walsh    error_message = ""
401*20f38712SPatrick Williams    rc, out_buf = gc.shell_cmd(
402*20f38712SPatrick Williams        "date -d '" + str(var_value) + "'", quiet=1, show_err=0, ignore_err=1
403*20f38712SPatrick Williams    )
4048333a18dSMichael Walsh    if rc:
4058333a18dSMichael Walsh        var_name = get_var_name(var_name)
4068333a18dSMichael Walsh        error_message += "Invalid date/time value:\n"
407*20f38712SPatrick Williams        error_message += gp.sprint_varx(
408*20f38712SPatrick Williams            var_name, var_value, gp.blank() | gp.show_type()
409*20f38712SPatrick Williams        )
4108333a18dSMichael Walsh        return process_error_message(error_message)
4118333a18dSMichael Walsh
4128333a18dSMichael Walsh    return process_error_message(error_message)
4138333a18dSMichael Walsh
4148333a18dSMichael Walsh
41589eff544SMichael Walshdef valid_dir_path(var_value, var_name=None):
416018e25fbSMichael Walsh    r"""
417da20f845SMichael Walsh    The variable value is valid if it contains the path of an existing directory.
418018e25fbSMichael Walsh
419018e25fbSMichael Walsh    Description of argument(s):
420018e25fbSMichael Walsh    var_value                       The value being validated.
42178bdfdd6SMichael Walsh    """
42278bdfdd6SMichael Walsh
42378bdfdd6SMichael Walsh    error_message = ""
42478bdfdd6SMichael Walsh    if not os.path.isdir(str(var_value)):
42589eff544SMichael Walsh        var_name = get_var_name(var_name)
426018e25fbSMichael Walsh        error_message += "The following directory does not exist:\n"
42700244345SMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
42878bdfdd6SMichael Walsh
429e23b5ad3SMichael Walsh    return process_error_message(error_message)
43078bdfdd6SMichael Walsh
43178bdfdd6SMichael Walsh
43289eff544SMichael Walshdef valid_file_path(var_value, var_name=None):
43378bdfdd6SMichael Walsh    r"""
434018e25fbSMichael Walsh    The variable value is valid if it contains the path of an existing file.
43578bdfdd6SMichael Walsh
436018e25fbSMichael Walsh    Description of argument(s):
43778bdfdd6SMichael Walsh    var_value                       The value being validated.
43878bdfdd6SMichael Walsh    """
43978bdfdd6SMichael Walsh
44078bdfdd6SMichael Walsh    error_message = ""
44178bdfdd6SMichael Walsh    if not os.path.isfile(str(var_value)):
44289eff544SMichael Walsh        var_name = get_var_name(var_name)
443018e25fbSMichael Walsh        error_message += "The following file does not exist:\n"
44400244345SMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
44578bdfdd6SMichael Walsh
446e23b5ad3SMichael Walsh    return process_error_message(error_message)
44778bdfdd6SMichael Walsh
44878bdfdd6SMichael Walsh
44989eff544SMichael Walshdef valid_path(var_value, var_name=None):
450e23b5ad3SMichael Walsh    r"""
451da20f845SMichael Walsh    The variable value is valid if it contains the path of an existing file or directory.
452e23b5ad3SMichael Walsh
453018e25fbSMichael Walsh    Description of argument(s):
454e23b5ad3SMichael Walsh    var_value                       The value being validated.
455e23b5ad3SMichael Walsh    """
456e23b5ad3SMichael Walsh
457e23b5ad3SMichael Walsh    error_message = ""
458e23b5ad3SMichael Walsh    if not (os.path.isfile(str(var_value)) or os.path.isdir(str(var_value))):
45989eff544SMichael Walsh        var_name = get_var_name(var_name)
460018e25fbSMichael Walsh        error_message += "Invalid path (file or directory does not exist):\n"
46100244345SMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
462e23b5ad3SMichael Walsh
463e23b5ad3SMichael Walsh    return process_error_message(error_message)
4642c687e98SMichael Walsh
4652c687e98SMichael Walsh
466*20f38712SPatrick Williamsdef valid_list(
467*20f38712SPatrick Williams    var_value,
468*20f38712SPatrick Williams    valid_values=[],
469*20f38712SPatrick Williams    invalid_values=[],
470*20f38712SPatrick Williams    required_values=[],
471*20f38712SPatrick Williams    fail_on_empty=False,
472*20f38712SPatrick Williams    var_name=None,
473*20f38712SPatrick Williams):
4742c687e98SMichael Walsh    r"""
475da20f845SMichael Walsh    The variable value is valid if it is a list where each entry can be found in the valid_values list or if
476da20f845SMichael Walsh    none of its values can be found in the invalid_values list or if all of the values in the required_values
477da20f845SMichael Walsh    list can be found in var_value.
47835026be5SMichael Walsh
479da20f845SMichael Walsh    The caller may only specify one of these 3 arguments: valid_values, invalid_values, required_values.
4802c687e98SMichael Walsh
481018e25fbSMichael Walsh    Description of argument(s):
482018e25fbSMichael Walsh    var_value                       The value being validated.
483da20f845SMichael Walsh    valid_values                    A list of valid values.  Each element in the var_value list must be equal
484da20f845SMichael Walsh                                    to one of these values to be considered valid.
485da20f845SMichael Walsh    invalid_values                  A list of invalid values.  If any element in var_value is equal to any of
486da20f845SMichael Walsh                                    the values in this argument, var_value is considered invalid.
487da20f845SMichael Walsh    required_values                 Every value in required_values must be found in var_value.  Otherwise,
488da20f845SMichael Walsh                                    var_value is considered invalid.
489da20f845SMichael Walsh    fail_on_empty                   Indicates that an empty list for the variable value should be considered
490da20f845SMichael Walsh                                    an error.
491ca193993SMichael Walsh    """
492ca193993SMichael Walsh
493ca193993SMichael Walsh    error_message = ""
494018e25fbSMichael Walsh
49535026be5SMichael Walsh    # Validate this function's arguments.
496*20f38712SPatrick Williams    if not (
497*20f38712SPatrick Williams        bool(len(valid_values))
498*20f38712SPatrick Williams        ^ bool(len(invalid_values))
499*20f38712SPatrick Williams        ^ bool(len(required_values))
500*20f38712SPatrick Williams    ):
50135026be5SMichael Walsh        error_message += "Programmer error - You must provide only one of the"
50235026be5SMichael Walsh        error_message += " following: valid_values, invalid_values,"
50335026be5SMichael Walsh        error_message += " required_values.\n"
504da20f845SMichael Walsh        error_message += gp.sprint_var(invalid_values, gp.show_type())
505da20f845SMichael Walsh        error_message += gp.sprint_var(valid_values, gp.show_type())
506da20f845SMichael Walsh        error_message += gp.sprint_var(required_values, gp.show_type())
50735026be5SMichael Walsh        return process_error_message(error_message)
50835026be5SMichael Walsh
509018e25fbSMichael Walsh    if type(var_value) is not list:
51089eff544SMichael Walsh        var_name = get_var_name(var_name)
511018e25fbSMichael Walsh        error_message = valid_type(var_value, list, var_name=var_name)
512018e25fbSMichael Walsh        if error_message:
513018e25fbSMichael Walsh            return process_error_message(error_message)
514018e25fbSMichael Walsh
515018e25fbSMichael Walsh    if fail_on_empty and len(var_value) == 0:
51689eff544SMichael Walsh        var_name = get_var_name(var_name)
517018e25fbSMichael Walsh        error_message += "Invalid empty list:\n"
518018e25fbSMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.show_type())
519018e25fbSMichael Walsh        return process_error_message(error_message)
520ca193993SMichael Walsh
52135026be5SMichael Walsh    if len(required_values):
52235026be5SMichael Walsh        found_error = 0
52335026be5SMichael Walsh        display_required_values = list(required_values)
52435026be5SMichael Walsh        for ix in range(0, len(required_values)):
52535026be5SMichael Walsh            if required_values[ix] not in var_value:
52635026be5SMichael Walsh                found_error = 1
527*20f38712SPatrick Williams                display_required_values[ix] = (
52835026be5SMichael Walsh                    str(display_required_values[ix]) + "*"
529*20f38712SPatrick Williams                )
53035026be5SMichael Walsh        if found_error:
53189eff544SMichael Walsh            var_name = get_var_name(var_name)
53235026be5SMichael Walsh            error_message += "The following list is invalid:\n"
533*20f38712SPatrick Williams            error_message += gp.sprint_varx(
534*20f38712SPatrick Williams                var_name, var_value, gp.blank() | gp.show_type()
535*20f38712SPatrick Williams            )
53635026be5SMichael Walsh            error_message += "\n"
53735026be5SMichael Walsh            error_message += "Because some of the values in the "
53835026be5SMichael Walsh            error_message += "required_values list are not present (see"
539*20f38712SPatrick Williams            error_message += ' entries marked with "*"):\n'
54035026be5SMichael Walsh            error_message += "\n"
541*20f38712SPatrick Williams            error_message += gp.sprint_varx(
542*20f38712SPatrick Williams                "required_values",
54335026be5SMichael Walsh                display_required_values,
544*20f38712SPatrick Williams                gp.blank() | gp.show_type(),
545*20f38712SPatrick Williams            )
54635026be5SMichael Walsh            error_message += "\n"
54735026be5SMichael Walsh
54835026be5SMichael Walsh        return process_error_message(error_message)
54935026be5SMichael Walsh
55035026be5SMichael Walsh    if len(invalid_values):
55135026be5SMichael Walsh        found_error = 0
55235026be5SMichael Walsh        display_var_value = list(var_value)
55335026be5SMichael Walsh        for ix in range(0, len(var_value)):
55435026be5SMichael Walsh            if var_value[ix] in invalid_values:
55535026be5SMichael Walsh                found_error = 1
55635026be5SMichael Walsh                display_var_value[ix] = str(var_value[ix]) + "*"
55735026be5SMichael Walsh
55835026be5SMichael Walsh        if found_error:
55989eff544SMichael Walsh            var_name = get_var_name(var_name)
56035026be5SMichael Walsh            error_message += "The following list is invalid (see entries"
561*20f38712SPatrick Williams            error_message += ' marked with "*"):\n'
562*20f38712SPatrick Williams            error_message += gp.sprint_varx(
563*20f38712SPatrick Williams                var_name, display_var_value, gp.blank() | gp.show_type()
564*20f38712SPatrick Williams            )
56535026be5SMichael Walsh            error_message += "\n"
56635026be5SMichael Walsh            error_message += gp.sprint_var(invalid_values, gp.show_type())
56735026be5SMichael Walsh        return process_error_message(error_message)
56835026be5SMichael Walsh
569ca193993SMichael Walsh    found_error = 0
570ca193993SMichael Walsh    display_var_value = list(var_value)
571ca193993SMichael Walsh    for ix in range(0, len(var_value)):
572ca193993SMichael Walsh        if var_value[ix] not in valid_values:
573ca193993SMichael Walsh            found_error = 1
57435026be5SMichael Walsh            display_var_value[ix] = str(var_value[ix]) + "*"
575ca193993SMichael Walsh
576ca193993SMichael Walsh    if found_error:
57789eff544SMichael Walsh        var_name = get_var_name(var_name)
578018e25fbSMichael Walsh        error_message += "The following list is invalid (see entries marked"
579*20f38712SPatrick Williams        error_message += ' with "*"):\n'
580*20f38712SPatrick Williams        error_message += gp.sprint_varx(
581*20f38712SPatrick Williams            var_name, display_var_value, gp.blank() | gp.show_type()
582*20f38712SPatrick Williams        )
583018e25fbSMichael Walsh        error_message += "\n"
58435026be5SMichael Walsh        error_message += gp.sprint_var(valid_values, gp.show_type())
585018e25fbSMichael Walsh        return process_error_message(error_message)
586ca193993SMichael Walsh
587ca193993SMichael Walsh    return process_error_message(error_message)
5887ac5fd81SMichael Walsh
5897ac5fd81SMichael Walsh
590*20f38712SPatrick Williamsdef valid_dict(
591*20f38712SPatrick Williams    var_value,
592*20f38712SPatrick Williams    required_keys=[],
593*20f38712SPatrick Williams    valid_values={},
594*20f38712SPatrick Williams    invalid_values={},
595*20f38712SPatrick Williams    var_name=None,
596*20f38712SPatrick Williams):
5977ac5fd81SMichael Walsh    r"""
59853902dd1SMichael Walsh    The dictionary variable value is valid if it contains all required keys and each entry passes the
59953902dd1SMichael Walsh    valid_value() call.
60053902dd1SMichael Walsh
60153902dd1SMichael Walsh    Examples:
60253902dd1SMichael Walsh    person_record = {'last_name': 'Jones', 'first_name': 'John'}
60353902dd1SMichael Walsh    valid_values = {'last_name': ['Doe', 'Jones', 'Johnson'], 'first_name': ['John', 'Mary']}
60453902dd1SMichael Walsh    invalid_values = {'last_name': ['Manson', 'Hitler', 'Presley'], 'first_name': ['Mickey', 'Goofy']}
60553902dd1SMichael Walsh
60653902dd1SMichael Walsh    valid_dict(person_record, valid_values=valid_values)
60753902dd1SMichael Walsh    valid_dict(person_record, invalid_values=invalid_values)
6087ac5fd81SMichael Walsh
609018e25fbSMichael Walsh    Description of argument(s):
610018e25fbSMichael Walsh    var_value                       The value being validated.
611da20f845SMichael Walsh    required_keys                   A list of keys which must be found in the dictionary for it to be
612da20f845SMichael Walsh                                    considered valid.
61353902dd1SMichael Walsh    valid_values                    A dictionary whose entries correspond to the entries in var_value.  Each
6148206851aSGeorge Keishing                                    value in valid_values is itself a valid_values list for the corresponding
61553902dd1SMichael Walsh                                    value in var_value.  For any var_value[key] to be considered valid, its
61653902dd1SMichael Walsh                                    value must be found in valid_values[key].
61753902dd1SMichael Walsh
61853902dd1SMichael Walsh    invalid_values                  A dictionary whose entries correspond to the entries in var_value.  Each
61953902dd1SMichael Walsh                                    value in invalid_values is itself an invalid_values list for the
62081384420SMichael Walsh                                    corresponding value in var_value.  For any var_value[key] to be
62181384420SMichael Walsh                                    considered valid, its value must NOT be found in invalid_values[key].
6227ac5fd81SMichael Walsh    """
6237ac5fd81SMichael Walsh
6247ac5fd81SMichael Walsh    error_message = ""
625018e25fbSMichael Walsh    missing_keys = list(set(required_keys) - set(var_value.keys()))
626018e25fbSMichael Walsh    if len(missing_keys) > 0:
62789eff544SMichael Walsh        var_name = get_var_name(var_name)
628018e25fbSMichael Walsh        error_message += "The following dictionary is invalid because it is"
629018e25fbSMichael Walsh        error_message += " missing required keys:\n"
630*20f38712SPatrick Williams        error_message += gp.sprint_varx(
631*20f38712SPatrick Williams            var_name, var_value, gp.blank() | gp.show_type()
632*20f38712SPatrick Williams        )
633018e25fbSMichael Walsh        error_message += "\n"
63435026be5SMichael Walsh        error_message += gp.sprint_var(missing_keys, gp.show_type())
6357ac5fd81SMichael Walsh        return process_error_message(error_message)
636018e25fbSMichael Walsh
63753902dd1SMichael Walsh    var_name = get_var_name(var_name)
63853902dd1SMichael Walsh    if len(valid_values):
63953902dd1SMichael Walsh        keys = valid_values.keys()
640*20f38712SPatrick Williams        error_message = valid_dict(
641*20f38712SPatrick Williams            var_value, required_keys=keys, var_name=var_name
642*20f38712SPatrick Williams        )
64353902dd1SMichael Walsh        if error_message:
64453902dd1SMichael Walsh            return process_error_message(error_message)
64553902dd1SMichael Walsh    for key, value in valid_values.items():
64653902dd1SMichael Walsh        key_name = "  [" + key + "]"
647*20f38712SPatrick Williams        sub_error_message = valid_value(
648*20f38712SPatrick Williams            var_value[key], valid_values=value, var_name=key_name
649*20f38712SPatrick Williams        )
65053902dd1SMichael Walsh        if sub_error_message:
651*20f38712SPatrick Williams            error_message += (
652*20f38712SPatrick Williams                "The following dictionary is invalid because one of its"
653*20f38712SPatrick Williams                " entries is invalid:\n"
654*20f38712SPatrick Williams            )
655*20f38712SPatrick Williams            error_message += gp.sprint_varx(
656*20f38712SPatrick Williams                var_name, var_value, gp.blank() | gp.show_type()
657*20f38712SPatrick Williams            )
65853902dd1SMichael Walsh            error_message += "\n"
65953902dd1SMichael Walsh            error_message += sub_error_message
66053902dd1SMichael Walsh            return process_error_message(error_message)
66153902dd1SMichael Walsh
66253902dd1SMichael Walsh    for key, value in invalid_values.items():
66353902dd1SMichael Walsh        if key not in var_value:
66453902dd1SMichael Walsh            continue
66553902dd1SMichael Walsh        key_name = "  [" + key + "]"
666*20f38712SPatrick Williams        sub_error_message = valid_value(
667*20f38712SPatrick Williams            var_value[key], invalid_values=value, var_name=key_name
668*20f38712SPatrick Williams        )
66953902dd1SMichael Walsh        if sub_error_message:
670*20f38712SPatrick Williams            error_message += (
671*20f38712SPatrick Williams                "The following dictionary is invalid because one of its"
672*20f38712SPatrick Williams                " entries is invalid:\n"
673*20f38712SPatrick Williams            )
674*20f38712SPatrick Williams            error_message += gp.sprint_varx(
675*20f38712SPatrick Williams                var_name, var_value, gp.blank() | gp.show_type()
676*20f38712SPatrick Williams            )
67753902dd1SMichael Walsh            error_message += "\n"
67853902dd1SMichael Walsh            error_message += sub_error_message
67953902dd1SMichael Walsh            return process_error_message(error_message)
68053902dd1SMichael Walsh
68153902dd1SMichael Walsh    return process_error_message(error_message)
68253902dd1SMichael Walsh
683018e25fbSMichael Walsh
68489eff544SMichael Walshdef valid_program(var_value, var_name=None):
685be3a8150SMichael Walsh    r"""
686da20f845SMichael Walsh    The variable value is valid if it contains the name of a program which can be located using the "which"
687da20f845SMichael Walsh    command.
688be3a8150SMichael Walsh
689be3a8150SMichael Walsh    Description of argument(s):
690be3a8150SMichael Walsh    var_value                       The value being validated.
691be3a8150SMichael Walsh    """
692be3a8150SMichael Walsh
693be3a8150SMichael Walsh    error_message = ""
694*20f38712SPatrick Williams    rc, out_buf = gc.shell_cmd(
695*20f38712SPatrick Williams        "which " + var_value, quiet=1, show_err=0, ignore_err=1
696*20f38712SPatrick Williams    )
697be3a8150SMichael Walsh    if rc:
69889eff544SMichael Walsh        var_name = get_var_name(var_name)
699be3a8150SMichael Walsh        error_message += "The following required program could not be found"
700be3a8150SMichael Walsh        error_message += " using the $PATH environment variable:\n"
70100244345SMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
702be3a8150SMichael Walsh        PATH = os.environ.get("PATH", "").split(":")
703be3a8150SMichael Walsh        error_message += "\n"
704be3a8150SMichael Walsh        error_message += gp.sprint_var(PATH)
705be3a8150SMichael Walsh    return process_error_message(error_message)
706be3a8150SMichael Walsh
707be3a8150SMichael Walsh
70889eff544SMichael Walshdef valid_length(var_value, min_length=None, max_length=None, var_name=None):
709b9d8dfd2SMichael Walsh    r"""
710da20f845SMichael Walsh    The variable value is valid if it is an object (e.g. list, dictionary) whose length is within the
711da20f845SMichael Walsh    specified range.
712b9d8dfd2SMichael Walsh
713b9d8dfd2SMichael Walsh    Description of argument(s):
714b9d8dfd2SMichael Walsh    var_value                       The value being validated.
715da20f845SMichael Walsh    min_length                      The minimum length of the object.  If not None, the length of var_value
716da20f845SMichael Walsh                                    must be greater than or equal to min_length.
717da20f845SMichael Walsh    max_length                      The maximum length of the object.  If not None, the length of var_value
718da20f845SMichael Walsh                                    must be less than or equal to min_length.
719b9d8dfd2SMichael Walsh    """
720b9d8dfd2SMichael Walsh
721b9d8dfd2SMichael Walsh    error_message = ""
722b9d8dfd2SMichael Walsh    length = len(var_value)
723b9d8dfd2SMichael Walsh    error_message = valid_range(length, min_length, max_length)
724b9d8dfd2SMichael Walsh    if error_message:
72589eff544SMichael Walsh        var_name = get_var_name(var_name)
726b9d8dfd2SMichael Walsh        error_message = "The length of the following object is not within the"
727b9d8dfd2SMichael Walsh        error_message += " expected range:\n"
728da20f845SMichael Walsh        error_message += gp.sprint_vars(min_length, max_length)
729b9d8dfd2SMichael Walsh        error_message += gp.sprint_var(length)
730b9d8dfd2SMichael Walsh        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
731b9d8dfd2SMichael Walsh        error_message += "\n"
732b9d8dfd2SMichael Walsh        return process_error_message(error_message)
733b9d8dfd2SMichael Walsh
734b9d8dfd2SMichael Walsh    return process_error_message(error_message)
735b9d8dfd2SMichael Walsh
736b9d8dfd2SMichael Walsh
737018e25fbSMichael Walsh# Modify selected function docstrings by adding headers/footers.
738018e25fbSMichael Walsh
739018e25fbSMichael Walshfunc_names = [
740*20f38712SPatrick Williams    "valid_type",
741*20f38712SPatrick Williams    "valid_value",
742*20f38712SPatrick Williams    "valid_range",
743*20f38712SPatrick Williams    "valid_integer",
744*20f38712SPatrick Williams    "valid_dir_path",
745*20f38712SPatrick Williams    "valid_file_path",
746*20f38712SPatrick Williams    "valid_path",
747*20f38712SPatrick Williams    "valid_list",
748*20f38712SPatrick Williams    "valid_dict",
749*20f38712SPatrick Williams    "valid_program",
750*20f38712SPatrick Williams    "valid_length",
751*20f38712SPatrick Williams    "valid_float",
752*20f38712SPatrick Williams    "valid_date_time",
753018e25fbSMichael Walsh]
754018e25fbSMichael Walsh
755018e25fbSMichael Walshraw_doc_strings = {}
756018e25fbSMichael Walsh
757018e25fbSMichael Walshfor func_name in func_names:
758018e25fbSMichael Walsh    cmd_buf = "raw_doc_strings['" + func_name + "'] = " + func_name
759018e25fbSMichael Walsh    cmd_buf += ".__doc__"
760018e25fbSMichael Walsh    exec(cmd_buf)
761018e25fbSMichael Walsh    cmd_buf = func_name + ".__doc__ = docstring_header + " + func_name
762*20f38712SPatrick Williams    cmd_buf += '.__doc__.rstrip(" \\n") + additional_args_docstring_footer'
763018e25fbSMichael Walsh    exec(cmd_buf)
764