Source code for semantic_release.version.declarations.toml

from __future__ import annotations

from logging import getLogger
from pathlib import Path
from typing import Any, Dict, cast

import tomlkit
from deprecated.sphinx import deprecated
from dotty_dict import Dotty

from semantic_release.cli.util import noop_report
from semantic_release.version.declarations.enum import VersionStampType
from semantic_release.version.declarations.i_version_replacer import IVersionReplacer
from semantic_release.version.version import Version

# globals
log = getLogger(__name__)


[docs] class TomlVersionDeclaration(IVersionReplacer): def __init__( self, path: Path | str, search_text: str, stamp_format: VersionStampType ) -> None: self._content: str | None = None self._path = Path(path).resolve() self._stamp_format = stamp_format self._search_text = search_text @property def content(self) -> str: """A cached property that stores the content of the configured source file.""" if self._content is None: log.debug("No content stored, reading from source file %s", self._path) if not self._path.exists(): raise FileNotFoundError(f"path {self._path!r} does not exist") self._content = self._path.read_text() return self._content @content.deleter def content(self) -> None: self._content = None
[docs] @deprecated( version="9.20.0", reason="Function is unused and will be removed in a future release", ) def parse(self) -> set[Version]: # pragma: no cover """Look for the version in the source content""" content = self._load() maybe_version: str = content.get(self._search_text) # type: ignore[return-value] if maybe_version is not None: log.debug( "Found a key %r that looks like a version (%r)", self._search_text, maybe_version, ) valid_version = Version.parse(maybe_version) return {valid_version} if valid_version else set() # Maybe in future raise error if not found? return set()
[docs] def replace(self, new_version: Version) -> str: """ Replace the version in the source content with `new_version`, and return the updated content. """ content = self._load() if self._search_text in content: log.info( "found %r in source file contents, replacing with %s", self._search_text, new_version, ) content[self._search_text] = ( new_version.as_tag() if self._stamp_format == VersionStampType.TAG_FORMAT else str(new_version) ) return tomlkit.dumps(cast(Dict[str, Any], content))
def _load(self) -> Dotty: """Load the content of the source file into a Dotty for easier searching""" return Dotty(tomlkit.loads(self.content))
[docs] def update_file_w_version( self, new_version: Version, noop: bool = False ) -> Path | None: if noop: if not self._path.exists(): noop_report( f"FILE NOT FOUND: cannot stamp version in non-existent file {self._path!r}", ) return None if self._search_text not in self._load(): noop_report( f"VERSION PATTERN NOT FOUND: no version to stamp in file {self._path!r}", ) return None return self._path new_content = self.replace(new_version) if new_content == self.content: return None self._path.write_text(new_content) del self.content return self._path
[docs] @classmethod def from_string_definition(cls, replacement_def: str) -> TomlVersionDeclaration: """ create an instance of self from a string representing one item of the "version_toml" list in the configuration """ parts = replacement_def.split(":", maxsplit=2) if len(parts) <= 1: raise ValueError( f"Invalid TOML 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, search_text, version_type = parts 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, search_text, stamp_type)