comparison 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
comparison
equal deleted inserted replaced
2:40fa08b261b9 3:f485ba500df3
1 import logging
2 from piecrust.data.base import PaginationData
3 from piecrust.data.filters import PaginationFilter
4 from piecrust.events import Event
5
6
7 logger = logging.getLogger(__name__)
8
9
10 class SliceIterator(object):
11 def __init__(self, it, offset=0, limit=-1):
12 self.it = it
13 self.offset = offset
14 self.limit = limit
15 self.current_page = None
16 self.has_more = False
17 self.inner_count = -1
18 self.next_page = None
19 self.prev_page = None
20 self._cache = None
21
22 def __iter__(self):
23 if self._cache is None:
24 inner_list = list(self.it)
25 self.inner_count = len(inner_list)
26 self.has_more = self.inner_count > (self.offset + self.limit)
27 self._cache = inner_list[self.offset:self.offset + self.limit]
28 if self.current_page:
29 idx = inner_list.index(self.current_page)
30 if idx >= 0:
31 if idx < self.inner_count - 1:
32 self.next_page = inner_list[idx + 1]
33 if idx > 0:
34 self.prev_page = inner_list[idx - 1]
35 return iter(self._cache)
36
37
38 class SettingFilterIterator(object):
39 def __init__(self, it, fil_conf, page_accessor=None):
40 self.it = it
41 self.fil_conf = fil_conf
42 self._fil = None
43 self.page_accessor = page_accessor
44
45 def __iter__(self):
46 if self._fil is None:
47 self._fil = PaginationFilter()
48 self._fil.addClausesFromConfig(self.fil_conf)
49
50 for i in self.it:
51 if self.page_accessor:
52 page = self.page_accessor(i)
53 else:
54 page = i
55 if self._fil.pageMatches(page):
56 yield i
57
58
59 class SettingSortIterator(object):
60 def __init__(self, it, name, reverse=False, value_accessor=None):
61 self.it = it
62 self.name = name
63 self.reverse = reverse
64 self.value_accessor = value_accessor
65
66 def __iter__(self):
67 def comparer(x, y):
68 if self.value_accessor:
69 v1 = self.value_accessor(x, self.name)
70 v2 = self.value_accessor(y, self.name)
71 else:
72 v1 = x.config.get(self.name)
73 v2 = y.config.get(self.name)
74
75 if v1 is None and v2 is None:
76 return 0
77 if v1 is None and v2 is not None:
78 return 1 if self.reverse else -1
79 if v1 is not None and v2 is None:
80 return -1 if self.reverse else 1
81
82 if v1 == v2:
83 return 0
84 if self.reverse:
85 return 1 if v1 < v2 else -1
86 else:
87 return -1 if v1 < v2 else 1
88
89 return sorted(self.it, cmp=self._comparer, reverse=self.reverse)
90
91
92 class DateSortIterator(object):
93 def __init__(self, it, reverse=True):
94 self.it = it
95 self.reverse = reverse
96
97 def __iter__(self):
98 return iter(sorted(self.it,
99 key=lambda x: x.datetime, reverse=self.reverse))
100
101
102 class PaginationFilterIterator(object):
103 def __init__(self, it, fil):
104 self.it = it
105 self._fil = fil
106
107 def __iter__(self):
108 for page in self.it:
109 if self._fil.pageMatches(page):
110 yield page
111
112
113 class SourceFactoryIterator(object):
114 def __init__(self, source):
115 self.source = source
116 self.it = None # This is to permit recursive traversal of the
117 # iterator chain. It acts as the end.
118
119 def __iter__(self):
120 for factory in self.source.getPageFactories():
121 yield factory.buildPage()
122
123
124 class PaginationDataBuilderIterator(object):
125 def __init__(self, it):
126 self.it = it
127
128 def __iter__(self):
129 for page in self.it:
130 yield PaginationData(page)
131
132
133 class PageIterator(object):
134 def __init__(self, source, current_page=None, pagination_filter=None,
135 offset=0, limit=-1, locked=False):
136 self._source = source
137 self._current_page = current_page
138 self._locked = False
139 self._pages = SourceFactoryIterator(source)
140 self._pagesData = None
141 self._pagination_slicer = None
142 self._has_sorter = False
143 self._next_page = None
144 self._prev_page = None
145 self._iter_event = Event()
146
147 # Apply any filter first, before we start sorting or slicing.
148 if pagination_filter is not None:
149 self._simpleNonSortedWrap(PaginationFilterIterator,
150 pagination_filter)
151
152 if offset > 0 or limit > 0:
153 self.slice(offset, limit)
154
155 self._locked = locked
156
157 @property
158 def total_count(self):
159 self._load()
160 if self._pagination_slicer is not None:
161 return self._pagination_slicer.inner_count
162 return len(self._pagesData)
163
164 @property
165 def next_page(self):
166 self._load()
167 return self._next_page
168
169 @property
170 def prev_page(self):
171 self._load()
172 return self._prev_page
173
174 def __len__(self):
175 self._load()
176 return len(self._pagesData)
177
178 def __getitem__(self, key):
179 self._load()
180 return self._pagesData[key]
181
182 def __iter__(self):
183 self._load()
184 self._iter_event.fire()
185 return iter(self._pagesData)
186
187 def __getattr__(self, name):
188 if name[:3] == 'is_' or name[:3] == 'in_':
189 def is_filter(value):
190 conf = {'is_%s' % name[3:]: value}
191 return self._simpleNonSortedWrap(SettingFilterIterator, conf)
192 return is_filter
193
194 if name[:4] == 'has_':
195 def has_filter(value):
196 conf = {name: value}
197 return self._simpleNonSortedWrap(SettingFilterIterator, conf)
198 return has_filter
199
200 if name[:5] == 'with_':
201 def has_filter(value):
202 conf = {'has_%s' % name[5:]: value}
203 return self._simpleNonSortedWrap(SettingFilterIterator, conf)
204 return has_filter
205
206 raise AttributeError()
207
208 def skip(self, count):
209 return self._simpleWrap(SliceIterator, count)
210
211 def limit(self, count):
212 return self._simpleWrap(SliceIterator, 0, count)
213
214 def slice(self, skip, limit):
215 return self._simpleWrap(SliceIterator, skip, limit)
216
217 def filter(self, filter_name):
218 if self._current_page is None:
219 raise Exception("Can't use `filter()` because no parent page was "
220 "set for this page iterator.")
221 filter_conf = self._current_page.config.get(filter_name)
222 if filter_conf is None:
223 raise Exception("Couldn't find filter '%s' in the configuration "
224 "header for page: %s" %
225 (filter_name, self._current_page.path))
226 return self._simpleNonSortedWrap(SettingFilterIterator, filter_conf)
227
228 def sort(self, setting_name, reverse=False):
229 self._ensureUnlocked()
230 self._unload()
231 self._pages = SettingSortIterator(self._pages, setting_name, reverse)
232 self._has_sorter = True
233 return self
234
235 def reset(self):
236 self._ensureUnlocked()
237 self._unload
238 return self
239
240 @property
241 def _has_more(self):
242 self._load()
243 if self._pagination_slicer:
244 return self._pagination_slicer.has_more
245 return False
246
247 def _simpleWrap(self, it_class, *args, **kwargs):
248 self._ensureUnlocked()
249 self._unload()
250 self._ensureSorter()
251 self._pages = it_class(self._pages, *args, **kwargs)
252 if self._pagination_slicer is None and it_class is SliceIterator:
253 self._pagination_slicer = self._pages
254 return self
255
256 def _simpleNonSortedWrap(self, it_class, *args, **kwargs):
257 self._ensureUnlocked()
258 self._unload()
259 self._pages = it_class(self._pages, *args, **kwargs)
260 return self
261
262 def _ensureUnlocked(self):
263 if self._locked:
264 raise Exception(
265 "This page iterator has been locked, probably because "
266 "you're trying to tamper with pagination data.")
267
268 def _ensureSorter(self):
269 if self._has_sorter:
270 return
271 self._pages = DateSortIterator(self._pages)
272 self._has_sorter = True
273
274 def _unload(self):
275 self._pagesData = None
276 self._next_page = None
277 self._prev_page = None
278
279 def _load(self):
280 if self._pagesData is not None:
281 return
282
283 self._ensureSorter()
284
285 it_chain = PaginationDataBuilderIterator(self._pages)
286 self._pagesData = list(it_chain)
287
288 if self._current_page and self._pagination_slicer:
289 self._prev_page = PaginationData(self._pagination_slicer.prev_page)
290 self._next_page = PaginationData(self._pagination_slicer.next_page)
291