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):