Coverage for src/turbo_themes/css_variables.py: 89%
91 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-04 10:58 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-04 10:58 +0000
1"""CSS variable generation utilities.
3Provides focused helper functions for generating CSS custom properties
4from theme tokens. Used by ThemeManager.apply_theme_to_css_variables().
5"""
7from __future__ import annotations
9from .mapping_config import (
10 MappingConfig,
11 OptionalGroupConfig,
12 get_mapping_config,
13 resolve_token_path,
14)
15from .models import Tokens
18def apply_core_mappings(
19 tokens: Tokens,
20 config: MappingConfig | None = None,
21) -> dict[str, str]:
22 """Apply core token mappings to generate CSS variables.
24 Args:
25 tokens: The theme tokens to map.
26 config: Optional mapping configuration. Uses default if not provided.
28 Returns:
29 Dictionary of CSS variable names to values.
30 """
31 if config is None:
32 config = get_mapping_config()
34 variables: dict[str, str] = {}
35 prefix = config.prefix
37 for mapping in config.core_mappings:
38 try:
39 value = resolve_token_path(tokens, mapping.token_path)
40 # Try fallback if primary path didn't resolve
41 if value is None and mapping.fallback:
42 value = resolve_token_path(tokens, mapping.fallback)
43 if value is not None: 43 ↛ 37line 43 didn't jump to line 37 because the condition on line 43 was always true
44 variables[f"--{prefix}-{mapping.css_var}"] = str(value)
45 except (AttributeError, KeyError):
46 pass
48 return variables
51def apply_optional_spacing(
52 tokens: Tokens,
53 config: OptionalGroupConfig | None = None,
54 prefix: str = "turbo",
55) -> dict[str, str]:
56 """Apply optional spacing tokens to generate CSS variables.
58 Args:
59 tokens: The theme tokens containing spacing.
60 config: Optional spacing group configuration.
61 prefix: CSS variable prefix.
63 Returns:
64 Dictionary of spacing CSS variable names to values.
65 """
66 variables: dict[str, str] = {}
68 if not tokens.spacing:
69 return variables
71 if config is None:
72 mapping_config = get_mapping_config()
73 config = mapping_config.optional_groups.get("spacing")
74 prefix = mapping_config.prefix
76 if config is None: 76 ↛ 77line 76 didn't jump to line 77 because the condition on line 76 was never true
77 return variables
79 spacing = tokens.spacing
80 for prop in config.properties:
81 value = getattr(spacing, prop, None)
82 if value is not None: 82 ↛ 80line 82 didn't jump to line 80 because the condition on line 82 was always true
83 variables[f"--{prefix}-{config.prefix}-{prop}"] = str(value)
85 return variables
88def apply_optional_elevation(
89 tokens: Tokens,
90 config: OptionalGroupConfig | None = None,
91 prefix: str = "turbo",
92) -> dict[str, str]:
93 """Apply optional elevation tokens to generate CSS variables.
95 Args:
96 tokens: The theme tokens containing elevation.
97 config: Optional elevation group configuration.
98 prefix: CSS variable prefix.
100 Returns:
101 Dictionary of elevation CSS variable names to values.
102 """
103 variables: dict[str, str] = {}
105 if not tokens.elevation:
106 return variables
108 if config is None:
109 mapping_config = get_mapping_config()
110 config = mapping_config.optional_groups.get("elevation")
111 prefix = mapping_config.prefix
113 if config is None: 113 ↛ 114line 113 didn't jump to line 114 because the condition on line 113 was never true
114 return variables
116 elevation = tokens.elevation
117 for prop in config.properties:
118 value = getattr(elevation, prop, None)
119 if value is not None: 119 ↛ 117line 119 didn't jump to line 117 because the condition on line 119 was always true
120 variables[f"--{prefix}-{config.prefix}-{prop}"] = str(value)
122 return variables
125def apply_optional_animation(
126 tokens: Tokens,
127 config: OptionalGroupConfig | None = None,
128 prefix: str = "turbo",
129) -> dict[str, str]:
130 """Apply optional animation tokens to generate CSS variables.
132 Args:
133 tokens: The theme tokens containing animation.
134 config: Optional animation group configuration.
135 prefix: CSS variable prefix.
137 Returns:
138 Dictionary of animation CSS variable names to values.
139 """
140 variables: dict[str, str] = {}
142 if not tokens.animation:
143 return variables
145 if config is None:
146 mapping_config = get_mapping_config()
147 config = mapping_config.optional_groups.get("animation")
148 prefix = mapping_config.prefix
150 if config is None or not config.mappings: 150 ↛ 151line 150 didn't jump to line 151 because the condition on line 150 was never true
151 return variables
153 for mapping in config.mappings:
154 value = resolve_token_path(tokens, mapping.token_path)
155 if value is not None: 155 ↛ 153line 155 didn't jump to line 153 because the condition on line 155 was always true
156 variables[f"--{prefix}-{config.prefix}-{mapping.css_var}"] = str(value)
158 return variables
161def apply_optional_opacity(
162 tokens: Tokens,
163 config: OptionalGroupConfig | None = None,
164 prefix: str = "turbo",
165) -> dict[str, str]:
166 """Apply optional opacity tokens to generate CSS variables.
168 Args:
169 tokens: The theme tokens containing opacity.
170 config: Optional opacity group configuration.
171 prefix: CSS variable prefix.
173 Returns:
174 Dictionary of opacity CSS variable names to values.
175 """
176 variables: dict[str, str] = {}
178 if not tokens.opacity:
179 return variables
181 if config is None:
182 mapping_config = get_mapping_config()
183 config = mapping_config.optional_groups.get("opacity")
184 prefix = mapping_config.prefix
186 if config is None: 186 ↛ 187line 186 didn't jump to line 187 because the condition on line 186 was never true
187 return variables
189 opacity = tokens.opacity
190 for prop in config.properties:
191 value = getattr(opacity, prop, None)
192 if value is not None: 192 ↛ 190line 192 didn't jump to line 190 because the condition on line 192 was always true
193 variables[f"--{prefix}-{config.prefix}-{prop}"] = str(value)
195 return variables
198def generate_css_variables(tokens: Tokens) -> dict[str, str]:
199 """Generate all CSS variables from theme tokens.
201 This is a convenience function that combines all mapping categories:
202 core mappings, spacing, elevation, animation, and opacity.
204 Args:
205 tokens: The theme tokens to convert.
207 Returns:
208 Complete dictionary of CSS variable names to values.
209 """
210 config = get_mapping_config()
211 prefix = config.prefix
213 variables: dict[str, str] = {}
215 # Apply all mapping categories
216 variables.update(apply_core_mappings(tokens, config))
217 variables.update(
218 apply_optional_spacing(
219 tokens,
220 config.optional_groups.get("spacing"),
221 prefix,
222 )
223 )
224 variables.update(
225 apply_optional_elevation(
226 tokens,
227 config.optional_groups.get("elevation"),
228 prefix,
229 )
230 )
231 variables.update(
232 apply_optional_animation(
233 tokens,
234 config.optional_groups.get("animation"),
235 prefix,
236 )
237 )
238 variables.update(
239 apply_optional_opacity(
240 tokens,
241 config.optional_groups.get("opacity"),
242 prefix,
243 )
244 )
246 return variables