changeset 675:3df808b133f8

internal: Improve how theme configuration is validated and merged. * Add default theme config up-front so it benefits from the usual validation. * Add an explicit `use_default_theme_content` setting. * Add/fix unit tests.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 06 Mar 2016 23:49:41 -0800
parents f987b29d6fab
children a04288f199a5
files piecrust/app.py piecrust/appconfig.py tests/bakes/test_theme.yaml tests/bakes/test_theme_site.yaml tests/servings/test_theme.yaml tests/servings/test_theme_site.yaml tests/test_appconfig.py
diffstat 7 files changed, 157 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/piecrust/app.py	Sun Mar 06 23:48:01 2016 -0800
+++ b/piecrust/app.py	Sun Mar 06 23:49:41 2016 -0800
@@ -12,7 +12,7 @@
 from piecrust.cache import ExtensibleCache, NullExtensibleCache
 from piecrust.plugins.base import PluginLoader
 from piecrust.environment import StandardEnvironment
-from piecrust.configuration import ConfigurationError
+from piecrust.configuration import ConfigurationError, merge_dicts
 from piecrust.routing import Route
 from piecrust.sources.base import REALM_THEME
 from piecrust.taxonomies import Taxonomy
@@ -66,23 +66,10 @@
                 paths.append(preview_path)
 
         config_cache = self.cache.getCache('app')
-        config = PieCrustConfiguration(paths, config_cache,
-                                       theme_config=self.theme_site)
-        if self.theme_dir:
-            # We'll need to flag all page sources as coming from
-            # the theme.
-            def _fixupThemeSources(index, config):
-                if index != 0:
-                    return
-                sitec = config.get('site')
-                if sitec is None:
-                    sitec = {}
-                    config['site'] = sitec
-                srcc = sitec.get('sources')
-                if srcc is not None:
-                    for sn, sc in srcc.items():
-                        sc['realm'] = REALM_THEME
-            config.addFixup(_fixupThemeSources)
+        config = PieCrustConfiguration(
+                paths, config_cache, theme_config=self.theme_site)
+        if not self.theme_site and self.theme_dir:
+            config.addFixup(_fixup_theme_config)
 
         self.env.stepTimer('SiteConfigLoad', time.perf_counter() - start_time)
         return config
@@ -216,6 +203,51 @@
         return dirs
 
 
+def _fixup_theme_config(index, config):
+    if index != 0:
+        # We only want to affect the theme config, which is first.
+        return
+
+    # See if we want to generate the default theme content model.
+    sitec = config.setdefault('site', {})
+    gen_default_model = sitec.setdefault('use_default_theme_content', True)
+    if gen_default_model:
+        # Create a default `theme_pages` source.
+        srcc = sitec.setdefault('sources', {})
+        if not isinstance(srcc, dict):
+            raise Exception("Theme configuration has invalid `site/sources`. "
+                            "Must be a dictionary.")
+        default_theme_sources = {
+                'theme_pages': {
+                    'type': 'default',
+                    'ignore_missing_dir': True,
+                    'fs_endpoint': 'pages',
+                    'data_endpoint': 'site.pages',
+                    'default_layout': 'default',
+                    'item_name': 'page'
+                    }
+                }
+        sitec['sources'] = merge_dicts(default_theme_sources, srcc)
+
+        sitec.setdefault('theme_tag_page', 'theme_pages:_tag.%ext%')
+        sitec.setdefault('theme_category_page', 'theme_pages:_category.%ext%')
+
+        # Create a default route for `theme_pages`.
+        rtc = sitec.setdefault('routes', [])
+        if not isinstance(rtc, list):
+            raise Exception("Theme configuration has invalid `site/routes`. "
+                            "Must be a list.")
+        rtc.append({
+                'url': '/%path:slug%',
+                'source': 'theme_pages',
+                'func': 'pcurl(slug)'})
+
+    # Make all sources belong to the "theme" realm.
+    srcc = sitec.get('sources')
+    if srcc and isinstance(srcc, dict):
+        for sn, sc in srcc.items():
+            sc['realm'] = REALM_THEME
+
 
 def apply_variant_and_values(app, config_variant=None, config_values=None):
     if config_variant is not None:
--- a/piecrust/appconfig.py	Sun Mar 06 23:48:01 2016 -0800
+++ b/piecrust/appconfig.py	Sun Mar 06 23:49:41 2016 -0800
@@ -170,11 +170,8 @@
         # Figure out if we need to generate the configuration for the
         # default content model.
         sitec = values.setdefault('site', {})
-        if (
-                ('sources' not in sitec and
-                 'routes' not in sitec and
-                 'taxonomies' not in sitec) or
-                sitec.get('use_default_content')):
+        gen_default_model = bool(sitec.get('use_default_content'))
+        if gen_default_model:
             logger.debug("Generating default content model...")
             values = self._generateDefaultContentModel(values)
 
@@ -269,7 +266,6 @@
 default_content_model_base = collections.OrderedDict({
         'site': collections.OrderedDict({
             'posts_fs': DEFAULT_POSTS_FS,
-            'date_format': DEFAULT_DATE_FORMAT,
             'default_page_layout': 'default',
             'default_post_layout': 'post',
             'post_url': '%year%/%month%/%day%/%slug%',
@@ -324,6 +320,12 @@
         fs_endpoint = 'posts'
         data_endpoint = 'blog'
         item_name = 'post'
+
+        if theme_site:
+            # If this is a theme site, show posts from a `sample` directory
+            # so it's clearer that those won't show up when the theme is
+            # actually applied to a normal site.
+            fs_endpoint = 'sample/posts'
     else:
         url_prefix = blog_name + '/'
         tax_page_prefix = blog_name + '/'
@@ -351,8 +353,12 @@
     tags_taxonomy = 'pages:%s_tag.%%ext%%' % tax_page_prefix
     category_taxonomy = 'pages:%s_category.%%ext%%' % tax_page_prefix
     if not theme_site:
-        tags_taxonomy += ';theme_pages:_tag.%ext%'
-        category_taxonomy += ';theme_pages:_category.%ext%'
+        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
 
     return collections.OrderedDict({
             'site': collections.OrderedDict({
@@ -476,28 +482,6 @@
         raise ConfigurationError("The 'site/sources' setting must be a "
                                  "dictionary.")
 
-    theme_site = values['site']['theme_site']
-    if not theme_site:
-        # Add the theme page source if no sources were defined in the theme
-        # configuration itself.
-        has_any_theme_source = False
-        for sn, sc in v.items():
-            if sc.get('realm') == REALM_THEME:
-                has_any_theme_source = True
-                break
-        if not has_any_theme_source:
-            v['theme_pages'] = {
-                    'theme_source': True,
-                    'fs_endpoint': 'pages',
-                    'ignore_missing_dir': True,
-                    'data_endpoint': 'site/pages',
-                    'item_name': 'page',
-                    'realm': REALM_THEME}
-            values['site']['routes'].append({
-                    'url': '/%path:slug%',
-                    'source': 'theme_pages',
-                    'func': 'pcurl(slug)'})
-
     # Sources have the `default` scanner by default, duh. Also, a bunch
     # of other default values for other configuration stuff.
     reserved_endpoints = set(['piecrust', 'site', 'page', 'route',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/bakes/test_theme.yaml	Sun Mar 06 23:49:41 2016 -0800
@@ -0,0 +1,32 @@
+---
+config:
+    site:
+        title: "Some Test"
+        default_page_layout: 'none'
+    foo: bar
+in:
+    pages/foo.md: "This is: {{foo}}, with no template"
+    theme/theme_config.yml: "name: testtheme"
+    theme/pages/_index.md: "This is {{site.title}} by {{name}}, with theme template"
+    theme/templates/default.html: "THEME: {{content}}"
+out:
+    index.html: "THEME: This is Some Test by testtheme, with theme template"
+    foo.html: "This is: bar, with no template"
+---
+config:
+    site:
+        default_page_layout: 'custom'
+in:
+    pages/foo.md: "FOO"
+    pages/bar.md: "---\nlayout: blah\n---\nBAR"
+    templates/custom.html: "CUSTOM: {{content}}"
+    theme/theme_config.yml: "site: {sources: {theme_pages: {default_layout: blah}}}"
+    theme/pages/_index.md: "theme index"
+    theme/pages/about.md: "about"
+    theme/templates/blah.html: "THEME: {{content}}"
+out:
+    index.html: "THEME: theme index"
+    about.html: "THEME: about"
+    foo.html: "CUSTOM: FOO"
+    bar.html: "THEME: BAR"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/bakes/test_theme_site.yaml	Sun Mar 06 23:49:41 2016 -0800
@@ -0,0 +1,18 @@
+---
+theme_config:
+    site:
+        title: "Some Test Theme"
+in:
+    pages/foo.md: "This is: {{site.title}}"
+outfiles:
+    foo.html: "This is: Some Test Theme"
+---
+theme_config:
+    site:
+        title: "Some Test Theme"
+in:
+    pages/foo.md: "This is: {{foo}}"
+    configs/theme_preview.yml: "foo: bar"
+outfiles:
+    foo.html: "This is: bar"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/servings/test_theme.yaml	Sun Mar 06 23:49:41 2016 -0800
@@ -0,0 +1,21 @@
+---
+url: /
+config:
+    site:
+        title: "Some Test"
+in:
+    theme/theme_config.yml: "name: testtheme"
+    theme/pages/_index.md: "This is {{site.title}} by {{name}}"
+    theme/templates/default.html: "THEME: {{content}}"
+out: "THEME: This is Some Test by testtheme"
+---
+url: /foo.html
+config:
+    site:
+        title: "Some Test"
+    foo: bar
+in:
+    pages/foo.md: "This is: {{foo}} by {{name}}"
+    theme/theme_config.yml: "name: testtheme"
+out: "This is: bar by testtheme"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/servings/test_theme_site.yaml	Sun Mar 06 23:49:41 2016 -0800
@@ -0,0 +1,18 @@
+---
+url: /foo.html
+theme_config:
+    site:
+        title: "Some Test Theme"
+in:
+    pages/foo.md: "This is: {{site.title}}"
+out: "This is: Some Test Theme"
+---
+url: /foo.html
+theme_config:
+    site:
+        title: "Some Test Theme"
+in:
+    pages/foo.md: "This is: {{foo}}"
+    configs/theme_preview.yml: "foo: bar"
+out: "This is: bar"
+
--- a/tests/test_appconfig.py	Sun Mar 06 23:48:01 2016 -0800
+++ b/tests/test_appconfig.py	Sun Mar 06 23:49:41 2016 -0800
@@ -5,13 +5,13 @@
     values = {}
     config = PieCrustConfiguration(values=values)
     assert config.get('site/root') == '/'
-    assert len(config.get('site/sources')) == 3  # pages, posts, theme_pages
+    assert len(config.get('site/sources')) == 2  # pages, posts
 
 
 def test_config_default2():
     config = PieCrustConfiguration()
     assert config.get('site/root') == '/'
-    assert len(config.get('site/sources')) == 3  # pages, posts, theme_pages
+    assert len(config.get('site/sources')) == 2  # pages, posts
 
 
 def test_config_site_override_title():
@@ -29,7 +29,7 @@
     config = PieCrustConfiguration(values=values)
     # The order of routes is important. Sources, not so much.
     assert list(map(lambda v: v['source'], config.get('site/routes'))) == [
-            'notes', 'posts', 'posts', 'posts', 'pages', 'theme_pages']
+            'notes', 'posts', 'posts', 'posts', 'pages']
     assert list(config.get('site/sources').keys()) == [
-            'pages', 'posts', 'notes', 'theme_pages']
+            'pages', 'posts', 'notes']