# HG changeset patch # User Ludovic Chabant # Date 1506915364 25200 # Node ID 1bb704434ee2b74996920346a5f4ec9887bd65f9 # Parent 5713b6a2850d6da01c8720b904668dfc9ae3f724 formatting: Remove segment parts, you can use template tags instead. Segment parts were used to switch formatters insides a given content segment, but that's also achievable with template tags like `pcformat` in Jinja to some degree. It's not totally the same but removing it simplifies the code and improves performance. diff -r 5713b6a2850d -r 1bb704434ee2 piecrust/page.py --- a/piecrust/page.py Fri Sep 29 19:59:19 2017 -0700 +++ b/piecrust/page.py Sun Oct 01 20:36:04 2017 -0700 @@ -222,32 +222,20 @@ class ContentSegment(object): debug_render_func = 'debug_render' - def __init__(self): - self.parts = [] - - def debug_render(self): - return '\n'.join([p.content for p in self.parts]) - - -class ContentSegmentPart(object): def __init__(self, content, fmt=None, offset=-1, line=-1): self.content = content self.fmt = fmt self.offset = offset self.line = line - def __str__(self): - return '%s [%s]' % (self.content, self.fmt or '') + def debug_render(self): + return '[%s] %s' % (self.fmt or '', self.content) def json_load_segments(data): segments = {} - for key, seg_data in data.items(): - seg = ContentSegment() - for p_data in seg_data: - part = ContentSegmentPart(p_data['c'], p_data['f'], p_data['o'], - p_data['l']) - seg.parts.append(part) + for key, sd in data.items(): + seg = ContentSegment(sd['c'], sd['f'], sd['o'], sd['l']) segments[key] = seg return segments @@ -255,11 +243,8 @@ def json_save_segments(segments): data = {} for key, seg in segments.items(): - seg_data = [] - for part in seg.parts: - p_data = {'c': part.content, 'f': part.fmt, 'o': part.offset, - 'l': part.line} - seg_data.append(p_data) + seg_data = { + 'c': seg.content, 'f': seg.fmt, 'o': seg.offset, 'l': seg.line} data[key] = seg_data return data @@ -314,24 +299,33 @@ segment_pattern = re.compile( r"""^\-\-\-\s*(?P\w+)(\:(?P\w+))?\s*\-\-\-\s*$""", re.M) -part_pattern = re.compile( - r"""^<\-\-\s*(?P\w+)\s*\-\->\s*$""", - re.M) -def _count_lines(s): - return len(s.split('\n')) +def _count_lines(txt, start=0, end=-1): + cur = start + line_count = 1 + while True: + nex = txt.find('\n', cur) + if nex < 0: + break + + cur = nex + 1 + line_count += 1 + + if end >= 0 and cur >= end: + break + + return line_count def _string_needs_parsing(txt, offset): txtlen = len(txt) index = txt.find('-', offset) while index >= 0 and index < txtlen - 8: - # Look for a potential `<--format-->` - if index > 0 and txt[index - 1] == '<' and txt[index + 1] == '-': - return True # Look for a potential `---segment---` - if txt[index + 1] == '-' and txt[index + 2] == '-': + if (index > 0 and + txt[index - 1] == '\n' and + txt[index + 1] == '-' and txt[index + 2] == '-'): return True index = txt.find('-', index + 1) return False @@ -339,18 +333,16 @@ def parse_segments(raw, offset=0): # Get the number of lines in the header. - header_lines = _count_lines(raw[:offset].rstrip()) + header_lines = _count_lines(raw, 0, offset) current_line = header_lines # Figure out if we need any parsing. do_parse = _string_needs_parsing(raw, offset) if not do_parse: - seg = ContentSegment() - seg.parts = [ - ContentSegmentPart(raw[offset:], None, offset, current_line)] + seg = ContentSegment(raw[offset:], None, offset, current_line) return {'content': seg} - # Start parsing segments and parts. + # Start parsing segments. matches = list(segment_pattern.finditer(raw, offset)) num_matches = len(matches) if num_matches > 0: @@ -359,70 +351,41 @@ first_offset = matches[0].start() if first_offset > 0: # There's some default content segment at the beginning. - seg = ContentSegment() - seg.parts, current_line = parse_segment_parts( - raw, offset, first_offset, current_line) + seg = ContentSegment( + raw[offset:first_offset], None, offset, current_line) + current_line += _count_lines(seg.content) contents['content'] = seg for i in range(1, num_matches): m1 = matches[i - 1] m2 = matches[i] - seg = ContentSegment() - seg.parts, current_line = parse_segment_parts( - raw, m1.end() + 1, m2.start(), current_line, - m1.group('fmt')) + + cur_seg_start = m1.end() + 1 + cur_seg_end = m2.start() + + seg = ContentSegment( + raw[cur_seg_start:cur_seg_end], + m1.group('fmt'), + cur_seg_start, + current_line) + current_line += _count_lines(seg.content) contents[m1.group('name')] = seg # Handle text past the last match. lastm = matches[-1] - seg = ContentSegment() - seg.parts, current_line = parse_segment_parts( - raw, lastm.end() + 1, len(raw), current_line, - lastm.group('fmt')) + + last_seg_start = lastm.end() + + seg = ContentSegment( + raw[last_seg_start:], + lastm.group('fmt'), + last_seg_start, + current_line) contents[lastm.group('name')] = seg + # No need to count lines for the last one. return contents else: # No segments, just content. - seg = ContentSegment() - seg.parts, current_line = parse_segment_parts( - raw, offset, len(raw), current_line) + seg = ContentSegment(raw[offset:], None, offset, current_line) return {'content': seg} - - -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(part_text, first_part_fmt, start, - 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( - part_text, m1.group('fmt'), m1.end() + 1, - line_offset)) - line_offset += _count_lines(part_text) - - lastm = matches[-1] - part_text = raw[lastm.end() + 1:end] - parts.append(ContentSegmentPart( - part_text, lastm.group('fmt'), lastm.end() + 1, - line_offset)) - - return parts, line_offset - else: - part_text = raw[start:end] - parts = [ContentSegmentPart(part_text, first_part_fmt, start, - line_offset)] - return parts, line_offset - diff -r 5713b6a2850d -r 1bb704434ee2 piecrust/rendering.py --- a/piecrust/rendering.py Fri Sep 29 19:59:19 2017 -0700 +++ b/piecrust/rendering.py Sun Oct 01 20:36:04 2017 -0700 @@ -301,20 +301,17 @@ formatted_segments = {} for seg_name, seg in page.segments.items(): - seg_text = '' - for seg_part in seg.parts: - part_format = seg_part.fmt or format_name - try: - with app.env.stats.timerScope( - engine.__class__.__name__ + '_segment'): - part_text = engine.renderSegmentPart( - page.content_spec, seg_part, page_data) - except TemplatingError as err: - err.lineno += seg_part.line - raise err + try: + with app.env.stats.timerScope( + engine.__class__.__name__ + '_segment'): + seg_text = engine.renderSegment( + page.content_spec, seg, page_data) + except TemplatingError as err: + err.lineno += seg.line + raise err - part_text = format_text(app, part_format, part_text) - seg_text += part_text + seg_format = seg.fmt or format_name + seg_text = format_text(app, seg_format, seg_text) formatted_segments[seg_name] = seg_text if seg_name == 'content': diff -r 5713b6a2850d -r 1bb704434ee2 piecrust/templating/base.py --- a/piecrust/templating/base.py Fri Sep 29 19:59:19 2017 -0700 +++ b/piecrust/templating/base.py Sun Oct 01 20:36:04 2017 -0700 @@ -27,7 +27,7 @@ def initialize(self, app): self.app = app - def renderSegmentPart(self, path, seg_part, data): + def renderSegment(self, path, segment, data): raise NotImplementedError() def renderFile(self, paths, data): diff -r 5713b6a2850d -r 1bb704434ee2 piecrust/templating/jinja/loader.py --- a/piecrust/templating/jinja/loader.py Fri Sep 29 19:59:19 2017 -0700 +++ b/piecrust/templating/jinja/loader.py Sun Oct 01 20:36:04 2017 -0700 @@ -5,11 +5,11 @@ class PieCrustLoader(FileSystemLoader): def __init__(self, searchpath, encoding='utf-8'): super(PieCrustLoader, self).__init__(searchpath, encoding) - self.segment_parts_cache = {} + self.segments_cache = {} def get_source(self, environment, template): - if template.startswith('$part='): - filename, seg_part = self.segment_parts_cache[template] + if template.startswith('$seg='): + filename, seg_content = self.segments_cache[template] mtime = os.path.getmtime(filename) @@ -19,6 +19,6 @@ except OSError: return False - return seg_part, filename, uptodate + return seg_content, filename, uptodate return super(PieCrustLoader, self).get_source(environment, template) diff -r 5713b6a2850d -r 1bb704434ee2 piecrust/templating/jinjaengine.py --- a/piecrust/templating/jinjaengine.py Fri Sep 29 19:59:19 2017 -0700 +++ b/piecrust/templating/jinjaengine.py Sun Oct 01 20:36:04 2017 -0700 @@ -17,17 +17,17 @@ self._jinja_syntax_error = None self._jinja_not_found = None - def renderSegmentPart(self, path, seg_part, data): - if not _string_needs_render(seg_part.content): - return seg_part.content + def renderSegment(self, path, segment, data): + if not _string_needs_render(segment.content): + return segment.content self._ensureLoaded() - part_path = _make_segment_part_path(path, seg_part.offset) - self.env.loader.segment_parts_cache[part_path] = ( - path, seg_part.content) + seg_path = _make_segment_path(path, segment.offset) + self.env.loader.segments_cache[seg_path] = ( + path, segment.content) try: - tpl = self.env.get_template(part_path) + tpl = self.env.get_template(seg_path) except self._jinja_syntax_error as tse: raise self._getTemplatingError(tse, filename=path) except self._jinja_not_found: @@ -145,6 +145,6 @@ return False -def _make_segment_part_path(path, start): - return '$part=%s:%d' % (path, start) +def _make_segment_path(path, start): + return '$seg=%s:%d' % (path, start) diff -r 5713b6a2850d -r 1bb704434ee2 piecrust/templating/pystacheengine.py --- a/piecrust/templating/pystacheengine.py Fri Sep 29 19:59:19 2017 -0700 +++ b/piecrust/templating/pystacheengine.py Sun Oct 01 20:36:04 2017 -0700 @@ -16,10 +16,10 @@ self._not_found_error = None self._pystache_error = None - def renderSegmentPart(self, path, seg_part, data): + def renderSegment(self, path, segment, data): self._ensureLoaded() try: - return self.renderer.render(seg_part.content, data) + return self.renderer.render(segment.content, data) except self._not_found_error as ex: raise TemplateNotFoundError() from ex except self._pystache_error as ex: