forked from py-pdf/pypdf
-
Notifications
You must be signed in to change notification settings - Fork 0
/
make_changelog.py
158 lines (125 loc) · 4.26 KB
/
make_changelog.py
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
"""Internal tool to update the changelog."""
import subprocess
from dataclasses import dataclass
from datetime import datetime
from typing import List
@dataclass(frozen=True)
class Change:
"""Capture the data of a git commit."""
commit_hash: str
prefix: str
message: str
def main(changelog_path: str):
"""Create a changelog."""
changelog = get_changelog(changelog_path)
git_tag = get_most_recent_git_tag()
changes = get_formatted_changes(git_tag)
print("-" * 80)
print(changes)
new_version = version_bump(git_tag)
today = datetime.now()
header = f"Version {new_version}, {today:%Y-%m-%d}\n"
header = header + "-" * (len(header) - 1) + "\n"
trailer = f"\n[Full Changelog](https://github.com/py-pdf/pypdf/compare/{git_tag}...{new_version})\n\n"
new_entry = header + changes + trailer
print(new_entry)
# TODO: Make idempotent - multiple calls to this script
# should not change the changelog
new_changelog = new_entry + changelog
write_changelog(new_changelog, changelog_path)
def version_bump(git_tag: str) -> str:
"""Increase the patch version of the git tag by one."""
# just assume a patch version change
major, minor, patch = git_tag.split(".")
return f"{major}.{minor}.{int(patch) + 1}"
def get_changelog(changelog_path: str) -> str:
"""Read the changelog."""
with open(changelog_path) as fh:
changelog = fh.read()
return changelog
def write_changelog(new_changelog: str, changelog_path: str) -> None:
"""Write the changelog."""
with open(changelog_path, "w") as fh:
fh.write(new_changelog)
def get_formatted_changes(git_tag: str) -> str:
"""Format the changes done since the last tag."""
commits = get_git_commits_since_tag(git_tag)
# Group by prefix
grouped = {}
for commit in commits:
if commit.prefix not in grouped:
grouped[commit.prefix] = []
grouped[commit.prefix].append({"msg": commit.message})
# Order prefixes
order = ["DEP", "ENH", "PI", "BUG", "ROB", "DOC", "DEV", "MAINT", "TST", "STY"]
abbrev2long = {
"DEP": "Deprecations",
"ENH": "New Features",
"BUG": "Bug Fixes",
"ROB": "Robustness",
"DOC": "Documentation",
"DEV": "Developer Experience",
"MAINT": "Maintenance",
"TST": "Testing",
"STY": "Code Style",
"PI": "Performance Improvements",
}
# Create output
output = ""
for prefix in order:
if prefix not in grouped:
continue
output += f"\n{abbrev2long[prefix]} ({prefix}):\n" # header
for commit in grouped[prefix]:
output += f"- {commit['msg']}\n"
del grouped[prefix]
if grouped:
print("@" * 80)
output += "\nYou forgot something!:\n"
for prefix in grouped:
output += f"- {prefix}: {grouped[prefix]}\n"
print("@" * 80)
return output
def get_most_recent_git_tag():
"""Get the git tag most recently created."""
git_tag = str(
subprocess.check_output(
["git", "describe", "--abbrev=0"], stderr=subprocess.STDOUT
)
).strip("'b\\n")
return git_tag
def get_git_commits_since_tag(git_tag) -> List[Change]:
"""Get all commits since the last tag."""
commits = str(
subprocess.check_output(
[
"git",
"--no-pager",
"log",
f"{git_tag}..HEAD",
'--pretty=format:"%h%x09%s"',
],
stderr=subprocess.STDOUT,
)
).strip("'b\\n")
return [parse_commit_line(line) for line in commits.split("\\n")]
def parse_commit_line(line) -> Change:
"""Parse the first line of a git commit message."""
if "\\t" not in line:
raise ValueError(f"Invalid commit line: {line}")
commit_hash, rest = line.split("\\t", 1)
if ":" in rest:
prefix, message = rest.split(":", 1)
else:
prefix = ""
message = rest
# Standardize
message.strip()
if message.endswith('"'):
message = message[:-1]
prefix = prefix.strip()
if prefix == "DOCS":
prefix = "DOC"
return Change(commit_hash=commit_hash, prefix=prefix, message=message)
if __name__ == "__main__":
main("CHANGELOG.md")