Mercurial > piecrust2
changeset 128:28444014ce7d
Fix error reporting and counting of lines.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Fri, 14 Nov 2014 22:49:50 +0100 |
parents | bc63dc20baa0 |
children | 3080b6d02f40 |
files | piecrust/page.py piecrust/rendering.py piecrust/serving.py piecrust/templating/base.py piecrust/templating/jinjaengine.py |
diffstat | 5 files changed, 143 insertions(+), 38 deletions(-) [+] |
line wrap: on
line diff
--- a/piecrust/page.py Fri Nov 14 22:47:18 2014 +0100 +++ b/piecrust/page.py Fri Nov 14 22:49:50 2014 +0100 @@ -252,7 +252,16 @@ re.M) +def _count_lines(s): + return len(s.split('\n')) + + def parse_segments(raw, offset=0): + # Get the number of lines in the header. + header_lines = _count_lines(raw[:offset].rstrip()) + current_line = header_lines + + # Start parsing. matches = list(segment_pattern.finditer(raw, offset)) num_matches = len(matches) if num_matches > 0: @@ -262,59 +271,65 @@ if first_offset > 0: # There's some default content segment at the beginning. seg = ContentSegment() - seg.parts = parse_segment_parts(raw, offset, first_offset) + seg.parts, current_line = parse_segment_parts( + raw, offset, first_offset, current_line) contents['content'] = seg for i in range(1, num_matches): m1 = matches[i - 1] m2 = matches[i] seg = ContentSegment() - seg.parts = parse_segment_parts(raw, m1.end() + 1, - m2.start(), m1.group('fmt')) + seg.parts, current_line = parse_segment_parts( + raw, m1.end() + 1, m2.start(), current_line, + m1.group('fmt')) contents[m1.group('name')] = seg # Handle text past the last match. lastm = matches[-1] seg = ContentSegment() - seg.parts = parse_segment_parts(raw, lastm.end() + 1, - len(raw), lastm.group('fmt')) + seg.parts, current_line = parse_segment_parts( + raw, lastm.end() + 1, len(raw), current_line, + lastm.group('fmt')) contents[lastm.group('name')] = seg return contents else: # No segments, just content. seg = ContentSegment() - seg.parts = parse_segment_parts(raw, offset, len(raw)) + seg.parts, current_line = parse_segment_parts( + raw, offset, len(raw), current_line) return {'content': seg} -def parse_segment_parts(raw, start, end, first_part_fmt=None): +def parse_segment_parts(raw, start, end, line_offset, first_part_fmt=None): matches = list(part_pattern.finditer(raw, start, end)) num_matches = len(matches) if num_matches > 0: parts = [] # First part, before the first format change. + part_text = raw[start:matches[0].start()] parts.append( - ContentSegmentPart(raw[start:matches[0].start()], - first_part_fmt, - start)) + ContentSegmentPart(part_text, first_part_fmt, line_offset)) + line_offset += _count_lines(part_text) for i in range(1, num_matches): m1 = matches[i - 1] m2 = matches[i] + part_text = raw[m1.end() + 1:m2.start()] parts.append( ContentSegmentPart( - raw[m1.end() + 1:m2.start()], - m1.group('fmt'), - m1.end() + 1)) + part_text, m1.group('fmt'), line_offset)) + line_offset += _count_lines(part_text) lastm = matches[-1] - parts.append(ContentSegmentPart(raw[lastm.end() + 1:end], - lastm.group('fmt'), - lastm.end() + 1)) + part_text = raw[lastm.end() + 1:end] + parts.append(ContentSegmentPart( + part_text, lastm.group('fmt'), line_offset)) - return parts + return parts, line_offset else: - return [ContentSegmentPart(raw[start:end], first_part_fmt)] + part_text = raw[start:end] + parts = [ContentSegmentPart(part_text, first_part_fmt, line_offset)] + return parts, line_offset
--- a/piecrust/rendering.py Fri Nov 14 22:47:18 2014 +0100 +++ b/piecrust/rendering.py Fri Nov 14 22:49:50 2014 +0100 @@ -4,6 +4,7 @@ from piecrust.data.builder import (DataBuildingContext, build_page_data, build_layout_data) from piecrust.sources.base import PageSource +from piecrust.templating.base import TemplatingError from piecrust.uriutil import get_slug @@ -164,8 +165,14 @@ seg_text = '' 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) + try: + part_text = engine.renderString( + seg_part.content, page_data, + filename=page.path) + except TemplatingError as err: + err.lineno += seg_part.line + raise err + part_text = format_text(app, part_format, part_text) seg_text += part_text formatted_content[seg_name] = seg_text @@ -211,12 +218,19 @@ return engine return None -def format_text(app, format_name, txt): +def format_text(app, format_name, txt, exact_format=False): + if exact_format and not format_name: + raise Exception("You need to specify a format name.") + + format_count = 0 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) + format_count += 1 if fmt.OUTPUT_FORMAT is not None: format_name = fmt.OUTPUT_FORMAT + if exact_format and format_count == 0: + raise Exception("No such format: %s" % format_name) return txt
--- a/piecrust/serving.py Fri Nov 14 22:47:18 2014 +0100 +++ b/piecrust/serving.py Fri Nov 14 22:49:50 2014 +0100 @@ -7,7 +7,7 @@ import logging import io from werkzeug.exceptions import (NotFound, MethodNotAllowed, - InternalServerError) + InternalServerError, HTTPException) from werkzeug.serving import run_simple from werkzeug.wrappers import Request, Response from werkzeug.wsgi import wrap_file @@ -129,19 +129,23 @@ try: response = self._try_serve_page(app, environ, request) return response(environ, start_response) + except HTTPException as ex: + raise except (RouteNotFoundError, SourceNotFoundError) as ex: logger.exception(ex) raise NotFound() except Exception as ex: - logger.exception(ex) if app.debug: + logger.exception(ex) raise - raise InternalServerError() + msg = str(ex) + logger.error(msg) + raise InternalServerError(msg) def _try_serve_asset(self, app, environ, request): logger.debug("Searching for asset with path: %s" % request.path) rel_req_path = request.path.lstrip('/').replace('/', os.sep) - entry = self._asset_record.findEntry(rel_req_path) + entry = self._asset_record.previous.findEntry(rel_req_path) if entry is None: return None @@ -246,6 +250,15 @@ rendered_page = render_page(render_ctx) rp_content = rendered_page.content + if taxonomy is not None: + paginator = rendered_page.data.get('pagination') + if (paginator and paginator.is_loaded and + len(paginator.items) == 0): + message = ("This URL matched a route for taxonomy '%s' but " + "no pages have been found to have it. This page " + "won't be generated by a bake." % taxonomy.name) + raise NotFound(message) + if entry is None: entry = ServeRecordPageEntry(req_path, page_num) self._page_record.addEntry(entry) @@ -308,10 +321,13 @@ def _handle_error(self, exception, environ, start_response): path = 'error' if isinstance(exception, NotFound): - path = '404' + path += '404' + description = str(exception) + if isinstance(exception, HTTPException): + description = exception.description env = Environment(loader=ErrorMessageLoader()) template = env.get_template(path) - context = {'details': str(exception)} + context = {'details': description} response = Response(template.render(context), mimetype='text/html') return response(environ, start_response)
--- a/piecrust/templating/base.py Fri Nov 14 22:47:18 2014 +0100 +++ b/piecrust/templating/base.py Fri Nov 14 22:49:50 2014 +0100 @@ -4,6 +4,23 @@ pass +class TemplatingError(Exception): + def __init__(self, message, filename=None, lineno=-1): + super(TemplatingError, self).__init__() + self.message = message + self.filename = filename + self.lineno = lineno + + def __str__(self): + msg = '' + if self.filename: + msg += self.filename + if self.lineno >= 0: + msg += ', line %d' % self.lineno + msg += ': ' + self.message + return msg + + class TemplateEngine(object): EXTENSIONS = []
--- a/piecrust/templating/jinjaengine.py Fri Nov 14 22:47:18 2014 +0100 +++ b/piecrust/templating/jinjaengine.py Fri Nov 14 22:49:50 2014 +0100 @@ -1,5 +1,6 @@ import re import time +import os.path import logging import threading import strict_rfc3339 @@ -15,7 +16,8 @@ from piecrust.data.paginator import Paginator from piecrust.rendering import format_text from piecrust.routing import CompositeRouteFunction -from piecrust.templating.base import TemplateEngine, TemplateNotFoundError +from piecrust.templating.base import (TemplateEngine, TemplateNotFoundError, + TemplatingError) from piecrust.uriutil import multi_replace, get_first_sub_uri @@ -30,18 +32,20 @@ def __init__(self): self.env = None - def renderString(self, txt, data, filename=None, line_offset=0): + def renderString(self, txt, data, filename=None): self._ensureLoaded() - tpl = self.env.from_string(txt) + + try: + tpl = self.env.from_string(txt) + except TemplateSyntaxError as tse: + raise self._getTemplatingError(tse, filename=filename) + except TemplateNotFound: + raise TemplateNotFoundError() + try: return tpl.render(data) except TemplateSyntaxError as tse: - tse.lineno += line_offset - if filename: - tse.filename = filename - import sys - _, __, traceback = sys.exc_info() - raise tse.with_traceback(traceback) + raise self._getTemplatingError(tse) def renderFile(self, paths, data): self._ensureLoaded() @@ -51,11 +55,25 @@ try: tpl = self.env.get_template(p) break + except TemplateSyntaxError as tse: + raise self._getTemplatingError(tse) except TemplateNotFound: pass + if tpl is None: raise TemplateNotFoundError() - return tpl.render(data) + + try: + return tpl.render(data) + except TemplateSyntaxError as tse: + raise self._getTemplatingError(tse) + + def _getTemplatingError(self, tse, filename=None): + filename = tse.filename or filename + if filename and os.path.isabs(filename): + filename = os.path.relpath(filename, self.env.app.root_dir) + err = TemplatingError(str(tse), filename, tse.lineno) + raise err from tse def _ensureLoaded(self): if self.env: @@ -74,6 +92,9 @@ PieCrustHighlightExtension, PieCrustCacheExtension, PieCrustSpacelessExtension] + twig_compatibility_mode = self.app.config.get('jinja/twig_compatibility') + if twig_compatibility_mode is None or twig_compatibility_mode is True: + extensions.append(PieCrustFormatExtension) if autoescape: extensions.append('jinja2.ext.autoescape') self.env = PieCrustEnvironment( @@ -210,6 +231,28 @@ return time.strftime(fmt, time.localtime(value)) +class PieCrustFormatExtension(Extension): + tags = set(['pcformat']) + + def __init__(self, environment): + super(PieCrustFormatExtension, self).__init__(environment) + + def parse(self, parser): + lineno = next(parser.stream).lineno + args = [parser.parse_expression()] + body = parser.parse_statements(['name:endpcformat'], drop_needle=True) + return CallBlock(self.call_method('_format', args), + [], [], body).set_lineno(lineno) + + def _format(self, format_name, caller=None): + body = caller() + text = format_text(self.environment.app, + format_name, + Markup(body.rstrip()).unescape(), + exact_format=True) + return text + + class PieCrustHighlightExtension(Extension): tags = set(['highlight', 'geshi']) @@ -240,7 +283,7 @@ drop_needle=True) return CallBlock(self.call_method('_highlight', args, kwargs), - [], [], body).set_lineno(lineno) + [], [], body).set_lineno(lineno) def _highlight(self, lang, line_numbers=False, use_classes=False, css_class=None, css_id=None, caller=None):