view piecrust/data/assetor.py @ 989:8adc27285d93

bake: Big pass on bake performance. - Reduce the amount of data passed between processes. - Make inter-process data simple objects to make it easier to test with alternatives to pickle. - Make sources have the basic requirement to be able to find a content item from an item spec (path). - Make Hoedown the default Markdown formatter.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 19 Nov 2017 14:29:17 -0800
parents a0a62d0da723
children b2a34a6ec5e5
line wrap: on
line source

import os
import os.path
import logging
from piecrust.sources.base import REL_ASSETS
from piecrust.uriutil import multi_replace


logger = logging.getLogger(__name__)


class UnsupportedAssetsError(Exception):
    pass


class _AssetInfo:
    def __init__(self, content_item, uri):
        self.content_item = content_item
        self.uri = uri


class Assetor:
    debug_render_doc = """Helps render URLs to files in the current page's
                          asset folder."""
    debug_render = []
    debug_render_dynamic = ['_debugRenderAssetNames']

    def __init__(self, page):
        self._page = page
        self._cache_map = None
        self._cache_list = None

    def __getattr__(self, name):
        try:
            self._cacheAssets()
            return self._cache_map[name].uri
        except KeyError:
            raise AttributeError()

    def __getitem__(self, name):
        self._cacheAssets()
        return self._cache_map[name].uri

    def __contains__(self, name):
        self._cacheAssets()
        return name in self._cache_map

    def __iter__(self):
        self._cacheAssets()
        return iter(self._cache_list)

    def __len__(self):
        self._cacheAssets()
        return len(self._cache_map)

    def _getAssetNames(self):
        self._cacheAssets()
        return self._cache_map.keys()

    def _getAssetItems(self):
        self._cacheAssets()
        return map(lambda i: i.content_item, self._cache_map.values())

    def _debugRenderAssetNames(self):
        self._cacheAssets()
        return list(self._cache_map.keys())

    def _cacheAssets(self):
        if self._cache_map is not None:
            return

        source = self._page.source
        content_item = self._page.content_item
        assets = source.getRelatedContents(content_item, REL_ASSETS)

        self._cache_map = {}
        self._cache_list = []

        if assets is None:
            return

        app = source.app
        root_dir = app.root_dir
        asset_url_format = app.config.get('site/asset_url_format')
        if not asset_url_format:
            raise Exception("No asset URL format was specified.")

        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_map:
                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_map[name] = _AssetInfo(a, uri)
            self._cache_list.append(uri)

        stack = app.env.render_ctx_stack
        cur_ctx = stack.current_ctx
        if cur_ctx is not None:
            cur_ctx.render_info['used_assets'] = True