diff piecrust/baking/records.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 2cd2b5d07129
line wrap: on
line diff
--- a/piecrust/baking/records.py	Sat Apr 04 07:55:49 2015 -0700
+++ b/piecrust/baking/records.py	Mon Apr 06 19:59:54 2015 -0700
@@ -1,3 +1,4 @@
+import copy
 import os.path
 import logging
 from piecrust.records import Record, TransitionalRecord
@@ -19,7 +20,7 @@
 
 
 class BakeRecord(Record):
-    RECORD_VERSION = 11
+    RECORD_VERSION = 12
 
     def __init__(self):
         super(BakeRecord, self).__init__()
@@ -28,10 +29,43 @@
         self.success = True
 
 
-FLAG_NONE = 0
-FLAG_SOURCE_MODIFIED = 2**0
-FLAG_OVERRIDEN = 2**1
-FLAG_FORCED_BY_SOURCE = 2**2
+class BakeRecordPassInfo(object):
+    def __init__(self):
+        self.used_source_names = set()
+        self.used_taxonomy_terms = set()
+
+
+class BakeRecordSubPageEntry(object):
+    FLAG_NONE = 0
+    FLAG_BAKED = 2**0
+    FLAG_FORCED_BY_SOURCE = 2**1
+    FLAG_FORCED_BY_NO_PREVIOUS = 2**2
+    FLAG_FORCED_BY_PREVIOUS_ERRORS = 2**3
+    FLAG_FORMATTING_INVALIDATED = 2**4
+
+    def __init__(self, out_uri, out_path):
+        self.out_uri = out_uri
+        self.out_path = out_path
+        self.flags = self.FLAG_NONE
+        self.errors = []
+        self.render_passes = {}
+
+    @property
+    def was_clean(self):
+        return (self.flags & self.FLAG_BAKED) == 0 and len(self.errors) == 0
+
+    @property
+    def was_baked(self):
+        return (self.flags & self.FLAG_BAKED) != 0
+
+    @property
+    def was_baked_successfully(self):
+        return self.was_baked and len(self.errors) == 0
+
+    def collapseRenderPasses(self, other):
+        for p, pinfo in self.render_passes.items():
+            if p not in other.render_passes:
+                other.render_passes[p] = copy.deepcopy(pinfo)
 
 
 class BakeRecordPageEntry(object):
@@ -40,49 +74,73 @@
         The `taxonomy_info` attribute should be a tuple of the form:
         (taxonomy name, term, source name)
     """
+    FLAG_NONE = 0
+    FLAG_NEW = 2**0
+    FLAG_SOURCE_MODIFIED = 2**1
+    FLAG_OVERRIDEN = 2**2
+
     def __init__(self, source_name, rel_path, path, taxonomy_info=None):
         self.source_name = source_name
         self.rel_path = rel_path
         self.path = path
         self.taxonomy_info = taxonomy_info
-
-        self.flags = FLAG_NONE
+        self.flags = self.FLAG_NONE
         self.config = None
-        self.errors = []
-        self.out_uris = []
-        self.out_paths = []
-        self.clean_uris = []
-        self.clean_out_paths = []
-        self.used_source_names = set()
-        self.used_taxonomy_terms = set()
-        self.used_pagination_item_count = 0
+        self.subs = []
+        self.assets = []
 
     @property
     def path_mtime(self):
         return os.path.getmtime(self.path)
 
     @property
-    def was_baked(self):
-        return len(self.out_paths) > 0 or len(self.errors) > 0
-
-    @property
-    def was_baked_successfully(self):
-        return len(self.out_paths) > 0 and len(self.errors) == 0
+    def was_overriden(self):
+        return (self.flags & self.FLAG_OVERRIDEN) != 0
 
     @property
     def num_subs(self):
-        return len(self.out_paths)
+        return len(self.subs)
+
+    @property
+    def was_any_sub_baked(self):
+        for o in self.subs:
+            if o.was_baked:
+                return True
+        return False
+
+    def getSub(self, sub_index):
+        return self.subs[sub_index - 1]
+
+    def getAllErrors(self):
+        for o in self.subs:
+            yield from o.errors
+
+    def getAllUsedSourceNames(self):
+        res = set()
+        for o in self.subs:
+            for p, pinfo in o.render_passes.items():
+                res |= pinfo.used_source_names
+        return res
+
+    def getAllUsedTaxonomyTerms(self):
+        res = set()
+        for o in self.subs:
+            for p, pinfo in o.render_passes.items():
+                res |= pinfo.used_taxonomy_terms
+        return res
 
 
 class TransitionalBakeRecord(TransitionalRecord):
     def __init__(self, previous_path=None):
         super(TransitionalBakeRecord, self).__init__(BakeRecord,
                                                      previous_path)
+        self.dirty_source_names = set()
 
     def addEntry(self, entry):
         if (self.previous.bake_time and
                 entry.path_mtime >= self.previous.bake_time):
-            entry.flags |= FLAG_SOURCE_MODIFIED
+            entry.flags |= BakeRecordPageEntry.FLAG_SOURCE_MODIFIED
+            self.dirty_source_names.add(entry.source_name)
         super(TransitionalBakeRecord, self).addEntry(entry)
 
     def getTransitionKey(self, entry):
@@ -91,18 +149,13 @@
 
     def getOverrideEntry(self, factory, uri):
         for pair in self.transitions.values():
-            prev = pair[0]
             cur = pair[1]
             if (cur and
                     (cur.source_name != factory.source.name or
-                        cur.rel_path != factory.rel_path) and
-                    len(cur.out_uris) > 0 and cur.out_uris[0] == uri):
-                return cur
-            if (prev and
-                    (prev.source_name != factory.source.name or
-                        prev.rel_path != factory.rel_path) and
-                    len(prev.out_uris) > 0 and prev.out_uris[0] == uri):
-                return prev
+                        cur.rel_path != factory.rel_path)):
+                    for o in cur.subs:
+                        if o.out_uri == uri:
+                            return cur
         return None
 
     def getPreviousEntry(self, source_name, rel_path, taxonomy_info=None):
@@ -112,32 +165,25 @@
             return pair[0]
         return None
 
-    def getCurrentEntries(self, source_name):
-        return [e for e in self.current.entries
-                if e.source_name == source_name]
-
-    def collapseRecords(self):
-        for prev, cur in self.transitions.values():
-            if prev and cur and not cur.was_baked:
-                # This page wasn't baked, so the information from last
-                # time is still valid (we didn't get any information
-                # since we didn't bake).
-                cur.flags = prev.flags
-                if prev.config:
-                    cur.config = prev.config.copy()
-                cur.out_uris = list(prev.out_uris)
-                cur.out_paths = list(prev.out_paths)
-                cur.errors = list(prev.errors)
-                cur.used_source_names = set(prev.used_source_names)
-                cur.used_taxonomy_terms = set(prev.used_taxonomy_terms)
+    def collapseEntry(self, prev_entry):
+        cur_entry = copy.deepcopy(prev_entry)
+        cur_entry.flags = BakeRecordPageEntry.FLAG_NONE
+        for o in cur_entry.subs:
+            o.flags = BakeRecordSubPageEntry.FLAG_NONE
+        self.addEntry(cur_entry)
 
     def getDeletions(self):
         for prev, cur in self.transitions.values():
             if prev and not cur:
-                for p in prev.out_paths:
-                    yield (p, 'previous source file was removed')
-            elif prev and cur and cur.was_baked_successfully:
-                diff = set(prev.out_paths) - set(cur.out_paths)
+                for sub in prev.subs:
+                    yield (sub.out_path, 'previous source file was removed')
+            elif prev and cur:
+                prev_out_paths = [o.out_path for o in prev.subs]
+                cur_out_paths = [o.out_path for o in cur.subs]
+                diff = set(prev_out_paths) - set(cur_out_paths)
                 for p in diff:
                     yield (p, 'source file changed outputs')
 
+    def _onNewEntryAdded(self, entry):
+        entry.flags |= BakeRecordPageEntry.FLAG_NEW
+