Mercurial > piecrust2
comparison piecrust/baking/baker.py @ 338:938be93215cb
bake: Improve render context and bake record, fix incremental bake bugs.
* Used sources and taxonomies are now stored on a per-render-pass basis.
This fixes bugs where sources/taxonomies were used for one pass, but that
pass is skipped on a later bake because its result is cached.
* Bake records are now created for all pages even when they're not baked.
Record collapsing is gone except for taxonomy index pages.
* Bake records now also have sub-entries in order to store information about
each sub-page, since some sub-pages could use sources/taxonomies differently
than others, or be missing from the output. This lets PieCrust handle
clean/dirty states on a sub-page level.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Mon, 06 Apr 2015 19:59:54 -0700 |
parents | b034f6f15e22 |
children | dd25bd3ce1f9 |
comparison
equal
deleted
inserted
replaced
337:49408002798e | 338:938be93215cb |
---|---|
101 | 101 |
102 # Save the bake record. | 102 # Save the bake record. |
103 t = time.clock() | 103 t = time.clock() |
104 record.current.bake_time = time.time() | 104 record.current.bake_time = time.time() |
105 record.current.out_dir = self.out_dir | 105 record.current.out_dir = self.out_dir |
106 record.collapseRecords() | |
107 record.saveCurrent(record_cache.getCachePath(record_name)) | 106 record.saveCurrent(record_cache.getCachePath(record_name)) |
108 logger.debug(format_timed(t, 'saved bake record', colored=False)) | 107 logger.debug(format_timed(t, 'saved bake record', colored=False)) |
109 | 108 |
110 # All done. | 109 # All done. |
111 self.app.config.set('baker/is_baking', False) | 110 self.app.config.set('baker/is_baking', False) |
192 record.current.success &= success | 191 record.current.success &= success |
193 | 192 |
194 def _bakeTaxonomies(self, record): | 193 def _bakeTaxonomies(self, record): |
195 logger.debug("Baking taxonomies") | 194 logger.debug("Baking taxonomies") |
196 | 195 |
197 class _TaxonomyTermsInfo(object): | |
198 def __init__(self): | |
199 self.dirty_terms = set() | |
200 self.all_terms = set() | |
201 | |
202 def __str__(self): | |
203 return 'dirty:%s, all:%s' % (self.dirty_terms, self.all_terms) | |
204 | |
205 def __repr__(self): | |
206 return 'dirty:%s, all:%s' % (self.dirty_terms, self.all_terms) | |
207 | |
208 # Let's see all the taxonomy terms for which we must bake a | 196 # Let's see all the taxonomy terms for which we must bake a |
209 # listing page... first, pre-populate our big map of used terms. | 197 # listing page... first, pre-populate our big map of used terms. |
210 # For each source name, we have a list of taxonomies, and for each | 198 # For each source name, we have a list of taxonomies, and for each |
211 # taxonomies, a list of terms, some being 'dirty', some used last | 199 # taxonomies, a list of terms, some being 'dirty', some used last |
212 # time, etc. | 200 # time, etc. |
220 source_taxonomies[tn] = _TaxonomyTermsInfo() | 208 source_taxonomies[tn] = _TaxonomyTermsInfo() |
221 | 209 |
222 # Now see which ones are 'dirty' based on our bake record. | 210 # Now see which ones are 'dirty' based on our bake record. |
223 logger.debug("Gathering dirty taxonomy terms") | 211 logger.debug("Gathering dirty taxonomy terms") |
224 for prev_entry, cur_entry in record.transitions.values(): | 212 for prev_entry, cur_entry in record.transitions.values(): |
213 # Re-bake all taxonomy pages that include new or changed | |
214 # pages. | |
215 if cur_entry and cur_entry.was_any_sub_baked: | |
216 entries = [cur_entry] | |
217 if prev_entry: | |
218 entries.append(prev_entry) | |
219 | |
220 for tax in self.app.taxonomies: | |
221 changed_terms = set() | |
222 for e in entries: | |
223 terms = e.config.get(tax.setting_name) | |
224 if terms: | |
225 if not tax.is_multiple: | |
226 terms = [terms] | |
227 changed_terms |= set(terms) | |
228 | |
229 if len(changed_terms) > 0: | |
230 tt_info = buckets[cur_entry.source_name][tax.name] | |
231 tt_info.dirty_terms |= changed_terms | |
232 | |
233 # Remember all terms used. | |
225 for tax in self.app.taxonomies: | 234 for tax in self.app.taxonomies: |
226 # Re-bake all taxonomy pages that include new or changed | 235 if cur_entry and not cur_entry.was_overriden: |
227 # pages. | |
228 if cur_entry and cur_entry.was_baked_successfully: | |
229 if prev_entry and prev_entry.was_baked_successfully: | |
230 # Entry was re-baked this time. Mark as dirty both the | |
231 # old and new terms. | |
232 changed_terms = [] | |
233 prev_terms = prev_entry.config.get(tax.setting_name) | |
234 cur_terms = cur_entry.config.get(tax.setting_name) | |
235 if tax.is_multiple: | |
236 if prev_terms is not None: | |
237 changed_terms += prev_terms | |
238 if cur_terms is not None: | |
239 changed_terms += cur_terms | |
240 else: | |
241 if prev_terms is not None: | |
242 changed_terms.append(prev_terms) | |
243 if cur_terms is not None: | |
244 changed_terms.append(cur_terms) | |
245 else: | |
246 # Entry was not baked last time. Just mark as dirty | |
247 # all the new terms. | |
248 changed_terms = cur_entry.config.get(tax.setting_name) | |
249 | |
250 if changed_terms is not None: | |
251 if not isinstance(changed_terms, list): | |
252 changed_terms = [changed_terms] | |
253 tt_info = buckets[cur_entry.source_name][tax.name] | |
254 tt_info.dirty_terms |= set(changed_terms) | |
255 | |
256 # Remember all terms used. | |
257 if cur_entry and cur_entry.was_baked_successfully: | |
258 cur_terms = cur_entry.config.get(tax.setting_name) | 236 cur_terms = cur_entry.config.get(tax.setting_name) |
259 if cur_terms is not None: | 237 if cur_terms: |
260 if not isinstance(cur_terms, list): | 238 if not tax.is_multiple: |
261 cur_terms = [cur_terms] | 239 cur_terms = [cur_terms] |
262 tt_info = buckets[cur_entry.source_name][tax.name] | 240 tt_info = buckets[cur_entry.source_name][tax.name] |
263 tt_info.all_terms |= set(cur_terms) | 241 tt_info.all_terms |= set(cur_terms) |
264 elif (prev_entry and prev_entry.was_baked_successfully and | |
265 cur_entry and not cur_entry.was_baked): | |
266 prev_terms = prev_entry.config.get(tax.setting_name) | |
267 if prev_terms is not None: | |
268 if not isinstance(prev_terms, list): | |
269 prev_terms = [prev_terms] | |
270 tt_info = buckets[prev_entry.source_name][tax.name] | |
271 tt_info.all_terms |= set(prev_terms) | |
272 | 242 |
273 # Re-bake the combination pages for terms that are 'dirty'. | 243 # Re-bake the combination pages for terms that are 'dirty'. |
274 known_combinations = set() | 244 known_combinations = set() |
275 logger.debug("Gathering dirty term combinations") | 245 logger.debug("Gathering dirty term combinations") |
276 for prev_entry, cur_entry in record.transitions.values(): | 246 for prev_entry, cur_entry in record.transitions.values(): |
277 if cur_entry and cur_entry.was_baked_successfully: | 247 if not cur_entry: |
278 known_combinations |= cur_entry.used_taxonomy_terms | 248 continue |
279 elif prev_entry: | 249 used_taxonomy_terms = cur_entry.getAllUsedTaxonomyTerms() |
280 known_combinations |= prev_entry.used_taxonomy_terms | 250 for sn, tn, terms in used_taxonomy_terms: |
251 if isinstance(terms, tuple): | |
252 known_combinations.add((sn, tn, terms)) | |
281 for sn, tn, terms in known_combinations: | 253 for sn, tn, terms in known_combinations: |
282 tt_info = buckets[sn][tn] | 254 tt_info = buckets[sn][tn] |
283 tt_info.all_terms.add(terms) | 255 tt_info.all_terms.add(terms) |
284 if not tt_info.dirty_terms.isdisjoint(set(terms)): | 256 if not tt_info.dirty_terms.isdisjoint(set(terms)): |
285 tt_info.dirty_terms.add(terms) | 257 tt_info.dirty_terms.add(terms) |
339 tn, tt, tsn = prev_entry.taxonomy_info | 311 tn, tt, tsn = prev_entry.taxonomy_info |
340 tt_info = buckets[tsn][tn] | 312 tt_info = buckets[tsn][tn] |
341 if tt in tt_info.all_terms: | 313 if tt in tt_info.all_terms: |
342 logger.debug("Creating unbaked entry for taxonomy " | 314 logger.debug("Creating unbaked entry for taxonomy " |
343 "term '%s:%s'." % (tn, tt)) | 315 "term '%s:%s'." % (tn, tt)) |
344 entry = BakeRecordPageEntry( | 316 record.collapseEntry(prev_entry) |
345 prev_entry.source_name, prev_entry.rel_path, | |
346 prev_entry.path, prev_entry.taxonomy_info) | |
347 record.addEntry(entry) | |
348 else: | 317 else: |
349 logger.debug("Taxonomy term '%s:%s' isn't used anymore." % | 318 logger.debug("Taxonomy term '%s:%s' isn't used anymore." % |
350 (tn, tt)) | 319 (tn, tt)) |
351 | 320 |
352 def _handleDeletetions(self, record): | 321 def _handleDeletetions(self, record): |
469 logger.debug("Got baking error. Adding it to the record.") | 438 logger.debug("Got baking error. Adding it to the record.") |
470 while ex: | 439 while ex: |
471 entry.errors.append(str(ex)) | 440 entry.errors.append(str(ex)) |
472 ex = ex.__cause__ | 441 ex = ex.__cause__ |
473 | 442 |
474 if entry.errors: | 443 has_error = False |
475 for e in entry.errors: | 444 for e in entry.getAllErrors(): |
476 logger.error(e) | 445 has_error = True |
446 logger.error(e) | |
447 if has_error: | |
477 return False | 448 return False |
478 | 449 |
479 if entry.was_baked_successfully: | 450 if entry.was_any_sub_baked: |
480 uri = entry.out_uris[0] | 451 first_sub = entry.subs[0] |
481 friendly_uri = uri if uri != '' else '[main page]' | 452 |
453 friendly_uri = first_sub.out_uri | |
454 if friendly_uri == '': | |
455 friendly_uri = '[main page]' | |
456 | |
482 friendly_count = '' | 457 friendly_count = '' |
483 if entry.num_subs > 1: | 458 if entry.num_subs > 1: |
484 friendly_count = ' (%d pages)' % entry.num_subs | 459 friendly_count = ' (%d pages)' % entry.num_subs |
485 logger.info(format_timed( | 460 logger.info(format_timed( |
486 start_time, '[%d] %s%s' % | 461 start_time, '[%d] %s%s' % |
487 (self.wid, friendly_uri, friendly_count))) | 462 (self.wid, friendly_uri, friendly_count))) |
488 | 463 |
489 return True | 464 return True |
490 | 465 |
466 | |
467 class _TaxonomyTermsInfo(object): | |
468 def __init__(self): | |
469 self.dirty_terms = set() | |
470 self.all_terms = set() | |
471 | |
472 def __str__(self): | |
473 return 'dirty:%s, all:%s' % (self.dirty_terms, self.all_terms) | |
474 | |
475 def __repr__(self): | |
476 return 'dirty:%s, all:%s' % (self.dirty_terms, self.all_terms) |