changeset 663:3ceeca7bb71c

themes: Add support for a `--theme` argument to `chef`.
author Ludovic Chabant <ludovic@chabant.com>
date Tue, 01 Mar 2016 22:27:28 -0800
parents cbd5cdec0695
children 8da29a497f61
files piecrust/app.py piecrust/appconfig.py piecrust/baking/baker.py piecrust/baking/worker.py piecrust/commands/base.py piecrust/commands/builtin/serving.py piecrust/commands/builtin/util.py piecrust/main.py piecrust/pathutil.py piecrust/processing/pipeline.py piecrust/processing/worker.py piecrust/serving/middlewares.py piecrust/serving/procloop.py piecrust/serving/server.py piecrust/serving/util.py piecrust/serving/wrappers.py
diffstat 16 files changed, 150 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- a/piecrust/app.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/app.py	Tue Mar 01 22:27:28 2016 -0800
@@ -54,12 +54,20 @@
         start_time = time.perf_counter()
 
         paths = []
-        if self.theme_dir:
-            paths.append(os.path.join(self.theme_dir, THEME_CONFIG_PATH))
-        paths.append(os.path.join(self.root_dir, CONFIG_PATH))
+        if not self.theme_site:
+            if self.theme_dir:
+                paths.append(os.path.join(self.theme_dir, THEME_CONFIG_PATH))
+            paths.append(os.path.join(self.root_dir, CONFIG_PATH))
+        else:
+            paths.append(os.path.join(self.root_dir, THEME_CONFIG_PATH))
+            preview_path = os.path.join(
+                    self.root_dir, 'configs', 'theme_preview.yml')
+            if os.path.isfile(preview_path):
+                paths.append(preview_path)
 
         config_cache = self.cache.getCache('app')
-        config = PieCrustConfiguration(paths, config_cache)
+        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.
@@ -107,6 +115,8 @@
 
     @cached_property
     def theme_dir(self):
+        if self.theme_site:
+            return None
         td = self._get_dir(THEME_DIR)
         if td is not None:
             return td
--- a/piecrust/appconfig.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/appconfig.py	Tue Mar 01 22:27:28 2016 -0800
@@ -29,11 +29,17 @@
 
 
 class PieCrustConfiguration(Configuration):
-    def __init__(self, paths=None, cache=None, values=None, validate=True):
-        super(PieCrustConfiguration, self).__init__(values, validate)
+    def __init__(self, paths=None, cache=None, values=None, validate=True,
+                 theme_config=False):
+        super(PieCrustConfiguration, self).__init__()
         self.paths = paths
         self.cache = cache or NullCache()
         self.fixups = []
+        self.theme_config = theme_config
+        # Set the values after we set the rest, since our validation needs
+        # our attributes.
+        if values:
+            self.setAll(values, validate=validate)
 
     def applyVariant(self, variant_path, raise_if_not_found=True):
         variant = self.get(variant_path)
@@ -112,6 +118,10 @@
         # Add the loaded values to the default configuration.
         values = merge_dicts(copy.deepcopy(default_configuration), values)
 
+        # Set the theme site flag.
+        if self.theme_config:
+            values['site']['theme_site'] = True
+
         # Figure out if we need to generate the configuration for the
         # default content model.
         sitec = values.setdefault('site', {})
@@ -159,7 +169,8 @@
         is_only_blog = (len(blogsc) == 1)
         for blog_name in blogsc:
             blog_cfg = get_default_content_model_for_blog(
-                    blog_name, is_only_blog, values)
+                    blog_name, is_only_blog, values,
+                    theme_site=self.theme_config)
             values = merge_dicts(blog_cfg, values)
 
         dcm = get_default_content_model(values)
@@ -199,7 +210,8 @@
             'cache_time': 28800,
             'enable_debug_info': True,
             'show_debug_info': False,
-            'use_default_content': True
+            'use_default_content': True,
+            'theme_site': False
             }),
         'baker': collections.OrderedDict({
             'no_bake_setting': 'draft',
@@ -256,7 +268,8 @@
             })
 
 
-def get_default_content_model_for_blog(blog_name, is_only_blog, values):
+def get_default_content_model_for_blog(
+        blog_name, is_only_blog, values, theme_site=False):
     posts_fs = values['site']['posts_fs']
     blog_cfg = values.get(blog_name, {})
 
@@ -290,6 +303,12 @@
             'category_url',
             url_prefix + values['site']['category_url']).lstrip('/')
 
+    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%'
+
     return collections.OrderedDict({
             'site': collections.OrderedDict({
                 'sources': collections.OrderedDict({
@@ -304,12 +323,8 @@
                         'date_format': date_format,
                         'default_layout': default_layout,
                         'taxonomy_pages': collections.OrderedDict({
-                            'tags': ('pages:%s_tag.%%ext%%;'
-                                     'theme_pages:_tag.%%ext%%' %
-                                     tax_page_prefix),
-                            'categories': ('pages:%s_category.%%ext%%;'
-                                           'theme_pages:_category.%%ext%%' %
-                                           tax_page_prefix)
+                            'tags': tags_taxonomy,
+                            'categories': category_taxonomy
                             })
                         })
                     }),
@@ -416,24 +431,27 @@
         raise ConfigurationError("The 'site/sources' setting must be a "
                                  "dictionary.")
 
-    # 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',
-                'data_endpoint': 'site/pages',
-                'item_name': 'page',
-                'realm': REALM_THEME}
-        values['site']['routes'].append({
-                'url': '/%path:slug%',
-                'source': 'theme_pages',
-                'func': 'pcurl(slug)'})
+    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.
--- a/piecrust/baking/baker.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/baking/baker.py	Tue Mar 01 22:27:28 2016 -0800
@@ -551,7 +551,8 @@
                 previous_record_path=previous_record_path,
                 config_variant=self.applied_config_variant,
                 config_values=self.applied_config_values,
-                force=self.force, debug=self.app.debug)
+                force=self.force, debug=self.app.debug,
+                theme_site=self.app.theme_site)
         pool = WorkerPool(
                 worker_count=worker_count,
                 batch_size=batch_size,
--- a/piecrust/baking/worker.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/baking/worker.py	Tue Mar 01 22:27:28 2016 -0800
@@ -15,10 +15,10 @@
 
 
 class BakeWorkerContext(object):
-    def __init__(self, root_dir, sub_cache_dir, out_dir,
+    def __init__(self, root_dir, sub_cache_dir, out_dir, *,
                  previous_record_path=None,
                  config_variant=None, config_values=None,
-                 force=False, debug=False):
+                 force=False, debug=False, theme_site=False):
         self.root_dir = root_dir
         self.sub_cache_dir = sub_cache_dir
         self.out_dir = out_dir
@@ -27,6 +27,7 @@
         self.config_values = config_values
         self.force = force
         self.debug = debug
+        self.theme_site = theme_site
         self.app = None
         self.previous_record = None
         self.previous_record_index = None
@@ -39,7 +40,8 @@
 
     def initialize(self):
         # Create the app local to this worker.
-        app = PieCrust(self.ctx.root_dir, debug=self.ctx.debug)
+        app = PieCrust(self.ctx.root_dir, debug=self.ctx.debug,
+                       theme_site=self.ctx.theme_site)
         app._useSubCacheDir(self.ctx.sub_cache_dir)
         app.config.set('baker/is_baking', True)
         app.config.set('baker/worker_id', self.wid)
--- a/piecrust/commands/base.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/commands/base.py	Tue Mar 01 22:27:28 2016 -0800
@@ -32,7 +32,7 @@
 
     def checkedRun(self, ctx):
         if ctx.app.root_dir is None and self.requires_website:
-            raise SiteNotFoundError()
+            raise SiteNotFoundError(theme=ctx.app.theme_site)
         return self.run(ctx)
 
 
--- a/piecrust/commands/builtin/serving.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/commands/builtin/serving.py	Tue Mar 01 22:27:28 2016 -0800
@@ -46,6 +46,7 @@
             run_werkzeug_server(
                     root_dir, host, port,
                     debug_piecrust=debug,
+                    theme_site=ctx.args.theme,
                     sub_cache_dir=ctx.app.sub_cache_dir,
                     use_debugger=debug,
                     use_reloader=ctx.args.use_reloader)
@@ -62,6 +63,7 @@
             run_gunicorn_server(
                     root_dir,
                     debug_piecrust=debug,
+                    theme_site=ctx.args.theme,
                     sub_cache_dir=ctx.app.sub_cache_dir,
                     gunicorn_options=options)
 
--- a/piecrust/commands/builtin/util.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/commands/builtin/util.py	Tue Mar 01 22:27:28 2016 -0800
@@ -4,7 +4,7 @@
 import codecs
 import logging
 import yaml
-from piecrust.app import CONFIG_PATH
+from piecrust.app import CONFIG_PATH, THEME_CONFIG_PATH
 from piecrust.commands.base import ChefCommand
 
 
@@ -19,7 +19,8 @@
         self.requires_website = False
 
     def setupParser(self, parser, app):
-        parser.add_argument('destination',
+        parser.add_argument(
+                'destination',
                 help="The destination directory in which to create the website.")
 
     def run(self, ctx):
@@ -31,6 +32,9 @@
             os.makedirs(destination, 0o755)
 
         config_path = os.path.join(destination, CONFIG_PATH)
+        if ctx.args.theme:
+            config_path = os.path.join(destination, THEME_CONFIG_PATH)
+
         if not os.path.isdir(os.path.dirname(config_path)):
             os.makedirs(os.path.dirname(config_path), 0o755)
 
--- a/piecrust/main.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/main.py	Tue Mar 01 22:27:28 2016 -0800
@@ -40,7 +40,8 @@
 
 
 class NullPieCrust:
-    def __init__(self):
+    def __init__(self, theme_site=False):
+        self.theme_site = theme_site
         self.root_dir = None
         self.debug = False
         self.templates_dirs = []
@@ -50,6 +51,9 @@
         self.plugin_loader = PluginLoader(self)
         self.env = None
 
+    def useSubCache(self, cache_name, cache_key):
+        pass
+
 
 def main():
     if sys.platform == 'darwin':
@@ -84,6 +88,10 @@
             '--root',
             help="The root directory of the website.")
     parser.add_argument(
+            '--theme',
+            action='store_true',
+            help="Makes the current command apply to a theme website.")
+    parser.add_argument(
             '--config',
             dest='config_variant',
             help="The configuration variant to use for this command.")
@@ -196,15 +204,19 @@
         root = os.path.expanduser(pre_args.root)
     else:
         try:
-            root = find_app_root()
+            root = find_app_root(theme=pre_args.theme)
         except SiteNotFoundError:
             root = None
 
     if not root:
-        app = NullPieCrust()
+        app = NullPieCrust(
+                theme_site=pre_args.theme)
     else:
-        app = PieCrust(root, cache=(not pre_args.no_cache),
-                       debug=pre_args.debug)
+        app = PieCrust(
+                root,
+                theme_site=pre_args.theme,
+                cache=(not pre_args.no_cache),
+                debug=pre_args.debug)
 
     # Build a hash for a custom cache directory.
     cache_key = 'default'
--- a/piecrust/pathutil.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/pathutil.py	Tue Mar 01 22:27:28 2016 -0800
@@ -2,18 +2,23 @@
 import os
 import os.path
 import fnmatch
+from piecrust import CONFIG_PATH, THEME_CONFIG_PATH
 
 
 re_terminal_path = re.compile(r'^(\w\:)?[/\\]$')
 
 
 class SiteNotFoundError(Exception):
-    def __init__(self, root=None, msg=None):
+    def __init__(self, root=None, msg=None, theme=False):
         if not root:
             root = os.getcwd()
+
+        cfg_name = CONFIG_PATH
+        if theme:
+            cfg_name = THEME_CONFIG_PATH
+
         full_msg = ("No PieCrust website in '%s' "
-                    "('config.yml' not found!)" %
-                    root)
+                    "('%s' not found!)" % (root, cfg_name))
         if msg:
             full_msg += ": " + msg
         else:
@@ -21,14 +26,18 @@
         Exception.__init__(self, full_msg)
 
 
-def find_app_root(cwd=None):
+def find_app_root(cwd=None, theme=False):
     if cwd is None:
         cwd = os.getcwd()
 
-    while not os.path.isfile(os.path.join(cwd, 'config.yml')):
+    cfg_name = CONFIG_PATH
+    if theme:
+        cfg_name = THEME_CONFIG_PATH
+
+    while not os.path.isfile(os.path.join(cwd, cfg_name)):
         cwd = os.path.dirname(cwd)
         if not cwd or re_terminal_path.match(cwd):
-            raise SiteNotFoundError(cwd)
+            raise SiteNotFoundError(cwd, theme=theme)
     return cwd
 
 
--- a/piecrust/processing/pipeline.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/processing/pipeline.py	Tue Mar 01 22:27:28 2016 -0800
@@ -252,7 +252,8 @@
 
         ctx = ProcessingWorkerContext(
                 self.app.root_dir, self.out_dir, self.tmp_dir,
-                self.force, self.app.debug)
+                force=self.force, debug=self.app.debug,
+                theme_site=self.app.theme_site)
         ctx.enabled_processors = self.enabled_processors
         if self.additional_processors_factories is not None:
             ctx.additional_processors = [
--- a/piecrust/processing/worker.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/processing/worker.py	Tue Mar 01 22:27:28 2016 -0800
@@ -23,13 +23,14 @@
 
 
 class ProcessingWorkerContext(object):
-    def __init__(self, root_dir, out_dir, tmp_dir,
-                 force=False, debug=False):
+    def __init__(self, root_dir, out_dir, tmp_dir, *,
+                 force=False, debug=False, theme_site=False):
         self.root_dir = root_dir
         self.out_dir = out_dir
         self.tmp_dir = tmp_dir
         self.force = force
         self.debug = debug
+        self.theme_site = theme_site
         self.is_profiling = False
         self.enabled_processors = None
         self.additional_processors = None
@@ -59,7 +60,8 @@
 
     def initialize(self):
         # Create the app local to this worker.
-        app = PieCrust(self.ctx.root_dir, debug=self.ctx.debug)
+        app = PieCrust(self.ctx.root_dir, debug=self.ctx.debug,
+                       theme_site=self.ctx.theme_site)
         app.env.registerTimer("PipelineWorker_%d_Total" % self.wid)
         app.env.registerTimer("PipelineWorkerInit")
         app.env.registerTimer("JobReceive")
--- a/piecrust/serving/middlewares.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/serving/middlewares.py	Tue Mar 01 22:27:28 2016 -0800
@@ -40,11 +40,12 @@
 class PieCrustDebugMiddleware(object):
     """ WSGI middleware that handles debugging of PieCrust stuff.
     """
-    def __init__(self, app, root_dir, debug=False,
+    def __init__(self, app, root_dir, debug=False, theme_site=False,
                  sub_cache_dir=None, run_sse_check=None):
         self.app = app
         self.root_dir = root_dir
         self.debug = debug
+        self.theme_site = theme_site
         self.sub_cache_dir = sub_cache_dir
         self.run_sse_check = run_sse_check
         self._proc_loop = None
@@ -63,6 +64,7 @@
             # time so we let the implementation tell us if this is OK.
             from piecrust.serving.procloop import ProcessingLoop
             self._proc_loop = ProcessingLoop(root_dir, self._out_dir,
+                                             theme_site=theme_site,
                                              sub_cache_dir=sub_cache_dir,
                                              debug=debug)
             self._proc_loop.start()
--- a/piecrust/serving/procloop.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/serving/procloop.py	Tue Mar 01 22:27:28 2016 -0800
@@ -6,6 +6,7 @@
 import logging
 import itertools
 import threading
+from piecrust import CONFIG_PATH, THEME_CONFIG_PATH
 from piecrust.app import PieCrust
 from piecrust.processing.pipeline import ProcessorPipeline
 
@@ -74,25 +75,30 @@
 
 
 class ProcessingLoop(threading.Thread):
-    def __init__(self, root_dir, out_dir, sub_cache_dir=None, debug=False):
+    def __init__(self, root_dir, out_dir, sub_cache_dir=None,
+                 theme_site=False, debug=False):
         super(ProcessingLoop, self).__init__(
                 name='pipeline-reloader', daemon=True)
         self.root_dir = root_dir
         self.out_dir = out_dir
         self.sub_cache_dir = sub_cache_dir
         self.debug = debug
+        self.theme_site = theme_site
         self.last_status_id = 0
         self.interval = 1
         self.app = None
         self._roots = []
         self._monitor_assets_root = False
         self._paths = set()
-        self._config_path = os.path.join(root_dir, 'config.yml')
         self._record = None
         self._last_bake = 0
         self._last_config_mtime = 0
         self._obs = []
         self._obs_lock = threading.Lock()
+        if theme_site:
+            self._config_path = os.path.join(root_dir, THEME_CONFIG_PATH)
+        else:
+            self._config_path = os.path.join(root_dir, CONFIG_PATH)
 
     def addObserver(self, obs):
         with self._obs_lock:
@@ -156,7 +162,8 @@
 
     def _initPipeline(self):
         # Create the app and pipeline.
-        self.app = PieCrust(root_dir=self.root_dir, debug=self.debug)
+        self.app = PieCrust(root_dir=self.root_dir, debug=self.debug,
+                            theme_site=self.theme_site)
         if self.sub_cache_dir:
             self.app._useSubCacheDir(self.sub_cache_dir)
         self.pipeline = ProcessorPipeline(self.app, self.out_dir)
--- a/piecrust/serving/server.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/serving/server.py	Tue Mar 01 22:27:28 2016 -0800
@@ -71,10 +71,12 @@
 
 class Server(object):
     def __init__(self, root_dir,
-                 debug=False, sub_cache_dir=None, enable_debug_info=True,
+                 debug=False, theme_site=False,
+                 sub_cache_dir=None, enable_debug_info=True,
                  root_url='/', static_preview=True):
         self.root_dir = root_dir
         self.debug = debug
+        self.theme_site = theme_site
         self.sub_cache_dir = sub_cache_dir
         self.enable_debug_info = enable_debug_info
         self.root_url = root_url
@@ -110,6 +112,7 @@
 
         # Create the app for this request.
         app = get_app_for_server(self.root_dir, debug=self.debug,
+                                 theme_site=self.theme_site,
                                  sub_cache_dir=self.sub_cache_dir,
                                  root_url=self.root_url)
         if (app.config.get('site/enable_debug_info') and
@@ -302,9 +305,9 @@
         desc = []
         while exception is not None:
             if isinstance(exception, MultipleNotFound):
-                desc += [e.description for e in exception._nfes]
+                desc += [str(e) for e in exception._nfes]
             elif isinstance(exception, HTTPException):
-                desc.append(exception.description)
+                desc.append(exception.get_description())
             else:
                 desc.append(str(exception))
 
--- a/piecrust/serving/util.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/serving/util.py	Tue Mar 01 22:27:28 2016 -0800
@@ -16,9 +16,9 @@
 logger = logging.getLogger(__name__)
 
 
-def get_app_for_server(root_dir, debug=False, sub_cache_dir=None,
-                       root_url='/'):
-    app = PieCrust(root_dir=root_dir, debug=debug)
+def get_app_for_server(root_dir, debug=False, theme_site=False,
+                       sub_cache_dir=None, root_url='/'):
+    app = PieCrust(root_dir=root_dir, debug=debug, theme_site=theme_site)
     if sub_cache_dir:
         app._useSubCacheDir(sub_cache_dir)
     app.config.set('site/root', root_url)
--- a/piecrust/serving/wrappers.py	Tue Mar 01 22:27:04 2016 -0800
+++ b/piecrust/serving/wrappers.py	Tue Mar 01 22:27:28 2016 -0800
@@ -9,7 +9,8 @@
 
 
 def run_werkzeug_server(root_dir, host, port,
-                        debug_piecrust=False, sub_cache_dir=None,
+                        debug_piecrust=False, theme_site=False,
+                        sub_cache_dir=None,
                         use_debugger=False, use_reloader=False):
     from werkzeug.serving import run_simple
 
@@ -25,6 +26,7 @@
 
     app = _get_piecrust_server(root_dir,
                                debug=debug_piecrust,
+                               theme_site=theme_site,
                                sub_cache_dir=sub_cache_dir,
                                run_sse_check=_run_sse_check)
 
@@ -78,7 +80,8 @@
 
 
 def run_gunicorn_server(root_dir,
-                        debug_piecrust=False, sub_cache_dir=None,
+                        debug_piecrust=False, theme_site=False,
+                        sub_cache_dir=None,
                         gunicorn_options=None):
     from gunicorn.app.base import BaseApplication
 
@@ -98,6 +101,7 @@
 
     app = _get_piecrust_server(root_dir,
                                debug=debug_piecrust,
+                               theme_site=theme_site,
                                sub_cache_dir=sub_cache_dir)
 
     gunicorn_options = gunicorn_options or {}
@@ -105,14 +109,17 @@
     app_wrapper.run()
 
 
-def _get_piecrust_server(root_dir, debug=False, sub_cache_dir=None,
-                         run_sse_check=None):
+def _get_piecrust_server(
+        root_dir, debug=False, theme_site=False,
+        sub_cache_dir=None, run_sse_check=None):
     from piecrust.serving.middlewares import (
             StaticResourcesMiddleware, PieCrustDebugMiddleware)
     from piecrust.serving.server import WsgiServer
-    app = WsgiServer(root_dir, debug=debug, sub_cache_dir=sub_cache_dir)
+    app = WsgiServer(root_dir, debug=debug, theme_site=theme_site,
+                     sub_cache_dir=sub_cache_dir)
     app = StaticResourcesMiddleware(app)
     app = PieCrustDebugMiddleware(app, root_dir,
+                                  theme_site=theme_site,
                                   sub_cache_dir=sub_cache_dir,
                                   run_sse_check=run_sse_check)
     return app