Source code for proclamation.render

#!/usr/bin/env python3 -i
# Copyright 2019-2020, Collabora, Ltd. and the Proclamation contributors
#
# SPDX-License-Identifier: Apache-2.0
#
# Original author: Rylie Pavlik <rylie.pavlik@collabora.com>
"""Use Jinja2 to render an addition to the CHANGES file.

This should be the only file that needs to import Jinja2,
so if you want to do something else for templating, you can.
"""

import logging
from datetime import date
from io import StringIO

from jinja2 import (
    ChoiceLoader,
    Environment,
    FileSystemLoader,
    PackageLoader,
    TemplateSyntaxError,
)


[docs] def render_template(project, project_version, release_date=None): """Render the CHANGES template for a project. Returns the rendered text. """ log = logging.getLogger(__name__) search_path = [project.default_base] log.debug( "Template search path is %s, followed by built-in templates.", str(search_path) ) loader = ChoiceLoader( [FileSystemLoader(search_path), PackageLoader("proclamation", "templates")] ) if release_date is None: release_date = date.today().isoformat().strip() env = Environment(autoescape=False, loader=loader) try: template = env.get_template(project.template) except TemplateSyntaxError as e: print( f"template syntax error during parse: {e.filename}:{e.lineno} " + f"error: {e.message}" ) raise RuntimeError("Jinja2 template syntax error") from e log.info("Loaded template %s from %s", project.template, template.filename) try: result = template.render( { "project_name": project.name, "project_version": project_version, "date": release_date, "sections": project.sections, "base_url": project.settings.base_url, } ) # ensure it ends with a blank line while not result.endswith("\n\n"): result += "\n" return result except TemplateSyntaxError as e: print( f"template syntax error during render: {e.filename}:{e.lineno} " + f"error: {e.message}" ) raise RuntimeError("Jinja2 template syntax error") from e
[docs] def split_changelog_contents(project_settings, contents): """ Split the contents of a changelog file based on the insert point pattern. Two strings are returned: the content before the insert point, and the content after the insert point, including the line that matched the pattern. """ io = StringIO(contents) before = [] after = [] found_first_line = False insert_point_re = project_settings.insert_point_re for line in io: if found_first_line: after.append(line) continue if insert_point_re.match(line): # OK, we hadn't found it, but now we did. found_first_line = True after.append(line) continue # haven't found it yet before.append(line) log = logging.getLogger(__name__) log.info( "%d lines before the insert point in existing file, %d after", len(before), len(after), ) if not found_first_line: log.warning( "Did not find a line that matches the " "insert_point_pattern, there may be an error in " "your settings" ) return "".join(before), "".join(after)
[docs] def get_split_changelog_file(project_settings): """Load configured changelog file and return the content for before and after our new entry. We make a minimal default if we can't open the original. """ fn = project_settings.news_filename try: with open(fn, encoding="utf-8") as fp: content = fp.read() return split_changelog_contents(project_settings, content) except FileNotFoundError: # Default "empty" changelog file return "# Changelog\n\n", ""
[docs] def combine_changelogs(before, after, project, project_version, release_date): """Return the text of the updated, complete changelog file given pre-split contents.""" new_portion = render_template(project, project_version, release_date) first_new_line = new_portion.split("\n", 1)[0] first_after_line = after.split("\n", 1)[0] log = logging.getLogger(__name__) log.info("First line of insert point: %s", first_after_line.rstrip()) if first_after_line.rstrip() == first_new_line.rstrip(): raise RuntimeError( "Your new changelog entry has the same heading as the most recent " "existing entry! Probably duplicating version numbers." ) return "".join((before, new_portion, after))
[docs] def generate_updated_changelog(project, project_version, release_date=None): """Return the text of the updated, complete changelog file.""" before, after = get_split_changelog_file(project.settings) return combine_changelogs(before, after, project, project_version, release_date)