Something went wrong. Try again.
Coding agent with a memory of all things
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136#!/usr/bin/env python3"""The version of the next eukhe release (the model: bend/semver.bend).
Usage: next_version.py [--workspace-version X.Y.Z] < tags
Reads the repository's tag names on stdin, one per line, and prints therelease's version, tag and title as `key=value` lines (GITHUB_OUTPUTformat):
version=0.10.4 tag=eukhe-v0.10.4 title=Eukhe v0.10.4
The rule:- A version is major.minor.patch, each a decimal number without leading zeros ("0", "7", "10"; never "", "07", "+7"), and nothing else.- The series is the workspace version of Cargo.toml ([workspace.package] version, or --workspace-version), which must be X.Y.0.- The next release is X.Y.P: P is 1 + the highest patch among the tags `eukhe-v<X>.<Y>.<P>`, 0 when there is none. Every other tag (other series, the upstream `v*` tags, `eukhe-v0.9.9-main.4`) does not count.- The release must be newer than every version tag, of any series (a tag of a newer series means Cargo.toml went back): else it fails.
LAWS.bend section release (section R1) states the rule's laws; test_next_version.py runsthis script and the Bend model on the same cases."""
from __future__ import annotations
import argparseimport reimport sysimport tomllibfrom pathlib import Path
ROOT = Path(__file__).resolve().parents[2]TAG_PREFIX = "eukhe-v"TITLE_PREFIX = "Eukhe v"NUMBER = r"(0|[1-9][0-9]*)"VERSION = re.compile(rf"{NUMBER}\.{NUMBER}\.{NUMBER}")
Version = tuple[int, int, int]
class ReleaseError(Exception): """The commit has no release (the message says why)."""
def parse(text: str) -> Version | None: """The version `text` names, None for any other text.""" match = VERSION.fullmatch(text) if match is None: return None major, minor, patch = (int(field) for field in match.groups()) return (major, minor, patch)
def show(version: Version) -> str: return ".".join(str(field) for field in version)
def tag_version(tag: str) -> Version | None: """The version a tag names (`eukhe-v` + version), None for any other.""" if not tag.startswith(TAG_PREFIX): return None return parse(tag[len(TAG_PREFIX):])
def next_in_series(series: Version, tags: list[str]) -> Version: """X.Y.P: 1 + the highest patch among the series' tags, 0 when none.""" major, minor, _ = series patches = [ version[2] for version in map(tag_version, tags) if version is not None and version[:2] == (major, minor) ] return (major, minor, max(patches) + 1 if patches else 0)
def release(workspace_version: str, tags: list[str]) -> Version: """The release of a commit with this workspace version and these tags.""" series = parse(workspace_version) if series is None: raise ReleaseError( f"the workspace version {workspace_version!r} is not major.minor.patch" ) if series[2] != 0: raise ReleaseError( f"the workspace version {workspace_version} names a series: it must be " f"{series[0]}.{series[1]}.0 (each release takes the next patch)" ) version = next_in_series(series, tags) newer = sorted( tag for tag in tags if (named := tag_version(tag)) is not None and named >= version ) if newer: raise ReleaseError( f"the next release {show(version)} is not newer than the tag(s) " f"{', '.join(newer)}: move the workspace version to a newer series" ) return version
def workspace_version(manifest: Path) -> str: with manifest.open("rb") as handle: return tomllib.load(handle)["workspace"]["package"]["version"]
def main(argv: list[str]) -> int: parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument( "--workspace-version", help="the series X.Y.0 (default: [workspace.package] version of Cargo.toml)", ) args = parser.parse_args(argv) current = ( workspace_version(ROOT / "Cargo.toml") if args.workspace_version is None else args.workspace_version ) tags = [line for line in sys.stdin.read().split("\n") if line] try: version = release(current, tags) except ReleaseError as error: print(f"error: {error}", file=sys.stderr) return 1 print(f"version={show(version)}") print(f"tag={TAG_PREFIX}{show(version)}") print(f"title={TITLE_PREFIX}{show(version)}") return 0
if __name__ == "__main__": raise SystemExit(main(sys.argv[1:]))