comparison piecrust/pipelines/page.py @ 852:4850f8c21b6e

core: Start of the big refactor for PieCrust 3.0. * Everything is a `ContentSource`, including assets directories. * Most content sources are subclasses of the base file-system source. * A source is processed by a "pipeline", and there are 2 built-in pipelines, one for assets and one for pages. The asset pipeline is vaguely functional, but the page pipeline is completely broken right now. * Rewrite the baking process as just running appropriate pipelines on each content item. This should allow for better parallelization.
author Ludovic Chabant <ludovic@chabant.com>
date Wed, 17 May 2017 00:11:48 -0700
parents
children 08e02c2a2a1a
comparison
equal deleted inserted replaced
851:2c7e57d80bba 852:4850f8c21b6e
1 import hashlib
2 from piecrust.pipelines.base import ContentPipeline
3
4
5 class PagePipeline(ContentPipeline):
6 PIPELINE_NAME = 'page'
7 PIPELINE_PASSES = 3
8
9 def initialize(self, ctx):
10 pass
11
12 def run(self, content_item, ctx):
13 raise NotImplementedError()
14
15 def shutdown(self, ctx):
16 pass
17
18 def collapseRecords(self, record_history):
19 pass
20
21 def getDeletions(self, record_history):
22 for prev, cur in record_history.diffs():
23 if prev and not cur:
24 for sub in prev.subs:
25 yield (sub.out_path, 'previous source file was removed')
26 elif prev and cur:
27 prev_out_paths = [o.out_path for o in prev.subs]
28 cur_out_paths = [o.out_path for o in cur.subs]
29 diff = set(prev_out_paths) - set(cur_out_paths)
30 for p in diff:
31 yield (p, 'source file changed outputs')
32
33
34 JOB_LOAD, JOB_RENDER_FIRST, JOB_BAKE = range(0, 3)
35
36
37 def _get_transition_key(path, extra_key=None):
38 key = path
39 if extra_key:
40 key += '+%s' % extra_key
41 return hashlib.md5(key.encode('utf8')).hexdigest()
42
43
44 # def getOverrideEntry(self, path, uri):
45 # for pair in self.transitions.values():
46 # cur = pair[1]
47 # if cur and cur.path != path:
48 # for o in cur.subs:
49 # if o.out_uri == uri:
50 # return cur
51 # return None
52
53
54
55 # # Create the job handlers.
56 # job_handlers = {
57 # JOB_LOAD: LoadJobHandler(self.ctx),
58 # JOB_RENDER_FIRST: RenderFirstSubJobHandler(self.ctx),
59 # JOB_BAKE: BakeJobHandler(self.ctx)}
60 # for jt, jh in job_handlers.items():
61 # app.env.registerTimer(type(jh).__name__)
62 # self.job_handlers = job_handlers
63 #
64 # def process(self, job):
65 # handler = self.job_handlers[job['type']]
66 # with self.ctx.app.env.timerScope(type(handler).__name__):
67 # return handler.handleJob(job['job'])
68
69 # def _loadRealmPages(self, record_history, pool, factories):
70 # def _handler(res):
71 # # Create the record entry for this page.
72 # # This will also update the `dirty_source_names` for the record
73 # # as we add page files whose last modification times are later
74 # # than the last bake.
75 # record_entry = BakeRecordEntry(res['source_name'], res['path'])
76 # record_entry.config = res['config']
77 # record_entry.timestamp = res['timestamp']
78 # if res['errors']:
79 # record_entry.errors += res['errors']
80 # record_history.current.success = False
81 # self._logErrors(res['path'], res['errors'])
82 # record_history.addEntry(record_entry)
83 #
84 # logger.debug("Loading %d realm pages..." % len(factories))
85 # with format_timed_scope(logger,
86 # "loaded %d pages" % len(factories),
87 # level=logging.DEBUG, colored=False,
88 # timer_env=self.app.env,
89 # timer_category='LoadJob'):
90 # jobs = []
91 # for fac in factories:
92 # job = {
93 # 'type': JOB_LOAD,
94 # 'job': save_factory(fac)}
95 # jobs.append(job)
96 # ar = pool.queueJobs(jobs, handler=_handler)
97 # ar.wait()
98 #
99 # def _renderRealmPages(self, record_history, pool, factories):
100 # def _handler(res):
101 # entry = record_history.getCurrentEntry(res['path'])
102 # if res['errors']:
103 # entry.errors += res['errors']
104 # record_history.current.success = False
105 # self._logErrors(res['path'], res['errors'])
106 #
107 # logger.debug("Rendering %d realm pages..." % len(factories))
108 # with format_timed_scope(logger,
109 # "prepared %d pages" % len(factories),
110 # level=logging.DEBUG, colored=False,
111 # timer_env=self.app.env,
112 # timer_category='RenderFirstSubJob'):
113 # jobs = []
114 # for fac in factories:
115 # record_entry = record_history.getCurrentEntry(fac.path)
116 # if record_entry.errors:
117 # logger.debug("Ignoring %s because it had previous "
118 # "errors." % fac.ref_spec)
119 # continue
120 #
121 # # Make sure the source and the route exist for this page,
122 # # otherwise we add errors to the record entry and we'll skip
123 # # this page for the rest of the bake.
124 # source = self.app.getSource(fac.source.name)
125 # if source is None:
126 # record_entry.errors.append(
127 # "Can't get source for page: %s" % fac.ref_spec)
128 # logger.error(record_entry.errors[-1])
129 # continue
130 #
131 # route = self.app.getSourceRoute(fac.source.name, fac.metadata)
132 # if route is None:
133 # record_entry.errors.append(
134 # "Can't get route for page: %s" % fac.ref_spec)
135 # logger.error(record_entry.errors[-1])
136 # continue
137 #
138 # # All good, queue the job.
139 # route_index = self.app.routes.index(route)
140 # job = {
141 # 'type': JOB_RENDER_FIRST,
142 # 'job': {
143 # 'factory_info': save_factory(fac),
144 # 'route_index': route_index
145 # }
146 # }
147 # jobs.append(job)
148 #
149 # ar = pool.queueJobs(jobs, handler=_handler)
150 # ar.wait()
151 #
152 # def _bakeRealmPages(self, record_history, pool, realm, factories):
153 # def _handler(res):
154 # entry = record_history.getCurrentEntry(res['path'])
155 # entry.subs = res['sub_entries']
156 # if res['errors']:
157 # entry.errors += res['errors']
158 # self._logErrors(res['path'], res['errors'])
159 # if entry.has_any_error:
160 # record_history.current.success = False
161 # if entry.subs and entry.was_any_sub_baked:
162 # record_history.current.baked_count[realm] += 1
163 # record_history.current.total_baked_count[realm] += len(entry.subs)
164 #
165 # logger.debug("Baking %d realm pages..." % len(factories))
166 # with format_timed_scope(logger,
167 # "baked %d pages" % len(factories),
168 # level=logging.DEBUG, colored=False,
169 # timer_env=self.app.env,
170 # timer_category='BakeJob'):
171 # jobs = []
172 # for fac in factories:
173 # job = self._makeBakeJob(record_history, fac)
174 # if job is not None:
175 # jobs.append(job)
176 #
177 # ar = pool.queueJobs(jobs, handler=_handler)
178 # ar.wait()
179 #
180
181
182 # def _makeBakeJob(self, record_history, fac):
183 # # Get the previous (if any) and current entry for this page.
184 # pair = record_history.getPreviousAndCurrentEntries(fac.path)
185 # assert pair is not None
186 # prev_entry, cur_entry = pair
187 # assert cur_entry is not None
188 #
189 # # Ignore if there were errors in the previous passes.
190 # if cur_entry.errors:
191 # logger.debug("Ignoring %s because it had previous "
192 # "errors." % fac.ref_spec)
193 # return None
194 #
195 # # Build the route metadata and find the appropriate route.
196 # page = fac.buildPage()
197 # route_metadata = create_route_metadata(page)
198 # route = self.app.getSourceRoute(fac.source.name, route_metadata)
199 # assert route is not None
200 #
201 # # Figure out if this page is overriden by another previously
202 # # baked page. This happens for example when the user has
203 # # made a page that has the same page/URL as a theme page.
204 # uri = route.getUri(route_metadata)
205 # override_entry = record_history.getOverrideEntry(page.path, uri)
206 # if override_entry is not None:
207 # override_source = self.app.getSource(
208 # override_entry.source_name)
209 # if override_source.realm == fac.source.realm:
210 # cur_entry.errors.append(
211 # "Page '%s' maps to URL '%s' but is overriden "
212 # "by page '%s'." %
213 # (fac.ref_spec, uri, override_entry.path))
214 # logger.error(cur_entry.errors[-1])
215 # cur_entry.flags |= BakeRecordEntry.FLAG_OVERRIDEN
216 # return None
217 #
218 # route_index = self.app.routes.index(route)
219 # job = {
220 # 'type': JOB_BAKE,
221 # 'job': {
222 # 'factory_info': save_factory(fac),
223 # 'generator_name': None,
224 # 'generator_record_key': None,
225 # 'route_index': route_index,
226 # 'route_metadata': route_metadata,
227 # 'dirty_source_names': record_history.dirty_source_names
228 # }
229 # }
230 # return job
231 #
232 # def _handleDeletetions(self, record_history):
233 # logger.debug("Handling deletions...")
234 # for path, reason in record_history.getDeletions():
235 # logger.debug("Removing '%s': %s" % (path, reason))
236 # record_history.current.deleted.append(path)
237 # try:
238 # os.remove(path)
239 # logger.info('[delete] %s' % path)
240 # except OSError:
241 # # Not a big deal if that file had already been removed
242 # # by the user.
243 # pass
244 #
245
246
247
248 #def save_factory(fac):
249 # return {
250 # 'source_name': fac.source.name,
251 # 'rel_path': fac.rel_path,
252 # 'metadata': fac.metadata}
253 #
254 #
255 #def load_factory(app, info):
256 # source = app.getSource(info['source_name'])
257 # return PageFactory(source, info['rel_path'], info['metadata'])
258 #
259 #
260 #class LoadJobHandler(JobHandler):
261 # def handleJob(self, job):
262 # # Just make sure the page has been cached.
263 # fac = load_factory(self.app, job)
264 # logger.debug("Loading page: %s" % fac.ref_spec)
265 # self.app.env.addManifestEntry('LoadJobs', fac.ref_spec)
266 # result = {
267 # 'source_name': fac.source.name,
268 # 'path': fac.path,
269 # 'config': None,
270 # 'timestamp': None,
271 # 'errors': None}
272 # try:
273 # page = fac.buildPage()
274 # page._load()
275 # result['config'] = page.config.getAll()
276 # result['timestamp'] = page.datetime.timestamp()
277 # except Exception as ex:
278 # logger.debug("Got loading error. Sending it to master.")
279 # result['errors'] = _get_errors(ex)
280 # if self.ctx.app.debug:
281 # logger.exception(ex)
282 # return result
283 #
284 #
285 #class RenderFirstSubJobHandler(JobHandler):
286 # def handleJob(self, job):
287 # # Render the segments for the first sub-page of this page.
288 # fac = load_factory(self.app, job['factory_info'])
289 # self.app.env.addManifestEntry('RenderJobs', fac.ref_spec)
290 #
291 # route_index = job['route_index']
292 # route = self.app.routes[route_index]
293 #
294 # page = fac.buildPage()
295 # qp = QualifiedPage(page, route, route_metadata)
296 # ctx = RenderingContext(qp)
297 # self.app.env.abort_source_use = True
298 #
299 # result = {
300 # 'path': fac.path,
301 # 'aborted': False,
302 # 'errors': None}
303 # logger.debug("Preparing page: %s" % fac.ref_spec)
304 # try:
305 # render_page_segments(ctx)
306 # except AbortedSourceUseError:
307 # logger.debug("Page %s was aborted." % fac.ref_spec)
308 # self.app.env.stepCounter("SourceUseAbortions")
309 # result['aborted'] = True
310 # except Exception as ex:
311 # logger.debug("Got rendering error. Sending it to master.")
312 # result['errors'] = _get_errors(ex)
313 # if self.ctx.app.debug:
314 # logger.exception(ex)
315 # finally:
316 # self.app.env.abort_source_use = False
317 # return result
318 #
319 #
320 #class BakeJobHandler(JobHandler):
321 # def __init__(self, ctx):
322 # super(BakeJobHandler, self).__init__(ctx)
323 # self.page_baker = PageBaker(ctx.app, ctx.out_dir, ctx.force)
324 #
325 # def shutdown(self):
326 # self.page_baker.shutdown()
327 #
328 # def handleJob(self, job):
329 # # Actually bake the page and all its sub-pages to the output folder.
330 # fac = load_factory(self.app, job['factory_info'])
331 # self.app.env.addManifestEntry('BakeJobs', fac.ref_spec)
332 #
333 # route_index = job['route_index']
334 # route_metadata = job['route_metadata']
335 # route = self.app.routes[route_index]
336 #
337 # gen_name = job['generator_name']
338 # gen_key = job['generator_record_key']
339 # dirty_source_names = job['dirty_source_names']
340 #
341 # page = fac.buildPage()
342 # qp = QualifiedPage(page, route, route_metadata)
343 #
344 # result = {
345 # 'path': fac.path,
346 # 'generator_name': gen_name,
347 # 'generator_record_key': gen_key,
348 # 'sub_entries': None,
349 # 'errors': None}
350 #
351 # if job.get('needs_config', False):
352 # result['config'] = page.config.getAll()
353 #
354 # previous_entry = None
355 # if self.ctx.previous_record_index is not None:
356 # key = _get_transition_key(fac.path, gen_key)
357 # previous_entry = self.ctx.previous_record_index.get(key)
358 #
359 # logger.debug("Baking page: %s" % fac.ref_spec)
360 # logger.debug("With route metadata: %s" % route_metadata)
361 # try:
362 # sub_entries = self.page_baker.bake(
363 # qp, previous_entry, dirty_source_names, gen_name)
364 # result['sub_entries'] = sub_entries
365 #
366 # except Exception as ex:
367 # logger.debug("Got baking error. Sending it to master.")
368 # result['errors'] = _get_errors(ex)
369 # if self.ctx.app.debug:
370 # logger.exception(ex)
371 #
372 # return result
373 #