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

1"""phml.utils.validate.test 

2 

3Logic that allows nodes to be tested against a series of conditions. 

4""" 

5 

6from __future__ import annotations 

7 

8from typing import TYPE_CHECKING, Callable, Optional 

9 

10from phml.nodes import Element 

11 

12if TYPE_CHECKING: 

13 from phml.nodes import All_Nodes, Root 

14 

15Test = None | str | list | dict | Callable 

16 

17 

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). 

26 

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. 

35 

36 If the `parent` arg is passed so should the `index` arg. 

37 

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. 

45 

46 Returns: 

47 True if all tests pass. 

48 """ 

49 

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 

60 

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 

64 

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 ) 

76 

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 ) 

83 

84 return bool( 

85 any(isinstance(cond, Test) and check(node, cond, index, parent) for cond in _test) 

86 ) 

87 

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) 

91 

92 raise Exception("Invalid test condition")