Mercurial > piecrust2
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 |