Coverage for src / turbo_themes / css_variables.py: 89%

92 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-17 07:32 +0000

1"""CSS variable generation utilities. 

2 

3Provides focused helper functions for generating CSS custom properties 

4from theme tokens. Used by ThemeManager.apply_theme_to_css_variables(). 

5""" 

6 

7from __future__ import annotations 

8 

9from typing import Dict, Optional 

10 

11from .models import Tokens 

12from .mapping_config import ( 

13 get_mapping_config, 

14 resolve_token_path, 

15 MappingConfig, 

16 OptionalGroupConfig, 

17) 

18 

19 

20def apply_core_mappings( 

21 tokens: Tokens, 

22 config: Optional[MappingConfig] = None, 

23) -> Dict[str, str]: 

24 """Apply core token mappings to generate CSS variables. 

25 

26 Args: 

27 tokens: The theme tokens to map. 

28 config: Optional mapping configuration. Uses default if not provided. 

29 

30 Returns: 

31 Dictionary of CSS variable names to values. 

32 """ 

33 if config is None: 

34 config = get_mapping_config() 

35 

36 variables: Dict[str, str] = {} 

37 prefix = config.prefix 

38 

39 for mapping in config.core_mappings: 

40 try: 

41 value = resolve_token_path(tokens, mapping.token_path) 

42 # Try fallback if primary path didn't resolve 

43 if value is None and mapping.fallback: 

44 value = resolve_token_path(tokens, mapping.fallback) 

45 if value is not None: 45 ↛ 39line 45 didn't jump to line 39 because the condition on line 45 was always true

46 variables[f"--{prefix}-{mapping.css_var}"] = str(value) 

47 except (AttributeError, KeyError): 

48 pass 

49 

50 return variables 

51 

52 

53def apply_optional_spacing( 

54 tokens: Tokens, 

55 config: Optional[OptionalGroupConfig] = None, 

56 prefix: str = "turbo", 

57) -> Dict[str, str]: 

58 """Apply optional spacing tokens to generate CSS variables. 

59 

60 Args: 

61 tokens: The theme tokens containing spacing. 

62 config: Optional spacing group configuration. 

63 prefix: CSS variable prefix. 

64 

65 Returns: 

66 Dictionary of spacing CSS variable names to values. 

67 """ 

68 variables: Dict[str, str] = {} 

69 

70 if not tokens.spacing: 

71 return variables 

72 

73 if config is None: 

74 mapping_config = get_mapping_config() 

75 config = mapping_config.optional_groups.get("spacing") 

76 prefix = mapping_config.prefix 

77 

78 if config is None: 78 ↛ 79line 78 didn't jump to line 79 because the condition on line 78 was never true

79 return variables 

80 

81 spacing = tokens.spacing 

82 for prop in config.properties: 

83 value = getattr(spacing, prop, None) 

84 if value is not None: 84 ↛ 82line 84 didn't jump to line 82 because the condition on line 84 was always true

85 variables[f"--{prefix}-{config.prefix}-{prop}"] = str(value) 

86 

87 return variables 

88 

89 

90def apply_optional_elevation( 

91 tokens: Tokens, 

92 config: Optional[OptionalGroupConfig] = None, 

93 prefix: str = "turbo", 

94) -> Dict[str, str]: 

95 """Apply optional elevation tokens to generate CSS variables. 

96 

97 Args: 

98 tokens: The theme tokens containing elevation. 

99 config: Optional elevation group configuration. 

100 prefix: CSS variable prefix. 

101 

102 Returns: 

103 Dictionary of elevation CSS variable names to values. 

104 """ 

105 variables: Dict[str, str] = {} 

106 

107 if not tokens.elevation: 

108 return variables 

109 

110 if config is None: 

111 mapping_config = get_mapping_config() 

112 config = mapping_config.optional_groups.get("elevation") 

113 prefix = mapping_config.prefix 

114 

115 if config is None: 115 ↛ 116line 115 didn't jump to line 116 because the condition on line 115 was never true

116 return variables 

117 

118 elevation = tokens.elevation 

119 for prop in config.properties: 

120 value = getattr(elevation, prop, None) 

121 if value is not None: 121 ↛ 119line 121 didn't jump to line 119 because the condition on line 121 was always true

122 variables[f"--{prefix}-{config.prefix}-{prop}"] = str(value) 

123 

124 return variables 

125 

126 

127def apply_optional_animation( 

128 tokens: Tokens, 

129 config: Optional[OptionalGroupConfig] = None, 

130 prefix: str = "turbo", 

131) -> Dict[str, str]: 

132 """Apply optional animation tokens to generate CSS variables. 

133 

134 Args: 

135 tokens: The theme tokens containing animation. 

136 config: Optional animation group configuration. 

137 prefix: CSS variable prefix. 

138 

139 Returns: 

140 Dictionary of animation CSS variable names to values. 

141 """ 

142 variables: Dict[str, str] = {} 

143 

144 if not tokens.animation: 

145 return variables 

146 

147 if config is None: 

148 mapping_config = get_mapping_config() 

149 config = mapping_config.optional_groups.get("animation") 

150 prefix = mapping_config.prefix 

151 

152 if config is None or not config.mappings: 152 ↛ 153line 152 didn't jump to line 153 because the condition on line 152 was never true

153 return variables 

154 

155 for mapping in config.mappings: 

156 value = resolve_token_path(tokens, mapping.token_path) 

157 if value is not None: 157 ↛ 155line 157 didn't jump to line 155 because the condition on line 157 was always true

158 variables[f"--{prefix}-{config.prefix}-{mapping.css_var}"] = str(value) 

159 

160 return variables 

161 

162 

163def apply_optional_opacity( 

164 tokens: Tokens, 

165 config: Optional[OptionalGroupConfig] = None, 

166 prefix: str = "turbo", 

167) -> Dict[str, str]: 

168 """Apply optional opacity tokens to generate CSS variables. 

169 

170 Args: 

171 tokens: The theme tokens containing opacity. 

172 config: Optional opacity group configuration. 

173 prefix: CSS variable prefix. 

174 

175 Returns: 

176 Dictionary of opacity CSS variable names to values. 

177 """ 

178 variables: Dict[str, str] = {} 

179 

180 if not tokens.opacity: 

181 return variables 

182 

183 if config is None: 

184 mapping_config = get_mapping_config() 

185 config = mapping_config.optional_groups.get("opacity") 

186 prefix = mapping_config.prefix 

187 

188 if config is None: 188 ↛ 189line 188 didn't jump to line 189 because the condition on line 188 was never true

189 return variables 

190 

191 opacity = tokens.opacity 

192 for prop in config.properties: 

193 value = getattr(opacity, prop, None) 

194 if value is not None: 194 ↛ 192line 194 didn't jump to line 192 because the condition on line 194 was always true

195 variables[f"--{prefix}-{config.prefix}-{prop}"] = str(value) 

196 

197 return variables 

198 

199 

200def generate_css_variables(tokens: Tokens) -> Dict[str, str]: 

201 """Generate all CSS variables from theme tokens. 

202 

203 This is a convenience function that combines all mapping categories: 

204 core mappings, spacing, elevation, animation, and opacity. 

205 

206 Args: 

207 tokens: The theme tokens to convert. 

208 

209 Returns: 

210 Complete dictionary of CSS variable names to values. 

211 """ 

212 config = get_mapping_config() 

213 prefix = config.prefix 

214 

215 variables: Dict[str, str] = {} 

216 

217 # Apply all mapping categories 

218 variables.update(apply_core_mappings(tokens, config)) 

219 variables.update( 

220 apply_optional_spacing( 

221 tokens, 

222 config.optional_groups.get("spacing"), 

223 prefix, 

224 ) 

225 ) 

226 variables.update( 

227 apply_optional_elevation( 

228 tokens, 

229 config.optional_groups.get("elevation"), 

230 prefix, 

231 ) 

232 ) 

233 variables.update( 

234 apply_optional_animation( 

235 tokens, 

236 config.optional_groups.get("animation"), 

237 prefix, 

238 ) 

239 ) 

240 variables.update( 

241 apply_optional_opacity( 

242 tokens, 

243 config.optional_groups.get("opacity"), 

244 prefix, 

245 ) 

246 ) 

247 

248 return variables