# 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 subprocess
import tomllib
from functools import cached_property
from typing import override
from .script_info import get_script_home
# Constants
GIT_ABBREV = 9 # Abbreviation length for git revision
# ScriptVersion class
[docs]
class ScriptVersion:
_instance = None
[docs]
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(ScriptVersion, cls).__new__(cls, *args, **kwargs)
return cls._instance
[docs]
def __init__(self):
pass
def _minimal_ext_cmd(self, cmd) -> 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()
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 = os.path.join(get_script_home(), "pyproject.toml")
try:
with open(pyproject_path, "rb") as f:
data = tomllib.load(f)
return data["project"]["version"]
except Exception:
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: {str(self)}>"
def __getattr__(key : str) -> str:
attr = getattr(ScriptVersion(), key, None)
if not isinstance(attr, str):
raise AttributeError(f"ScriptVersion has no attribute '{key}'")
return attr