Source code for semantic_release.version.declarations.file

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from deprecated.sphinx import deprecated

from semantic_release.globals import logger
from semantic_release.version.declarations.enum import VersionStampType
from semantic_release.version.declarations.i_version_replacer import IVersionReplacer

if TYPE_CHECKING:  # pragma: no cover
    from semantic_release.version.version import Version


[docs] class FileVersionDeclaration(IVersionReplacer): """ IVersionReplacer implementation that replaces the entire file content with the version string. This is useful for files that contain only a version number, such as VERSION files or similar single-line version storage files. """ def __init__(self, path: Path | str, stamp_format: VersionStampType) -> None: self._content: str | None = None self._path = Path(path).resolve() self._stamp_format = stamp_format @property def content(self) -> str: """A cached property that stores the content of the configured source file.""" if self._content is None: logger.debug("No content stored, reading from source file %s", self._path) if not self._path.exists(): logger.debug( f"path {self._path!r} does not exist, assuming empty content" ) self._content = "" else: self._content = self._path.read_text() return self._content @content.deleter def content(self) -> None: self._content = None
[docs] @deprecated( version="10.6.0", reason="Function is unused and will be removed in a future release", ) def parse(self) -> set[Version]: raise NotImplementedError # pragma: no cover
[docs] def replace(self, new_version: Version) -> str: """ Replace the file content with the new version string. :param new_version: The new version number as a `Version` instance :return: The new content (just the version string) """ new_content = ( new_version.as_tag() if self._stamp_format == VersionStampType.TAG_FORMAT else str(new_version) ) logger.debug( "Replacing entire file content: path=%r old_content=%r new_content=%r", self._path, self.content.strip(), new_content, ) return new_content
[docs] def update_file_w_version( self, new_version: Version, noop: bool = False ) -> Path | None: if noop: if not self._path.exists(): logger.warning( f"FILE NOT FOUND: file '{self._path}' does not exist but it will be created" ) return self._path new_content = self.replace(new_version) if new_content == self.content.strip(): return None self._path.write_text(f"{new_content}\n") del self.content return self._path
[docs] @classmethod def from_string_definition(cls, replacement_def: str) -> FileVersionDeclaration: """ Create an instance of self from a string representing one item of the "version_variables" list in the configuration. This method expects a definition in the format: "file:*:format_type" where: - file is the path to the file - * is the literal asterisk character indicating file replacement - format_type is either "nf" (number format) or "tf" (tag format) """ parts = replacement_def.split(":", maxsplit=2) if len(parts) <= 1: raise ValueError( f"Invalid replacement definition {replacement_def!r}, missing ':'" ) if len(parts) == 2: # apply default version_type of "number_format" (ie. "1.2.3") parts = [*parts, VersionStampType.NUMBER_FORMAT.value] path, pattern, version_type = parts # Validate that the pattern is exactly "*" if pattern != "*": raise ValueError( f"Invalid pattern {pattern!r} for FileVersionDeclaration, expected '*'" ) try: stamp_type = VersionStampType(version_type) except ValueError as err: raise ValueError( str.join( " ", [ "Invalid stamp type, must be one of:", str.join(", ", [e.value for e in VersionStampType]), ], ) ) from err return cls(path, stamp_type)