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 overload 9 10from phml.nodes import Element, Node 11 12__all__ = ["classnames", "ClassList"] 13 14def classnames( # pylint: disable=keyword-arg-before-vararg 15 node: Element | None = None, 16 *conditionals: str | int | list | dict[str, bool] | Element, 17) -> str | None: 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, conditions = validate_node(node, conditionals) 38 39 classes = init_classes(node) 40 41 for condition in conditions: 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 [ 65 klass 66 for klass in classnames(*condition).split(" ") 67 if klass not in classes 68 ], 69 ) 70 else: 71 raise TypeError(f"Unkown conditional statement: {condition}") 72 73 if node is None: 74 return " ".join(classes) 75 76 node["class"] = " ".join(classes) 77 return "" 78 79 80class ClassList: 81 """Utility class to manipulate the class list on a node. 82 83 Based on the hast-util-class-list: 84 https://github.com/brechtcs/hast-util-class-list 85 """ 86 87 def __init__(self, node: Element) -> None: 88 self.node = node 89 self._classes = str(node["class"]).split(" ") if "class" in node else [] 90 91 def __contains__(self, klass: str) -> bool: 92 return klass.strip().replace(" ", "-") in self._classes 93 94 def toggle(self, *klasses: str): 95 """Toggle a class in `class`.""" 96 97 for klass in klasses: 98 if klass.strip().replace(" ", "-") in self._classes: 99 self._classes.remove(klass.strip().replace(" ", "-")) 100 else: 101 self._classes.append(klass.strip().replace(" ", "-")) 102 103 self.node["class"] = self.classes 104 105 def add(self, *klasses: str): 106 """Add one or more classes to `class`.""" 107 108 for klass in klasses: 109 if klass not in self._classes: 110 self._classes.append(klass.strip().replace(" ", "-")) 111 112 self.node["class"] = self.classes 113 114 def replace(self, old_class: str, new_class: str): 115 """Replace a certain class in `class` with 116 another class. 117 """ 118 119 old_class = old_class.strip().replace(" ", "-") 120 new_class = new_class.strip().replace(" ", "-") 121 122 if old_class in self._classes: 123 idx = self._classes.index(old_class) 124 self._classes[idx] = new_class 125 self.node["class"] = self.classes 126 127 def remove(self, *klasses: str): 128 """Remove one or more classes from `class`.""" 129 130 for klass in klasses: 131 if klass in self._classes: 132 self._classes.remove(klass) 133 134 if len(self._classes) == 0: 135 self.node.attributes.pop("class", None) 136 else: 137 self.node["class"] = self.classes 138 139 @property 140 def classes(self) -> str: 141 """Return the formatted string of classes.""" 142 return " ".join(self._classes) 143 144 145def validate_node( 146 node: Element | None, conditionals: tuple 147) -> tuple[Element | None, tuple]: 148 """Validate a node is a node and that it is an element.""" 149 150 if isinstance(node, (str , int , list , dict)): 151 return None, (node, *conditionals) 152 153 if not isinstance(node, Element): 154 raise TypeError("Node must be an element") 155 156 return node, conditionals 157 158 159def init_classes(node) -> list[str]: 160 """Get the list of classes from an element.""" 161 if node is not None: 162 if "class" in node.attributes: 163 return sub(r" +", " ", node["class"]).split(" ") 164 165 node["class"] = "" 166 return [] 167 168 return []
def
classnames( node: phml.nodes.Element | None = None, *conditionals: str | int | list | dict[str, bool] | phml.nodes.Element) -> str | None:
15def classnames( # pylint: disable=keyword-arg-before-vararg 16 node: Element | None = None, 17 *conditionals: str | int | list | dict[str, bool] | Element, 18) -> str | None: 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, conditions = validate_node(node, conditionals) 39 40 classes = init_classes(node) 41 42 for condition in conditions: 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 [ 66 klass 67 for klass in classnames(*condition).split(" ") 68 if klass not in classes 69 ], 70 ) 71 else: 72 raise TypeError(f"Unkown conditional statement: {condition}") 73 74 if node is None: 75 return " ".join(classes) 76 77 node["class"] = " ".join(classes) 78 return ""
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'
Args
- 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.
class
ClassList:
81class ClassList: 82 """Utility class to manipulate the class list on a node. 83 84 Based on the hast-util-class-list: 85 https://github.com/brechtcs/hast-util-class-list 86 """ 87 88 def __init__(self, node: Element) -> None: 89 self.node = node 90 self._classes = str(node["class"]).split(" ") if "class" in node else [] 91 92 def __contains__(self, klass: str) -> bool: 93 return klass.strip().replace(" ", "-") in self._classes 94 95 def toggle(self, *klasses: str): 96 """Toggle a class in `class`.""" 97 98 for klass in klasses: 99 if klass.strip().replace(" ", "-") in self._classes: 100 self._classes.remove(klass.strip().replace(" ", "-")) 101 else: 102 self._classes.append(klass.strip().replace(" ", "-")) 103 104 self.node["class"] = self.classes 105 106 def add(self, *klasses: str): 107 """Add one or more classes to `class`.""" 108 109 for klass in klasses: 110 if klass not in self._classes: 111 self._classes.append(klass.strip().replace(" ", "-")) 112 113 self.node["class"] = self.classes 114 115 def replace(self, old_class: str, new_class: str): 116 """Replace a certain class in `class` with 117 another class. 118 """ 119 120 old_class = old_class.strip().replace(" ", "-") 121 new_class = new_class.strip().replace(" ", "-") 122 123 if old_class in self._classes: 124 idx = self._classes.index(old_class) 125 self._classes[idx] = new_class 126 self.node["class"] = self.classes 127 128 def remove(self, *klasses: str): 129 """Remove one or more classes from `class`.""" 130 131 for klass in klasses: 132 if klass in self._classes: 133 self._classes.remove(klass) 134 135 if len(self._classes) == 0: 136 self.node.attributes.pop("class", None) 137 else: 138 self.node["class"] = self.classes 139 140 @property 141 def classes(self) -> str: 142 """Return the formatted string of classes.""" 143 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
ClassList(node: phml.nodes.Element)
def
toggle(self, *klasses: str):
95 def toggle(self, *klasses: str): 96 """Toggle a class in `class`.""" 97 98 for klass in klasses: 99 if klass.strip().replace(" ", "-") in self._classes: 100 self._classes.remove(klass.strip().replace(" ", "-")) 101 else: 102 self._classes.append(klass.strip().replace(" ", "-")) 103 104 self.node["class"] = self.classes
Toggle a class in class
.
def
add(self, *klasses: str):
106 def add(self, *klasses: str): 107 """Add one or more classes to `class`.""" 108 109 for klass in klasses: 110 if klass not in self._classes: 111 self._classes.append(klass.strip().replace(" ", "-")) 112 113 self.node["class"] = self.classes
Add one or more classes to class
.
def
replace(self, old_class: str, new_class: str):
115 def replace(self, old_class: str, new_class: str): 116 """Replace a certain class in `class` with 117 another class. 118 """ 119 120 old_class = old_class.strip().replace(" ", "-") 121 new_class = new_class.strip().replace(" ", "-") 122 123 if old_class in self._classes: 124 idx = self._classes.index(old_class) 125 self._classes[idx] = new_class 126 self.node["class"] = self.classes
Replace a certain class in class
with
another class.
def
remove(self, *klasses: str):
128 def remove(self, *klasses: str): 129 """Remove one or more classes from `class`.""" 130 131 for klass in klasses: 132 if klass in self._classes: 133 self._classes.remove(klass) 134 135 if len(self._classes) == 0: 136 self.node.attributes.pop("class", None) 137 else: 138 self.node["class"] = self.classes
Remove one or more classes from class
.