@@ -122,6 +122,8 @@ def warn_or_raise_unmapped_variable(unmapped: str):
122122
123123
124124class BrandBootstrapConfigFromYaml :
125+ """Validate a Bootstrap config from a YAML source"""
126+
125127 def __init__ (
126128 self ,
127129 path : str ,
@@ -133,7 +135,8 @@ def __init__(
133135 rules : Any = None ,
134136 ):
135137
136- self .path = path
138+ # TODO: Remove `path` and handle in try/except block in caller
139+ self ._path = path
137140 self .version = version
138141 self .preset : str | None = self ._validate_str (preset , "preset" )
139142 self .functions : str | None = self ._validate_str (functions , "functions" )
@@ -148,29 +151,31 @@ def _validate_str(self, x: Any, param: str) -> str | None:
148151 return x
149152
150153 raise ValueError (
151- f"Invalid brand `{ self .path } .{ param } `. Must be a string or empty."
154+ f"Invalid brand `{ self ._path } .{ param } `. Must be a string or empty."
152155 )
153156
154157 def _validate_defaults (self , x : Any ) -> dict [str , YamlScalarType ] | None :
155158 if x is None :
156159 return None
157160
158- path = self .path
159- if path == "defaults.shiny.theme" :
160- path += ".defaults"
161-
162161 if not isinstance (x , dict ):
163- raise ValueError (f"Invalid brand `{ path } `, must be a dictionary." )
162+ raise ValueError (
163+ f"Invalid brand `{ self ._path } .defaults`, must be a dictionary."
164+ )
164165
165166 y : dict [Any , Any ] = x
166167
167168 if not all ([isinstance (k , str ) for k in y .keys ()]):
168- raise ValueError (f"Invalid brand `{ path } `, all keys must be strings." )
169+ raise ValueError (
170+ f"Invalid brand `{ self ._path } .defaults`, all keys must be strings."
171+ )
169172
170173 if not all (
171174 [v is None or isinstance (v , (str , int , float , bool )) for v in y .values ()]
172175 ):
173- raise ValueError (f"Invalid brand `{ path } `, all values must be scalar." )
176+ raise ValueError (
177+ f"Invalid brand `{ self ._path } .defaults`, all values must be scalar."
178+ )
174179
175180 res : dict [str , YamlScalarType ] = y
176181 return res
@@ -217,52 +222,38 @@ def from_brand(cls, brand: Brand):
217222 if not brand .defaults :
218223 return cls (version = v_bootstrap , preset = "shiny" )
219224
220- defaults : dict [str , YamlScalarType ] = {}
221-
222- d_bootstrap = cls ._brand_defaults_bootstrap (brand )
223- d_shiny = cls ._brand_defaults_shiny (brand )
224-
225- defaults .update (d_bootstrap .defaults or {})
226- defaults .update (d_shiny .defaults or {})
225+ shiny_args = {}
226+ if "shiny" in brand .defaults and "theme" in brand .defaults ["shiny" ]:
227+ shiny_args = brand .defaults ["shiny" ]["theme" ]
227228
228- return cls (
229- version = d_shiny .version or d_bootstrap .version or v_bootstrap ,
230- preset = d_shiny .preset or d_bootstrap .preset ,
231- functions = d_shiny .functions ,
232- defaults = defaults ,
233- mixins = d_shiny .mixins ,
234- rules = d_shiny .rules ,
229+ shiny = BrandBootstrapConfigFromYaml (
230+ path = "defaults.shiny.theme" ,
231+ ** shiny_args ,
235232 )
236233
237- @staticmethod
238- def _brand_defaults_shiny (brand : Brand ) -> BrandBootstrapConfigFromYaml :
239- if (
240- not brand .defaults
241- or not isinstance (brand .defaults .get ("shiny" ), dict )
242- or not isinstance (brand .defaults ["shiny" ].get ("theme" ), dict )
243- ):
244- return BrandBootstrapConfigFromYaml (path = "defaults.shiny.theme" )
234+ bs_args = {}
235+ if "bootstrap" in brand .defaults :
236+ bs_args = brand .defaults ["bootstrap" ]
245237
246- return BrandBootstrapConfigFromYaml (
247- path = "defaults.shiny.theme " ,
248- ** brand . defaults [ "shiny" ][ "theme" ] ,
238+ bootstrap = BrandBootstrapConfigFromYaml (
239+ path = "defaults.bootstrap " ,
240+ ** bs_args ,
249241 )
250242
251- @staticmethod
252- def _brand_defaults_bootstrap (brand : Brand ) -> BrandBootstrapConfigFromYaml :
253- if not brand .defaults or not isinstance (brand .defaults .get ("bootstrap" ), dict ):
254- return BrandBootstrapConfigFromYaml (path = "defaults.bootstrap" )
243+ # now combine bootstrap and shiny config options in a way that makes sense
244+ def join_str (x : str | None , y : str | None ):
245+ return "\n " .join ([z for z in [x , y ] if z is not None ])
255246
256- bootstrap : dict [str , Any ] = brand .defaults ["bootstrap" ]
257- defaults : dict [str , Any ] = {
258- k : v for k , v in bootstrap .items () if k not in ("version" , "preset" )
259- }
247+ defaults = bootstrap .defaults or {}
248+ defaults .update (shiny .defaults or {})
260249
261- return BrandBootstrapConfigFromYaml (
262- path = "defaults. bootstrap" ,
263- version = bootstrap .get ( "version" ) ,
264- preset = bootstrap .get ( "preset" ),
250+ return cls (
251+ version = shiny . version or bootstrap . version or v_bootstrap ,
252+ preset = shiny . preset or bootstrap .preset ,
253+ functions = join_str ( bootstrap .functions , shiny . functions ),
265254 defaults = defaults ,
255+ mixins = join_str (bootstrap .mixins , shiny .mixins ),
256+ rules = join_str (bootstrap .rules , shiny .rules ),
266257 )
267258
268259
0 commit comments