Visitar URL original
Title the release the way the previous ones were titled · stonebig/winpython@c4704bb · GitHub
Skip to content

Commit c4704bb

Browse files
stonebigclaude
andcommitted
Title the release the way the previous ones were titled
The workflow titled a draft "WinPython 2026-04b1", where every release for years has read "WinPython 2026-03 b3 of August 8th 2026". The tag is what URLs are built on and it does not change; the title is what people read on the releases page, and there is no reason for it to look new. So the title is now cycle, then the level after a space -- the tag runs them together, the title does not -- then the date with no comma, which is how seventeen of the last eighteen titles read. Six of those titles are reproduced letter for letter in the tests. Built in cycle_config.py rather than in the job's shell: ordinal dates are miserable to write in bash and impossible to test there. The date is the day the draft is opened, the only day a build knows about, and a cycle published much later can still have its title edited by hand -- a re-run leaves an existing release's title alone, so that edit survives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MBk5k7WdpPvx4SUFyEk3U3
1 parent c5a6e82 commit c4704bb

3 files changed

Lines changed: 85 additions & 2 deletions

File tree

‎.github/scripts/cycle_config.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
2828
Paths are relative to the working directory, which in CI is the checkout root.
2929
"""
30+
import datetime
3031
import json
3132
import os
3233
import sys
@@ -128,6 +129,31 @@ def release_tag(cfg: dict, cycle_name: str) -> str:
128129
return tag
129130

130131

132+
def ordinal(day: int) -> str:
133+
"""1st, 2nd, 3rd, 4th ... and the 11th/12th/13th exceptions."""
134+
if 11 <= day % 100 <= 13:
135+
return f"{day}th"
136+
return f"{day}{ {1: 'st', 2: 'nd', 3: 'rd'}.get(day % 10, 'th') }"
137+
138+
139+
def release_title(cfg: dict, cycle_name: str, today: datetime.date | None = None) -> str:
140+
""""WinPython 2026-04 b1 of September 6th 2026", the shape releases have used.
141+
142+
Kept deliberately close to the titles written by hand before this: cycle,
143+
then the level after a space -- the tag runs them together, the title does
144+
not -- then the date, no comma, which is how all but one of them read.
145+
146+
The date is the day the draft is opened, since that is the only day the
147+
build knows about. A cycle publishing much later than it was built can
148+
still have the title edited; the tag, which is what URLs are built on,
149+
does not move.
150+
"""
151+
today = today or datetime.date.today()
152+
level = cfg.get("release_level", "")
153+
name = f"{cycle_name.replace('_', '-')} {level}".strip()
154+
return f"WinPython {name} of {today:%B} {ordinal(today.day)} {today.year}"
155+
156+
131157
def build_config(cfg: dict, requested: str, cycle_name: str) -> dict:
132158
pythons = cfg["pythons"]
133159
if requested == "all":
@@ -156,6 +182,7 @@ def build_config(cfg: dict, requested: str, cycle_name: str) -> dict:
156182
"cycle_dir": cfg["cycle_dir"],
157183
"release_level": cfg.get("release_level", ""),
158184
"release_tag": release_tag(cfg, cycle_name),
185+
"release_title": release_title(cfg, cycle_name),
159186
"pandoc_source": cfg["pandoc"]["source"],
160187
"pandoc_sha256": cfg["pandoc"]["sha256"],
161188
# consumed by the build job as strategy.matrix via fromJSON

‎.github/workflows/build_winpython_cycle.yml‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ jobs:
5252
pandoc_sha256: ${{ steps.cfg.outputs.pandoc_sha256 }}
5353
release_level: ${{ steps.cfg.outputs.release_level }}
5454
release_tag: ${{ steps.cfg.outputs.release_tag }}
55+
release_title: ${{ steps.cfg.outputs.release_title }}
5556
matrix: ${{ steps.cfg.outputs.matrix }}
5657
steps:
5758
- uses: actions/checkout@v6
@@ -78,15 +79,17 @@ jobs:
7879
GH_TOKEN: ${{ github.token }}
7980
GH_REPO: ${{ github.repository }}
8081
TAG: ${{ needs.config.outputs.release_tag }}
82+
TITLE: ${{ needs.config.outputs.release_title }}
8183
CYCLE: ${{ inputs.cycle }}
8284
run: |
8385
if gh release view "$TAG" >/dev/null 2>&1; then
84-
# re-running one missing flavor is normal; say which release its files land on
86+
# re-running one missing flavor is normal; say which release its files
87+
# land on, and leave the title alone in case it was edited by hand
8588
state=$(gh release view "$TAG" --json isDraft --jq 'if .isDraft then "still a draft" else "ALREADY PUBLISHED" end')
8689
echo "release $TAG exists ($state); this run adds its files to it"
8790
else
8891
gh release create "$TAG" --draft \
89-
--title "WinPython $TAG" \
92+
--title "$TITLE" \
9093
--notes "Draft opened by the build workflow for cycle $CYCLE. Files land as each build finishes; publish once they are all here."
9194
echo "opened draft release $TAG"
9295
fi

‎tests/test_cycle_config.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,54 @@ def test_every_cycle_has_a_usable_tag(self, cycle_config, at_repo_root, cycle_fi
192192
assert tag and not set(tag) & cycle_config.TAG_FORBIDDEN
193193

194194

195+
@needs_script
196+
class TestReleaseTitle:
197+
"""The title people actually read on the releases page.
198+
199+
Reproduced from the titles written by hand for years rather than invented:
200+
cycle, then the level after a space -- the tag runs them together, the
201+
title does not -- then the date with no comma, which is how all but one of
202+
the previous titles read.
203+
"""
204+
205+
@pytest.mark.parametrize("cycle,level,date,expected", [
206+
# the real titles of these releases, to the letter
207+
("2026_03", "", (2026, 8, 22), "WinPython 2026-03 of August 22nd 2026"),
208+
("2026_03", "b3", (2026, 8, 8), "WinPython 2026-03 b3 of August 8th 2026"),
209+
("2026_02", "b2", (2026, 5, 1), "WinPython 2026-02 b2 of May 1st 2026"),
210+
("2026_01", "final", (2026, 3, 10), "WinPython 2026-01 final of March 10th 2026"),
211+
("2025_05", "rc", (2025, 12, 22), "WinPython 2025-05 rc of December 22nd 2025"),
212+
("2026_01", "b3", (2026, 2, 24), "WinPython 2026-01 b3 of February 24th 2026"),
213+
])
214+
def test_matches_the_titles_used_before(self, cycle_config, cycle, level, date, expected):
215+
import datetime
216+
217+
cfg = {"release_level": level} if level else {}
218+
assert cycle_config.release_title(cfg, cycle, datetime.date(*date)) == expected
219+
220+
def test_no_double_space_when_there_is_no_level(self, cycle_config):
221+
"""Some older titles read "2026-02 of May 17th": a level that was empty."""
222+
import datetime
223+
224+
title = cycle_config.release_title({"release_level": ""}, "2026_04", datetime.date(2026, 5, 17))
225+
assert " " not in title
226+
227+
@pytest.mark.parametrize("day,expected", [
228+
(1, "1st"), (2, "2nd"), (3, "3rd"), (4, "4th"),
229+
(11, "11th"), (12, "12th"), (13, "13th"), # not 11st/12nd/13rd
230+
(21, "21st"), (22, "22nd"), (23, "23rd"), (30, "30th"), (31, "31st"),
231+
])
232+
def test_ordinals(self, cycle_config, day, expected):
233+
assert cycle_config.ordinal(day) == expected
234+
235+
@needs_cycles
236+
@pytest.mark.parametrize("cycle_file", cycle_files, ids=lambda p: p.stem)
237+
def test_every_cycle_produces_a_title(self, cycle_config, at_repo_root, cycle_file):
238+
title = config_for(cycle_config, cycle_file)["release_title"]
239+
assert title.startswith("WinPython ") and " of " in title
240+
assert "\n" not in title, "a GITHUB_OUTPUT value has to stay on one line"
241+
242+
195243
@needs_script
196244
@needs_workflow
197245
@needs_cycles
@@ -260,6 +308,11 @@ def test_publishing_is_gated_both_ways(self, workflow_text):
260308
assert "if: ${{ inputs.publish }}" in workflow_text
261309
assert "if: ${{ !inputs.publish }}" in workflow_text
262310

311+
def test_the_release_title_is_built_by_the_script(self, workflow_text):
312+
"""Ordinal dates are miserable in shell, and untestable there."""
313+
assert 'TITLE: ${{ needs.config.outputs.release_title }}' in workflow_text
314+
assert '--title "$TITLE"' in workflow_text
315+
263316
def test_the_changelog_name_carries_the_release_level(self, workflow_text):
264317
"""A beta and the final it becomes share a ver2.
265318

0 commit comments

Comments
 (0)