You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
5.2 KiB

2 years ago
2 years ago
  1. import re
  2. from functools import wraps
  3. from gooey.gui.components.filtering.prefix_filter import OperatorType
  4. class SuperBool(object):
  5. """
  6. A boolean which keeps with it the rationale
  7. for when it is false.
  8. """
  9. def __init__(self, value, rationale):
  10. self.value = value
  11. self.rationale = rationale
  12. def __bool__(self):
  13. return self.value
  14. __nonzero__ = __bool__
  15. def __str__(self):
  16. return str(self.value)
  17. def lift(f):
  18. """
  19. Lifts a basic predicate to the SuperBool type
  20. stealing the docstring as the rationale message.
  21. This is largely just goofing around and experimenting
  22. since it's a private internal API.
  23. """
  24. @wraps(f)
  25. def inner(value):
  26. result = f(value)
  27. return SuperBool(result, f.__doc__) if not isinstance(result, SuperBool) else result
  28. return inner
  29. @lift
  30. def is_tuple_or_list(value):
  31. """Must be either a list or tuple"""
  32. return isinstance(value, list) or isinstance(value, tuple)
  33. @lift
  34. def is_str(value):
  35. """Must be of type `str`"""
  36. return isinstance(value, str)
  37. @lift
  38. def is_str_or_coll(value):
  39. """
  40. Colors must be either a hex string or collection of RGB values.
  41. e.g.
  42. Hex string: #fff0ce
  43. RGB Collection: [0, 255, 128] or (0, 255, 128)
  44. """
  45. return bool(is_str(value)) or bool(is_tuple_or_list(value))
  46. @lift
  47. def has_valid_channel_values(rgb_coll):
  48. """Colors in an RGB collection must all be in the range 0-255"""
  49. return all([is_0to255(c) and is_int(c) for c in rgb_coll])
  50. @lift
  51. def is_three_channeled(value):
  52. """Missing channels! Colors in an RGB collection should be of the form [R,G,B] or (R,G,B)"""
  53. return len(value) == 3
  54. @lift
  55. def is_hex_string(value: str):
  56. """Invalid hexadecimal format. Expected: "#FFFFFF" """
  57. return isinstance(value, str) and bool(re.match('^#[\dABCDEF]{6}$', value, flags=2))
  58. @lift
  59. def is_bool(value):
  60. """Must be of type Boolean"""
  61. return isinstance(value, bool)
  62. @lift
  63. def non_empty_string(value):
  64. """Must be a non-empty non-blank string"""
  65. return bool(value) and bool(value.strip())
  66. @lift
  67. def is_tokenization_operator(value):
  68. """Operator must be a valid OperatorType i.e. one of: (AND, OR)"""
  69. return bool(value) in (OperatorType.AND, OperatorType.OR)
  70. @lift
  71. def is_tokenizer(value):
  72. """Tokenizers must be valid Regular expressions. see: options.PrefixTokenizers"""
  73. return bool(non_empty_string(value))
  74. @lift
  75. def is_int(value):
  76. """Invalid type. Expected `int`"""
  77. return isinstance(value, int)
  78. @lift
  79. def is_0to255(value):
  80. """RGB values must be in the range 0 - 255 (inclusive)"""
  81. return 0 <= value <= 255
  82. def is_0to20(value):
  83. """Precision values must be in the range 0 - 20 (inclusive)"""
  84. return 0 <= value <= 20
  85. @lift
  86. def is_valid_color(value):
  87. """Must be either a valid hex string or RGB list"""
  88. if is_str(value):
  89. return is_hex_string(value)
  90. elif is_tuple_or_list(value):
  91. return (is_tuple_or_list(value)
  92. and is_three_channeled(value)
  93. and has_valid_channel_values(value))
  94. else:
  95. return is_str_or_coll(value)
  96. validators = {
  97. 'label_color': is_valid_color,
  98. 'label_bg_color': is_valid_color,
  99. 'help_color': is_valid_color,
  100. 'help_bg_color': is_valid_color,
  101. 'error_color': is_valid_color,
  102. 'error_bg_color': is_valid_color,
  103. 'show_label': is_bool,
  104. 'show_help': is_bool,
  105. 'visible': is_bool,
  106. 'full_width': is_bool,
  107. 'height': is_int,
  108. 'readonly': is_bool,
  109. 'initial_selection': is_int,
  110. 'title': non_empty_string,
  111. 'checkbox_label': non_empty_string,
  112. 'placeholder': non_empty_string,
  113. 'empty_message': non_empty_string,
  114. 'max_size': is_int,
  115. 'choice_tokenizer': is_tokenizer,
  116. 'input_tokenizer': is_tokenizer,
  117. 'ignore_case': is_bool,
  118. 'operator': is_tokenization_operator,
  119. 'index_suffix': is_bool,
  120. 'wildcard': non_empty_string,
  121. 'default_dir': non_empty_string,
  122. 'default_file': non_empty_string,
  123. 'default_path': non_empty_string,
  124. 'message': non_empty_string,
  125. 'precision': is_0to20
  126. }
  127. def collect_errors(predicates, m):
  128. return {
  129. k:predicates[k](v).rationale
  130. for k,v in m.items()
  131. if k in predicates and not predicates[k](v)}
  132. def validate(pred, value):
  133. result = pred(value)
  134. if not result:
  135. raise ValueError(result.rationale)
  136. if __name__ == '__main__':
  137. # TODO: there should be tests
  138. pass
  139. # print(validateColor((1, 'ergerg', 1234)))
  140. # print(validateColor(1234))
  141. # print(validateColor(123.234))
  142. # print(validateColor('123.234'))
  143. # print(validateColor('FFFAAA'))
  144. # print(validateColor('#FFFAAA'))
  145. # print(validateColor([]))
  146. # print(validateColor(()))
  147. # print(validateColor((1, 2)))
  148. # print(validateColor((1, 2, 1234)))
  149. # print(is_lifted(lift(is_int)))
  150. # print(is_lifted(is_int))
  151. # print(OR(is_poop, is_int)('poop'))
  152. # print(AND(is_poop, is_lower, is_lower)('pooP'))
  153. # print(OR(is_poop, is_int))
  154. # print(is_lifted(OR(is_poop, is_int)))
  155. # print(validate(is_valid_color, [255, 255, 256]))
  156. # print(is_valid_color('#fff000'))
  157. # print(is_valid_color([255, 244, 256]))
  158. # print(non_empty_string('asdf') and non_empty_string('asdf'))
  159. # validate(is_valid_color, 1234)