changeset 12:30a42341cfa8

Define page slugs properly, avoid recursions with debug data.
author Ludovic Chabant <ludovic@chabant.com>
date Mon, 18 Aug 2014 16:49:54 -0700
parents 617191dec18e
children a8f9c78a6608
files piecrust/data/base.py piecrust/data/builder.py piecrust/environment.py piecrust/rendering.py piecrust/uriutil.py
diffstat 5 files changed, 98 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/piecrust/data/base.py	Mon Aug 18 16:47:44 2014 -0700
+++ b/piecrust/data/base.py	Mon Aug 18 16:49:54 2014 -0700
@@ -1,12 +1,16 @@
 import time
 import logging
 from piecrust.data.assetor import Assetor
+from piecrust.uriutil import get_slug
 
 
 logger = logging.getLogger(__name__)
 
 
 class IPaginationSource(object):
+    """ Defines the interface for a source that can be used as the data
+        for an iterator or a pagination.
+    """
     def getItemsPerPage(self):
         raise NotImplementedError()
 
@@ -28,6 +32,9 @@
         but also allows for additional data. It's meant to be exposed
         to the templating system.
     """
+    debug_render = []
+    debug_render_dynamic = ['_debugRenderKeys']
+
     def __init__(self, page):
         self._page = page
         self._values = None
@@ -37,7 +44,16 @@
     def page(self):
         return self._page
 
+    def __getattr__(self, name):
+        try:
+            return self.getValue(name)
+        except KeyError:
+            raise AttributeError
+
     def __getitem__(self, name):
+        return self.getValue(name)
+
+    def getValue(self, name):
         self._load()
 
         if self._loaders:
@@ -46,10 +62,8 @@
                 try:
                     self._values[name] = loader(self, name)
                 except Exception as ex:
-                    logger.error("Error while loading attribute '%s' for: %s"
-                            % (name, self._page.path))
-                    logger.exception(ex)
-                    raise Exception("Internal Error: %s" % ex)
+                    raise Exception("Error while loading attribute '%s' for: %s"
+                            % (name, self._page.rel_path)) from ex
 
                 # We need to double-check `_loaders` here because
                 # the loader could have removed all loaders, which
@@ -87,13 +101,18 @@
         try:
             self._loadCustom()
         except Exception as ex:
-            logger.error("Error while loading data for: %s" % self._page.path)
-            logger.exception(ex)
-            raise Exception("Internal Error: %s" % ex)
+            raise Exception("Error while loading data for: %s" % self._page.rel_path) from ex
 
     def _loadCustom(self):
         pass
 
+    def _debugRenderKeys(self):
+        self._load()
+        keys = set(self._values.keys())
+        if self._loaders:
+            keys |= set(self._loaders.keys())
+        return list(keys)
+
 
 class PaginationData(LazyPageConfigData):
     def __init__(self, page):
@@ -109,7 +128,7 @@
     def _loadCustom(self):
         page_url = self._get_uri()
         self.setValue('url', page_url)
-        self.setValue('slug', page_url)
+        self.setValue('slug', get_slug(self._page.app, page_url))
         self.setValue('timestamp',
                 time.mktime(self.page.datetime.timetuple()))
         date_format = self.page.app.config.get('site/date_format')
@@ -124,16 +143,29 @@
             self.mapLoader(name, self._load_rendered_segment)
 
     def _load_rendered_segment(self, data, name):
-        from piecrust.rendering import PageRenderingContext, render_page_segments
+        do_render = True
+        eis = self._page.app.env.exec_info_stack
+        if eis is not None and eis.hasPage(self._page):
+            # This is the pagination data for the page that is currently
+            # being rendered! Inception! But this is possible... so just
+            # prevent infinite recursion.
+            do_render = False
 
         assert self is data
-        uri = self._get_uri()
-        try:
-            ctx = PageRenderingContext(self._page, uri)
-            segs = render_page_segments(ctx)
-        except Exception as e:
-            logger.exception("Error rendering segments for '%s': %s" % (uri, e))
-            raise
+
+        if do_render:
+            uri = self._get_uri()
+            try:
+                from piecrust.rendering import (PageRenderingContext,
+                        render_page_segments)
+                ctx = PageRenderingContext(self._page, uri)
+                segs = render_page_segments(ctx)
+            except Exception as e:
+                raise Exception("Error rendering segments for '%s'" % uri) from e
+        else:
+            segs = {}
+            for name in self.page.config.get('segments'):
+                segs[name] = "<unavailable: current page>"
 
         for k, v in segs.items():
             self.mapLoader(k, None)
--- a/piecrust/data/builder.py	Mon Aug 18 16:47:44 2014 -0700
+++ b/piecrust/data/builder.py	Mon Aug 18 16:49:54 2014 -0700
@@ -5,6 +5,7 @@
 from piecrust.data.debug import build_debug_info
 from piecrust.data.linker import Linker
 from piecrust.data.paginator import Paginator
+from piecrust.uriutil import get_slug
 
 
 logger = logging.getLogger(__name__)
@@ -18,18 +19,23 @@
         self.pagination_source = None
         self.pagination_filter = None
 
+    @property
+    def slug(self):
+        return get_slug(self.page.app, self.uri)
+
 
 def build_page_data(ctx):
     page = ctx.page
     app = page.app
 
+    pc_data = PieCrustData()
     pgn_source = ctx.pagination_source or get_default_pagination_source(page)
     paginator = Paginator(page, pgn_source, ctx.uri, ctx.page_num,
             ctx.pagination_filter)
     assetor = Assetor(page, ctx.uri)
     linker = Linker(page)
     data = {
-            'piecrust': build_piecrust_data(),
+            'piecrust': pc_data,
             'page': dict(page.config.get()),
             'assets': assetor,
             'pagination': paginator,
@@ -38,6 +44,7 @@
             }
     page_data = data['page']
     page_data['url'] = ctx.uri
+    page_data['slug'] = ctx.slug
     page_data['timestamp'] = time.mktime(page.datetime.timetuple())
     date_format = app.config.get('site/date_format')
     if date_format:
@@ -52,7 +59,7 @@
     # displayed in the debugger window.
     if (app.debug and app.config.get('site/enable_debug_info') and
             not app.config.get('baker/is_baking')):
-        data['piecrust']['debug_info'] = build_debug_info(page, data)
+        pc_data._enableDebugInfo(page, data)
 
     return data
 
@@ -73,14 +80,31 @@
     from piecrust import APP_VERSION as VERSION
 
 
-def build_piecrust_data():
-    data = {
-            'version': VERSION,
-            'url': 'http://bolt80.com/piecrust/',
-            'branding': 'Baked with <em><a href="%s">PieCrust</a> %s</em>.' % (
+class PieCrustData(object):
+    debug_render = ['version', 'url', 'branding', 'debug_info']
+    debug_render_invoke = ['version', 'url', 'branding', 'debug_info']
+    debug_render_redirect = {'debug_info': '_debugRenderDebugInfo'}
+
+    def __init__(self):
+        self.version = VERSION
+        self.url = 'http://bolt80.com/piecrust/'
+        self.branding = 'Baked with <em><a href="%s">PieCrust</a> %s</em>.' % (
                 'http://bolt80.com/piecrust/', VERSION)
-            }
-    return data
+        self._page = None
+        self._data = None
+
+    @property
+    def debug_info(self):
+        if self._page is not None and self._data is not None:
+            return build_debug_info(self._page, self._data)
+        return None
+
+    def _enableDebugInfo(self, page, data):
+        self._page = page
+        self._data = data
+
+    def _debugRenderDebugInfo(self):
+        return "The very thing you're looking at!"
 
 
 def build_site_data(page):
--- a/piecrust/environment.py	Mon Aug 18 16:47:44 2014 -0700
+++ b/piecrust/environment.py	Mon Aug 18 16:49:54 2014 -0700
@@ -53,6 +53,12 @@
     def is_main_page(self):
         return len(self._page_stack) == 1
 
+    def hasPage(self, page):
+        for ei in self._page_stack:
+            if ei.page == page:
+                return True
+        return False
+
     def pushPage(self, page, phase, render_ctx):
         self._page_stack.append(ExecutionInfo(page, phase, render_ctx))
 
--- a/piecrust/rendering.py	Mon Aug 18 16:47:44 2014 -0700
+++ b/piecrust/rendering.py	Mon Aug 18 16:49:54 2014 -0700
@@ -4,6 +4,7 @@
 from piecrust.data.builder import (DataBuildingContext, build_page_data,
         build_layout_data)
 from piecrust.environment import PHASE_PAGE_FORMATTING, PHASE_PAGE_RENDERING
+from piecrust.uriutil import get_slug
 
 
 logger = logging.getLogger(__name__)
@@ -49,6 +50,10 @@
         return self.page.app
 
     @property
+    def slug(self):
+        return get_slug(self.page.app, self.uri)
+
+    @property
     def source_metadata(self):
         return self.page.source_metadata
 
--- a/piecrust/uriutil.py	Mon Aug 18 16:47:44 2014 -0700
+++ b/piecrust/uriutil.py	Mon Aug 18 16:49:54 2014 -0700
@@ -72,3 +72,9 @@
     pattern = re.compile("|".join(list(reps.keys())))
     return pattern.sub(lambda m: reps[re.escape(m.group(0))], text)
 
+
+def get_slug(app, uri):
+    site_root = app.config.get('site/root')
+    uri = uri[len(site_root):]
+    return uri.lstrip('/')
+