Source code for app.util.helpers.script_version

# SPDX-License-Identifier: GPLv3-or-later
# Copyright © 2025 pygaindalf Rui Pinheiro

"""Version and revision utilities for pygaindalf.

Provides version string and git revision helpers.
"""

import os
import pathlib
import subprocess
import tomllib

from functools import cached_property
from typing import TYPE_CHECKING, Self, override

from .script_info import get_script_home


if TYPE_CHECKING:
    from collections.abc import Sequence


# Constants
GIT_ABBREV = 9  # Abbreviation length for git revision


# ScriptVersion class
[docs] class ScriptVersion: _instance = None
[docs] def __new__(cls, *args, **kwargs) -> Self: if not cls._instance: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance
[docs] def __init__(self) -> None: pass
def _minimal_ext_cmd(self, cmd: Sequence[str]) -> str | None: # construct minimal environment env = {} for k in ["SYSTEMROOT", "PATH"]: v = os.environ.get(k) if v is not None: env[k] = v # LANGUAGE is used on win32 env["LANGUAGE"] = "C" env["LANG"] = "C" env["LC_ALL"] = "C" _out = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env).communicate() # noqa: S603 if _out[1]: return None return _out[0].strip().decode("ascii")
[docs] @cached_property def git_revision(self) -> str | None: """Get the current git revision string, if available. Returns: str or None: The git revision string, or None if not available. """ try: return self._minimal_ext_cmd(["git", "describe", "--exclude", "*", "--always", "--broken", "--dirty", f"--abbrev={GIT_ABBREV}"]) except OSError: return None
@property def version(self) -> str: pyproject_path = get_script_home() / "pyproject.toml" try: with pathlib.Path(pyproject_path).open("rb") as f: data = tomllib.load(f) return data["project"]["version"] except OSError: return "unknown"
[docs] @cached_property def version_string(self) -> str: """Get the full version string, including git revision if available. Returns: str: The version string. """ ver = self.version git_revision = self.git_revision if git_revision: ver += f"-{git_revision}" return ver
@override def __str__(self) -> str: return self.version_string @override def __repr__(self) -> str: return f"<ScriptVersion: {self!s}>"
def __getattr__(key: str) -> str: attr = getattr(ScriptVersion(), key, None) if not isinstance(attr, str): msg = f"ScriptVersion has no attribute '{key}'" raise AttributeError(msg) # noqa: TRY004 return attr