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