Mercurial > piecrust2
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/piecrust/rendering.py Sun Aug 10 23:43:16 2014 -0700 @@ -0,0 +1,198 @@ +import re +import os.path +import codecs +import logging +from piecrust.data.builder import (DataBuildingContext, build_page_data, + build_layout_data) +from piecrust.environment import PHASE_PAGE_FORMATTING, PHASE_PAGE_RENDERING + + +logger = logging.getLogger(__name__) + + +content_abstract_re = re.compile(r'^<!--\s*(more|(page)?break)\s*-->\s*$', + re.MULTILINE) + + +class PageRenderingError(Exception): + pass + + +class RenderedPage(object): + def __init__(self, page, uri, num=1): + self.page = page + self.uri = uri + self.num = num + self.data = None + self.content = None + self.execution_info = None + + @property + def app(self): + return self.page.app + + +class PageRenderingContext(object): + def __init__(self, page, uri, page_num=1): + self.page = page + self.uri = uri + self.page_num = page_num + self.pagination_source = None + self.pagination_filter = None + self.custom_data = None + self.use_cache = False + self.used_pagination = None + self.used_source_names = set() + self.used_taxonomy_terms = set() + + @property + def app(self): + return self.page.app + + @property + def source_metadata(self): + return self.page.source_metadata + + def reset(self): + self.used_pagination = None + + def setPagination(self, paginator): + if self.used_pagination is not None: + raise Exception("Pagination has already been used.") + self.used_pagination = paginator + + +def render_page(ctx): + eis = ctx.app.env.exec_info_stack + eis.pushPage(ctx.page, PHASE_PAGE_RENDERING, ctx) + try: + page = ctx.page + + # Build the data for both segment and layout rendering. + data_ctx = DataBuildingContext(page, ctx.uri, ctx.page_num) + data_ctx.pagination_source = ctx.pagination_source + data_ctx.pagination_filter = ctx.pagination_filter + page_data = build_page_data(data_ctx) + if ctx.custom_data: + page_data.update(ctx.custom_data) + + # Render content segments. + repo = ctx.app.env.rendered_segments_repository + if repo: + cache_key = '%s:%s' % (ctx.uri, ctx.page_num) + contents = repo.get(cache_key, + lambda: _do_render_page_segments(page, page_data)) + else: + contents = _do_render_page_segments(page, page_data) + + # Render layout. + layout_name = page.config.get('layout') + if layout_name is None: + layout_name = page.source.config.get('default_layout', 'default') + null_names = ['', 'none', 'nil'] + if layout_name not in null_names: + layout_data = build_layout_data(page, page_data, contents) + output = render_layout(layout_name, page, layout_data) + else: + output = contents['content'] + + rp = RenderedPage(page, ctx.uri, ctx.page_num) + rp.data = page_data + rp.content = codecs.encode(output, 'utf8') + rp.execution_info = eis.current_page_info + return rp + finally: + eis.popPage() + + +def render_page_segments(ctx): + repo = ctx.app.env.rendered_segments_repository + if repo: + cache_key = '%s:%s' % (ctx.uri, ctx.page_num) + return repo.get(cache_key, + lambda: _do_render_page_segments_from_ctx(ctx)) + + return _do_render_page_segments_from_ctx(ctx) + + +def _do_render_page_segments_from_ctx(ctx): + eis = ctx.app.env.exec_info_stack + eis.pushPage(ctx.page, PHASE_PAGE_FORMATTING, ctx) + try: + data_ctx = DataBuildingContext(ctx.page, ctx.uri, ctx.page_num) + page_data = build_page_data(data_ctx) + return _do_render_page_segments(ctx.page, page_data) + finally: + eis.popPage() + + +def _do_render_page_segments(page, page_data): + app = page.app + engine_name = page.config.get('template_engine') + format_name = page.config.get('format') + + engine = get_template_engine(app, engine_name) + if engine is None: + raise PageRenderingError("Can't find template engine '%s'." % engine_name) + + formatted_content = {} + for seg_name, seg in page.raw_content.iteritems(): + seg_text = u'' + for seg_part in seg.parts: + part_format = seg_part.fmt or format_name + part_text = engine.renderString(seg_part.content, page_data, + filename=page.path, line_offset=seg_part.line) + part_text = format_text(app, part_format, part_text) + seg_text += part_text + formatted_content[seg_name] = seg_text + + if seg_name == 'content': + m = content_abstract_re.search(seg_text) + if m: + offset = m.start() + content_abstract = seg_text[:offset] + formatted_content['content.abstract'] = content_abstract + + return formatted_content + + +def render_layout(layout_name, page, layout_data): + names = layout_name.split(',') + default_template_engine = get_template_engine(page.app, None) + default_exts = ['.' + e.lstrip('.') for e in default_template_engine.EXTENSIONS] + full_names = [] + for name in names: + if '.' not in name: + full_names.append(name + '.html') + for ext in default_exts: + full_names.append(name + ext) + else: + full_names.append(name) + + _, engine_name = os.path.splitext(full_names[0]) + engine_name = engine_name.lstrip('.') + engine = get_template_engine(page.app, engine_name) + if engine is None: + raise PageRenderingError("No such template engine: %s" % engine_name) + output = engine.renderFile(full_names, layout_data) + return output + + +def get_template_engine(app, engine_name): + if engine_name == 'html': + engine_name = None + engine_name = engine_name or app.config.get('site/default_template_engine') + for engine in app.plugin_loader.getTemplateEngines(): + if engine_name in engine.ENGINE_NAMES: + return engine + return None + +def format_text(app, format_name, txt): + format_name = format_name or app.config.get('site/default_format') + for fmt in app.plugin_loader.getFormatters(): + if fmt.FORMAT_NAMES is None or format_name in fmt.FORMAT_NAMES: + txt = fmt.render(format_name, txt) + if fmt.OUTPUT_FORMAT is not None: + format_name = fmt.OUTPUT_FORMAT + return txt +