Repository navigation
Expand file tree
/
Copy pathbump.py
More file actions
141 lines (120 loc) · 4.58 KB
/
Copy pathbump.py
File metadata and controls
141 lines (120 loc) · 4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import json
import re
import argparse
import sys
from pathlib import Path
VERSION_RE = re.compile(r"^\d+\.\d+(?:\.\d+)?$")
BUMP_PART_ALIASES = {
"major": "major",
"minor": "minor",
"patch": "patch",
"path": "patch", # common typo alias
}
def normalize_version(version_string: str) -> str:
return (version_string or "").strip()
def validate_version(version_string: str) -> str:
normalized = normalize_version(version_string)
if not VERSION_RE.fullmatch(normalized):
raise ValueError(
f"Invalid version '{version_string}'. Expected format: major.minor[.patch]"
)
return normalized
def sync_version(version_string: str, addon_root: Path) -> None:
validate_version(version_string)
if not addon_root.is_dir():
raise FileNotFoundError(f"Addon directory not found: {addon_root}")
manifest_path = addon_root / "manifest.json"
with manifest_path.open("r", encoding="utf-8") as f:
manifest = json.load(f)
manifest["version"] = version_string
manifest["human_version"] = version_string
with manifest_path.open("w", encoding="utf-8") as f:
json.dump(manifest, f, indent=2)
f.write("\n")
version_path = addon_root / "VERSION"
version_path.write_text(f"{version_string}\n", encoding="utf-8")
def normalize_bump_part(part: str) -> str:
normalized = (part or "").strip().lower()
if VERSION_RE.fullmatch(normalized):
return normalized
mapped = BUMP_PART_ALIASES.get(normalized)
if not mapped:
valid = ", ".join(sorted(k for k in BUMP_PART_ALIASES if k != "path"))
raise ValueError(f"Invalid bump part '{part}'. Expected one of: {valid} or explicitly major.minor[.patch]")
return mapped
def increment_version(version_string: str, bump_part: str = "patch") -> str:
part = normalize_bump_part(bump_part)
if VERSION_RE.fullmatch(part):
return part
parts = version_string.split(".")
try:
major = int(parts[0])
minor = int(parts[1])
patch = int(parts[2]) if len(parts) > 2 else None
except (ValueError, IndexError) as e:
raise ValueError(
f"Invalid version '{version_string}'. Expected major.minor[.patch]"
) from e
part = normalize_bump_part(bump_part)
if part == "major":
major += 1
return f"{major}.0"
elif part == "minor":
minor += 1
return f"{major}.{minor}"
else:
patch = (patch or 0) + 1
return f"{major}.{minor}.{patch}"
def increment_patch(version_string: str) -> str:
return increment_version(version_string, "patch")
def read_current_version(addon_dir: Path) -> str:
version_file = addon_dir / "VERSION"
if version_file.exists():
version = version_file.read_text(encoding="utf-8").strip()
return validate_version(version)
manifest_file = addon_dir / "manifest.json"
if manifest_file.exists():
manifest = json.loads(manifest_file.read_text(encoding="utf-8"))
for key in ("human_version", "version"):
value = str(manifest.get(key, "")).strip()
if value:
try:
return validate_version(value)
except ValueError:
continue
raise FileNotFoundError(
f"Could not determine current version from {version_file} or {manifest_file}"
)
def bump_version(addon_dir: Path = Path("addon"), bump_part: str = "patch") -> int:
try:
part = normalize_bump_part(bump_part)
current_version = read_current_version(addon_dir)
new_version = increment_version(current_version, part)
print(f"Bumping {part} version: {current_version} → {new_version}")
sync_version(new_version, addon_dir)
print(f"Successfully updated manifest.json and VERSION to {new_version}")
return 0
except Exception as e:
print(f"Failed to bump version: {e}")
return 1
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Bump add-on version (major, minor, patch). Default is patch."
)
parser.add_argument(
"part",
nargs="?",
default="patch",
help="Bump part: major, minor, patch (or 'path' alias).",
)
parser.add_argument(
"--addon-dir",
default="addon",
help="Path to the addon directory (default: addon).",
)
return parser.parse_args(argv[1:])
def main(argv: list[str]) -> int:
args = parse_args(argv)
return bump_version(Path(args.addon_dir), args.part)
if __name__ == "__main__":
raise SystemExit(main(sys.argv))