diff piecrust/data/provider.py @ 3:f485ba500df3

Gigantic change to basically make PieCrust 2 vaguely functional. - Serving works, with debug window. - Baking works, multi-threading, with dependency handling. - Various things not implemented yet.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 10 Aug 2014 23:43:16 -0700
parents
children 474c9882decf
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/piecrust/data/provider.py	Sun Aug 10 23:43:16 2014 -0700
@@ -0,0 +1,249 @@
+import time
+import itertools
+from piecrust.data.iterators import PageIterator
+from piecrust.sources.base import ArraySource
+
+
+class DataProvider(object):
+    debug_render_dynamic = ['_debugRenderUserData']
+    debug_render_invoke_dynamic = ['_debugRenderUserData']
+
+    def __init__(self, source, page, user_data):
+        if source.app is not page.app:
+            raise Exception("The given source and page don't belong to "
+                            "the same application.")
+        self._source = source
+        self._page = page
+        self._user_data = user_data
+
+    def __getattr__(self, name):
+        if self._user_data is not None:
+            return self._user_data[name]
+        raise AttributeError()
+
+    def __getitem__(self, name):
+        if self._user_data is not None:
+            return self._user_data[name]
+        raise KeyError()
+
+    def _debugRenderUserData(self):
+        if self._user_data:
+            return self._user_data.keys()
+        return []
+
+
+class CompositeDataProvider(object):
+    def __init__(self, providers):
+        self._providers = providers
+
+    def __getattr__(self, name):
+        for p in self._providers:
+            try:
+                return getattr(p, name)
+            except AttributeError:
+                pass
+        raise AttributeError()
+
+
+class IteratorDataProvider(DataProvider):
+    PROVIDER_NAME = 'iterator'
+
+    debug_render_doc = """Provides a list of pages."""
+
+    def __init__(self, source, page, user_data):
+        super(IteratorDataProvider, self).__init__(source, page, user_data)
+        self._pages = PageIterator(source, current_page=page)
+        self._pages._iter_event += self._onIteration
+        self._ctx_set = False
+
+    def __len__(self):
+        return len(self._pages)
+
+    def __getitem__(self, key):
+        return self._pages[key]
+
+    def __iter__(self):
+        return iter(self._pages)
+
+    def _onIteration(self):
+        if not self._ctx_set:
+            eis = self._page.app.env.exec_info_stack
+            eis.current_page_info.render_ctx.used_source_names.add(
+                    self._source.name)
+            self._ctx_set = True
+
+
+class BlogDataProvider(DataProvider):
+    PROVIDER_NAME = 'blog'
+
+    debug_render_doc = """Provides a list of blog posts and yearly/monthly
+                          archives."""
+    debug_render = ['posts', 'years', 'months']
+    debug_render_dynamic = (['_debugRenderTaxonomies'] +
+            DataProvider.debug_render_dynamic)
+
+    def __init__(self, source, page, user_data):
+        super(BlogDataProvider, self).__init__(source, page, user_data)
+        self._yearly = None
+        self._monthly = None
+        self._taxonomies = {}
+        self._ctx_set = False
+
+    def __getattr__(self, name):
+        if self._source.app.getTaxonomy(name) is not None:
+            return self._buildTaxonomy(name)
+        return super(BlogDataProvider, self).__getattr__(name)
+
+    @property
+    def posts(self):
+        it = PageIterator(self._source, current_page=self._page)
+        it._iter_event += self._onIteration
+        return it
+
+    @property
+    def years(self):
+        return self._buildYearlyArchive()
+
+    @property
+    def months(self):
+        return self._buildMonthlyArchive()
+
+    def _debugRenderTaxonomies(self):
+        return [t.name for t in self._source.app.taxonomies]
+
+    def _buildYearlyArchive(self):
+        if self._yearly is not None:
+            return self._yearly
+
+        self._yearly = []
+        for fac in self._source.getPageFactories():
+            post = fac.buildPage()
+            year = post.datetime.strftime('%Y')
+
+            posts_this_year = next(
+                    itertools.ifilter(lambda y: y.name == year, self._yearly),
+                    None)
+            if posts_this_year is None:
+                timestamp = time.mktime(
+                        (post.datetime.year, 1, 1, 0, 0, 0, 0, 0, -1))
+                posts_this_year = BlogArchiveEntry(self._page, year, timestamp)
+                self._yearly.append(posts_this_year)
+
+            posts_this_year._data_source.append(post)
+        self._yearly = sorted(self._yearly,
+                key=lambda e: e.timestamp,
+                reverse=True)
+        self._onIteration()
+        return self._yearly
+
+    def _buildMonthlyArchive(self):
+        if self._monthly is not None:
+            return self._monthly
+
+        self._monthly = []
+        for fac in self._source.getPageFactories():
+            post = fac.buildPage()
+            month = post.datetime.strftime('%B %Y')
+
+            posts_this_month = next(
+                    itertools.ifilter(lambda m: m.name == month, self._monthly),
+                    None)
+            if posts_this_month is None:
+                timestamp = time.mktime(
+                        (post.datetime.year, post.datetime.month, 1,
+                            0, 0, 0, 0, 0, -1))
+                posts_this_month = BlogArchiveEntry(self._page, month, timestamp)
+                self._monthly.append(posts_this_month)
+
+            posts_this_month._data_source.append(post)
+        self._monthly = sorted(self._monthly,
+                key=lambda e: e.timestamp,
+                reverse=True)
+        self._onIteration()
+        return self._monthly
+
+    def _buildTaxonomy(self, tax_name):
+        if tax_name in self._taxonomies:
+            return self._taxonomies[tax_name]
+
+        posts_by_tax_value = {}
+        for fac in self._source.getPageFactories():
+            post = fac.buildPage()
+            tax_values = post.config.get(tax_name)
+            if not isinstance(tax_values, list):
+                tax_values = [tax_values]
+            for val in tax_values:
+                posts_by_tax_value.setdefault(val, [])
+                posts_by_tax_value[val].append(post)
+
+        entries = []
+        for value, ds in posts_by_tax_value.iteritems():
+            source = ArraySource(self._page.app, ds)
+            entries.append(BlogTaxonomyEntry(self._page, source, value))
+        self._taxonomies[tax_name] = sorted(entries, key=lambda k: k.name)
+
+        self._onIteration()
+        return self._taxonomies[tax_name]
+
+    def _onIteration(self):
+        if not self._ctx_set:
+            eis = self._page.app.env.exec_info_stack
+            eis.current_page_info.render_ctx.used_source_names.add(
+                    self._source.name)
+            self._ctx_set = True
+
+
+class BlogArchiveEntry(object):
+    def __init__(self, page, name, timestamp):
+        self.name = name
+        self.timestamp = timestamp
+        self._page = page
+        self._data_source = []
+        self._iterator = None
+
+    def __str__(self):
+        return self.name
+
+    @property
+    def posts(self):
+        self._load()
+        self._iterator.reset()
+        return self._iterator
+
+    def _load(self):
+        if self._iterator is not None:
+            return
+        source = ArraySource(self._page.app, self._data_source)
+        self._iterator = PageIterator(source, current_page=self._page)
+
+
+class BlogTaxonomyEntry(object):
+    def __init__(self, page, source, property_value):
+        self._page = page
+        self._source = source
+        self._property_value = property_value
+        self._iterator = None
+
+    def __str__(self):
+        return self._property_value
+
+    @property
+    def name(self):
+        return self._property_value
+
+    @property
+    def posts(self):
+        self._load()
+        self._iterator.reset()
+        return self._iterator
+
+    @property
+    def post_count(self):
+        return self._source.page_count
+
+    def _load(self):
+        if self._iterator is not None:
+            return
+
+        self._iterator = PageIterator(self._source, self._page)
+