|
2 | 2 |
|
3 | 3 | // D++ changelog generator, saves 15 minutes for each release :-)
|
4 | 4 |
|
| 5 | +// Categories, in display order |
| 6 | +$catgroup = [ |
| 7 | + '💣 Breaking Changes' => [], |
| 8 | + '✨ New Features' => [], |
| 9 | + '🐞 Bug Fixes' => [], |
| 10 | + '🚀 Performance Improvements' => [], |
| 11 | + '♻️ Refactoring' => [], |
| 12 | + '🚨 Testing' => [], |
| 13 | + '📚 Documentation' => [], |
| 14 | + '💎 Style Changes' => [], |
| 15 | + '🔧 Chore' => [], |
| 16 | + '📜 Miscellaneous Changes' => [] |
| 17 | +]; |
| 18 | + |
5 | 19 | // Pattern list
|
6 | 20 | $categories = [
|
7 | 21 | 'break' => '💣 Breaking Changes',
|
|
42 | 56 | 'updated' => '📜 Miscellaneous Changes',
|
43 | 57 | ];
|
44 | 58 |
|
45 |
| -$catgroup = []; |
46 | 59 | $changelog = [];
|
47 | 60 | $githubstyle = true;
|
48 | 61 | if (count($argv) > 2 && $argv[1] == '--discord') {
|
49 | 62 | $githubstyle = false;
|
50 | 63 | }
|
| 64 | +$errors = []; |
51 | 65 |
|
52 | 66 | // Magic sauce
|
53 | 67 | exec("git log --format=\"%s\" $(git log --no-walk --tags | head -n1 | cut -d ' ' -f 2)..HEAD | grep -v '^Merge '", $changelog);
|
54 | 68 |
|
55 |
| -// Leadin |
56 |
| -if ($githubstyle) { |
57 |
| - echo "The changelog is listed below:\n\nRelease Changelog\n===========\n"; |
58 |
| -} else { |
59 |
| - echo "The changelog is listed below:\n\n__**Release Changelog**__\n"; |
60 |
| -} |
61 |
| - |
62 | 69 | // Case insensitive removal of duplicates
|
63 | 70 | $changelog = array_intersect_key($changelog, array_unique(array_map("strtolower", $changelog)));
|
64 | 71 |
|
|
78 | 85 | }
|
79 | 86 | }
|
80 | 87 |
|
| 88 | +function add_change(string $change, string $category, string $scope = null) { |
| 89 | + global $catgroup; |
| 90 | + |
| 91 | + if (isset($scope) && !empty($scope)) { |
| 92 | + // Group by feature inside the section |
| 93 | + if (!isset($catgroup[$category][$scope])) { |
| 94 | + $catgroup[$category][$scope] = []; |
| 95 | + } |
| 96 | + $catgroup[$category][$scope][] = $change; |
| 97 | + return; |
| 98 | + } |
| 99 | + $catgroup[$category][] = $change; |
| 100 | +} |
| 101 | + |
81 | 102 | foreach ($changelog as $change) {
|
82 | 103 |
|
83 | 104 | // Wrap anything that looks like a symbol name in backticks
|
|
92 | 113 | $change = preg_replace("/\b(was|is|wo)nt\b/i", '$1n\'t', $change);
|
93 | 114 | $change = preg_replace("/\bfreebsd\b/", 'FreeBSD', $change);
|
94 | 115 | $change = preg_replace("/``/", "`", $change);
|
| 116 | + $change = trim($change); |
95 | 117 |
|
96 |
| - // Match keywords against categories |
97 | 118 | $matched = false;
|
98 |
| - foreach ($categories as $cat => $header) { |
99 |
| - // Purposefully ignored: comments that are one word, merge commits, and version bumps |
100 |
| - if (strpos($change, ' ') === false || preg_match("/^Merge (branch|pull request|remote-tracking branch) /", $change) || preg_match("/version bump/i", $change)) { |
101 |
| - $matched = true; |
102 |
| - continue; |
103 |
| - } |
104 |
| - // Groupings |
105 |
| - if ((preg_match("/^" . $cat . ":/i", $change)) || (preg_match("/^\[" . $cat . "\//i", $change)) || (preg_match("/^\[" . $cat . "\]/i", $change)) || (preg_match("/^\[" . $cat . ":/i", $change)) || (preg_match("/^" . $cat . "\//i", $change)) || (preg_match("/^" . $cat . ":/i", $change))) { |
106 |
| - if (!isset($catgroup[$header])) { |
107 |
| - $catgroup[$header] = []; |
| 119 | + $matches = []; |
| 120 | + // Extract leading category section |
| 121 | + if (preg_match("/^((?:(?:[\w_]+(?:\([\w_]+\))?+)(?:[\s]*[,\/][\s]*)?)+):/i", $change, $matches) || preg_match("/^\[((?:(?:[\w_]+(?:\([\w_]+\))?+)(?:[\s]*[,\/][\s]*)?)+)\](?:\s*:)?/i", $change, $matches)) { |
| 122 | + $categorysection = $matches[0]; |
| 123 | + $changecategories = $matches[1]; |
| 124 | + $matchflags = PREG_SET_ORDER | PREG_UNMATCHED_AS_NULL; |
| 125 | + // Extract each category and scope |
| 126 | + if (preg_match_all("/(?:[\s]*)([\w_]+)(?:\(([\w_]+)\))?(?:[\s]*)(?:[,\/]+)?/i", $changecategories, $matches, $matchflags) !== false) { |
| 127 | + /** |
| 128 | + * Given a commit "foo, bar(foobar): add many foos and bars" : |
| 129 | + * $matches is [ |
| 130 | + * 0 => [[0] => 'foo,', [1] => 'foo', [2] => null], |
| 131 | + * 1 => [[0] => ' bar(foobar)', [1] => 'bar', [2] => 'foobar'], |
| 132 | + * ] |
| 133 | + * In other words, for a matched category N, matches[N][1] is the category, matches[N][2] is the scope |
| 134 | + */ |
| 135 | + $header = $matches[0][1]; |
| 136 | + $scope = $matches[0][2]; |
| 137 | + $change = trim(substr($change, strlen($categorysection))); |
| 138 | + // List in breaking if present |
| 139 | + foreach ($matches as $nb => $match) { |
| 140 | + if ($nb == 0) // Skip the first category which will be added anyways |
| 141 | + continue; |
| 142 | + $category = $match[1]; |
| 143 | + if (isset($categories[$category]) && $categories[$category] === '💣 Breaking Changes') |
| 144 | + add_change($change, $categories[$category], $scope); |
| 145 | + } |
| 146 | + if (!isset($categories[$header])) { |
| 147 | + $errors[] = "could not find category \"" . $header . "\" for commit \"" . $change . "\", adding it to misc"; |
| 148 | + $header = $categories['misc']; |
| 149 | + } |
| 150 | + else { |
| 151 | + $header = $categories[$header]; |
108 | 152 | }
|
109 |
| - $matched = true; |
110 |
| - $catgroup[$header][] = preg_replace("/^\S+\s+/", "", $change); |
111 |
| - break; |
112 |
| - } else if (preg_match("/^" . $cat . " /i", $change)) { |
113 | 153 | if (!isset($catgroup[$header])) {
|
114 | 154 | $catgroup[$header] = [];
|
115 | 155 | }
|
116 | 156 | $matched = true;
|
117 |
| - $catgroup[$header][] = $change; |
118 |
| - break; |
| 157 | + // Ignore version bumps |
| 158 | + if (!preg_match("/^(version )?bump/i", $change)) { |
| 159 | + add_change($change, $header, $scope); |
| 160 | + } |
| 161 | + } |
| 162 | + } |
| 163 | + if (!$matched) { // Could not parse category section, try keywords |
| 164 | + // Match keywords against categories |
| 165 | + foreach ($categories as $cat => $header) { |
| 166 | + // Purposefully ignored: comments that are one word, merge commits, and version bumps |
| 167 | + if (strpos($change, ' ') === false || preg_match("/^Merge (branch|pull request|remote-tracking branch) /", $change) || preg_match("/^(version )?bump/i", $change)) { |
| 168 | + $matched = true; |
| 169 | + break; |
| 170 | + } |
| 171 | + if (preg_match("/^" . $cat . " /i", $change)) { |
| 172 | + if (!isset($catgroup[$header])) { |
| 173 | + $catgroup[$header] = []; |
| 174 | + } |
| 175 | + $matched = true; |
| 176 | + $catgroup[$header][] = $change; |
| 177 | + break; |
| 178 | + } |
119 | 179 | }
|
120 | 180 | }
|
| 181 | + if (!$matched) { |
| 182 | + $errors[] = "could not guess category for commit \"" . $change . "\", adding it to misc"; |
| 183 | + $header = $categories['misc']; |
| 184 | + if (!isset($catgroup[$header])) { |
| 185 | + $catgroup[$header] = []; |
| 186 | + } |
| 187 | + $matched = true; |
| 188 | + $catgroup[$header][] = $change; |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +// Leadin |
| 193 | +if ($githubstyle) { |
| 194 | + echo "The changelog is listed below:\n\nRelease Changelog\n===========\n"; |
| 195 | +} else { |
| 196 | + echo "The changelog is listed below:\n\n__**Release Changelog**__\n"; |
| 197 | +} |
| 198 | + |
| 199 | +function print_change(string $change) { |
| 200 | + global $githubstyle; |
| 201 | + |
| 202 | + // Exclude bad commit messages like 'typo fix', 'test push' etc by pattern |
| 203 | + if (!preg_match("/^(typo|test|fix)\s\w+$/", $change) && strpos($change, ' ') !== false) { |
| 204 | + echo ($githubstyle ? '-' : '•') . ' ' . ucfirst(str_replace('@', '', $change)) . "\n"; |
| 205 | + } |
121 | 206 | }
|
122 | 207 |
|
123 | 208 | // Output tidy formatting
|
124 | 209 | foreach ($catgroup as $cat => $list) {
|
125 |
| - echo "\n" . ($githubstyle ? '## ' : '__**') . $cat . ($githubstyle ? '' : '**__') . "\n"; |
126 |
| - foreach ($list as $item) { |
127 |
| - // Exclude bad commit messages like 'typo fix', 'test push' etc by pattern |
128 |
| - if (!preg_match("/^(typo|test|fix)\s\w+$/", $item) && strpos($item, ' ') !== false) { |
129 |
| - echo ($githubstyle ? '-' : '•') . ' ' . ucfirst(str_replace('@', '', $item)) . "\n"; |
| 210 | + if (!empty($list)) { |
| 211 | + echo "\n" . ($githubstyle ? '## ' : '__**') . $cat . ($githubstyle ? '' : '**__') . "\n"; |
| 212 | + foreach ($list as $key => $item) { |
| 213 | + if (is_array($item)) { |
| 214 | + foreach ($item as $change) { |
| 215 | + print_change("$key: $change"); |
| 216 | + } |
| 217 | + } |
| 218 | + else { |
| 219 | + print_change($item); |
| 220 | + } |
130 | 221 | }
|
131 | 222 | }
|
132 | 223 | }
|
|
138 | 229 | echo 'The ' . $version . ' download can be found here: <https://dl.dpp.dev/' . $version . '>';
|
139 | 230 | echo "\n";
|
140 | 231 | }
|
| 232 | + |
| 233 | +foreach ($errors as $err) { |
| 234 | + trigger_error($err, E_USER_WARNING); |
| 235 | +} |
0 commit comments