Mercurial > piecrust2
comparison piecrust/dataproviders/pageiterator.py @ 979:45ad976712ec
tests: Big push to get the tests to pass again.
- Lots of fixes everywhere in the code.
- Try to handle debug logging in the multiprocessing worker pool when running in pytest. Not perfect, but usable for now.
- Replace all `.md` test files with `.html` since now a auto-format extension always sets the format.
- Replace `out` with `outfiles` in most places since now blog archives are added to the bake output and I don't want to add expected outputs for blog archives everywhere.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sun, 29 Oct 2017 22:51:57 -0700 |
parents | abc52a6262a1 |
children | 492b66482f12 |
comparison
equal
deleted
inserted
replaced
978:7e51d14097cb | 979:45ad976712ec |
---|---|
7 | 7 |
8 | 8 |
9 logger = logging.getLogger(__name__) | 9 logger = logging.getLogger(__name__) |
10 | 10 |
11 | 11 |
12 class _ItInfo: | 12 class _CombinedSource: |
13 def __init__(self): | 13 def __init__(self, sources): |
14 self.sources = sources | |
15 self.app = sources[0].app | |
16 self.name = None | |
17 | |
18 # This is for recursive traversal of the iterator chain. | |
19 # See later in `PageIterator`. | |
14 self.it = None | 20 self.it = None |
15 self.iterated = False | 21 |
16 self.source_name = None | 22 def __iter__(self): |
23 sources = self.sources | |
24 | |
25 if len(sources) == 1: | |
26 source = sources[0] | |
27 self.name = source.name | |
28 yield from source.getAllPages() | |
29 self.name = None | |
30 return | |
31 | |
32 # Return the pages from all the combined sources, but skip | |
33 # those that are "overridden" -- e.g. a theme page that gets | |
34 # replaced by a user page of the same name. | |
35 used_uris = set() | |
36 for source in sources: | |
37 self.name = source.name | |
38 for page in source.getAllPages(): | |
39 page_uri = page.getUri() | |
40 if page_uri not in used_uris: | |
41 used_uris.add(page_uri) | |
42 yield page | |
43 | |
44 self.name = None | |
17 | 45 |
18 | 46 |
19 class PageIteratorDataProvider(DataProvider): | 47 class PageIteratorDataProvider(DataProvider): |
20 """ A data provider that reads a content source as a list of pages. | 48 """ A data provider that reads a content source as a list of pages. |
21 | 49 |
29 debug_render_doc_dynamic = ['_debugRenderDoc'] | 57 debug_render_doc_dynamic = ['_debugRenderDoc'] |
30 debug_render_not_empty = True | 58 debug_render_not_empty = True |
31 | 59 |
32 def __init__(self, source, page): | 60 def __init__(self, source, page): |
33 super().__init__(source, page) | 61 super().__init__(source, page) |
34 self._its = None | |
35 self._app = source.app | 62 self._app = source.app |
63 self._it = None | |
64 self._iterated = False | |
36 | 65 |
37 def __len__(self): | 66 def __len__(self): |
38 self._load() | 67 self._load() |
39 return sum([len(i.it) for i in self._its]) | 68 return len(self._it) |
40 | 69 |
41 def __iter__(self): | 70 def __iter__(self): |
42 self._load() | 71 self._load() |
43 for i in self._its: | 72 yield from self._it |
44 yield from i.it | |
45 | 73 |
46 def _load(self): | 74 def _load(self): |
47 if self._its is not None: | 75 if self._it is not None: |
48 return | 76 return |
49 | 77 |
50 self._its = [] | 78 combined_source = _CombinedSource(list(reversed(self._sources))) |
51 for source in self._sources: | 79 self._it = PageIterator(combined_source, current_page=self._page) |
52 i = _ItInfo() | 80 self._it._iter_event += self._onIteration |
53 i.it = PageIterator(source, current_page=self._page) | |
54 i.it._iter_event += self._onIteration | |
55 i.source_name = source.name | |
56 self._its.append(i) | |
57 | 81 |
58 def _onIteration(self, it): | 82 def _onIteration(self, it): |
59 ii = next(filter(lambda i: i.it == it, self._its)) | 83 if not self._iterated: |
60 if not ii.iterated: | |
61 rcs = self._app.env.render_ctx_stack | 84 rcs = self._app.env.render_ctx_stack |
62 rcs.current_ctx.addUsedSource(ii.source_name) | 85 rcs.current_ctx.addUsedSource(it._source) |
63 ii.iterated = True | 86 self._iterated = True |
87 | |
88 def _addSource(self, source): | |
89 if self._it is not None: | |
90 raise Exception("Can't add sources after the data provider " | |
91 "has been loaded.") | |
92 super()._addSource(source) | |
64 | 93 |
65 def _debugRenderDoc(self): | 94 def _debugRenderDoc(self): |
66 return 'Provides a list of %d items' % len(self) | 95 return 'Provides a list of %d items' % len(self) |
67 | 96 |
68 | 97 |
69 class PageIterator: | 98 class PageIterator: |
70 def __init__(self, source, *, current_page=None): | 99 def __init__(self, source, *, current_page=None): |
71 self._source = source | 100 self._source = source |
72 self._is_content_source = isinstance(source, ContentSource) | 101 self._is_content_source = isinstance( |
102 source, (ContentSource, _CombinedSource)) | |
73 self._cache = None | 103 self._cache = None |
74 self._pagination_slicer = None | 104 self._pagination_slicer = None |
75 self._has_sorter = False | 105 self._has_sorter = False |
76 self._next_page = None | 106 self._next_page = None |
77 self._prev_page = None | 107 self._prev_page = None |
148 raise Exception("Couldn't find filter '%s' in the configuration " | 178 raise Exception("Couldn't find filter '%s' in the configuration " |
149 "header for page: %s" % | 179 "header for page: %s" % |
150 (filter_name, self._current_page.path)) | 180 (filter_name, self._current_page.path)) |
151 return self._simpleNonSortedWrap(SettingFilterIterator, filter_conf) | 181 return self._simpleNonSortedWrap(SettingFilterIterator, filter_conf) |
152 | 182 |
153 def sort(self, setting_name, reverse=False): | 183 def sort(self, setting_name=None, reverse=False): |
154 if not setting_name: | 184 if setting_name: |
155 raise Exception("You need to specify a configuration setting " | 185 self._wrapAsSort(SettingSortIterator, setting_name, reverse) |
156 "to sort by.") | 186 else: |
157 self._ensureUnlocked() | 187 self._wrapAsSort(NaturalSortIterator, reverse) |
158 self._ensureUnloaded() | |
159 self._pages = SettingSortIterator(self._pages, setting_name, reverse) | |
160 self._has_sorter = True | |
161 return self | 188 return self |
162 | 189 |
163 def reset(self): | 190 def reset(self): |
164 self._ensureUnlocked() | 191 self._ensureUnlocked() |
165 self._unload() | 192 self._unload() |
169 def _is_loaded(self): | 196 def _is_loaded(self): |
170 return self._cache is not None | 197 return self._cache is not None |
171 | 198 |
172 @property | 199 @property |
173 def _has_more(self): | 200 def _has_more(self): |
174 if self._cache is None: | 201 self._load() |
175 return False | |
176 if self._pagination_slicer: | 202 if self._pagination_slicer: |
177 return self._pagination_slicer.has_more | 203 return self._pagination_slicer.has_more |
178 return False | 204 return False |
205 | |
206 @property | |
207 def _is_loaded_and_has_more(self): | |
208 return self._is_loaded and self._has_more | |
179 | 209 |
180 def _simpleWrap(self, it_class, *args, **kwargs): | 210 def _simpleWrap(self, it_class, *args, **kwargs): |
181 self._ensureUnlocked() | 211 self._ensureUnlocked() |
182 self._ensureUnloaded() | 212 self._ensureUnloaded() |
183 self._ensureSorter() | 213 self._ensureSorter() |
224 self._it = DateSortIterator(self._it, reverse=True) | 254 self._it = DateSortIterator(self._it, reverse=True) |
225 self._has_sorter = True | 255 self._has_sorter = True |
226 | 256 |
227 def _initIterator(self): | 257 def _initIterator(self): |
228 if self._is_content_source: | 258 if self._is_content_source: |
229 self._it = PageContentSourceIterator(self._source) | 259 if isinstance(self._source, _CombinedSource): |
260 self._it = self._source | |
261 else: | |
262 self._it = PageContentSourceIterator(self._source) | |
263 | |
230 app = self._source.app | 264 app = self._source.app |
231 if app.config.get('baker/is_baking'): | 265 if app.config.get('baker/is_baking'): |
232 # While baking, automatically exclude any page with | 266 # While baking, automatically exclude any page with |
233 # the `draft` setting. | 267 # the `draft` setting. |
234 draft_setting = app.config['baker/no_bake_setting'] | 268 draft_setting = app.config['baker/no_bake_setting'] |
331 self.prev_page = inner_list[idx - 1] | 365 self.prev_page = inner_list[idx - 1] |
332 | 366 |
333 return iter(self._cache) | 367 return iter(self._cache) |
334 | 368 |
335 | 369 |
370 class NaturalSortIterator: | |
371 def __init__(self, it, reverse=False): | |
372 self.it = it | |
373 self.reverse = reverse | |
374 | |
375 def __iter__(self): | |
376 return iter(sorted(self.it, reverse=self.reverse)) | |
377 | |
378 | |
336 class SettingSortIterator: | 379 class SettingSortIterator: |
337 def __init__(self, it, name, reverse=False): | 380 def __init__(self, it, name, reverse=False): |
338 self.it = it | 381 self.it = it |
339 self.name = name | 382 self.name = name |
340 self.reverse = reverse | 383 self.reverse = reverse |
342 def __iter__(self): | 385 def __iter__(self): |
343 return iter(sorted(self.it, key=self._key_getter, | 386 return iter(sorted(self.it, key=self._key_getter, |
344 reverse=self.reverse)) | 387 reverse=self.reverse)) |
345 | 388 |
346 def _key_getter(self, item): | 389 def _key_getter(self, item): |
347 key = item.config.get(item) | 390 key = item.config.get(self.name) |
348 if key is None: | 391 if key is None: |
349 return 0 | 392 return 0 |
350 return key | 393 return key |
351 | 394 |
352 | 395 |