Coverage for phml\utils\validate\check.py: 79%
19 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 11:07 -0600
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 11:07 -0600
1"""phml.utils.validate.test
3Logic that allows nodes to be tested against a series of conditions.
4"""
6from __future__ import annotations
8from typing import TYPE_CHECKING, Callable, Optional
10from phml.nodes import Element
12if TYPE_CHECKING:
13 from phml.nodes import All_Nodes, Root
15Test = None | str | list | dict | Callable
18def check(
19 node: All_Nodes,
20 _test: Test,
21 index: Optional[int] = None,
22 parent: Optional[Root | Element] = None,
23 strict: bool = True,
24) -> bool:
25 """Test if a node passes the given test(s).
27 Test Types:
28 - `None`: Just checks that the node is a valid node.
29 - `str`: Checks that the test value is == the `node.type`.
30 - `dict`: Checks all items are valid attributes on the node.
31 and that the values are strictly equal.
32 - `Callable`: Passes the given function the node and it's index, if provided,
33 and checks if the callable returned true.
34 - `list[Test]`: Apply all the rules above for each Test in the list.
36 If the `parent` arg is passed so should the `index` arg.
38 Args:
39 node (All_Nodes): Node to test. Can be any phml node.
40 test (Test): Test to apply to the node. See previous section
41 for more info.
42 index (Optional[int], optional): Index in the parent where the
43 node exists. Defaults to None.
44 parent (Optional[Root | Element], optional): The nodes parent. Defaults to None.
46 Returns:
47 True if all tests pass.
48 """
50 if parent is not None:
51 # If parent is given then index has to be also.
52 # Validate index is correct in parent.children
53 if index is None or parent.children[index] != node:
54 return False
56 if isinstance(_test, str):
57 # If string then validate that the type is the same
58 return hasattr(node, "type") and node.type == _test
60 if isinstance(_test, dict):
61 # If dict validate all items with properties are the same
62 # Either in attributes or in
63 return bool(
64 isinstance(node, Element)
65 and all(
66 (hasattr(node, key) and value == getattr(node, key))
67 or (hasattr(node, "properties") and key in node.properties and value == node[key])
68 for key, value in _test.items()
69 )
70 )
72 if isinstance(_test, list):
73 # If list then recursively apply tests
74 if strict:
75 return bool(
76 all(isinstance(cond, Test) and check(node, cond, index, parent) for cond in _test)
77 )
79 return bool(
80 any(isinstance(cond, Test) and check(node, cond, index, parent) for cond in _test)
81 )
83 if isinstance(_test, Callable):
84 # If callable return result of collable after passing node, index, and parent
85 return _test(node, index, node.parent)
87 raise Exception("Invalid test condition")