diff piecrust/data/iterators.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 f5ca5c5bed85
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/piecrust/data/iterators.py	Sun Aug 10 23:43:16 2014 -0700
@@ -0,0 +1,291 @@
+import logging
+from piecrust.data.base import PaginationData
+from piecrust.data.filters import PaginationFilter
+from piecrust.events import Event
+
+
+logger = logging.getLogger(__name__)
+
+
+class SliceIterator(object):
+    def __init__(self, it, offset=0, limit=-1):
+        self.it = it
+        self.offset = offset
+        self.limit = limit
+        self.current_page = None
+        self.has_more = False
+        self.inner_count = -1
+        self.next_page = None
+        self.prev_page = None
+        self._cache = None
+
+    def __iter__(self):
+        if self._cache is None:
+            inner_list = list(self.it)
+            self.inner_count = len(inner_list)
+            self.has_more = self.inner_count > (self.offset + self.limit)
+            self._cache = inner_list[self.offset:self.offset + self.limit]
+            if self.current_page:
+                idx = inner_list.index(self.current_page)
+                if idx >= 0:
+                    if idx < self.inner_count - 1:
+                        self.next_page = inner_list[idx + 1]
+                    if idx > 0:
+                        self.prev_page = inner_list[idx - 1]
+        return iter(self._cache)
+
+
+class SettingFilterIterator(object):
+    def __init__(self, it, fil_conf, page_accessor=None):
+        self.it = it
+        self.fil_conf = fil_conf
+        self._fil = None
+        self.page_accessor = page_accessor
+
+    def __iter__(self):
+        if self._fil is None:
+            self._fil = PaginationFilter()
+            self._fil.addClausesFromConfig(self.fil_conf)
+
+        for i in self.it:
+            if self.page_accessor:
+                page = self.page_accessor(i)
+            else:
+                page = i
+            if self._fil.pageMatches(page):
+                yield i
+
+
+class SettingSortIterator(object):
+    def __init__(self, it, name, reverse=False, value_accessor=None):
+        self.it = it
+        self.name = name
+        self.reverse = reverse
+        self.value_accessor = value_accessor
+
+    def __iter__(self):
+        def comparer(x, y):
+            if self.value_accessor:
+                v1 = self.value_accessor(x, self.name)
+                v2 = self.value_accessor(y, self.name)
+            else:
+                v1 = x.config.get(self.name)
+                v2 = y.config.get(self.name)
+
+            if v1 is None and v2 is None:
+                return 0
+            if v1 is None and v2 is not None:
+                return 1 if self.reverse else -1
+            if v1 is not None and v2 is None:
+                return -1 if self.reverse else 1
+
+            if v1 == v2:
+                return 0
+            if self.reverse:
+                return 1 if v1 < v2 else -1
+            else:
+                return -1 if v1 < v2 else 1
+
+        return sorted(self.it, cmp=self._comparer, reverse=self.reverse)
+
+
+class DateSortIterator(object):
+    def __init__(self, it, reverse=True):
+        self.it = it
+        self.reverse = reverse
+
+    def __iter__(self):
+        return iter(sorted(self.it,
+                           key=lambda x: x.datetime, reverse=self.reverse))
+
+
+class PaginationFilterIterator(object):
+    def __init__(self, it, fil):
+        self.it = it
+        self._fil = fil
+
+    def __iter__(self):
+        for page in self.it:
+            if self._fil.pageMatches(page):
+                yield page
+
+
+class SourceFactoryIterator(object):
+    def __init__(self, source):
+        self.source = source
+        self.it = None # This is to permit recursive traversal of the
+                       # iterator chain. It acts as the end.
+
+    def __iter__(self):
+        for factory in self.source.getPageFactories():
+            yield factory.buildPage()
+
+
+class PaginationDataBuilderIterator(object):
+    def __init__(self, it):
+        self.it = it
+
+    def __iter__(self):
+        for page in self.it:
+            yield PaginationData(page)
+
+
+class PageIterator(object):
+    def __init__(self, source, current_page=None, pagination_filter=None,
+            offset=0, limit=-1, locked=False):
+        self._source = source
+        self._current_page = current_page
+        self._locked = False
+        self._pages = SourceFactoryIterator(source)
+        self._pagesData = None
+        self._pagination_slicer = None
+        self._has_sorter = False
+        self._next_page = None
+        self._prev_page = None
+        self._iter_event = Event()
+
+        # Apply any filter first, before we start sorting or slicing.
+        if pagination_filter is not None:
+            self._simpleNonSortedWrap(PaginationFilterIterator,
+                    pagination_filter)
+
+        if offset > 0 or limit > 0:
+            self.slice(offset, limit)
+
+        self._locked = locked
+
+    @property
+    def total_count(self):
+        self._load()
+        if self._pagination_slicer is not None:
+            return self._pagination_slicer.inner_count
+        return len(self._pagesData)
+
+    @property
+    def next_page(self):
+        self._load()
+        return self._next_page
+
+    @property
+    def prev_page(self):
+        self._load()
+        return self._prev_page
+
+    def __len__(self):
+        self._load()
+        return len(self._pagesData)
+
+    def __getitem__(self, key):
+        self._load()
+        return self._pagesData[key]
+
+    def __iter__(self):
+        self._load()
+        self._iter_event.fire()
+        return iter(self._pagesData)
+
+    def __getattr__(self, name):
+        if name[:3] == 'is_' or name[:3] == 'in_':
+            def is_filter(value):
+                conf = {'is_%s' % name[3:]: value}
+                return self._simpleNonSortedWrap(SettingFilterIterator, conf)
+            return is_filter
+
+        if name[:4] == 'has_':
+            def has_filter(value):
+                conf = {name: value}
+                return self._simpleNonSortedWrap(SettingFilterIterator, conf)
+            return has_filter
+
+        if name[:5] == 'with_':
+            def has_filter(value):
+                conf = {'has_%s' % name[5:]: value}
+                return self._simpleNonSortedWrap(SettingFilterIterator, conf)
+            return has_filter
+
+        raise AttributeError()
+
+    def skip(self, count):
+        return self._simpleWrap(SliceIterator, count)
+
+    def limit(self, count):
+        return self._simpleWrap(SliceIterator, 0, count)
+
+    def slice(self, skip, limit):
+        return self._simpleWrap(SliceIterator, skip, limit)
+
+    def filter(self, filter_name):
+        if self._current_page is None:
+            raise Exception("Can't use `filter()` because no parent page was "
+                            "set for this page iterator.")
+        filter_conf = self._current_page.config.get(filter_name)
+        if filter_conf is None:
+            raise Exception("Couldn't find filter '%s' in the configuration "
+                            "header for page: %s" %
+                            (filter_name, self._current_page.path))
+        return self._simpleNonSortedWrap(SettingFilterIterator, filter_conf)
+
+    def sort(self, setting_name, reverse=False):
+        self._ensureUnlocked()
+        self._unload()
+        self._pages = SettingSortIterator(self._pages, setting_name, reverse)
+        self._has_sorter = True
+        return self
+
+    def reset(self):
+        self._ensureUnlocked()
+        self._unload
+        return self
+
+    @property
+    def _has_more(self):
+        self._load()
+        if self._pagination_slicer:
+            return self._pagination_slicer.has_more
+        return False
+
+    def _simpleWrap(self, it_class, *args, **kwargs):
+        self._ensureUnlocked()
+        self._unload()
+        self._ensureSorter()
+        self._pages = it_class(self._pages, *args, **kwargs)
+        if self._pagination_slicer is None and it_class is SliceIterator:
+            self._pagination_slicer = self._pages
+        return self
+
+    def _simpleNonSortedWrap(self, it_class, *args, **kwargs):
+        self._ensureUnlocked()
+        self._unload()
+        self._pages = it_class(self._pages, *args, **kwargs)
+        return self
+
+    def _ensureUnlocked(self):
+        if self._locked:
+            raise Exception(
+                    "This page iterator has been locked, probably because "
+                    "you're trying to tamper with pagination data.")
+
+    def _ensureSorter(self):
+        if self._has_sorter:
+            return
+        self._pages = DateSortIterator(self._pages)
+        self._has_sorter = True
+
+    def _unload(self):
+        self._pagesData = None
+        self._next_page = None
+        self._prev_page = None
+
+    def _load(self):
+        if self._pagesData is not None:
+            return
+
+        self._ensureSorter()
+
+        it_chain = PaginationDataBuilderIterator(self._pages)
+        self._pagesData = list(it_chain)
+
+        if self._current_page and self._pagination_slicer:
+            self._prev_page = PaginationData(self._pagination_slicer.prev_page)
+            self._next_page = PaginationData(self._pagination_slicer.next_page)
+