Mercurial > piecrust2
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 # |