comparison piecrust/rendering.py @ 3:f485ba500df3

Gigantic change to basically make PieCrust 2 vaguely functional. - Serving works, with debug window. - Baking works, multi-threading, with dependency handling. - Various things not implemented yet.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 10 Aug 2014 23:43:16 -0700
parents
children 474c9882decf
comparison
equal deleted inserted replaced
2:40fa08b261b9 3:f485ba500df3
1 import re
2 import os.path
3 import codecs
4 import logging
5 from piecrust.data.builder import (DataBuildingContext, build_page_data,
6 build_layout_data)
7 from piecrust.environment import PHASE_PAGE_FORMATTING, PHASE_PAGE_RENDERING
8
9
10 logger = logging.getLogger(__name__)
11
12
13 content_abstract_re = re.compile(r'^<!--\s*(more|(page)?break)\s*-->\s*$',
14 re.MULTILINE)
15
16
17 class PageRenderingError(Exception):
18 pass
19
20
21 class RenderedPage(object):
22 def __init__(self, page, uri, num=1):
23 self.page = page
24 self.uri = uri
25 self.num = num
26 self.data = None
27 self.content = None
28 self.execution_info = None
29
30 @property
31 def app(self):
32 return self.page.app
33
34
35 class PageRenderingContext(object):
36 def __init__(self, page, uri, page_num=1):
37 self.page = page
38 self.uri = uri
39 self.page_num = page_num
40 self.pagination_source = None
41 self.pagination_filter = None
42 self.custom_data = None
43 self.use_cache = False
44 self.used_pagination = None
45 self.used_source_names = set()
46 self.used_taxonomy_terms = set()
47
48 @property
49 def app(self):
50 return self.page.app
51
52 @property
53 def source_metadata(self):
54 return self.page.source_metadata
55
56 def reset(self):
57 self.used_pagination = None
58
59 def setPagination(self, paginator):
60 if self.used_pagination is not None:
61 raise Exception("Pagination has already been used.")
62 self.used_pagination = paginator
63
64
65 def render_page(ctx):
66 eis = ctx.app.env.exec_info_stack
67 eis.pushPage(ctx.page, PHASE_PAGE_RENDERING, ctx)
68 try:
69 page = ctx.page
70
71 # Build the data for both segment and layout rendering.
72 data_ctx = DataBuildingContext(page, ctx.uri, ctx.page_num)
73 data_ctx.pagination_source = ctx.pagination_source
74 data_ctx.pagination_filter = ctx.pagination_filter
75 page_data = build_page_data(data_ctx)
76 if ctx.custom_data:
77 page_data.update(ctx.custom_data)
78
79 # Render content segments.
80 repo = ctx.app.env.rendered_segments_repository
81 if repo:
82 cache_key = '%s:%s' % (ctx.uri, ctx.page_num)
83 contents = repo.get(cache_key,
84 lambda: _do_render_page_segments(page, page_data))
85 else:
86 contents = _do_render_page_segments(page, page_data)
87
88 # Render layout.
89 layout_name = page.config.get('layout')
90 if layout_name is None:
91 layout_name = page.source.config.get('default_layout', 'default')
92 null_names = ['', 'none', 'nil']
93 if layout_name not in null_names:
94 layout_data = build_layout_data(page, page_data, contents)
95 output = render_layout(layout_name, page, layout_data)
96 else:
97 output = contents['content']
98
99 rp = RenderedPage(page, ctx.uri, ctx.page_num)
100 rp.data = page_data
101 rp.content = codecs.encode(output, 'utf8')
102 rp.execution_info = eis.current_page_info
103 return rp
104 finally:
105 eis.popPage()
106
107
108 def render_page_segments(ctx):
109 repo = ctx.app.env.rendered_segments_repository
110 if repo:
111 cache_key = '%s:%s' % (ctx.uri, ctx.page_num)
112 return repo.get(cache_key,
113 lambda: _do_render_page_segments_from_ctx(ctx))
114
115 return _do_render_page_segments_from_ctx(ctx)
116
117
118 def _do_render_page_segments_from_ctx(ctx):
119 eis = ctx.app.env.exec_info_stack
120 eis.pushPage(ctx.page, PHASE_PAGE_FORMATTING, ctx)
121 try:
122 data_ctx = DataBuildingContext(ctx.page, ctx.uri, ctx.page_num)
123 page_data = build_page_data(data_ctx)
124 return _do_render_page_segments(ctx.page, page_data)
125 finally:
126 eis.popPage()
127
128
129 def _do_render_page_segments(page, page_data):
130 app = page.app
131 engine_name = page.config.get('template_engine')
132 format_name = page.config.get('format')
133
134 engine = get_template_engine(app, engine_name)
135 if engine is None:
136 raise PageRenderingError("Can't find template engine '%s'." % engine_name)
137
138 formatted_content = {}
139 for seg_name, seg in page.raw_content.iteritems():
140 seg_text = u''
141 for seg_part in seg.parts:
142 part_format = seg_part.fmt or format_name
143 part_text = engine.renderString(seg_part.content, page_data,
144 filename=page.path, line_offset=seg_part.line)
145 part_text = format_text(app, part_format, part_text)
146 seg_text += part_text
147 formatted_content[seg_name] = seg_text
148
149 if seg_name == 'content':
150 m = content_abstract_re.search(seg_text)
151 if m:
152 offset = m.start()
153 content_abstract = seg_text[:offset]
154 formatted_content['content.abstract'] = content_abstract
155
156 return formatted_content
157
158
159 def render_layout(layout_name, page, layout_data):
160 names = layout_name.split(',')
161 default_template_engine = get_template_engine(page.app, None)
162 default_exts = ['.' + e.lstrip('.') for e in default_template_engine.EXTENSIONS]
163 full_names = []
164 for name in names:
165 if '.' not in name:
166 full_names.append(name + '.html')
167 for ext in default_exts:
168 full_names.append(name + ext)
169 else:
170 full_names.append(name)
171
172 _, engine_name = os.path.splitext(full_names[0])
173 engine_name = engine_name.lstrip('.')
174 engine = get_template_engine(page.app, engine_name)
175 if engine is None:
176 raise PageRenderingError("No such template engine: %s" % engine_name)
177 output = engine.renderFile(full_names, layout_data)
178 return output
179
180
181 def get_template_engine(app, engine_name):
182 if engine_name == 'html':
183 engine_name = None
184 engine_name = engine_name or app.config.get('site/default_template_engine')
185 for engine in app.plugin_loader.getTemplateEngines():
186 if engine_name in engine.ENGINE_NAMES:
187 return engine
188 return None
189
190 def format_text(app, format_name, txt):
191 format_name = format_name or app.config.get('site/default_format')
192 for fmt in app.plugin_loader.getFormatters():
193 if fmt.FORMAT_NAMES is None or format_name in fmt.FORMAT_NAMES:
194 txt = fmt.render(format_name, txt)
195 if fmt.OUTPUT_FORMAT is not None:
196 format_name = fmt.OUTPUT_FORMAT
197 return txt
198