Coverage for tdom/classnames.py: 100%
19 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-17 19:54 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-17 19:54 +0000
1def classnames(*args: object) -> str:
2 """
3 Construct a space-separated class string from various inputs.
5 Accepts strings, lists/tuples of strings, and dicts mapping class names to
6 boolean values. Ignores None and False values.
8 Examples:
9 classnames("btn", "btn-primary") -> "btn btn-primary"
10 classnames("btn", {"btn-primary": True, "disabled": False}) -> "btn btn-primary"
11 classnames(["btn", "btn-primary"], {"disabled": True}) -> "btn btn-primary disabled"
12 classnames("btn", None, False, "active") -> "btn active"
14 Args:
15 *args: Variable length argument list containing strings, lists/tuples,
16 or dicts.
18 Returns:
19 A single string with class names separated by spaces.
20 """
21 classes: list[str] = []
22 # Use a queue to process arguments iteratively, preserving order.
23 queue = list(args)
25 while queue:
26 arg = queue.pop(0)
28 if not arg: # Handles None, False, empty strings/lists/dicts
29 continue
31 if isinstance(arg, str):
32 classes.append(arg)
33 elif isinstance(arg, dict):
34 for key, value in arg.items():
35 if value:
36 classes.append(key)
37 elif isinstance(arg, (list, tuple)):
38 # Add items to the front of the queue to process them next, in order.
39 queue[0:0] = arg
40 elif isinstance(arg, bool):
41 pass # Explicitly ignore booleans not in a dict
42 else:
43 raise ValueError(f"Invalid class argument type: {type(arg).__name__}")
45 # Filter out empty strings and join the result.
46 return " ".join(stripped for c in classes if (stripped := c.strip()))