15 from collections.abc
import Callable, Iterable
16 from difflib
import unified_diff
17 from subprocess
import run
18 from typing
import Union
25 Convert a version in format "vXrY" or "X.Y" in the pair ("X.Y", "vXrY").
27 >>> normalize_version("v37r0")
29 >>> normalize_version("37.0.1")
33 numbers = re.findall(
r"\d+", version)
36 "".join(
"{}{}".
format(*pair)
for pair
in zip(
"vrpt", numbers)),
42 Helper to carry the allowed fields for formatting replacement strings.
44 >>> f = Fields("v37r1", datetime.date(2023, 9, 25))
46 Fields('37.1', datetime.date(2023, 9, 25))
48 {'cmake_version': '37.1', 'tag_version': 'v37r1', 'date': datetime.date(2023, 9, 25)}
51 def __init__(self, version: str, date: datetime.date):
54 cmake_version=cmake_version,
55 tag_version=tag_version,
61 f
"Fields({repr(self._data['cmake_version'])}, {repr(self._data['date'])})"
71 Helper to replace lines with patterns or applying functions.
73 >>> r = ReplacementRule(r"^version: ", "version: {cmake_version}")
74 >>> f = Fields("v1r1", datetime.date(2023, 9, 25))
75 >>> r("nothing to change\\n", f)
76 'nothing to change\\n'
77 >>> r("version: 1.0\\n", f)
83 pattern: Union[str, re.Pattern],
84 replace: Union[str, Callable[[str, Fields], str]],
86 self.
pattern = re.compile(pattern)
87 if isinstance(replace, str):
88 replace = f
"{replace.rstrip()}\n"
89 self.
replace =
lambda _line, fields: replace.format(**fields.data)
93 def __call__(self, line: str, fields: Fields) -> str:
95 return self.
replace(line, fields)
101 self, filename: str, rules: Iterable[Union[ReplacementRule, tuple[str, str]]]
105 r
if isinstance(r, ReplacementRule)
else ReplacementRule(*r)
for r
in rules
109 for rule
in self.
rules:
110 line = rule(line, fields)
113 def __call__(self, fields: Fields) -> tuple[str, list[str], list[str]]:
121 Special updater to fill draft changelog entry.
124 [
"git",
"describe",
"--tags",
"--abbrev=0"], capture_output=
True, text=
True
129 [
"git",
"log",
"--first-parent",
"--format=%s<=>%b|", f
"{latest_tag}.."],
134 changes_txt =
" ".join(changes_txt.strip().rstrip(
"|").splitlines())
137 changes_txt.replace(
"Closes #",
"gaudi/Gaudi#")
138 .replace(
"See merge request ",
"")
143 f
"- {msg.strip()} ({', '.join(refs.split())})\n"
145 else f
"- {msg.strip()}\n"
146 for change
in changes
147 for msg, refs
in [change.split(
"<=>", 1)]
150 filename =
"CHANGELOG.md"
151 with open(filename)
as f:
153 for idx, line
in enumerate(old):
154 if line.startswith(
"## ["):
160 "## [{tag_version}](https://gitlab.cern.ch/gaudi/Gaudi/-/releases/{tag_version}) - {date}\n".
format(
171 data.extend([
"\n",
"\n"])
172 data.extend(old[idx:])
174 return filename, old, data
178 @click.argument(
"version", type=str)
181 type=click.DateTime((
"%Y-%m-%d",)),
183 default=datetime.datetime.now(),
190 help=
"only show what would change, but do not modify the files",
194 Helper to easily update the project version number in all needed files.
196 fields =
Fields(version, date.date())
198 "Bumping version to {cmake_version} (tag: {tag_version})".
format(**fields.data)
204 [(
r"^project\(Gaudi VERSION",
"project(Gaudi VERSION {cmake_version}")],
209 (
r"^version: ",
"version: {tag_version}"),
210 (
r"^date-released: ",
"date-released: '{date}'"),
214 "docs/source/conf.py",
216 (
r"^version = ",
'version = "{cmake_version}"'),
217 (
r"^release = ",
'release = "{tag_version}"'),
222 filename, old, new = updater(fields)
226 sys.stdout.writelines(
230 fromfile=f
"a/{filename}",
231 tofile=f
"b/{filename}",
235 click.echo(f
"updated {filename}")
236 with open(filename,
"w")
as f:
240 if __name__ ==
"__main__":