Source code for ravenpy.utilities.publishing

"""Publishing utilities for RavenPy."""

from __future__ import annotations
import os
import re
from io import StringIO
from pathlib import Path
from typing import TextIO


[docs] def publish_release_notes( style: str = "md", file: os.PathLike[str] | StringIO | TextIO | None = None, changes: str | os.PathLike[str] | None = None, ) -> str | None: """ Format release notes in Markdown or ReStructuredText. Parameters ---------- style : {"rst", "md"} Use ReStructuredText formatting or Markdown. Default: Markdown. file : {os.PathLike, StringIO, TextIO}, optional If provided, prints to the given file-like object. Otherwise, returns a string. changes : str or os.PathLike[str], optional If provided, manually points to the file where the changelog can be found. Assumes a relative path otherwise. Returns ------- str, optional If `file` not provided, the formatted release notes. Notes ----- This function is used solely for development and packaging purposes. """ if isinstance(changes, str | Path): changes_file = Path(changes).absolute() else: changes_file = Path(__file__).absolute().parents[3].joinpath("CHANGELOG.rst") if not changes_file.exists(): raise FileNotFoundError("Changelog file not found in RavenPy folder tree.") with Path(changes_file).open(encoding="utf-8") as hf: changes = hf.read() if style == "rst": hyperlink_replacements = { r":issue:`([0-9]+)`": r"`GH/\1 <https://github.com/CSHS-CWRA/RavenPy/issues/\1>`_", r":pull:`([0-9]+)`": r"`PR/\1 <https://github.com/CSHS-CWRA/RavenPy/pull/\>`_", r":user:`([a-zA-Z0-9_.-]+)`": r"`@\1 <https://github.com/\1>`_", } elif style == "md": hyperlink_replacements = { r":issue:`([0-9]+)`": r"[GH/\1](https://github.com/CSHS-CWRA/RavenPy/issues/\1)", r":pull:`([0-9]+)`": r"[PR/\1](https://github.com/CSHS-CWRA/RavenPy/pull/\1)", r":user:`([a-zA-Z0-9_.-]+)`": r"[@\1](https://github.com/\1)", } else: msg = f"Formatting style not supported: {style}" raise NotImplementedError(msg) for search, replacement in hyperlink_replacements.items(): changes = re.sub(search, replacement, changes) if style == "md": changes = changes.replace("=========\nChangelog\n=========", "# Changelog") titles = {r"\n(.*?)\n([\-]{1,})": "-", r"\n(.*?)\n([\^]{1,})": "^"} for title_expression, level in titles.items(): found = re.findall(title_expression, changes) for grouping in found: fixed_grouping = str(grouping[0]).replace("(", r"\(").replace(")", r"\)") search = rf"({fixed_grouping})\n([\{level}]{'{' + str(len(grouping[1])) + '}'})" replacement = f"{'##' if level == '-' else '###'} {grouping[0]}" changes = re.sub(search, replacement, changes) link_expressions = r"[\`]{1}([\w\s]+)\s<(.+)>`\_" found = re.findall(link_expressions, changes) for grouping in found: search = rf"`{grouping[0]} <.+>`\_" replacement = f"[{str(grouping[0]).strip()}]({grouping[1]})" changes = re.sub(search, replacement, changes) if not file: return changes if isinstance(file, Path | os.PathLike): with Path(file).open("w", encoding="utf-8") as f: print(changes, file=f) else: print(changes, file=file) return None