Mercurial > piecrust2
comparison piecrust/appconfig.py @ 711:ab5c6a8ae90a
bake: Replace hard-coded taxonomy support with "generator" system.
* Taxonomies are now implemented one or more `TaxonomyGenerator`s.
* A `BlogArchivesGenerator` stub is there but non-functional.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Thu, 26 May 2016 19:52:47 -0700 |
parents | 1a6c4c2683fd |
children | 606f6d57b5df |
comparison
equal
deleted
inserted
replaced
710:e85f29b28b84 | 711:ab5c6a8ae90a |
---|---|
140 for f in self._post_fixups: | 140 for f in self._post_fixups: |
141 f(values) | 141 f(values) |
142 | 142 |
143 self._values = self._validateAll(values) | 143 self._values = self._validateAll(values) |
144 except Exception as ex: | 144 except Exception as ex: |
145 logger.exception(ex) | |
145 raise Exception( | 146 raise Exception( |
146 "Error loading configuration from: %s" % | 147 "Error loading configuration from: %s" % |
147 ', '.join(paths)) from ex | 148 ', '.join(paths)) from ex |
148 | 149 |
149 logger.debug("Caching configuration...") | 150 logger.debug("Caching configuration...") |
219 callback = globs.get(callback_name) | 220 callback = globs.get(callback_name) |
220 if callback: | 221 if callback: |
221 try: | 222 try: |
222 val2 = callback(val, values, cache_writer) | 223 val2 = callback(val, values, cache_writer) |
223 except Exception as ex: | 224 except Exception as ex: |
225 logger.exception(ex) | |
224 raise Exception("Error raised in validator '%s'." % | 226 raise Exception("Error raised in validator '%s'." % |
225 callback_name) from ex | 227 callback_name) from ex |
226 if val2 is None: | 228 if val2 is None: |
227 raise Exception("Validator '%s' isn't returning a " | 229 raise Exception("Validator '%s' isn't returning a " |
228 "coerced value." % callback_name) | 230 "coerced value." % callback_name) |
286 'source': 'theme_pages', | 288 'source': 'theme_pages', |
287 'func': 'pcurl(slug)' | 289 'func': 'pcurl(slug)' |
288 } | 290 } |
289 ], | 291 ], |
290 'theme_tag_page': 'theme_pages:_tag.%ext%', | 292 'theme_tag_page': 'theme_pages:_tag.%ext%', |
291 'theme_category_page': 'theme_pages:_category.%ext%' | 293 'theme_category_page': 'theme_pages:_category.%ext%', |
294 'theme_month_page': 'theme_pages:_month.%ext%', | |
295 'theme_year_page': 'theme_pages:_year.%ext%' | |
292 }) | 296 }) |
293 }) | 297 }) |
294 | 298 |
295 | 299 |
296 default_configuration = collections.OrderedDict({ | 300 default_configuration = collections.OrderedDict({ |
330 default_content_model_base = collections.OrderedDict({ | 334 default_content_model_base = collections.OrderedDict({ |
331 'site': collections.OrderedDict({ | 335 'site': collections.OrderedDict({ |
332 'posts_fs': DEFAULT_POSTS_FS, | 336 'posts_fs': DEFAULT_POSTS_FS, |
333 'default_page_layout': 'default', | 337 'default_page_layout': 'default', |
334 'default_post_layout': 'post', | 338 'default_post_layout': 'post', |
335 'post_url': '%year%/%month%/%day%/%slug%', | 339 'post_url': '/%year%/%month%/%day%/%slug%', |
336 'tag_url': 'tag/%tag%', | 340 'year_url': '/%year%', |
337 'category_url': '%category%', | 341 'tag_url': '/tag/%path:tag%', |
342 'category_url': '/%category%', | |
338 'posts_per_page': 5 | 343 'posts_per_page': 5 |
339 }) | 344 }) |
340 }) | 345 }) |
341 | 346 |
342 | 347 |
360 'url': '/%path:slug%', | 365 'url': '/%path:slug%', |
361 'source': 'pages', | 366 'source': 'pages', |
362 'func': 'pcurl(slug)' | 367 'func': 'pcurl(slug)' |
363 } | 368 } |
364 ], | 369 ], |
365 'taxonomies': collections.OrderedDict({ | 370 'taxonomies': collections.OrderedDict([ |
366 'tags': { | 371 ('tags', { |
367 'multiple': True, | 372 'multiple': True, |
368 'term': 'tag' | 373 'term': 'tag' |
369 }, | 374 }), |
370 'categories': { | 375 ('categories', { |
371 'term': 'category' | 376 'term': 'category' |
372 } | 377 }) |
373 }) | 378 ]) |
374 }) | 379 }) |
375 }) | 380 }) |
376 | 381 |
377 | 382 |
378 def get_default_content_model_for_blog( | 383 def get_default_content_model_for_blog( |
379 blog_name, is_only_blog, values, user_overrides, theme_site=False): | 384 blog_name, is_only_blog, values, user_overrides, theme_site=False): |
380 # Get the global values for various things we're interested in. | 385 # Get the global (default) values for various things we're interested in. |
381 defs = {} | 386 defs = {} |
382 names = ['posts_fs', 'posts_per_page', 'date_format', | 387 names = ['posts_fs', 'posts_per_page', 'date_format', |
383 'default_post_layout', 'post_url', 'tag_url', 'category_url'] | 388 'default_post_layout', 'post_url', 'year_url'] |
384 for n in names: | 389 for n in names: |
385 defs[n] = try_get_dict_value( | 390 defs[n] = try_get_dict_value( |
386 user_overrides, 'site/%s' % n, | 391 user_overrides, 'site/%s' % n, |
387 values['site'][n]) | 392 values['site'][n]) |
388 | 393 |
389 # More stuff we need. | 394 # More stuff we need. |
390 if is_only_blog: | 395 if is_only_blog: |
391 url_prefix = '' | 396 url_prefix = '' |
392 tax_page_prefix = '' | 397 page_prefix = '' |
393 fs_endpoint = 'posts' | 398 fs_endpoint = 'posts' |
394 data_endpoint = 'blog' | 399 data_endpoint = 'blog' |
395 item_name = 'post' | 400 item_name = 'post' |
396 | 401 |
397 if theme_site: | 402 if theme_site: |
399 # so it's clearer that those won't show up when the theme is | 404 # so it's clearer that those won't show up when the theme is |
400 # actually applied to a normal site. | 405 # actually applied to a normal site. |
401 fs_endpoint = 'sample/posts' | 406 fs_endpoint = 'sample/posts' |
402 else: | 407 else: |
403 url_prefix = blog_name + '/' | 408 url_prefix = blog_name + '/' |
404 tax_page_prefix = blog_name + '/' | 409 page_prefix = blog_name + '/' |
405 fs_endpoint = 'posts/%s' % blog_name | 410 fs_endpoint = 'posts/%s' % blog_name |
406 data_endpoint = blog_name | 411 data_endpoint = blog_name |
407 item_name = '%s-post' % blog_name | 412 item_name = '%s-post' % blog_name |
408 | 413 |
409 # Figure out the settings values for this blog, specifically. | 414 # Figure out the settings values for this blog, specifically. |
412 # `defs` map that we computed above. | 417 # `defs` map that we computed above. |
413 blog_cfg = user_overrides.get(blog_name, {}) | 418 blog_cfg = user_overrides.get(blog_name, {}) |
414 blog_values = {} | 419 blog_values = {} |
415 for n in names: | 420 for n in names: |
416 blog_values[n] = blog_cfg.get(n, defs[n]) | 421 blog_values[n] = blog_cfg.get(n, defs[n]) |
417 if n in ['post_url', 'tag_url', 'category_url']: | |
418 blog_values[n] = url_prefix + blog_values[n] | |
419 | 422 |
420 posts_fs = blog_values['posts_fs'] | 423 posts_fs = blog_values['posts_fs'] |
421 posts_per_page = blog_values['posts_per_page'] | 424 posts_per_page = blog_values['posts_per_page'] |
422 date_format = blog_values['date_format'] | 425 date_format = blog_values['date_format'] |
423 default_layout = blog_values['default_post_layout'] | 426 default_layout = blog_values['default_post_layout'] |
424 post_url = '/' + blog_values['post_url'].lstrip('/') | 427 post_url = '/' + url_prefix + blog_values['post_url'].lstrip('/') |
425 tag_url = '/' + blog_values['tag_url'].lstrip('/') | 428 year_url = '/' + url_prefix + blog_values['year_url'].lstrip('/') |
426 category_url = '/' + blog_values['category_url'].lstrip('/') | 429 |
427 | 430 year_archive = 'pages:%s_year.%%ext%%' % page_prefix |
428 tags_taxonomy = 'pages:%s_tag.%%ext%%' % tax_page_prefix | |
429 category_taxonomy = 'pages:%s_category.%%ext%%' % tax_page_prefix | |
430 if not theme_site: | 431 if not theme_site: |
431 theme_tag_page = values['site'].get('theme_tag_page') | 432 theme_year_page = values['site'].get('theme_year_page') |
432 if theme_tag_page: | 433 if theme_year_page: |
433 tags_taxonomy += ';' + theme_tag_page | 434 year_archive += ';' + theme_year_page |
434 theme_category_page = values['site'].get('theme_category_page') | 435 |
435 if theme_category_page: | 436 cfg = collections.OrderedDict({ |
436 category_taxonomy += ';' + theme_category_page | |
437 | |
438 return collections.OrderedDict({ | |
439 'site': collections.OrderedDict({ | 437 'site': collections.OrderedDict({ |
440 'sources': collections.OrderedDict({ | 438 'sources': collections.OrderedDict({ |
441 blog_name: collections.OrderedDict({ | 439 blog_name: collections.OrderedDict({ |
442 'type': 'posts/%s' % posts_fs, | 440 'type': 'posts/%s' % posts_fs, |
443 'fs_endpoint': fs_endpoint, | 441 'fs_endpoint': fs_endpoint, |
445 'item_name': item_name, | 443 'item_name': item_name, |
446 'ignore_missing_dir': True, | 444 'ignore_missing_dir': True, |
447 'data_type': 'blog', | 445 'data_type': 'blog', |
448 'items_per_page': posts_per_page, | 446 'items_per_page': posts_per_page, |
449 'date_format': date_format, | 447 'date_format': date_format, |
450 'default_layout': default_layout, | 448 'default_layout': default_layout |
451 'taxonomy_pages': collections.OrderedDict({ | 449 }) |
452 'tags': tags_taxonomy, | 450 }), |
453 'categories': category_taxonomy | 451 'generators': collections.OrderedDict({ |
454 }) | 452 ('%s_archives' % blog_name): collections.OrderedDict({ |
453 'type': 'blog_archives', | |
454 'source': blog_name, | |
455 'page': year_archive | |
455 }) | 456 }) |
456 }), | 457 }), |
457 'routes': [ | 458 'routes': [ |
458 { | 459 { |
459 'url': post_url, | 460 'url': post_url, |
460 'source': blog_name, | 461 'source': blog_name, |
461 'func': 'pcposturl(year,month,day,slug)' | 462 'func': 'pcposturl(year,month,day,slug)' |
462 }, | 463 }, |
463 { | 464 { |
464 'url': tag_url, | 465 'url': year_url, |
465 'source': blog_name, | 466 'generator': ('%s_archives' % blog_name), |
466 'taxonomy': 'tags', | 467 'func': 'pcyearurl(year)' |
467 'func': 'pctagurl(tag)' | |
468 }, | |
469 { | |
470 'url': category_url, | |
471 'source': blog_name, | |
472 'taxonomy': 'categories', | |
473 'func': 'pccaturl(category)' | |
474 } | 468 } |
475 ] | 469 ] |
476 }) | 470 }) |
477 }) | 471 }) |
472 | |
473 # Add a generator and a route for each taxonomy. | |
474 taxonomies_cfg = values.get('site', {}).get('taxonomies', {}).copy() | |
475 taxonomies_cfg.update( | |
476 user_overrides.get('site', {}).get('taxonomies', {})) | |
477 for tax_name, tax_cfg in taxonomies_cfg.items(): | |
478 term = tax_cfg.get('term', tax_name) | |
479 | |
480 # Generator. | |
481 page_ref = 'pages:%s_%s.%%ext%%' % (page_prefix, term) | |
482 if not theme_site: | |
483 theme_page_ref = values['site'].get('theme_%s_page' % term) | |
484 if theme_page_ref: | |
485 page_ref += ';' + theme_page_ref | |
486 tax_gen_name = '%s_%s' % (blog_name, tax_name) | |
487 tax_gen = collections.OrderedDict({ | |
488 'type': 'taxonomy', | |
489 'source': blog_name, | |
490 'taxonomy': tax_name, | |
491 'page': page_ref | |
492 }) | |
493 cfg['site']['generators'][tax_gen_name] = tax_gen | |
494 | |
495 # Route. | |
496 tax_url_cfg_name = '%s_url' % term | |
497 tax_url = blog_cfg.get(tax_url_cfg_name, | |
498 try_get_dict_value( | |
499 user_overrides, | |
500 'site/%s' % tax_url_cfg_name, | |
501 values['site'].get( | |
502 tax_url_cfg_name, | |
503 '%s/%%%s%%' % (term, term)))) | |
504 tax_url = '/' + url_prefix + tax_url.lstrip('/') | |
505 term_arg = term | |
506 if tax_cfg.get('multiple') is True: | |
507 term_arg = '+' + term | |
508 tax_func = 'pc%surl(%s)' % (term, term_arg) | |
509 tax_route = collections.OrderedDict({ | |
510 'url': tax_url, | |
511 'generator': tax_gen_name, | |
512 'taxonomy': tax_name, | |
513 'func': tax_func | |
514 }) | |
515 cfg['site']['routes'].append(tax_route) | |
516 | |
517 return cfg | |
478 | 518 |
479 | 519 |
480 # Configuration value validators. | 520 # Configuration value validators. |
481 # | 521 # |
482 # Make sure we have basic site stuff. | 522 # Make sure we have basic site stuff. |
488 if not routes: | 528 if not routes: |
489 raise ConfigurationError("No routes were defined.") | 529 raise ConfigurationError("No routes were defined.") |
490 taxonomies = v.get('taxonomies') | 530 taxonomies = v.get('taxonomies') |
491 if taxonomies is None: | 531 if taxonomies is None: |
492 v['taxonomies'] = {} | 532 v['taxonomies'] = {} |
493 return v | 533 generators = v.get('generators') |
534 if generators is None: | |
535 v['generators'] = {} | |
536 return v | |
537 | |
494 | 538 |
495 # Make sure the site root starts and ends with a slash. | 539 # Make sure the site root starts and ends with a slash. |
496 def _validate_site_root(v, values, cache): | 540 def _validate_site_root(v, values, cache): |
497 if not v.startswith('/'): | 541 if not v.startswith('/'): |
498 raise ConfigurationError("The `site/root` setting must start " | 542 raise ConfigurationError("The `site/root` setting must start " |
581 if endpoint in reserved_endpoints: | 625 if endpoint in reserved_endpoints: |
582 raise ConfigurationError( | 626 raise ConfigurationError( |
583 "Source '%s' is using a reserved endpoint name: %s" % | 627 "Source '%s' is using a reserved endpoint name: %s" % |
584 (sn, endpoint)) | 628 (sn, endpoint)) |
585 | 629 |
630 # Validate generators. | |
631 for gn, gc in sc.get('generators', {}).items(): | |
632 if not isinstance(gc, dict): | |
633 raise ConfigurationError( | |
634 "Generators for source '%s' should be defined in a " | |
635 "dictionary." % sn) | |
636 gc['source'] = sn | |
637 | |
586 return v | 638 return v |
587 | 639 |
588 | 640 |
589 def _validate_site_routes(v, values, cache): | 641 def _validate_site_routes(v, values, cache): |
590 if not v: | 642 if not v: |
603 if not rc_url: | 655 if not rc_url: |
604 raise ConfigurationError("All routes in 'site/routes' must " | 656 raise ConfigurationError("All routes in 'site/routes' must " |
605 "have an 'url'.") | 657 "have an 'url'.") |
606 if rc_url[0] != '/': | 658 if rc_url[0] != '/': |
607 raise ConfigurationError("Route URLs must start with '/'.") | 659 raise ConfigurationError("Route URLs must start with '/'.") |
608 if rc.get('source') is None: | 660 |
609 raise ConfigurationError("Routes must specify a source.") | 661 r_source = rc.get('source') |
610 if rc['source'] not in list(values['site']['sources'].keys()): | 662 r_generator = rc.get('generator') |
663 if r_source is None and r_generator is None: | |
664 raise ConfigurationError("Routes must specify a source or " | |
665 "generator.") | |
666 if (r_source and | |
667 r_source not in list(values['site']['sources'].keys())): | |
611 raise ConfigurationError("Route is referencing unknown " | 668 raise ConfigurationError("Route is referencing unknown " |
612 "source: %s" % rc['source']) | 669 "source: %s" % r_source) |
613 rc.setdefault('taxonomy', None) | 670 if (r_generator and |
671 r_generator not in list(values['site']['generators'].keys())): | |
672 raise ConfigurationError("Route is referencing unknown " | |
673 "generator: %s" % r_generator) | |
674 | |
675 rc.setdefault('generator', None) | |
614 rc.setdefault('page_suffix', '/%num%') | 676 rc.setdefault('page_suffix', '/%num%') |
615 | 677 |
616 return v | 678 return v |
617 | 679 |
618 | 680 |
619 def _validate_site_taxonomies(v, values, cache): | 681 def _validate_site_taxonomies(v, values, cache): |
682 if not isinstance(v, dict): | |
683 raise ConfigurationError( | |
684 "The 'site/taxonomies' setting must be a mapping.") | |
620 for tn, tc in v.items(): | 685 for tn, tc in v.items(): |
621 tc.setdefault('multiple', False) | 686 tc.setdefault('multiple', False) |
622 tc.setdefault('term', tn) | 687 tc.setdefault('term', tn) |
623 tc.setdefault('page', '_%s.%%ext%%' % tc['term']) | 688 tc.setdefault('page', '_%s.%%ext%%' % tc['term']) |
624 | 689 return v |
690 | |
691 | |
692 def _validate_site_generators(v, values, cache): | |
693 if not isinstance(v, dict): | |
694 raise ConfigurationError( | |
695 "The 'site/generators' setting must be a mapping.") | |
696 for gn, gc in v.items(): | |
697 if 'type' not in gc: | |
698 raise ConfigurationError( | |
699 "Generator '%s' doesn't specify a type." % gn) | |
625 return v | 700 return v |
626 | 701 |
627 | 702 |
628 def _validate_site_plugins(v, values, cache): | 703 def _validate_site_plugins(v, values, cache): |
629 if isinstance(v, str): | 704 if isinstance(v, str): |