diff 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
line wrap: on
line diff
--- a/piecrust/appconfig.py	Thu May 26 19:46:28 2016 -0700
+++ b/piecrust/appconfig.py	Thu May 26 19:52:47 2016 -0700
@@ -142,6 +142,7 @@
 
             self._values = self._validateAll(values)
         except Exception as ex:
+            logger.exception(ex)
             raise Exception(
                     "Error loading configuration from: %s" %
                     ', '.join(paths)) from ex
@@ -221,6 +222,7 @@
                 try:
                     val2 = callback(val, values, cache_writer)
                 except Exception as ex:
+                    logger.exception(ex)
                     raise Exception("Error raised in validator '%s'." %
                                     callback_name) from ex
                 if val2 is None:
@@ -288,7 +290,9 @@
                     }
                 ],
             'theme_tag_page': 'theme_pages:_tag.%ext%',
-            'theme_category_page': 'theme_pages:_category.%ext%'
+            'theme_category_page': 'theme_pages:_category.%ext%',
+            'theme_month_page': 'theme_pages:_month.%ext%',
+            'theme_year_page': 'theme_pages:_year.%ext%'
             })
         })
 
@@ -332,9 +336,10 @@
             'posts_fs': DEFAULT_POSTS_FS,
             'default_page_layout': 'default',
             'default_post_layout': 'post',
-            'post_url': '%year%/%month%/%day%/%slug%',
-            'tag_url': 'tag/%tag%',
-            'category_url': '%category%',
+            'post_url': '/%year%/%month%/%day%/%slug%',
+            'year_url': '/%year%',
+            'tag_url': '/tag/%path:tag%',
+            'category_url': '/%category%',
             'posts_per_page': 5
             })
         })
@@ -362,25 +367,25 @@
                         'func': 'pcurl(slug)'
                         }
                     ],
-                'taxonomies': collections.OrderedDict({
-                    'tags': {
+                'taxonomies': collections.OrderedDict([
+                    ('tags', {
                         'multiple': True,
                         'term': 'tag'
-                        },
-                    'categories': {
+                        }),
+                    ('categories', {
                         'term': 'category'
-                        }
-                    })
+                        })
+                    ])
                 })
             })
 
 
 def get_default_content_model_for_blog(
         blog_name, is_only_blog, values, user_overrides, theme_site=False):
-    # Get the global values for various things we're interested in.
+    # Get the global (default) values for various things we're interested in.
     defs = {}
     names = ['posts_fs', 'posts_per_page', 'date_format',
-             'default_post_layout', 'post_url', 'tag_url', 'category_url']
+             'default_post_layout', 'post_url', 'year_url']
     for n in names:
         defs[n] = try_get_dict_value(
                 user_overrides, 'site/%s' % n,
@@ -389,7 +394,7 @@
     # More stuff we need.
     if is_only_blog:
         url_prefix = ''
-        tax_page_prefix = ''
+        page_prefix = ''
         fs_endpoint = 'posts'
         data_endpoint = 'blog'
         item_name = 'post'
@@ -401,7 +406,7 @@
             fs_endpoint = 'sample/posts'
     else:
         url_prefix = blog_name + '/'
-        tax_page_prefix = blog_name + '/'
+        page_prefix = blog_name + '/'
         fs_endpoint = 'posts/%s' % blog_name
         data_endpoint = blog_name
         item_name = '%s-post' % blog_name
@@ -414,28 +419,21 @@
     blog_values = {}
     for n in names:
         blog_values[n] = blog_cfg.get(n, defs[n])
-        if n in ['post_url', 'tag_url', 'category_url']:
-            blog_values[n] = url_prefix + blog_values[n]
 
     posts_fs = blog_values['posts_fs']
     posts_per_page = blog_values['posts_per_page']
     date_format = blog_values['date_format']
     default_layout = blog_values['default_post_layout']
-    post_url = '/' + blog_values['post_url'].lstrip('/')
-    tag_url = '/' + blog_values['tag_url'].lstrip('/')
-    category_url = '/' + blog_values['category_url'].lstrip('/')
+    post_url = '/' + url_prefix + blog_values['post_url'].lstrip('/')
+    year_url = '/' + url_prefix + blog_values['year_url'].lstrip('/')
 
-    tags_taxonomy = 'pages:%s_tag.%%ext%%' % tax_page_prefix
-    category_taxonomy = 'pages:%s_category.%%ext%%' % tax_page_prefix
+    year_archive = 'pages:%s_year.%%ext%%' % page_prefix
     if not theme_site:
-        theme_tag_page = values['site'].get('theme_tag_page')
-        if theme_tag_page:
-            tags_taxonomy += ';' + theme_tag_page
-        theme_category_page = values['site'].get('theme_category_page')
-        if theme_category_page:
-            category_taxonomy += ';' + theme_category_page
+        theme_year_page = values['site'].get('theme_year_page')
+        if theme_year_page:
+            year_archive += ';' + theme_year_page
 
-    return collections.OrderedDict({
+    cfg = collections.OrderedDict({
             'site': collections.OrderedDict({
                 'sources': collections.OrderedDict({
                     blog_name: collections.OrderedDict({
@@ -447,11 +445,14 @@
                         'data_type': 'blog',
                         'items_per_page': posts_per_page,
                         'date_format': date_format,
-                        'default_layout': default_layout,
-                        'taxonomy_pages': collections.OrderedDict({
-                            'tags': tags_taxonomy,
-                            'categories': category_taxonomy
-                            })
+                        'default_layout': default_layout
+                        })
+                    }),
+                'generators': collections.OrderedDict({
+                    ('%s_archives' % blog_name): collections.OrderedDict({
+                        'type': 'blog_archives',
+                        'source': blog_name,
+                        'page': year_archive
                         })
                     }),
                 'routes': [
@@ -461,21 +462,60 @@
                         'func': 'pcposturl(year,month,day,slug)'
                         },
                     {
-                        'url': tag_url,
-                        'source': blog_name,
-                        'taxonomy': 'tags',
-                        'func': 'pctagurl(tag)'
-                        },
-                    {
-                        'url': category_url,
-                        'source': blog_name,
-                        'taxonomy': 'categories',
-                        'func': 'pccaturl(category)'
+                        'url': year_url,
+                        'generator': ('%s_archives' % blog_name),
+                        'func': 'pcyearurl(year)'
                         }
                     ]
                 })
             })
 
+    # Add a generator and a route for each taxonomy.
+    taxonomies_cfg = values.get('site', {}).get('taxonomies', {}).copy()
+    taxonomies_cfg.update(
+            user_overrides.get('site', {}).get('taxonomies', {}))
+    for tax_name, tax_cfg in taxonomies_cfg.items():
+        term = tax_cfg.get('term', tax_name)
+
+        # Generator.
+        page_ref = 'pages:%s_%s.%%ext%%' % (page_prefix, term)
+        if not theme_site:
+            theme_page_ref = values['site'].get('theme_%s_page' % term)
+            if theme_page_ref:
+                page_ref += ';' + theme_page_ref
+        tax_gen_name = '%s_%s' % (blog_name, tax_name)
+        tax_gen = collections.OrderedDict({
+            'type': 'taxonomy',
+            'source': blog_name,
+            'taxonomy': tax_name,
+            'page': page_ref
+            })
+        cfg['site']['generators'][tax_gen_name] = tax_gen
+
+        # Route.
+        tax_url_cfg_name = '%s_url' % term
+        tax_url = blog_cfg.get(tax_url_cfg_name,
+                               try_get_dict_value(
+                                   user_overrides,
+                                   'site/%s' % tax_url_cfg_name,
+                                   values['site'].get(
+                                       tax_url_cfg_name,
+                                       '%s/%%%s%%' % (term, term))))
+        tax_url = '/' + url_prefix + tax_url.lstrip('/')
+        term_arg = term
+        if tax_cfg.get('multiple') is True:
+            term_arg = '+' + term
+        tax_func = 'pc%surl(%s)' % (term, term_arg)
+        tax_route = collections.OrderedDict({
+            'url': tax_url,
+            'generator': tax_gen_name,
+            'taxonomy': tax_name,
+            'func': tax_func
+            })
+        cfg['site']['routes'].append(tax_route)
+
+    return cfg
+
 
 # Configuration value validators.
 #
@@ -490,8 +530,12 @@
     taxonomies = v.get('taxonomies')
     if taxonomies is None:
         v['taxonomies'] = {}
+    generators = v.get('generators')
+    if generators is None:
+        v['generators'] = {}
     return v
 
+
 # Make sure the site root starts and ends with a slash.
 def _validate_site_root(v, values, cache):
     if not v.startswith('/'):
@@ -583,6 +627,14 @@
                     "Source '%s' is using a reserved endpoint name: %s" %
                     (sn, endpoint))
 
+        # Validate generators.
+        for gn, gc in sc.get('generators', {}).items():
+            if not isinstance(gc, dict):
+                raise ConfigurationError(
+                    "Generators for source '%s' should be defined in a "
+                    "dictionary." % sn)
+            gc['source'] = sn
+
     return v
 
 
@@ -605,23 +657,46 @@
                                      "have an 'url'.")
         if rc_url[0] != '/':
             raise ConfigurationError("Route URLs must start with '/'.")
-        if rc.get('source') is None:
-            raise ConfigurationError("Routes must specify a source.")
-        if rc['source'] not in list(values['site']['sources'].keys()):
+
+        r_source = rc.get('source')
+        r_generator = rc.get('generator')
+        if r_source is None and r_generator is None:
+            raise ConfigurationError("Routes must specify a source or "
+                                     "generator.")
+        if (r_source and
+                r_source not in list(values['site']['sources'].keys())):
             raise ConfigurationError("Route is referencing unknown "
-                                     "source: %s" % rc['source'])
-        rc.setdefault('taxonomy', None)
+                                     "source: %s" % r_source)
+        if (r_generator and
+                r_generator not in list(values['site']['generators'].keys())):
+            raise ConfigurationError("Route is referencing unknown "
+                                     "generator: %s" % r_generator)
+
+        rc.setdefault('generator', None)
         rc.setdefault('page_suffix', '/%num%')
 
     return v
 
 
 def _validate_site_taxonomies(v, values, cache):
+    if not isinstance(v, dict):
+        raise ConfigurationError(
+                "The 'site/taxonomies' setting must be a mapping.")
     for tn, tc in v.items():
         tc.setdefault('multiple', False)
         tc.setdefault('term', tn)
         tc.setdefault('page', '_%s.%%ext%%' % tc['term'])
+    return v
 
+
+def _validate_site_generators(v, values, cache):
+    if not isinstance(v, dict):
+        raise ConfigurationError(
+                "The 'site/generators' setting must be a mapping.")
+    for gn, gc in v.items():
+        if 'type' not in gc:
+            raise ConfigurationError(
+                    "Generator '%s' doesn't specify a type." % gn)
     return v