35 lines
1.1 KiB
Python
35 lines
1.1 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
import re
|
|
|
|
_VERSION_PATTERN = re.compile(r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$")
|
|
|
|
|
|
@dataclass(frozen=True, order=True)
|
|
class SoftwareVersion:
|
|
major: int
|
|
minor: int
|
|
patch: int
|
|
|
|
@classmethod
|
|
def parse(cls, value: str) -> "SoftwareVersion":
|
|
if not isinstance(value, str):
|
|
raise ValueError("software version must be a string")
|
|
match = _VERSION_PATTERN.fullmatch(value.strip())
|
|
if match is None:
|
|
raise ValueError("software version must use strict MAJOR.MINOR.PATCH digits")
|
|
return cls(*(int(part) for part in match.groups()))
|
|
|
|
def __str__(self) -> str:
|
|
return f"{self.major}.{self.minor}.{self.patch}"
|
|
|
|
|
|
def read_software_version(code_root: Path) -> SoftwareVersion:
|
|
path = Path(code_root) / "VERSION"
|
|
try:
|
|
return SoftwareVersion.parse(path.read_text(encoding="utf-8"))
|
|
except OSError as exc:
|
|
raise RuntimeError(f"software VERSION file is unreadable: {path}") from exc
|