phml.utils.misc.classes

utils.misc

A collection of utilities that don't fit in with finding, selecting, testing, transforming, traveling, or validating nodes.

  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(  # 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    * `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
 54    node.properties["class"] = node.properties["class"] or "" + f" {' '.join(classes)}"
 55    return None
 56
 57
 58class ClassList:
 59    """Utility class to manipulate the class list on a node.
 60
 61    Based on the hast-util-class-list:
 62    https://github.com/brechtcs/hast-util-class-list
 63    """
 64
 65    def __init__(self, node: Element):
 66        self.node = node
 67
 68    def contains(self, klass: str):
 69        """Check if `class` contains a certain class."""
 70        from phml.utils import has_property  # pylint: disable=import-outside-toplevel
 71
 72        if has_property(self.node, "class"):
 73            return search(klass, self.node.properties["class"]) is not None
 74        return False
 75
 76    def toggle(self, *klasses: str):
 77        """Toggle a class in `class`."""
 78
 79        for klass in klasses:
 80            if search(f"\b{klass}\b", self.node.properties["class"]) is not None:
 81                sub(f"\b{klass}\b", "", self.node.properties["class"])
 82                sub(r" +", " ", self.node.properties["class"])
 83            else:
 84                self.node.properties["class"] = self.node.properties["class"].strip() + f" {klass}"
 85
 86    def add(self, *klasses: str):
 87        """Add one or more classes to `class`."""
 88
 89        for klass in klasses:
 90            if search(f"\b{klass}\b", self.node.properties["class"]) is None:
 91                self.node.properties["class"] = self.node.properties["class"].strip() + f" {klass}"
 92
 93    def replace(self, old_klass: str, new_klass: str):
 94        """Replace a certain class in `class` with
 95        another class.
 96        """
 97
 98        if search(f"\b{old_klass}\b", self.node.properties["class"]) is not None:
 99            sub(f"\b{old_klass}\b", f"\b{new_klass}\b", self.node.properties["class"])
100            sub(r" +", " ", self.node.properties["class"])
101
102    def remove(self, *klasses: str):
103        """Remove one or more classes from `class`."""
104
105        for klass in klasses:
106            if search(f"\b{klass}\b", self.node.properties["class"]) is not None:
107                sub(f"\b{klass}\b", "", self.node.properties["class"])
108                sub(r" +", " ", self.node.properties["class"])
def classnames( node: Optional[phml.nodes.element.Element] = None, *conditionals: str | int | list | dict[str, bool]) -> str:
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    * `classnames(node, 'flex')` yields `'flex'`
25    * `classnames(node, 13)` yields `'13'`
26    * `classnames(node, {'shadow': True, 'border': 0})` yields `'shadow'`
27    * `classnames(node, '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    classes = []
38    for condition in conditionals:
39        if isinstance(condition, str):
40            classes.extend(split(r" ", sub(r" +", "", condition.strip())))
41        elif isinstance(condition, int):
42            classes.append(str(condition))
43        elif isinstance(condition, dict):
44            for key, value in condition.items():
45                if value:
46                    classes.extend(split(r" ", sub(r" +", "", key.strip())))
47        elif isinstance(condition, list):
48            classes.extend(classnames(*condition).split(" "))
49        else:
50            raise TypeError(f"Unkown conditional statement: {condition}")
51
52    if node is None:
53        return " ".join(classes)
54
55    node.properties["class"] = node.properties["class"] or "" + f" {' '.join(classes)}"
56    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:

  • classnames(node, 'flex') yields 'flex'
  • classnames(node, 13) yields '13'
  • classnames(node, {'shadow': True, 'border': 0}) yields 'shadow'
  • classnames(node, '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:
 59class ClassList:
 60    """Utility class to manipulate the class list on a node.
 61
 62    Based on the hast-util-class-list:
 63    https://github.com/brechtcs/hast-util-class-list
 64    """
 65
 66    def __init__(self, node: Element):
 67        self.node = node
 68
 69    def contains(self, klass: str):
 70        """Check if `class` contains a certain class."""
 71        from phml.utils import has_property  # pylint: disable=import-outside-toplevel
 72
 73        if has_property(self.node, "class"):
 74            return search(klass, self.node.properties["class"]) is not None
 75        return False
 76
 77    def toggle(self, *klasses: str):
 78        """Toggle a class in `class`."""
 79
 80        for klass in klasses:
 81            if search(f"\b{klass}\b", self.node.properties["class"]) is not None:
 82                sub(f"\b{klass}\b", "", self.node.properties["class"])
 83                sub(r" +", " ", self.node.properties["class"])
 84            else:
 85                self.node.properties["class"] = self.node.properties["class"].strip() + f" {klass}"
 86
 87    def add(self, *klasses: str):
 88        """Add one or more classes to `class`."""
 89
 90        for klass in klasses:
 91            if search(f"\b{klass}\b", self.node.properties["class"]) is None:
 92                self.node.properties["class"] = self.node.properties["class"].strip() + f" {klass}"
 93
 94    def replace(self, old_klass: str, new_klass: str):
 95        """Replace a certain class in `class` with
 96        another class.
 97        """
 98
 99        if search(f"\b{old_klass}\b", self.node.properties["class"]) is not None:
100            sub(f"\b{old_klass}\b", f"\b{new_klass}\b", self.node.properties["class"])
101            sub(r" +", " ", self.node.properties["class"])
102
103    def remove(self, *klasses: str):
104        """Remove one or more classes from `class`."""
105
106        for klass in klasses:
107            if search(f"\b{klass}\b", self.node.properties["class"]) is not None:
108                sub(f"\b{klass}\b", "", self.node.properties["class"])
109                sub(r" +", " ", self.node.properties["class"])

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.Element)
66    def __init__(self, node: Element):
67        self.node = node
def contains(self, klass: str):
69    def contains(self, klass: str):
70        """Check if `class` contains a certain class."""
71        from phml.utils import has_property  # pylint: disable=import-outside-toplevel
72
73        if has_property(self.node, "class"):
74            return search(klass, self.node.properties["class"]) is not None
75        return False

Check if class contains a certain class.

def toggle(self, *klasses: str):
77    def toggle(self, *klasses: str):
78        """Toggle a class in `class`."""
79
80        for klass in klasses:
81            if search(f"\b{klass}\b", self.node.properties["class"]) is not None:
82                sub(f"\b{klass}\b", "", self.node.properties["class"])
83                sub(r" +", " ", self.node.properties["class"])
84            else:
85                self.node.properties["class"] = self.node.properties["class"].strip() + f" {klass}"

Toggle a class in class.

def add(self, *klasses: str):
87    def add(self, *klasses: str):
88        """Add one or more classes to `class`."""
89
90        for klass in klasses:
91            if search(f"\b{klass}\b", self.node.properties["class"]) is None:
92                self.node.properties["class"] = self.node.properties["class"].strip() + f" {klass}"

Add one or more classes to class.

def replace(self, old_klass: str, new_klass: str):
 94    def replace(self, old_klass: str, new_klass: str):
 95        """Replace a certain class in `class` with
 96        another class.
 97        """
 98
 99        if search(f"\b{old_klass}\b", self.node.properties["class"]) is not None:
100            sub(f"\b{old_klass}\b", f"\b{new_klass}\b", self.node.properties["class"])
101            sub(r" +", " ", self.node.properties["class"])

Replace a certain class in class with another class.

def remove(self, *klasses: str):
103    def remove(self, *klasses: str):
104        """Remove one or more classes from `class`."""
105
106        for klass in klasses:
107            if search(f"\b{klass}\b", self.node.properties["class"]) is not None:
108                sub(f"\b{klass}\b", "", self.node.properties["class"])
109                sub(r" +", " ", self.node.properties["class"])

Remove one or more classes from class.