changeset 862:fddaf43424e2

refactor: Get the page assets to work again in the server.
author Ludovic Chabant <ludovic@chabant.com>
date Thu, 08 Jun 2017 23:09:34 -0700
parents d214918d4d2c
children 01458d3b646b
files piecrust/data/assetor.py piecrust/data/linker.py piecrust/serving/server.py piecrust/serving/util.py piecrust/sources/base.py piecrust/sources/default.py piecrust/sources/fs.py piecrust/sources/mixins.py piecrust/sources/posts.py
diffstat 9 files changed, 104 insertions(+), 84 deletions(-) [+]
line wrap: on
line diff
--- a/piecrust/data/assetor.py	Thu Jun 08 08:52:45 2017 -0700
+++ b/piecrust/data/assetor.py	Thu Jun 08 23:09:34 2017 -0700
@@ -2,6 +2,7 @@
 import os.path
 import shutil
 import logging
+import collections.abc
 from piecrust import ASSET_DIR_SUFFIX
 from piecrust.sources.base import REL_ASSETS
 from piecrust.uriutil import multi_replace
@@ -14,26 +15,13 @@
     pass
 
 
-def build_base_url(app, uri, rel_assets_path):
-    base_url_format = app.config.get('site/base_asset_url_format')
-    rel_assets_path = rel_assets_path.replace('\\', '/')
-
-    # Remove any extension since we'll be copying assets into the 1st
-    # sub-page's folder.
-    pretty = app.config.get('site/pretty_urls')
-    if not pretty:
-        uri, _ = os.path.splitext(uri)
-
-    base_url = multi_replace(
-        base_url_format,
-        {
-            '%path%': rel_assets_path,
-            '%uri%': uri})
-
-    return base_url.rstrip('/') + '/'
+class _AssetInfo:
+    def __init__(self, content_item, uri):
+        self.content_item = content_item
+        self.uri = uri
 
 
-class Assetor:
+class Assetor(collections.abc.Mapping):
     debug_render_doc = """Helps render URLs to files in the current page's
                           asset folder."""
     debug_render = []
@@ -46,21 +34,21 @@
     def __getattr__(self, name):
         try:
             self._cacheAssets()
-            return self._cache[name][0]
+            return self._cache[name].uri
         except KeyError:
             raise AttributeError()
 
     def __getitem__(self, key):
         self._cacheAssets()
-        return self._cache[key][0]
+        return self._cache[key].uri
 
     def __iter__(self):
         self._cacheAssets()
-        return map(lambda i: i[0], self._cache.values())
+        return self._cache.keys()
 
-    def allNames(self):
+    def __len__(self):
         self._cacheAssets()
-        return list(self._cache.keys())
+        return len(self._cache)
 
     def _debugRenderAssetNames(self):
         self._cacheAssets()
@@ -70,25 +58,52 @@
         if self._cache is not None:
             return
 
-        self._cache = self.findAssets() or {}
-
-    def findAssets(self):
+        source = self._page.source
         content_item = self._page.content_item
-        source = content_item.source
-        assets = source.getRelatedContent(content_item, REL_ASSETS)
+
+        assets = source.getRelatedContents(content_item, REL_ASSETS)
         if assets is None:
-            return {}
+            self._cache = {}
+            return
+
+        self._cache = {}
 
         app = source.app
+        root_dir = app.root_dir
+        asset_url_format = app.config.get('site/asset_url_format')
+
+        page_uri = self._page.getUri()
+        pretty_urls = app.config.get('site/pretty_urls')
+        if not pretty_urls:
+            page_uri, _ = os.path.splitext(page_uri)
+
+        uri_build_tokens = {
+            '%path%': None,
+            '%filename%': None,
+            '%page_uri%': page_uri
+        }
+
+        for a in assets:
+            name = a.metadata['name']
+            if name in self._cache:
+                raise UnsupportedAssetsError(
+                    "An asset with name '%s' already exists for item '%s'. "
+                    "Do you have multiple assets with colliding names?" %
+                    (name, content_item.spec))
+
+            # TODO: this assumes a file-system source!
+            uri_build_tokens['%path%'] = (
+                os.path.relpath(a.spec, root_dir).replace('\\', '/'))
+            uri_build_tokens['%filename%'] = a.metadata['filename'],
+            uri = multi_replace(asset_url_format, uri_build_tokens)
+
+            self._cache[name] = _AssetInfo(a, uri)
+
         stack = app.env.render_ctx_stack
         cur_ctx = stack.current_ctx
         if cur_ctx is not None:
             cur_ctx.current_pass_info.used_assets = True
 
-        # base_url = build_base_url(app, self._uri, rel_assets_dir)
-
-        return assets
-
     def copyAssets(self, dest_dir):
         page_pathname, _ = os.path.splitext(self._page.path)
         in_assets_dir = page_pathname + ASSET_DIR_SUFFIX
--- a/piecrust/data/linker.py	Thu Jun 08 08:52:45 2017 -0700
+++ b/piecrust/data/linker.py	Thu Jun 08 23:09:34 2017 -0700
@@ -1,7 +1,7 @@
 import logging
 from piecrust.data.paginationdata import PaginationData
 from piecrust.sources.base import (
-    REL_PARENT_GROUP, REL_LOGICAL_PARENT_ITEM, REL_LOGICAl_CHILD_GROUP)
+    REL_LOGICAL_PARENT_ITEM, REL_LOGICAl_CHILD_GROUP)
 
 
 logger = logging.getLogger(__name__)
@@ -67,8 +67,7 @@
     def siblings(self):
         if self._siblings is None:
             self._siblings = []
-            parent_group = self._source.getRelatedContents(
-                self._content_item, REL_PARENT_GROUP)
+            parent_group = self._source.getParentGroup(self._content_item)
             for i in self._source.getContents(parent_group):
                 if not i.is_group:
                     ipage = self._app.getPage(self._source, i)
--- a/piecrust/serving/server.py	Thu Jun 08 08:52:45 2017 -0700
+++ b/piecrust/serving/server.py	Thu Jun 08 23:09:34 2017 -0700
@@ -107,10 +107,6 @@
                 '!debug' in request.args):
             app.config.set('site/show_debug_info', True)
 
-        # We'll serve page assets directly from where they are.
-        app.config.set('site/asset_url_format',
-                       self.root_url + '_asset/%path%')
-
         # Let's try to serve a page.
         try:
             response = self._try_serve_page(app, environ, request)
--- a/piecrust/serving/util.py	Thu Jun 08 08:52:45 2017 -0700
+++ b/piecrust/serving/util.py	Thu Jun 08 23:09:34 2017 -0700
@@ -17,6 +17,8 @@
     app = appfactory.create()
     app.config.set('site/root', root_url)
     app.config.set('server/is_serving', True)
+    # We'll serve page assets directly from where they are.
+    app.config.set('site/asset_url_format', root_url + '_asset/%path%')
     return app
 
 
--- a/piecrust/sources/base.py	Thu Jun 08 08:52:45 2017 -0700
+++ b/piecrust/sources/base.py	Thu Jun 08 23:09:34 2017 -0700
@@ -13,9 +13,8 @@
 
 
 # Types of relationships a content source can be asked for.
-REL_PARENT_GROUP = 1
-REL_LOGICAL_PARENT_ITEM = 2
-REL_LOGICAl_CHILD_GROUP = 3
+REL_LOGICAL_PARENT_ITEM = 1
+REL_LOGICAl_CHILD_GROUP = 2
 REL_ASSETS = 10
 
 
@@ -119,6 +118,9 @@
         raise NotImplementedError("'%s' doesn't implement 'getContents'." %
                                   self.__class__)
 
+    def getParentGroup(self, item):
+        raise NotImplementedError()
+
     def getRelatedContents(self, item, relationship):
         raise NotImplementedError()
 
--- a/piecrust/sources/default.py	Thu Jun 08 08:52:45 2017 -0700
+++ b/piecrust/sources/default.py	Thu Jun 08 23:09:34 2017 -0700
@@ -31,8 +31,7 @@
         return self._doCreateItemMetadata(path)
 
     def _finalizeContent(self, parent_group, items, groups):
-        SimpleAssetsSubDirMixin._onFinalizeContent(
-            self, parent_group, items, groups)
+        SimpleAssetsSubDirMixin._removeAssetGroups(groups)
 
     def _doCreateItemMetadata(self, path):
         slug = self._makeSlug(path)
@@ -62,8 +61,9 @@
 
     def getRelatedContents(self, item, relationship):
         if relationship == REL_ASSETS:
-            SimpleAssetsSubDirMixin._getRelatedAssetsContents(self, item)
-        raise NotImplementedError()
+            return SimpleAssetsSubDirMixin._getRelatedAssetsContents(
+                self, item)
+        return FSContentSource.getRelatedContents(self, item, relationship)
 
     def getSupportedRouteParameters(self):
         return [
--- a/piecrust/sources/fs.py	Thu Jun 08 08:52:45 2017 -0700
+++ b/piecrust/sources/fs.py	Thu Jun 08 23:09:34 2017 -0700
@@ -7,7 +7,7 @@
 from piecrust.routing import RouteParameter
 from piecrust.sources.base import (
     ContentItem, ContentGroup, ContentSource,
-    REL_PARENT_GROUP, REL_LOGICAL_PARENT_ITEM, REL_LOGICAl_CHILD_GROUP)
+    REL_LOGICAL_PARENT_ITEM, REL_LOGICAl_CHILD_GROUP)
 
 
 logger = logging.getLogger(__name__)
@@ -110,6 +110,16 @@
         self._finalizeContent(group, items, groups)
         return items + groups
 
+    def getParentGroup(self, item):
+        parent_dir = os.path.dirname(item.spec)
+        if len(parent_dir) >= len(self.fs_endpoint_path):
+            metadata = self._createGroupMetadata(parent_dir)
+            return ContentGroup(parent_dir, metadata)
+
+        # Don't return a group for paths that are outside of our
+        # endpoint directory.
+        return None
+
     def _filterIgnored(self, path):
         rel_path = os.path.relpath(path, self.fs_endpoint_path)
         for g in self._ignore_globs:
@@ -130,16 +140,6 @@
         pass
 
     def getRelatedContents(self, item, relationship):
-        if relationship == REL_PARENT_GROUP:
-            parent_dir = os.path.dirname(item.spec)
-            if len(parent_dir) >= len(self.fs_endpoint_path):
-                metadata = self._createGroupMetadata(parent_dir)
-                return ContentGroup(parent_dir, metadata)
-
-            # Don't return a group for paths that are outside of our
-            # endpoint directory.
-            return None
-
         if relationship == REL_LOGICAL_PARENT_ITEM:
             # If we want the logical parent item of a folder, we find a
             # page file with the same name as the folder.
--- a/piecrust/sources/mixins.py	Thu Jun 08 08:52:45 2017 -0700
+++ b/piecrust/sources/mixins.py	Thu Jun 08 23:09:34 2017 -0700
@@ -10,31 +10,34 @@
 
 
 class SimpleAssetsSubDirMixin:
-    def _getRelatedAssetsContents(self, item, relationship):
-        if not item.metadata.get('__has_assets', False):
+    """ A content source mixin for sources that are file-system-based,
+        and have item assets stored in a sub-folder that is named after
+        the item.
+
+        More specifically, assets are stored in a sub-folder named:
+        `<item_path>-assets`
+    """
+    def _getRelatedAssetsContents(self, item):
+        spec_no_ext, _ = os.path.splitext(item.spec)
+        assets_dir = spec_no_ext + assets_suffix
+        try:
+            asset_files = osutil.listdir(assets_dir)
+        except OSError:
             return None
 
-        assets = {}
-        assets_dir = item.spec + assets_suffix
-        for f in osutil.listdir(assets_dir):
+        assets = []
+        for f in asset_files:
             fpath = os.path.join(assets_dir, f)
             name, _ = os.path.splitext(f)
-            if name in assets:
-                raise Exception("Multiple assets are named '%s'." %
-                                name)
-            assets[name] = ContentItem(fpath, {'__is_asset': True})
+            assets.append(ContentItem(
+                fpath,
+                {'name': name,
+                 'filename': f,
+                 '__is_asset': True}))
         return assets
 
-    def _onFinalizeContent(self, parent_group, items, groups):
-        assetsGroups = []
-        for g in groups:
-            if not g.spec.endswith(assets_suffix):
-                continue
-            match = g.spec[:-len(assets_suffix)]
-            item = next(filter(lambda i: i.spec == match), None)
-            if item:
-                item.metadata['__has_assets'] = True
-                assetsGroups.append(g)
-        for g in assetsGroups:
+    def _removeAssetGroups(self, groups):
+        asset_groups = [g for g in groups
+                        if g.spec.endswith(assets_suffix)]
+        for g in asset_groups:
             groups.remove(g)
-
--- a/piecrust/sources/posts.py	Thu Jun 08 08:52:45 2017 -0700
+++ b/piecrust/sources/posts.py	Thu Jun 08 23:09:34 2017 -0700
@@ -36,14 +36,17 @@
     def path_format(self):
         return self.__class__.PATH_FORMAT
 
-    def _finalizeContent(self, parent_group, items, groups):
-        SimpleAssetsSubDirMixin._onFinalizeContent(
-            parent_group, items, groups)
+    def _finalizeContent(self, groups):
+        SimpleAssetsSubDirMixin._removeAssetGroups(groups)
+
+    def getParentGroup(self, item):
+        return None
 
     def getRelatedContents(self, item, relationship):
         if relationship == REL_ASSETS:
-            SimpleAssetsSubDirMixin._getRelatedAssetsContents(item)
-        raise NotImplementedError()
+            return SimpleAssetsSubDirMixin._getRelatedAssetsContents(
+                self, item)
+        return FSContentSource.getRelatedContents(self, item, relationship)
 
     def findContent(self, route_params):
         year = route_params.get('year')