1#!/usr/bin/env python
2
3r"""
4This module provides validation functions like valid_value(), valid_integer(),
5etc.
6"""
7
8import os
9import gen_print as gp
10import gen_cmd as gc
11import func_args as fa
12
13exit_on_error = False
14
15
16def set_exit_on_error(value):
17    r"""
18    Set the exit_on_error value to either True or False.
19
20    If exit_on_error is set, validation functions like valid_value() will exit
21    the program on error instead of returning False.
22
23    Description of argument(s):
24    value                           Value to set global exit_on_error to.
25    """
26
27    global exit_on_error
28    exit_on_error = value
29
30
31def get_var_name(var_name):
32    r"""
33    If var_name is not None, simply return its value.  Otherwise, get the
34    variable name of the first argument used to call the validation function
35    (e.g. valid, valid_integer, etc.) and return it.
36
37    This function is designed solely for use by other functions in this file.
38
39    Example:
40
41    A programmer codes this:
42
43    valid_value(last_name)
44
45    Which results in the following call stack:
46
47    valid_value(last_name)
48      -> get_var_name(var_name)
49
50    In this example, this function will return "last_name".
51
52    Example:
53
54    err_msg = valid_value(last_name, var_name="some_other_name")
55
56    Which results in the following call stack:
57
58    valid_value(var_value, var_name="some_other_name")
59      -> get_var_name(var_name)
60
61    In this example, this function will return "some_other_name".
62
63    Description of argument(s):
64    var_name                        The name of the variable.
65    """
66
67    return var_name or gp.get_arg_name(0, 1, stack_frame_ix=3)
68
69
70def process_error_message(error_message):
71    r"""
72    Process the error_message in the manner described below.
73
74    This function is designed solely for use by other functions in this file.
75
76    NOTE: A blank error_message means that there is no error.
77
78    For the following explanations, assume the caller of this function is a
79    function with the following definition:
80    valid_value(var_value, valid_values=[], invalid_values=[], var_name=None):
81
82    If the user of valid_value() is assigning the valid_value() return value
83    to a variable, process_error_message() will simply return the
84    error_message.  This mode of usage is illustrated by the following example:
85
86    error_message = valid_value(var1)
87
88    This mode is useful for callers who wish to validate a variable and then
89    decide for themselves what to do with the error_message (e.g.
90    raise(error_message), BuiltIn().fail(error_message), etc.).
91
92    If the user of valid_value() is NOT assigning the valid_value() return
93    value to a variable, process_error_message() will behave as follows.
94
95    First, if error_message is non-blank, it will be printed to stderr via a
96    call to gp.print_error_report(error_message).
97
98    If exit_on_error is set:
99    - If the error_message is blank, simply return.
100    - If the error_message is non-blank, exit the program with a return code
101      of 1.
102
103    If exit_on_error is NOT set:
104    - If the error_message is blank, return True.
105    - If the error_message is non-blank, return False.
106
107    Description of argument(s):
108    error_message                   An error message.
109    """
110
111    # Determine whether the caller's caller is assigning the result to a
112    # variable.
113    l_value = gp.get_arg_name(None, -1, stack_frame_ix=3)
114    if l_value:
115        return error_message
116
117    if error_message == "":
118        if exit_on_error:
119            return
120        return True
121
122    gp.print_error_report(error_message, stack_frame_ix=4)
123    if exit_on_error:
124        exit(1)
125    return False
126
127
128# Note to programmers:  All of the validation functions in this module should
129# follow the same basic template:
130# def valid_value(var_value, var1, var2, varn, var_name=None):
131#
132#     error_message = ""
133#     if not valid:
134#         var_name = get_var_name(var_name)
135#         error_message += "The following variable is invalid because...:\n"
136#         error_message += gp.sprint_varx(var_name, var_value, gp.blank())
137#
138#     return process_error_message(error_message)
139
140
141# The docstring header and footer will be added to each validation function's
142# existing docstring.
143docstring_header = \
144    r"""
145    Determine whether var_value is valid, construct an error_message and call
146    process_error_message(error_message).
147
148    See the process_error_message() function defined in this module for a
149    description of how error messages are processed.
150    """
151
152additional_args_docstring_footer = \
153    r"""
154    var_name                        The name of the variable whose value is
155                                    passed in var_value.  For the general
156                                    case, this argument is unnecessary as this
157                                    function can figure out the var_name.
158                                    This is provided for Robot callers in
159                                    which case, this function lacks the
160                                    ability to determine the variable name.
161    """
162
163
164def valid_type(var_value, required_type, var_name=None):
165    r"""
166    The variable value is valid if it is of the required type.
167
168    Examples:
169
170    valid_type(var1, int)
171
172    valid_type(var1, (list, dict))
173
174    Description of argument(s):
175    var_value                       The value being validated.
176    required_type                   A type or a tuple of types (e.g. str, int,
177                                    etc.).
178    """
179
180    error_message = ""
181    if type(required_type) is tuple:
182        if type(var_value) in required_type:
183            return process_error_message(error_message)
184    else:
185        if type(var_value) is required_type:
186            return process_error_message(error_message)
187
188    # If we get to this point, the validation has failed.
189    var_name = get_var_name(var_name)
190    error_message += "Invalid variable type:\n"
191    error_message += gp.sprint_varx(var_name, var_value,
192                                    gp.blank() | gp.show_type())
193    error_message += "\n"
194    error_message += gp.sprint_var(required_type)
195
196    return process_error_message(error_message)
197
198
199def valid_value(var_value, valid_values=[], invalid_values=[], var_name=None):
200
201    r"""
202    The variable value is valid if it is either contained in the valid_values
203    list or if it is NOT contained in the invalid_values list.  If the caller
204    specifies nothing for either of these 2 arguments, invalid_values will be
205    initialized to ['', None].  This is a good way to fail on variables which
206    contain blank values.
207
208    It is illegal to specify both valid_values and invalid values.
209
210    Example:
211
212    var1 = ''
213    valid_value(var1)
214
215    This code would fail because var1 is blank and the default value for
216    invalid_values is ['', None].
217
218    Example:
219    var1 = 'yes'
220    valid_value(var1, valid_values=['yes', 'true'])
221
222    This code would pass.
223
224    Description of argument(s):
225    var_value                       The value being validated.
226    valid_values                    A list of valid values.  The variable
227                                    value must be equal to one of these values
228                                    to be considered valid.
229    invalid_values                  A list of invalid values.  If the variable
230                                    value is equal to any of these, it is
231                                    considered invalid.
232    """
233
234    error_message = ""
235
236    # Validate this function's arguments.
237    len_valid_values = len(valid_values)
238    len_invalid_values = len(invalid_values)
239    if len_valid_values > 0 and len_invalid_values > 0:
240        error_message += "Programmer error - You must provide either an"
241        error_message += " invalid_values list or a valid_values"
242        error_message += " list but NOT both:\n"
243        error_message += gp.sprint_var(invalid_values)
244        error_message += gp.sprint_var(valid_values)
245        return process_error_message(error_message)
246
247    if len_valid_values > 0:
248        # Processing the valid_values list.
249        if var_value in valid_values:
250            return process_error_message(error_message)
251        var_name = get_var_name(var_name)
252        error_message += "Invalid variable value:\n"
253        error_message += gp.sprint_varx(var_name, var_value,
254                                        gp.blank() | gp.verbose()
255                                        | gp.show_type())
256        error_message += "\n"
257        error_message += "It must be one of the following values:\n"
258        error_message += "\n"
259        error_message += gp.sprint_var(valid_values,
260                                       gp.blank() | gp.show_type())
261        return process_error_message(error_message)
262
263    if len_invalid_values == 0:
264        # Assign default value.
265        invalid_values = ["", None]
266
267    # Assertion: We have an invalid_values list.  Processing it now.
268    if var_value not in invalid_values:
269        return process_error_message(error_message)
270
271    var_name = get_var_name(var_name)
272    error_message += "Invalid variable value:\n"
273    error_message += gp.sprint_varx(var_name, var_value,
274                                    gp.blank() | gp.verbose()
275                                    | gp.show_type())
276    error_message += "\n"
277    error_message += "It must NOT be one of the following values:\n"
278    error_message += "\n"
279    error_message += gp.sprint_var(invalid_values,
280                                   gp.blank() | gp.show_type())
281    return process_error_message(error_message)
282
283
284def valid_range(var_value, lower=None, upper=None, var_name=None):
285    r"""
286    The variable value is valid if it is within the specified range.
287
288    This function can be used with any type of operands where they can have a
289    greater than/less than relationship to each other (e.g. int, float, str).
290
291    Description of argument(s):
292    var_value                       The value being validated.
293    lower                           The lower end of the range.  If not None,
294                                    the var_value must be greater than or
295                                    equal to lower.
296    upper                           The upper end of the range.  If not None,
297                                    the var_value must be less than or equal
298                                    to upper.
299    """
300
301    error_message = ""
302    if lower is None and upper is None:
303        return process_error_message(error_message)
304    if lower is None and var_value <= upper:
305        return process_error_message(error_message)
306    if upper is None and var_value >= lower:
307        return process_error_message(error_message)
308    if lower and upper:
309        if lower > upper:
310            var_name = get_var_name(var_name)
311            error_message += "Programmer error - the lower value is greater"
312            error_message += " than the upper value:\n"
313            error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type())
314            return process_error_message(error_message)
315        if lower <= var_value <= upper:
316            return process_error_message(error_message)
317
318    var_name = get_var_name(var_name)
319    error_message += "The following variable is not within the expected"
320    error_message += " range:\n"
321    error_message += gp.sprint_varx(var_name, var_value, gp.show_type())
322    error_message += "\n"
323    error_message += "range:\n"
324    error_message += gp.sprint_vars(lower, upper, fmt=gp.show_type(), indent=2)
325    return process_error_message(error_message)
326
327
328def valid_integer(var_value, lower=None, upper=None, var_name=None):
329    r"""
330    The variable value is valid if it is an integer or can be interpreted as
331    an integer (e.g. 7, "7", etc.).
332
333    This function also calls valid_range to make sure the integer value is
334    within the specified range (if any).
335
336    Description of argument(s):
337    var_value                       The value being validated.
338    lower                           The lower end of the range.  If not None,
339                                    the var_value must be greater than or
340                                    equal to lower.
341    upper                           The upper end of the range.  If not None,
342                                    the var_value must be less than or equal
343                                    to upper.
344    """
345
346    error_message = ""
347    var_name = get_var_name(var_name)
348    try:
349        var_value = int(str(var_value), 0)
350    except ValueError:
351        error_message += "Invalid integer value:\n"
352        error_message += gp.sprint_varx(var_name, var_value,
353                                        gp.blank() | gp.show_type())
354        return process_error_message(error_message)
355
356    # Check the range (if any).
357    if lower:
358        lower = int(str(lower), 0)
359    if upper:
360        upper = int(str(upper), 0)
361    error_message = valid_range(var_value, lower, upper, var_name=var_name)
362
363    return process_error_message(error_message)
364
365
366def valid_dir_path(var_value, var_name=None):
367    r"""
368    The variable value is valid if it contains the path of an existing
369    directory.
370
371    Description of argument(s):
372    var_value                       The value being validated.
373    """
374
375    error_message = ""
376    if not os.path.isdir(str(var_value)):
377        var_name = get_var_name(var_name)
378        error_message += "The following directory does not exist:\n"
379        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
380
381    return process_error_message(error_message)
382
383
384def valid_file_path(var_value, var_name=None):
385    r"""
386    The variable value is valid if it contains the path of an existing file.
387
388    Description of argument(s):
389    var_value                       The value being validated.
390    """
391
392    error_message = ""
393    if not os.path.isfile(str(var_value)):
394        var_name = get_var_name(var_name)
395        error_message += "The following file does not exist:\n"
396        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
397
398    return process_error_message(error_message)
399
400
401def valid_path(var_value, var_name=None):
402    r"""
403    The variable value is valid if it contains the path of an existing file or
404    directory.
405
406    Description of argument(s):
407    var_value                       The value being validated.
408    """
409
410    error_message = ""
411    if not (os.path.isfile(str(var_value)) or os.path.isdir(str(var_value))):
412        var_name = get_var_name(var_name)
413        error_message += "Invalid path (file or directory does not exist):\n"
414        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
415
416    return process_error_message(error_message)
417
418
419def valid_list(var_value, valid_values=[], invalid_values=[],
420               required_values=[], fail_on_empty=False, var_name=None):
421    r"""
422    The variable value is valid if it is a list where each entry can be found
423    in the valid_values list or if none of its values can be found in the
424    invalid_values list or if all of the values in the required_values list
425    can be found in var_value.
426
427    The caller may only specify one of these 3 arguments: valid_values,
428    invalid_values, required_values.
429
430    Description of argument(s):
431    var_value                       The value being validated.
432    valid_values                    A list of valid values.  Each element in
433                                    the var_value list must be equal to one of
434                                    these values to be considered valid.
435    invalid_values                  A list of invalid values.  If any element
436                                    in var_value is equal to any of the values
437                                    in this argument, var_value is considered
438                                    invalid.
439    required_values                 Every value in required_values must be
440                                    found in var_value.  Otherwise, var_value
441                                    is considered invalid.
442    fail_on_empty                   Indicates that an empty list for the
443                                    variable value should be considered an
444                                    error.
445    """
446
447    error_message = ""
448
449    # Validate this function's arguments.
450    len_valid_values = len(valid_values)
451    len_invalid_values = len(invalid_values)
452    len_required_values = len(required_values)
453    if (len_valid_values + len_invalid_values + len_required_values) > 1:
454        error_message += "Programmer error - You must provide only one of the"
455        error_message += " following: valid_values, invalid_values,"
456        error_message += " required_values.\n"
457        error_message += gp.sprint_var(invalid_values)
458        error_message += gp.sprint_var(valid_values)
459        error_message += gp.sprint_var(required_values)
460        return process_error_message(error_message)
461
462    if type(var_value) is not list:
463        var_name = get_var_name(var_name)
464        error_message = valid_type(var_value, list, var_name=var_name)
465        if error_message:
466            return process_error_message(error_message)
467
468    if fail_on_empty and len(var_value) == 0:
469        var_name = get_var_name(var_name)
470        error_message += "Invalid empty list:\n"
471        error_message += gp.sprint_varx(var_name, var_value, gp.show_type())
472        return process_error_message(error_message)
473
474    if len(required_values):
475        found_error = 0
476        display_required_values = list(required_values)
477        for ix in range(0, len(required_values)):
478            if required_values[ix] not in var_value:
479                found_error = 1
480                display_required_values[ix] = \
481                    str(display_required_values[ix]) + "*"
482        if found_error:
483            var_name = get_var_name(var_name)
484            error_message += "The following list is invalid:\n"
485            error_message += gp.sprint_varx(var_name, var_value,
486                                            gp.blank() | gp.show_type())
487            error_message += "\n"
488            error_message += "Because some of the values in the "
489            error_message += "required_values list are not present (see"
490            error_message += " entries marked with \"*\"):\n"
491            error_message += "\n"
492            error_message += gp.sprint_varx('required_values',
493                                            display_required_values,
494                                            gp.blank() | gp.show_type())
495            error_message += "\n"
496
497        return process_error_message(error_message)
498
499    if len(invalid_values):
500        found_error = 0
501        display_var_value = list(var_value)
502        for ix in range(0, len(var_value)):
503            if var_value[ix] in invalid_values:
504                found_error = 1
505                display_var_value[ix] = str(var_value[ix]) + "*"
506
507        if found_error:
508            var_name = get_var_name(var_name)
509            error_message += "The following list is invalid (see entries"
510            error_message += " marked with \"*\"):\n"
511            error_message += gp.sprint_varx(var_name, display_var_value,
512                                            gp.blank() | gp.show_type())
513            error_message += "\n"
514            error_message += gp.sprint_var(invalid_values, gp.show_type())
515        return process_error_message(error_message)
516
517    found_error = 0
518    display_var_value = list(var_value)
519    for ix in range(0, len(var_value)):
520        if var_value[ix] not in valid_values:
521            found_error = 1
522            display_var_value[ix] = str(var_value[ix]) + "*"
523
524    if found_error:
525        var_name = get_var_name(var_name)
526        error_message += "The following list is invalid (see entries marked"
527        error_message += " with \"*\"):\n"
528        error_message += gp.sprint_varx(var_name, display_var_value,
529                                        gp.blank() | gp.show_type())
530        error_message += "\n"
531        error_message += gp.sprint_var(valid_values, gp.show_type())
532        return process_error_message(error_message)
533
534    return process_error_message(error_message)
535
536
537def valid_dict(var_value, required_keys=[], var_name=None):
538    r"""
539    The variable value is valid if it is a dictionary containing all of the
540    required keys.
541
542    Description of argument(s):
543    var_value                       The value being validated.
544    required_keys                   A list of keys which must be found in the
545                                    dictionary for it to be considered valid.
546    """
547
548    error_message = ""
549    missing_keys = list(set(required_keys) - set(var_value.keys()))
550    if len(missing_keys) > 0:
551        var_name = get_var_name(var_name)
552        error_message += "The following dictionary is invalid because it is"
553        error_message += " missing required keys:\n"
554        error_message += gp.sprint_varx(var_name, var_value,
555                                        gp.blank() | gp.show_type())
556        error_message += "\n"
557        error_message += gp.sprint_var(missing_keys, gp.show_type())
558    return process_error_message(error_message)
559
560
561def valid_program(var_value, var_name=None):
562    r"""
563    The variable value is valid if it contains the name of a program which can
564    be located using the "which" command.
565
566    Description of argument(s):
567    var_value                       The value being validated.
568    """
569
570    error_message = ""
571    rc, out_buf = gc.shell_cmd("which " + var_value, quiet=1, show_err=0,
572                               ignore_err=1)
573    if rc:
574        var_name = get_var_name(var_name)
575        error_message += "The following required program could not be found"
576        error_message += " using the $PATH environment variable:\n"
577        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
578        PATH = os.environ.get("PATH", "").split(":")
579        error_message += "\n"
580        error_message += gp.sprint_var(PATH)
581    return process_error_message(error_message)
582
583
584def valid_length(var_value, min_length=None, max_length=None, var_name=None):
585    r"""
586    The variable value is valid if it is an object (e.g. list, dictionary)
587    whose length is within the specified range.
588
589    Description of argument(s):
590    var_value                       The value being validated.
591    min_length                      The minimum length of the object.  If not
592                                    None, the length of var_value must be
593                                    greater than or equal to min_length.
594    max_length                      The maximum length of the object.  If not
595                                    None, the length of var_value must be less
596                                    than or equal to min_length.
597    """
598
599    error_message = ""
600    length = len(var_value)
601    error_message = valid_range(length, min_length, max_length)
602    if error_message:
603        var_name = get_var_name(var_name)
604        error_message = "The length of the following object is not within the"
605        error_message += " expected range:\n"
606        error_message += gp.sprint_var(length)
607        error_message += gp.sprint_varx(var_name, var_value, gp.blank())
608        error_message += "\n"
609        error_message += gp.sprint_vars(min_length, max_length)
610        return process_error_message(error_message)
611
612    return process_error_message(error_message)
613
614
615# Modify selected function docstrings by adding headers/footers.
616
617func_names = [
618    "valid_type", "valid_value", "valid_range", "valid_integer",
619    "valid_dir_path", "valid_file_path", "valid_path", "valid_list",
620    "valid_dict", "valid_program", "valid_length"
621]
622
623raw_doc_strings = {}
624
625for func_name in func_names:
626    cmd_buf = "raw_doc_strings['" + func_name + "'] = " + func_name
627    cmd_buf += ".__doc__"
628    exec(cmd_buf)
629    cmd_buf = func_name + ".__doc__ = docstring_header + " + func_name
630    cmd_buf += ".__doc__.rstrip(\" \\n\") + additional_args_docstring_footer"
631    exec(cmd_buf)
632