Coverage for phml\validate\check.py: 100%
19 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 14:27 -0600
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-08 14:27 -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 (
54 index is None
55 or len(parent.children) == 0
56 or index >= len(parent.children)
57 or parent.children[index] != node
58 ):
59 return False
61 if isinstance(_test, str):
62 # If string then validate that the type is the same
63 return hasattr(node, "type") and node.type == _test
65 if isinstance(_test, dict):
66 # If dict validate all items with properties are the same
67 # Either in attributes or in
68 return bool(
69 isinstance(node, Element)
70 and all(
71 (hasattr(node, key) and value == getattr(node, key))
72 or (hasattr(node, "properties") and key in node.properties and value == node[key])
73 for key, value in _test.items()
74 )
75 )
77 if isinstance(_test, list):
78 # If list then recursively apply tests
79 if strict:
80 return bool(
81 all(isinstance(cond, Test) and check(node, cond, index, parent) for cond in _test)
82 )
84 return bool(
85 any(isinstance(cond, Test) and check(node, cond, index, parent) for cond in _test)
86 )
88 if isinstance(_test, Callable):
89 # If callable return result of collable after passing node, index, and parent
90 return _test(node, index, node.parent)
92 raise Exception("Invalid test condition")