phml.utilities.misc.classes
utilities.misc
A collection of utilities that don't fit in with finding, selecting, testing, transforming, traveling, or validating nodes.
1"""utilities.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 split, sub 8from typing import Optional 9 10from phml.core.nodes import NODE, Element 11 12__all__ = ["classnames", "ClassList"] 13 14 15def classnames( # pylint: disable=keyword-arg-before-vararg 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 Assume that the current class on node is `bold` 24 * `classnames(node, 'flex')` yields `'bold flex'` 25 * `classnames(node, 13)` yields `'bold 13'` 26 * `classnames(node, {'shadow': True, 'border': 0})` yields `'bold shadow'` 27 * `classnames('a', 13, {'b': True}, ['c', {'d': False}])` yields `'a b c'` 28 29 Args: 30 node (Element | None): Node to apply the classes too. If no node is given 31 then the function returns a string. 32 33 Returns: 34 str: The concat string of classes after processing. 35 """ 36 37 node, conditionals = validate_node(node, conditionals) 38 39 classes = init_classes(node) 40 41 for condition in conditionals: 42 if isinstance(condition, str): 43 classes.extend( 44 [ 45 klass 46 for klass in split(r" ", sub(r" +", "", condition.strip())) 47 if klass not in classes 48 ] 49 ) 50 elif isinstance(condition, int) and str(condition) not in classes: 51 classes.append(str(condition)) 52 elif isinstance(condition, dict): 53 for key, value in condition.items(): 54 if value: 55 classes.extend( 56 [ 57 klass 58 for klass in split(r" ", sub(r" +", "", key.strip())) 59 if klass not in classes 60 ] 61 ) 62 elif isinstance(condition, list): 63 classes.extend( 64 [klass for klass in classnames(*condition).split(" ") if klass not in classes] 65 ) 66 else: 67 raise TypeError(f"Unkown conditional statement: {condition}") 68 69 if node is None: 70 return " ".join(classes) 71 72 node["class"] = " ".join(classes) 73 return None 74 75 76class ClassList: 77 """Utility class to manipulate the class list on a node. 78 79 Based on the hast-util-class-list: 80 https://github.com/brechtcs/hast-util-class-list 81 """ 82 83 def __init__(self, node: Element): 84 self.node = node 85 self.classes = node["class"].split(" ") if "class" in node.properties else [] 86 87 def contains(self, klass: str): 88 """Check if `class` contains a certain class.""" 89 90 return klass.strip().replace(" ", "-") in self.classes 91 92 def toggle(self, *klasses: str): 93 """Toggle a class in `class`.""" 94 95 for klass in klasses: 96 if klass.strip().replace(" ", "-") in self.classes: 97 self.classes.remove(klass.strip().replace(" ", "-")) 98 else: 99 self.classes.append(klass.strip().replace(" ", "-")) 100 101 self.node["class"] = self.class_list() 102 103 def add(self, *klasses: str): 104 """Add one or more classes to `class`.""" 105 106 for klass in klasses: 107 if klass not in self.classes: 108 self.classes.append(klass.strip().replace(" ", "-")) 109 110 self.node["class"] = self.class_list() 111 112 def replace(self, old_class: str, new_class: str): 113 """Replace a certain class in `class` with 114 another class. 115 """ 116 117 old_class = old_class.strip().replace(" ", "-") 118 new_class = new_class.strip().replace(" ", "-") 119 120 if old_class in self.classes: 121 idx = self.classes.index(old_class) 122 self.classes[idx] = new_class 123 self.node["class"] = self.class_list() 124 125 def remove(self, *klasses: str): 126 """Remove one or more classes from `class`.""" 127 128 for klass in klasses: 129 if klass in self.classes: 130 self.classes.remove(klass) 131 132 if len(self.classes) == 0: 133 self.node.properties.pop("class", None) 134 else: 135 self.node["class"] = self.class_list() 136 137 def class_list(self) -> str: 138 """Return the formatted string of classes.""" 139 return ' '.join(self.classes) 140 141 142def validate_node(node, conditionals: list) -> bool: 143 """Validate a node is a node and that it is an element.""" 144 if not isinstance(node, NODE): 145 return None, [node, *conditionals] 146 147 if not isinstance(node, Element): 148 raise TypeError("Node must be an element") 149 150 return node, conditionals 151 152 153def init_classes(node) -> list[str]: 154 """Get the list of classes from an element.""" 155 if node is not None: 156 if "class" in node.properties: 157 return sub(r" +", " ", node["class"]).split(" ") 158 159 node["class"] = "" 160 return [] 161 162 return []
16def classnames( # pylint: disable=keyword-arg-before-vararg 17 node: Optional[Element] = None, *conditionals: str | int | list | dict[str, bool] 18) -> str: 19 """Concat a bunch of class names. Can take a str as a class, 20 int which is cast to a str to be a class, a dict of conditional classes, 21 and a list of all the previous conditions including itself. 22 23 Examples: 24 Assume that the current class on node is `bold` 25 * `classnames(node, 'flex')` yields `'bold flex'` 26 * `classnames(node, 13)` yields `'bold 13'` 27 * `classnames(node, {'shadow': True, 'border': 0})` yields `'bold shadow'` 28 * `classnames('a', 13, {'b': True}, ['c', {'d': False}])` yields `'a b c'` 29 30 Args: 31 node (Element | None): Node to apply the classes too. If no node is given 32 then the function returns a string. 33 34 Returns: 35 str: The concat string of classes after processing. 36 """ 37 38 node, conditionals = validate_node(node, conditionals) 39 40 classes = init_classes(node) 41 42 for condition in conditionals: 43 if isinstance(condition, str): 44 classes.extend( 45 [ 46 klass 47 for klass in split(r" ", sub(r" +", "", condition.strip())) 48 if klass not in classes 49 ] 50 ) 51 elif isinstance(condition, int) and str(condition) not in classes: 52 classes.append(str(condition)) 53 elif isinstance(condition, dict): 54 for key, value in condition.items(): 55 if value: 56 classes.extend( 57 [ 58 klass 59 for klass in split(r" ", sub(r" +", "", key.strip())) 60 if klass not in classes 61 ] 62 ) 63 elif isinstance(condition, list): 64 classes.extend( 65 [klass for klass in classnames(*condition).split(" ") if klass not in classes] 66 ) 67 else: 68 raise TypeError(f"Unkown conditional statement: {condition}") 69 70 if node is None: 71 return " ".join(classes) 72 73 node["class"] = " ".join(classes) 74 return None
Concat a bunch of class names. Can take a str as a class, int which is cast to a str to be a class, a dict of conditional classes, and a list of all the previous conditions including itself.
Examples:
Assume that the current class on node is
bold
classnames(node, 'flex')
yields'bold flex'
classnames(node, 13)
yields'bold 13'
classnames(node, {'shadow': True, 'border': 0})
yields'bold shadow'
classnames('a', 13, {'b': True}, ['c', {'d': False}])
yields'a b c'
Arguments:
- node (Element | None): Node to apply the classes too. If no node is given
- then the function returns a string.
Returns:
str: The concat string of classes after processing.
77class ClassList: 78 """Utility class to manipulate the class list on a node. 79 80 Based on the hast-util-class-list: 81 https://github.com/brechtcs/hast-util-class-list 82 """ 83 84 def __init__(self, node: Element): 85 self.node = node 86 self.classes = node["class"].split(" ") if "class" in node.properties else [] 87 88 def contains(self, klass: str): 89 """Check if `class` contains a certain class.""" 90 91 return klass.strip().replace(" ", "-") in self.classes 92 93 def toggle(self, *klasses: str): 94 """Toggle a class in `class`.""" 95 96 for klass in klasses: 97 if klass.strip().replace(" ", "-") in self.classes: 98 self.classes.remove(klass.strip().replace(" ", "-")) 99 else: 100 self.classes.append(klass.strip().replace(" ", "-")) 101 102 self.node["class"] = self.class_list() 103 104 def add(self, *klasses: str): 105 """Add one or more classes to `class`.""" 106 107 for klass in klasses: 108 if klass not in self.classes: 109 self.classes.append(klass.strip().replace(" ", "-")) 110 111 self.node["class"] = self.class_list() 112 113 def replace(self, old_class: str, new_class: str): 114 """Replace a certain class in `class` with 115 another class. 116 """ 117 118 old_class = old_class.strip().replace(" ", "-") 119 new_class = new_class.strip().replace(" ", "-") 120 121 if old_class in self.classes: 122 idx = self.classes.index(old_class) 123 self.classes[idx] = new_class 124 self.node["class"] = self.class_list() 125 126 def remove(self, *klasses: str): 127 """Remove one or more classes from `class`.""" 128 129 for klass in klasses: 130 if klass in self.classes: 131 self.classes.remove(klass) 132 133 if len(self.classes) == 0: 134 self.node.properties.pop("class", None) 135 else: 136 self.node["class"] = self.class_list() 137 138 def class_list(self) -> str: 139 """Return the formatted string of classes.""" 140 return ' '.join(self.classes)
Utility class to manipulate the class list on a node.
Based on the hast-util-class-list: https://github.com/brechtcs/hast-util-class-list
88 def contains(self, klass: str): 89 """Check if `class` contains a certain class.""" 90 91 return klass.strip().replace(" ", "-") in self.classes
Check if class
contains a certain class.
93 def toggle(self, *klasses: str): 94 """Toggle a class in `class`.""" 95 96 for klass in klasses: 97 if klass.strip().replace(" ", "-") in self.classes: 98 self.classes.remove(klass.strip().replace(" ", "-")) 99 else: 100 self.classes.append(klass.strip().replace(" ", "-")) 101 102 self.node["class"] = self.class_list()
Toggle a class in class
.
104 def add(self, *klasses: str): 105 """Add one or more classes to `class`.""" 106 107 for klass in klasses: 108 if klass not in self.classes: 109 self.classes.append(klass.strip().replace(" ", "-")) 110 111 self.node["class"] = self.class_list()
Add one or more classes to class
.
113 def replace(self, old_class: str, new_class: str): 114 """Replace a certain class in `class` with 115 another class. 116 """ 117 118 old_class = old_class.strip().replace(" ", "-") 119 new_class = new_class.strip().replace(" ", "-") 120 121 if old_class in self.classes: 122 idx = self.classes.index(old_class) 123 self.classes[idx] = new_class 124 self.node["class"] = self.class_list()
Replace a certain class in class
with
another class.
126 def remove(self, *klasses: str): 127 """Remove one or more classes from `class`.""" 128 129 for klass in klasses: 130 if klass in self.classes: 131 self.classes.remove(klass) 132 133 if len(self.classes) == 0: 134 self.node.properties.pop("class", None) 135 else: 136 self.node["class"] = self.class_list()
Remove one or more classes from class
.