Commit 6ea97229 authored by one's avatar one
Browse files

[hytop[ Update version bump tool

parent 9cb5ca98
...@@ -33,7 +33,7 @@ clean: ...@@ -33,7 +33,7 @@ clean:
find src tests -type d -name "__pycache__" -delete find src tests -type d -name "__pycache__" -delete
bump: bump:
uv run python -m hytop._bump $(part) uvx bump-my-version bump $(part)
build: build:
uv build uv build
...@@ -144,27 +144,29 @@ make clean # Remove build caches and the virtual environment ...@@ -144,27 +144,29 @@ make clean # Remove build caches and the virtual environment
### Version bump ### Version bump
Version is sourced from `src/hytop/__init__.py` (`__version__`). Version is managed automatically via `bump-my-version`. Running the bump command will:
1. Update `__version__` in `src/hytop/__init__.py`
2. Update `current_version` in `pyproject.toml`
3. Create a commit (e.g., `[hytop] Bump version: 0.1.1 → 0.1.2`)
4. Create a tag (e.g., `hytop-0.1.2`)
```bash ```bash
make bump part=patch # 0.1.0 -> 0.1.1 make bump part=patch # 0.1.1 -> 0.1.2
make bump part=minor # 0.1.1 -> 0.2.0 make bump part=minor # 0.1.2 -> 0.2.0
make bump part=major # 0.2.0 -> 1.0.0 make bump part=major # 0.2.0 -> 1.0.0
make bump part="set 1.2.3" # set an explicit version make bump part=1.2.3 # set an explicit version
``` ```
### Publish ### Publish
Releases are automatically published to PyPI when pushing a version tag: Releases are automatically published to PyPI via GitHub Actions when pushing a version tag.
```bash ```bash
# Bump version and commit # 1. Bump version (auto-commits and auto-tags)
make bump part=patch make bump part=patch
git commit -am "Bump version to 0.1.1"
# Tag and push to trigger GitHub Actions release # 2. Push commits and tags to trigger GitHub Actions release
git tag hytop-0.1.1 git push --follow-tags
git push origin hytop-0.1.1
``` ```
To test building distributions locally: To test building distributions locally:
......
...@@ -62,3 +62,16 @@ select = [ ...@@ -62,3 +62,16 @@ select = [
"SIM", # flake8-simplify "SIM", # flake8-simplify
"RUF", # ruff-specific "RUF", # ruff-specific
] ]
[tool.bumpversion]
current_version = "0.1.1"
commit = true
commit_args = ""
tag = true
tag_name = "hytop-{new_version}"
message = "[hytop] Bump version: {current_version} → {new_version}"
[[tool.bumpversion.files]]
filename = "src/hytop/__init__.py"
search = "__version__ = \"{current_version}\""
replace = "__version__ = \"{new_version}\""
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
VERSION_PATTERN = re.compile(r'^(\s*__version__\s*=\s*")(\d+)\.(\d+)\.(\d+)(".*)\s*$')
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Bump hytop version in src/hytop/__init__.py")
parser.add_argument(
"action",
choices=["patch", "minor", "major", "set"],
help="Bump strategy or set an explicit version",
)
parser.add_argument(
"version",
nargs="?",
help="Target version when action is 'set' (format: X.Y.Z)",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Print the next version without writing files",
)
return parser.parse_args()
def parse_semver(version_text: str) -> tuple[int, int, int]:
match = re.fullmatch(r"(\d+)\.(\d+)\.(\d+)", version_text)
if not match:
raise ValueError(f"Invalid version: {version_text!r}. Expected format: X.Y.Z")
major, minor, patch = (int(part) for part in match.groups())
return major, minor, patch
def bump_version(current: tuple[int, int, int], action: str) -> tuple[int, int, int]:
major, minor, patch = current
if action == "patch":
return major, minor, patch + 1
if action == "minor":
return major, minor + 1, 0
if action == "major":
return major + 1, 0, 0
raise ValueError(f"Unsupported bump action: {action}")
def main() -> int:
args = parse_args()
# Find the project root by walking up from cwd until src/hytop/__init__.py is found
root = Path.cwd()
for candidate in [root, *root.parents]:
if (candidate / "src" / "hytop" / "__init__.py").exists():
root = candidate
break
init_file = root / "src" / "hytop" / "__init__.py"
if not init_file.exists():
print(f"Cannot find version file: {init_file}", file=sys.stderr)
return 1
lines = init_file.read_text(encoding="utf-8").splitlines(keepends=True)
matched_index = -1
matched_groups: tuple[str, str, str, str, str] | None = None
for i, line in enumerate(lines):
match = VERSION_PATTERN.match(line.rstrip("\r\n"))
if match:
if matched_index != -1:
print("Found multiple __version__ lines; aborting.", file=sys.stderr)
return 1
matched_index = i
matched_groups = match.groups()
if matched_index == -1 or matched_groups is None:
print("Cannot find __version__ declaration in __init__.py", file=sys.stderr)
return 1
prefix, major_text, minor_text, patch_text, suffix = matched_groups
current_tuple = (int(major_text), int(minor_text), int(patch_text))
current_version = ".".join((major_text, minor_text, patch_text))
if args.action == "set":
if not args.version:
print("Action 'set' requires a version argument (X.Y.Z).", file=sys.stderr)
return 1
try:
next_tuple = parse_semver(args.version)
except ValueError as exc:
print(str(exc), file=sys.stderr)
return 1
else:
if args.version:
print("Version argument is only allowed with action 'set'.", file=sys.stderr)
return 1
next_tuple = bump_version(current_tuple, args.action)
next_version = ".".join(str(part) for part in next_tuple)
if next_version == current_version:
print(f"Version unchanged: {current_version}")
return 0
new_line = f"{prefix}{next_version}{suffix}"
line_ending = "\n"
if lines[matched_index].endswith("\r\n"):
line_ending = "\r\n"
lines[matched_index] = f"{new_line}{line_ending}"
if args.dry_run:
print(f"{current_version} -> {next_version} (dry-run)")
return 0
init_file.write_text("".join(lines), encoding="utf-8")
print(f"{current_version} -> {next_version}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment