comparison piecrust/appconfig.py @ 852:4850f8c21b6e

core: Start of the big refactor for PieCrust 3.0. * Everything is a `ContentSource`, including assets directories. * Most content sources are subclasses of the base file-system source. * A source is processed by a "pipeline", and there are 2 built-in pipelines, one for assets and one for pages. The asset pipeline is vaguely functional, but the page pipeline is completely broken right now. * Rewrite the baking process as just running appropriate pipelines on each content item. This should allow for better parallelization.
author Ludovic Chabant <ludovic@chabant.com>
date Wed, 17 May 2017 00:11:48 -0700
parents b8f089092281
children f070a4fc033c
comparison
equal deleted inserted replaced
851:2c7e57d80bba 852:4850f8c21b6e
14 default_content_model_base, 14 default_content_model_base,
15 get_default_content_model, get_default_content_model_for_blog) 15 get_default_content_model, get_default_content_model_for_blog)
16 from piecrust.cache import NullCache 16 from piecrust.cache import NullCache
17 from piecrust.configuration import ( 17 from piecrust.configuration import (
18 Configuration, ConfigurationError, ConfigurationLoader, 18 Configuration, ConfigurationError, ConfigurationLoader,
19 try_get_dict_values, try_get_dict_value, set_dict_value, 19 try_get_dict_values, set_dict_value,
20 merge_dicts, visit_dict, 20 merge_dicts, visit_dict)
21 MERGE_NEW_VALUES, MERGE_OVERWRITE_VALUES, MERGE_PREPEND_LISTS,
22 MERGE_APPEND_LISTS)
23 from piecrust.sources.base import REALM_USER, REALM_THEME 21 from piecrust.sources.base import REALM_USER, REALM_THEME
24 22
25 23
26 logger = logging.getLogger(__name__) 24 logger = logging.getLogger(__name__)
27 25
175 # If we have a theme, apply the theme on that. So stuff like routes 173 # If we have a theme, apply the theme on that. So stuff like routes
176 # will now look like: 174 # will now look like:
177 # [custom theme] + [default theme] + [default] 175 # [custom theme] + [default theme] + [default]
178 if theme_values is not None: 176 if theme_values is not None:
179 self._processThemeLayer(theme_values, values) 177 self._processThemeLayer(theme_values, values)
180 merge_dicts(values, theme_values)
181 178
182 # Make all sources belong to the "theme" realm at this point. 179 # Make all sources belong to the "theme" realm at this point.
183 srcc = values['site'].get('sources') 180 srcc = values['site'].get('sources')
184 if srcc: 181 if srcc:
185 for sn, sc in srcc.items(): 182 for sn, sc in srcc.items():
188 # Now we apply the site stuff. We want to end up with: 185 # Now we apply the site stuff. We want to end up with:
189 # [custom site] + [default site] + [custom theme] + [default theme] + 186 # [custom site] + [default site] + [custom theme] + [default theme] +
190 # [default] 187 # [default]
191 if site_values is not None: 188 if site_values is not None:
192 self._processSiteLayer(site_values, values) 189 self._processSiteLayer(site_values, values)
193 merge_dicts(values, site_values)
194 190
195 # Set the theme site flag. 191 # Set the theme site flag.
196 if self.theme_config: 192 if self.theme_config:
197 values['site']['theme_site'] = True 193 values['site']['theme_site'] = True
198 194
207 203
208 def _processThemeLayer(self, theme_values, values): 204 def _processThemeLayer(self, theme_values, values):
209 # Generate the default theme model. 205 # Generate the default theme model.
210 gen_default_theme_model = bool(try_get_dict_values( 206 gen_default_theme_model = bool(try_get_dict_values(
211 (theme_values, 'site/use_default_theme_content'), 207 (theme_values, 'site/use_default_theme_content'),
212 (values, 'site/use_default_theme_content'),
213 default=True)) 208 default=True))
214 if gen_default_theme_model: 209 if gen_default_theme_model:
215 self._generateDefaultThemeModel(theme_values, values) 210 logger.debug("Generating default theme content model...")
211 cc = copy.deepcopy(default_theme_content_model_base)
212 merge_dicts(values, cc)
213
214 # Merge the theme config into the result config.
215 merge_dicts(values, theme_values)
216 216
217 def _processSiteLayer(self, site_values, values): 217 def _processSiteLayer(self, site_values, values):
218 # Default site content. 218 # Default site content.
219 gen_default_site_model = bool(try_get_dict_values( 219 gen_default_site_model = bool(try_get_dict_values(
220 (site_values, 'site/use_default_content'), 220 (site_values, 'site/use_default_content'),
221 (values, 'site/use_default_content'), 221 (values, 'site/use_default_content'),
222 default=True)) 222 default=True))
223 if gen_default_site_model: 223 if gen_default_site_model:
224 self._generateDefaultSiteModel(site_values, values) 224 logger.debug("Generating default content model...")
225 225 cc = copy.deepcopy(default_content_model_base)
226 def _generateDefaultThemeModel(self, theme_values, values): 226 merge_dicts(values, cc)
227 logger.debug("Generating default theme content model...") 227
228 cc = copy.deepcopy(default_theme_content_model_base) 228 dcm = get_default_content_model(site_values, values)
229 merge_dicts(values, cc) 229 merge_dicts(values, dcm)
230 230
231 def _generateDefaultSiteModel(self, site_values, values): 231 blogsc = try_get_dict_values(
232 logger.debug("Generating default content model...") 232 (site_values, 'site/blogs'),
233 cc = copy.deepcopy(default_content_model_base) 233 (values, 'site/blogs'))
234 merge_dicts(values, cc) 234 if blogsc is None:
235 235 blogsc = ['posts']
236 dcm = get_default_content_model(site_values, values) 236 set_dict_value(site_values, 'site/blogs', blogsc)
237 merge_dicts(values, dcm) 237
238 238 is_only_blog = (len(blogsc) == 1)
239 blogsc = try_get_dict_values( 239 for blog_name in reversed(blogsc):
240 (site_values, 'site/blogs'), 240 blog_cfg = get_default_content_model_for_blog(
241 (values, 'site/blogs')) 241 blog_name, is_only_blog, site_values, values,
242 if blogsc is None: 242 theme_site=self.theme_config)
243 blogsc = ['posts'] 243 merge_dicts(values, blog_cfg)
244 set_dict_value(site_values, 'site/blogs', blogsc) 244
245 245 # Merge the site config into the result config.
246 is_only_blog = (len(blogsc) == 1) 246 merge_dicts(values, site_values)
247 for blog_name in reversed(blogsc):
248 blog_cfg = get_default_content_model_for_blog(
249 blog_name, is_only_blog, site_values, values,
250 theme_site=self.theme_config)
251 merge_dicts(values, blog_cfg)
252 247
253 def _validateAll(self, values): 248 def _validateAll(self, values):
254 if values is None: 249 if values is None:
255 values = {} 250 values = {}
256 251
302 if not routes: 297 if not routes:
303 raise ConfigurationError("No routes were defined.") 298 raise ConfigurationError("No routes were defined.")
304 taxonomies = v.get('taxonomies') 299 taxonomies = v.get('taxonomies')
305 if taxonomies is None: 300 if taxonomies is None:
306 v['taxonomies'] = {} 301 v['taxonomies'] = {}
307 generators = v.get('generators')
308 if generators is None:
309 v['generators'] = {}
310 return v 302 return v
311 303
312 304
313 # Make sure the site root ends with a slash. 305 # Make sure the site root ends with a slash.
314 def _validate_site_root(v, values, cache): 306 def _validate_site_root(v, values, cache):
331 raise ConfigurationError("The 'site/auto_formats' setting must be " 323 raise ConfigurationError("The 'site/auto_formats' setting must be "
332 "a dictionary.") 324 "a dictionary.")
333 325
334 v.setdefault('html', values['site']['default_format']) 326 v.setdefault('html', values['site']['default_format'])
335 auto_formats_re = r"\.(%s)$" % ( 327 auto_formats_re = r"\.(%s)$" % (
336 '|'.join( 328 '|'.join(
337 [re.escape(i) for i in list(v.keys())])) 329 [re.escape(i) for i in list(v.keys())]))
338 cache.write('auto_formats_re', auto_formats_re) 330 cache.write('auto_formats_re', auto_formats_re)
339 return v 331 return v
340 332
341 333
342 # Check that the default auto-format is known. 334 # Check that the default auto-format is known.
343 def _validate_site_default_auto_format(v, values, cache): 335 def _validate_site_default_auto_format(v, values, cache):
344 if v not in values['site']['auto_formats']: 336 if v not in values['site']['auto_formats']:
345 raise ConfigurationError( 337 raise ConfigurationError(
346 "Default auto-format '%s' is not declared." % v) 338 "Default auto-format '%s' is not declared." % v)
347 return v 339 return v
348 340
349 341
350 # Cache pagination suffix regex and format. 342 # Cache pagination suffix regex and format.
351 def _validate_site_pagination_suffix(v, values, cache): 343 def _validate_site_pagination_suffix(v, values, cache):
391 raise ConfigurationError("All sources in 'site/sources' must " 383 raise ConfigurationError("All sources in 'site/sources' must "
392 "be dictionaries.") 384 "be dictionaries.")
393 sc.setdefault('type', 'default') 385 sc.setdefault('type', 'default')
394 sc.setdefault('fs_endpoint', sn) 386 sc.setdefault('fs_endpoint', sn)
395 sc.setdefault('ignore_missing_dir', False) 387 sc.setdefault('ignore_missing_dir', False)
396 sc.setdefault('data_endpoint', sn) 388 sc.setdefault('data_endpoint', None)
397 sc.setdefault('data_type', 'iterator') 389 sc.setdefault('data_type', 'iterator')
398 sc.setdefault('item_name', sn) 390 sc.setdefault('item_name', sn)
399 sc.setdefault('items_per_page', 5) 391 sc.setdefault('items_per_page', 5)
400 sc.setdefault('date_format', DEFAULT_DATE_FORMAT) 392 sc.setdefault('date_format', DEFAULT_DATE_FORMAT)
401 sc.setdefault('realm', REALM_USER) 393 sc.setdefault('realm', REALM_USER)
394 sc.setdefault('pipeline', 'page')
402 395
403 # Validate endpoints. 396 # Validate endpoints.
404 endpoint = sc['data_endpoint'] 397 endpoint = sc['data_endpoint']
405 if endpoint in reserved_endpoints: 398 if endpoint in reserved_endpoints:
406 raise ConfigurationError( 399 raise ConfigurationError(
407 "Source '%s' is using a reserved endpoint name: %s" % 400 "Source '%s' is using a reserved endpoint name: %s" %
408 (sn, endpoint)) 401 (sn, endpoint))
409
410 # Validate generators.
411 for gn, gc in sc.get('generators', {}).items():
412 if not isinstance(gc, dict):
413 raise ConfigurationError(
414 "Generators for source '%s' should be defined in a "
415 "dictionary." % sn)
416 gc['source'] = sn
417 402
418 return v 403 return v
419 404
420 405
421 def _validate_site_routes(v, values, cache): 406 def _validate_site_routes(v, values, cache):
437 "have an 'url'.") 422 "have an 'url'.")
438 if rc_url[0] != '/': 423 if rc_url[0] != '/':
439 raise ConfigurationError("Route URLs must start with '/'.") 424 raise ConfigurationError("Route URLs must start with '/'.")
440 425
441 r_source = rc.get('source') 426 r_source = rc.get('source')
442 r_generator = rc.get('generator') 427 if r_source is None:
443 if r_source is None and r_generator is None: 428 raise ConfigurationError("Routes must specify a source.")
444 raise ConfigurationError("Routes must specify a source or "
445 "generator.")
446 if (r_source and 429 if (r_source and
447 r_source not in list(values['site']['sources'].keys())): 430 r_source not in list(values['site']['sources'].keys())):
448 raise ConfigurationError("Route is referencing unknown " 431 raise ConfigurationError("Route is referencing unknown "
449 "source: %s" % r_source) 432 "source: %s" % r_source)
450 if (r_generator and 433
451 r_generator not in list(values['site']['generators'].keys())): 434 rc.setdefault('pass', 0)
452 raise ConfigurationError("Route is referencing unknown "
453 "generator: %s" % r_generator)
454
455 rc.setdefault('generator', None)
456 rc.setdefault('page_suffix', '/%num%') 435 rc.setdefault('page_suffix', '/%num%')
457 436
458 return v 437 return v
459 438
460 439
461 def _validate_site_taxonomies(v, values, cache): 440 def _validate_site_taxonomies(v, values, cache):
462 if not isinstance(v, dict): 441 if not isinstance(v, dict):
463 raise ConfigurationError( 442 raise ConfigurationError(
464 "The 'site/taxonomies' setting must be a mapping.") 443 "The 'site/taxonomies' setting must be a mapping.")
465 for tn, tc in v.items(): 444 for tn, tc in v.items():
466 tc.setdefault('multiple', False) 445 tc.setdefault('multiple', False)
467 tc.setdefault('term', tn) 446 tc.setdefault('term', tn)
468 tc.setdefault('page', '_%s.%%ext%%' % tc['term']) 447 tc.setdefault('page', '_%s.%%ext%%' % tc['term'])
469 return v
470
471
472 def _validate_site_generators(v, values, cache):
473 if not isinstance(v, dict):
474 raise ConfigurationError(
475 "The 'site/generators' setting must be a mapping.")
476 for gn, gc in v.items():
477 if 'type' not in gc:
478 raise ConfigurationError(
479 "Generator '%s' doesn't specify a type." % gn)
480 return v 448 return v
481 449
482 450
483 def _validate_site_plugins(v, values, cache): 451 def _validate_site_plugins(v, values, cache):
484 if isinstance(v, str): 452 if isinstance(v, str):
485 v = v.split(',') 453 v = v.split(',')
486 elif not isinstance(v, list): 454 elif not isinstance(v, list):
487 raise ConfigurationError( 455 raise ConfigurationError(
488 "The 'site/plugins' setting must be an array, or a " 456 "The 'site/plugins' setting must be an array, or a "
489 "comma-separated list.") 457 "comma-separated list.")
490 return v 458 return v
491 459