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)