10func matches(conditions:Dictionary, context:Dictionary = {}) -> bool:
11 if conditions.is_empty():
13 for key
in conditions.keys():
14 var expected = conditions[key]
15 if str(key) ==
"all" and expected
is Array:
16 for child
in expected:
17 if child
is Dictionary
and not matches(child, context):
20 if str(key) ==
"any" and expected
is Array:
22 for child
in expected:
23 if child
is Dictionary
and matches(child, context):
29 if str(key) ==
"not" and expected
is Dictionary:
30 if matches(expected, context):
33 if not _value_matches(value_at(context, str(key)), expected):
39func value_at(context:Dictionary, dotted_key:String, default_value = null):
41 for part
in dotted_key.split(
"."):
42 if current
is Dictionary
and current.has(part):
43 current = current[part]
49func _value_matches(actual, expected) -> bool:
50 if expected
is Dictionary:
51 if expected.has(
"exists"):
52 return (actual != null) == bool(expected.get(
"exists", true))
53 if expected.has(
"equals")
and actual != expected.get(
"equals"):
55 if expected.has(
"not_equals")
and actual == expected.get(
"not_equals"):
57 if expected.has(
"at_least")
and _to_float(actual) < _to_float(expected.get(
"at_least", 0.0)):
59 if expected.has(
"at_most")
and _to_float(actual) > _to_float(expected.get(
"at_most", 0.0)):
61 if expected.has(
"greater_than")
and _to_float(actual) <= _to_float(expected.get(
"greater_than", 0.0)):
63 if expected.has(
"less_than")
and _to_float(actual) >= _to_float(expected.get(
"less_than", 0.0)):
65 if expected.has(
"contains"):
67 if not actual.has(expected.get(
"contains")):
69 elif not str(actual).contains(str(expected.get(
"contains"))):
71 if expected.has(
"in"):
72 var allowed = expected.get(
"in")
73 if not allowed
is Array
or not allowed.has(actual):
76 return actual == expected
79func _to_float(value) -> float:
80 if value
is int
or value
is float:
83 return 1.0
if value
else 0.0
86 return str(value).to_float()