Skip to content

Commit f705997

Browse files
committed
Review fixes v2
1 parent e39e50c commit f705997

File tree

3 files changed

+136
-96
lines changed

3 files changed

+136
-96
lines changed

scripts/release/changelog.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
DEFAULT_INITIAL_GIT_TAG_VERSION = "1.0.0"
1111
FILENAME_DATE_FORMAT = "%Y%m%d"
1212
FRONTMATTER_DATE_FORMAT = "%Y-%m-%d"
13+
MAX_TITLE_LENGTH = 50
1314

1415
PRELUDE_ENTRIES = ["prelude"]
1516
BREAKING_CHANGE_ENTRIES = ["breaking", "major"]
@@ -130,3 +131,33 @@ def strip_changelog_entry_frontmatter(file_contents: str) -> (ChangeMeta, str):
130131
contents = data.content + "\n"
131132

132133
return meta, contents
134+
135+
136+
def get_changelog_filename(title: str, kind: ChangeKind, date: datetime) -> str:
137+
sanitized_title = sanitize_title(title)
138+
filename_date = datetime.datetime.strftime(date, FILENAME_DATE_FORMAT)
139+
140+
return f"{filename_date}_{kind}_{sanitized_title}.md"
141+
142+
143+
def sanitize_title(title: str) -> str:
144+
# Only keep alphanumeric characters, dashes, underscores and spaces
145+
regex = re.compile("[^a-zA-Z0-9-_ ]+")
146+
title = regex.sub("", title)
147+
148+
# Replace multiple dashes, underscores and spaces with underscores
149+
regex_underscore = re.compile("[-_ ]+")
150+
title = regex_underscore.sub(" ", title).strip()
151+
152+
# Lowercase and split by space
153+
words = [word.lower() for word in title.split(" ")]
154+
155+
result = words[0]
156+
157+
for word in words[1:]:
158+
if len(result) + len("_") + len(word) <= MAX_TITLE_LENGTH:
159+
result = result + "_" + word
160+
else:
161+
break
162+
163+
return result

scripts/release/changelog_test.py

Lines changed: 96 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,85 @@
11
import datetime
2+
import unittest
23

3-
import pytest
44
from changelog import (
5+
MAX_TITLE_LENGTH,
56
ChangeKind,
67
extract_date_and_kind_from_file_name,
8+
sanitize_title,
79
strip_changelog_entry_frontmatter,
810
)
911

1012

11-
def test_extract_changelog_data_from_file_name():
12-
# Test prelude
13-
assert extract_date_and_kind_from_file_name("20250502_prelude_release_notes.md") == (
14-
datetime.date(2025, 5, 2),
15-
ChangeKind.PRELUDE,
16-
)
13+
class TestExtractChangelogDataFromFileName(unittest.TestCase):
14+
def test_prelude(self):
15+
date, kind = extract_date_and_kind_from_file_name("20250502_prelude_release_notes.md")
16+
self.assertEqual(date, datetime.date(2025, 5, 2))
17+
self.assertEqual(kind, ChangeKind.PRELUDE)
1718

18-
# Test breaking changes
19-
assert extract_date_and_kind_from_file_name("20250101_breaking_api_update.md") == (
20-
datetime.date(2025, 1, 1),
21-
ChangeKind.BREAKING,
22-
)
23-
assert extract_date_and_kind_from_file_name("20250508_breaking_remove_deprecated.md") == (
24-
datetime.date(2025, 5, 8),
25-
ChangeKind.BREAKING,
26-
)
27-
assert extract_date_and_kind_from_file_name("20250509_major_schema_change.md") == (
28-
datetime.date(2025, 5, 9),
29-
ChangeKind.BREAKING,
30-
)
19+
def test_breaking_changes(self):
20+
date, kind = extract_date_and_kind_from_file_name("20250101_breaking_api_update.md")
21+
self.assertEqual(date, datetime.date(2025, 1, 1))
22+
self.assertEqual(kind, ChangeKind.BREAKING)
3123

32-
# Test features
33-
assert extract_date_and_kind_from_file_name("20250509_feature_new_dashboard.md") == (
34-
datetime.date(2025, 5, 9),
35-
ChangeKind.FEATURE,
36-
)
37-
assert extract_date_and_kind_from_file_name("20250511_feat_add_metrics.md") == (
38-
datetime.date(2025, 5, 11),
39-
ChangeKind.FEATURE,
40-
)
24+
date, kind = extract_date_and_kind_from_file_name("20250508_breaking_remove_deprecated.md")
25+
self.assertEqual(date, datetime.date(2025, 5, 8))
26+
self.assertEqual(kind, ChangeKind.BREAKING)
4127

42-
# Test fixes
43-
assert extract_date_and_kind_from_file_name("20251210_fix_olm_missing_images.md") == (
44-
datetime.date(2025, 12, 10),
45-
ChangeKind.FIX,
46-
)
47-
assert extract_date_and_kind_from_file_name("20251010_bugfix_memory_leak.md") == (
48-
datetime.date(2025, 10, 10),
49-
ChangeKind.FIX,
50-
)
51-
assert extract_date_and_kind_from_file_name("20250302_hotfix_security_issue.md") == (
52-
datetime.date(2025, 3, 2),
53-
ChangeKind.FIX,
54-
)
55-
assert extract_date_and_kind_from_file_name("20250301_patch_typo_correction.md") == (
56-
datetime.date(2025, 3, 1),
57-
ChangeKind.FIX,
58-
)
28+
date, kind = extract_date_and_kind_from_file_name("20250509_major_schema_change.md")
29+
self.assertEqual(date, datetime.date(2025, 5, 9))
30+
self.assertEqual(kind, ChangeKind.BREAKING)
5931

60-
# Test other
61-
assert extract_date_and_kind_from_file_name("20250520_docs_update_readme.md") == (
62-
datetime.date(2025, 5, 20),
63-
ChangeKind.OTHER,
64-
)
65-
assert extract_date_and_kind_from_file_name("20250610_refactor_codebase.md") == (
66-
datetime.date(2025, 6, 10),
67-
ChangeKind.OTHER,
68-
)
32+
def test_features(self):
33+
date, kind = extract_date_and_kind_from_file_name("20250509_feature_new_dashboard.md")
34+
self.assertEqual(date, datetime.date(2025, 5, 9))
35+
self.assertEqual(kind, ChangeKind.FEATURE)
36+
37+
date, kind = extract_date_and_kind_from_file_name("20250511_feat_add_metrics.md")
38+
self.assertEqual(date, datetime.date(2025, 5, 11))
39+
self.assertEqual(kind, ChangeKind.FEATURE)
40+
41+
def test_fixes(self):
42+
date, kind = extract_date_and_kind_from_file_name("20251210_fix_olm_missing_images.md")
43+
self.assertEqual(date, datetime.date(2025, 12, 10))
44+
self.assertEqual(kind, ChangeKind.FIX)
6945

70-
# Invalid date part (day 40 does not exist)
71-
with pytest.raises(Exception) as e:
72-
extract_date_and_kind_from_file_name("20250640_refactor_codebase.md")
73-
assert str(e.value) == "20250640_refactor_codebase.md - date 20250640 is not in the expected format YYYYMMDD"
46+
date, kind = extract_date_and_kind_from_file_name("20251010_bugfix_memory_leak.md")
47+
self.assertEqual(date, datetime.date(2025, 10, 10))
48+
self.assertEqual(kind, ChangeKind.FIX)
7449

75-
# Wrong file name format (date part)
76-
with pytest.raises(Exception) as e:
77-
extract_date_and_kind_from_file_name("202yas_refactor_codebase.md")
78-
assert str(e.value) == "202yas_refactor_codebase.md - doesn't match expected pattern"
50+
date, kind = extract_date_and_kind_from_file_name("20250302_hotfix_security_issue.md")
51+
self.assertEqual(date, datetime.date(2025, 3, 2))
52+
self.assertEqual(kind, ChangeKind.FIX)
7953

80-
# Wrong file name format (missing title part)
81-
with pytest.raises(Exception) as e:
82-
extract_date_and_kind_from_file_name("20250620_change.md")
83-
assert str(e.value) == "20250620_change.md - doesn't match expected pattern"
54+
date, kind = extract_date_and_kind_from_file_name("20250301_patch_typo_correction.md")
55+
self.assertEqual(date, datetime.date(2025, 3, 1))
56+
self.assertEqual(kind, ChangeKind.FIX)
57+
58+
def test_other(self):
59+
date, kind = extract_date_and_kind_from_file_name("20250520_docs_update_readme.md")
60+
self.assertEqual(date, datetime.date(2025, 5, 20))
61+
self.assertEqual(kind, ChangeKind.OTHER)
62+
63+
date, kind = extract_date_and_kind_from_file_name("20250610_refactor_codebase.md")
64+
self.assertEqual(date, datetime.date(2025, 6, 10))
65+
self.assertEqual(kind, ChangeKind.OTHER)
66+
67+
def test_invalid_date(self):
68+
with self.assertRaises(Exception) as context:
69+
extract_date_and_kind_from_file_name("20250640_refactor_codebase.md")
70+
self.assertEqual(
71+
str(context.exception), "20250640_refactor_codebase.md - date 20250640 is not in the expected format %Y%m%d"
72+
)
73+
74+
def test_wrong_file_name_format_date(self):
75+
with self.assertRaises(Exception) as context:
76+
extract_date_and_kind_from_file_name("202yas_refactor_codebase.md")
77+
self.assertEqual(str(context.exception), "202yas_refactor_codebase.md - doesn't match expected pattern")
78+
79+
def test_wrong_file_name_format_missing_title(self):
80+
with self.assertRaises(Exception) as context:
81+
extract_date_and_kind_from_file_name("20250620_change.md")
82+
self.assertEqual(str(context.exception), "20250620_change.md - doesn't match expected pattern")
8483

8584

8685
def test_strip_changelog_entry_frontmatter():
@@ -107,3 +106,33 @@ def test_strip_changelog_entry_frontmatter():
107106
* Added new property [spec.search](https://www.mongodb.com/docs/kubernetes/current/mongodb/specification/#spec-search) to enable MongoDB Search.
108107
"""
109108
)
109+
110+
111+
class TestSanitizeTitle(unittest.TestCase):
112+
def test_basic_case(self):
113+
self.assertEqual(sanitize_title("Simple Title"), "simple_title")
114+
115+
def test_non_alphabetic_chars(self):
116+
self.assertEqual(sanitize_title("Title tha@t-_ contain's strange char&s!"), "title_that_contains_strange_chars")
117+
118+
def test_with_numbers_and_dashes(self):
119+
self.assertEqual(sanitize_title("Title with 123 numbers to-go!"), "title_with_123_numbers_to_go")
120+
121+
def test_mixed_case(self):
122+
self.assertEqual(sanitize_title("MiXeD CaSe TiTlE"), "mixed_case_title")
123+
124+
def test_length_limit(self):
125+
long_title = "This is a very long title that should be truncated because it exceeds the maximum length"
126+
sanitized_title = sanitize_title(long_title)
127+
self.assertTrue(len(sanitized_title) <= MAX_TITLE_LENGTH)
128+
self.assertEqual(sanitized_title, "this_is_a_very_long_title_that_should_be_truncated")
129+
130+
def test_leading_trailing_spaces(self):
131+
sanitized_title = sanitize_title(" Title with spaces ")
132+
self.assertEqual(sanitized_title, "title_with_spaces")
133+
134+
def test_empty_title(self):
135+
self.assertEqual(sanitize_title(""), "")
136+
137+
def test_only_non_alphabetic(self):
138+
self.assertEqual(sanitize_title("!@#"), "")

scripts/release/create_changelog.py

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,19 @@
11
import argparse
22
import datetime
33
import os
4-
import re
54

65
from scripts.release.changelog import (
76
BREAKING_CHANGE_ENTRIES,
87
BUGFIX_ENTRIES,
98
DEFAULT_CHANGELOG_PATH,
109
FEATURE_ENTRIES,
11-
FILENAME_DATE_FORMAT,
1210
FRONTMATTER_DATE_FORMAT,
1311
PRELUDE_ENTRIES,
12+
get_change_kind,
13+
get_changelog_filename,
1414
parse_change_date,
1515
)
1616

17-
MAX_TITLE_LENGTH = 50
18-
19-
20-
def sanitize_title(title: str) -> str:
21-
# Remove non-alphabetic and space characters
22-
regex = re.compile("[^a-zA-Z ]+")
23-
title = regex.sub("", title)
24-
25-
# Lowercase and split by space
26-
words = [word.lower() for word in title.split(" ")]
27-
28-
result = words[0]
29-
30-
for word in words[1:]:
31-
if len(result) + len("_") + len(word) <= MAX_TITLE_LENGTH:
32-
result = result + "_" + word
33-
else:
34-
break
35-
36-
return result
37-
38-
3917
if __name__ == "__main__":
4018
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
4119
parser.add_argument(
@@ -79,9 +57,11 @@ def sanitize_title(title: str) -> str:
7957
parser.add_argument("title", type=str, help="Title for the changelog entry")
8058
args = parser.parse_args()
8159

60+
title = args.title
61+
date_str = args.date
8262
date = parse_change_date(args.date, FRONTMATTER_DATE_FORMAT)
83-
sanitized_title = sanitize_title(args.title)
84-
filename = f"{datetime.datetime.strftime(date, FILENAME_DATE_FORMAT)}_{args.kind}_{sanitized_title}.md"
63+
kind = get_change_kind(args.kind)
64+
filename = get_changelog_filename(title, kind, date)
8565

8666
working_dir = os.getcwd()
8767
changelog_path = os.path.join(working_dir, args.changelog_path, filename)
@@ -93,9 +73,9 @@ def sanitize_title(title: str) -> str:
9373
with open(changelog_path, "w") as f:
9474
# Add frontmatter based on args
9575
f.write("---\n")
96-
f.write(f"title: {args.title}\n")
97-
f.write(f"kind: {args.kind}\n")
98-
f.write(f"date: {date}\n")
76+
f.write(f"title: {title}\n")
77+
f.write(f"kind: {str(kind)}\n")
78+
f.write(f"date: {date_str}\n")
9979
f.write("---\n\n")
10080

10181
if args.editor:

0 commit comments

Comments
 (0)