Coverage for phml\utils\misc\classes.py: 21%

48 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-30 09:38 -0600

1"""utils.misc 

2 

3A collection of utilities that don't fit in with finding, selecting, testing, 

4transforming, traveling, or validating nodes. 

5""" 

6 

7from re import search, split, sub 

8from typing import Optional 

9 

10from phml.nodes import Element 

11 

12__all__ = ["classnames", "ClassList"] 

13 

14 

15def classnames( 

16 node: Optional[Element] = None, *conditionals: str | int | list | dict[str, bool] 

17) -> str: 

18 """Concat a bunch of class names. Can take a str as a class, 

19 int which is cast to a str to be a class, a dict of conditional classes, 

20 and a list of all the previous conditions including itself. 

21 

22 Examples: 

23 * `classnames(node, 'flex')` yields `'flex'` 

24 * `classnames(node, 13)` yields `'13'` 

25 * `classnames(node, {'shadow': True, 'border': 0})` yields `'shadow'` 

26 * `classnames(node, 'a', 13, {'b': True}, ['c', {'d': False}])` yields `'a b c'` 

27 

28 Args: 

29 node (Element | None): Node to apply the classes too. If no node is given 

30 then the function returns a string. 

31 

32 Returns: 

33 str: The concat string of classes after processing. 

34 """ 

35 

36 classes = [] 

37 for condition in conditionals: 

38 if isinstance(condition, str): 

39 classes.extend(split(r" ", sub(r" +", "", condition.strip()))) 

40 elif isinstance(condition, int): 

41 classes.append(str(condition)) 

42 elif isinstance(condition, dict): 

43 for key, value in condition.items(): 

44 if value: 

45 classes.extend(split(r" ", sub(r" +", "", key.strip()))) 

46 elif isinstance(condition, list): 

47 classes.extend(classnames(*condition).split(" ")) 

48 else: 

49 raise TypeError(f"Unkown conditional statement: {condition}") 

50 

51 if node is None: 

52 return " ".join(classes) 

53 else: 

54 node.properties["class"] = node.properties["class"] or "" + f" {' '.join(classes)}" 

55 

56 

57class ClassList: 

58 """Utility class to manipulate the class list on a node. 

59 

60 Based on the hast-util-class-list: 

61 https://github.com/brechtcs/hast-util-class-list 

62 """ 

63 

64 def __init__(self, node: Element): 

65 self.node = node 

66 

67 def contains(self, klass: str): 

68 """Check if `class` contains a certain class.""" 

69 from phml.utils import has_property 

70 

71 if has_property(self.node, "class"): 

72 return search(klass, self.node.properties["class"]) is not None 

73 return False 

74 

75 def toggle(self, *klasses: str): 

76 """Toggle a class in `class`.""" 

77 

78 for klass in klasses: 

79 if search(f"\b{klass}\b", self.node.properties["class"]) is not None: 

80 sub(f"\b{klass}\b", "", self.node.properties["class"]) 

81 sub(r" +", " ", self.node.properties["class"]) 

82 else: 

83 self.node.properties["class"] = self.node.properties["class"].strip() + f" {klass}" 

84 

85 def add(self, *klasses: str): 

86 """Add one or more classes to `class`.""" 

87 

88 for klass in klasses: 

89 if search(f"\b{klass}\b", self.node.properties["class"]) is None: 

90 self.node.properties["class"] = self.node.properties["class"].strip() + f" {klass}" 

91 

92 def replace(self, old_klass: str, new_klass: str): 

93 """Replace a certain class in `class` with 

94 another class. 

95 """ 

96 

97 if search(f"\b{old_klass}\b", self.node.properties["class"]) is not None: 

98 sub(f"\b{old_klass}\b", f"\b{new_klass}\b", self.node.properties["class"]) 

99 sub(r" +", " ", self.node.properties["class"]) 

100 

101 def remove(self, *klasses: str): 

102 """Remove one or more classes from `class`.""" 

103 

104 for klass in klasses: 

105 if search(f"\b{klass}\b", self.node.properties["class"]) is not None: 

106 sub(f"\b{klass}\b", "", self.node.properties["class"]) 

107 sub(r" +", " ", self.node.properties["class"])