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