Mercurial > piecrust2
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 |