Source code for semantic_release.changelog.context

from __future__ import annotations

import logging
import os
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from re import compile as regexp
from typing import TYPE_CHECKING, Any, Callable, Literal

if TYPE_CHECKING:
    from jinja2 import Environment

    from semantic_release.changelog.release_history import Release, ReleaseHistory
    from semantic_release.hvcs._base import HvcsBase
    from semantic_release.version.version import Version


[docs] @dataclass class ReleaseNotesContext: repo_name: str repo_owner: str hvcs_type: str version: Version release: Release filters: tuple[Callable[..., Any], ...] = ()
[docs] def bind_to_environment(self, env: Environment) -> Environment: env_globals = dict( filter(lambda k_v: k_v[0] != "filters", self.__dict__.items()) ) for g, v in env_globals.items(): env.globals[g] = v for f in self.filters: env.filters[f.__name__] = f return env
[docs] class ChangelogMode(Enum): INIT = "init" UPDATE = "update"
[docs] @dataclass class ChangelogContext: repo_name: str repo_owner: str hvcs_type: str history: ReleaseHistory changelog_mode: Literal["update", "init"] prev_changelog_file: str changelog_insertion_flag: str filters: tuple[Callable[..., Any], ...] = ()
[docs] def bind_to_environment(self, env: Environment) -> Environment: env.globals["context"] = self env.globals["ctx"] = self for f in self.filters: env.filters[f.__name__] = f return env
[docs] def make_changelog_context( hvcs_client: HvcsBase, release_history: ReleaseHistory, mode: ChangelogMode, prev_changelog_file: Path, insertion_flag: str, ) -> ChangelogContext: return ChangelogContext( repo_name=hvcs_client.repo_name, repo_owner=hvcs_client.owner, history=release_history, changelog_mode=mode.value, changelog_insertion_flag=insertion_flag, prev_changelog_file=str(prev_changelog_file), hvcs_type=hvcs_client.__class__.__name__.lower(), filters=( *hvcs_client.get_changelog_context_filters(), read_file, convert_md_to_rst, autofit_text_width, ), )
[docs] def read_file(filepath: str) -> str: try: if not filepath: raise FileNotFoundError("No file path provided") # noqa: TRY301 with Path(filepath).open(newline=os.linesep) as rfd: return rfd.read() except FileNotFoundError as err: logging.warning(err) return ""
[docs] def convert_md_to_rst(md_content: str) -> str: rst_content = md_content replacements = { # Replace markdown doubleunder bold with rst bold "bold-inline": (regexp(r"(?<=\s)__(.+?)__(?=\s|$)"), r"**\1**"), # Replace markdown italics with rst italics "italic-inline": (regexp(r"(?<=\s)_([^_].+?[^_])_(?=\s|$)"), r"*\1*"), # Replace markdown bullets with rst bullets "bullets": (regexp(r"^(\s*)-(\s)"), r"\1*\2"), # Replace markdown inline raw content with rst inline raw content "raw-inline": (regexp(r"(?<=\s)(`[^`]+`)(?![`_])"), r"`\1`"), } for pattern, replacement in replacements.values(): rst_content = pattern.sub(replacement, rst_content) return rst_content
[docs] def autofit_text_width(text: str, maxwidth: int = 100, indent_size: int = 0) -> str: """Format the description text to fit within a specified width""" input_text = text.strip() if len(input_text) <= maxwidth: # If the text is already within the maxwidth, return immediately return input_text indent = " " * indent_size formatted_description = [] # Re-format text to fit within the maxwidth for paragraph in input_text.split("\n\n"): formatted_paragraph = [] # Split the paragraph into words with no empty strings words = list( filter( None, paragraph.replace("\r", "").replace("\n", " ").strip().split(" ") ) ) # Initialize the line for each paragraph line = words[0] next_line = "" for word in words[1:]: # Check if the current line + the next word (and a space) will fit within the maxwidth # If it does, then update the current line next_line = f"{line} {word}" if len(next_line) <= maxwidth: line = next_line continue # Add the current line to the paragraph and start a new line formatted_paragraph.append(line) line = f"{indent}{word}" # Store the last line in the paragraph since it hasn't reached the maxwidth yet formatted_paragraph.append(line) # formatted_description.append(str.join("\n", formatted_paragraph)) # Print the formatted description return str.join("\n\n", formatted_description).strip()