From 84776990b9b5d45f664fd4c52922c7c0b4522274 Mon Sep 17 00:00:00 2001 From: Andreas Troelsen Date: Sun, 31 Dec 2023 03:25:14 +0100 Subject: [PATCH] Add release note extraction script. Introduces a crude Python script that can extract release notes from the changelog for a given version and convert it to one of three different output formats: - `github` for GitHub Releases. This is just the Markdown itself, but with the very first line (the version) removed, because the version is also the title of the release itself. - `spigot` for Spigot Resources. This is the BBCode format used on the forums and in the resource descriptions. - `curse` for CurseForge File Uploads. Curse uses a so-called "WYSIWYG" format that's really just HTML underneath. The formats for Spigot and CurseForge are straightforward to convert to as long as we only use simple text formatting, bullet lists, and links, but that is really all the changelog should consist of anyway. While this script already makes the release process quite a bit easier on its own, the end goal is to _automate_ releases as much as possible, and to do that, we need to be able to extract release notes, and we need to be able to do it from GitHub Actions, which is quite a bit simpler if we don't use third-party libraries. Publishing releases on GitHub is almost trivial, while CurseForge is pretty easy, and Hangar should be very doable as well. Spigot, on the other hand, is stuck in the dark ages, so we must continue to upload files manually there. --- scripts/extract-release-notes | 224 ++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100755 scripts/extract-release-notes diff --git a/scripts/extract-release-notes b/scripts/extract-release-notes new file mode 100755 index 0000000..8b75743 --- /dev/null +++ b/scripts/extract-release-notes @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 + +import argparse +import os +import re +import sys + + +VERSION_PREFIX = '## ' +SECTION_PREFIX = '### ' +LIST_ITEM_PREFIX = '- ' + + +def main(): + args = parse_args() + lines = extract(args.version) + output(lines, args.format) + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + 'version', + help='the version to extract release notes from the changelog for', + ) + parser.add_argument( + '--format', + '-f', + choices=['github', 'spigot', 'curse'], + help='the format to output the release notes in', + ) + + return parser.parse_args() + + +def extract(target): + filename = 'changelog.md' + if not os.path.isfile(filename): + filename = os.path.join('..', filename) + if not os.path.isfile(filename): + print('error: changelog.md not found!') + sys.exit(1) + + lines = [] + + with open(filename) as changelog: + found = False + + for entry in changelog: + if entry.startswith(VERSION_PREFIX): + if found: + break + + i = entry.find('[') + 1 + j = entry.find(']') + version = entry[i:j] + + if version == target: + if version[0].isdigit(): + version = f'v{version}' + + lines.append(f'{VERSION_PREFIX}{version}') + lines.append('') + found = True + + continue + + if not found: + continue + + lines.append(entry.strip()) + + if not found: + print(f'error: version {target} not found!') + sys.exit(1) + + return lines + + +def output(lines, fmt): + if fmt == 'github': + output_as_github_markdown(lines) + elif fmt == 'spigot': + output_as_spigot_bbcode(lines) + elif fmt == 'curse': + output_as_curseforge_html(lines) + else: + output_raw(lines) + + +def output_as_github_markdown(lines): + """ + GitHub Releases Markdown is printed as the raw output from the changelog + except for the version header (the first line), because the version number + is already used as the release title, so we don't want it to appear twice. + """ + output_raw(lines[1:]) + + +def output_as_spigot_bbcode(lines): + """ + Spigot uses BBCode for resource update descriptions. It's very similar to + regular HTML, which makes it fairly easy to convert from Markdown. We just + need to use a [FONT] tag with Courier New for code bits. + """ + listing = False + + for line in lines: + line = line.strip() + + if line.startswith(VERSION_PREFIX): + i = len(VERSION_PREFIX) + version = line[i:] + print(f'[B]{version}[/B]') + continue + + if line.startswith(SECTION_PREFIX): + if listing: + print('[/LIST]') + listing = False + + i = len(SECTION_PREFIX) + section = line[i:] + print(f'[B]{section}:[/B]') + continue + + if line.startswith(LIST_ITEM_PREFIX): + if not listing: + print('[LIST]') + listing = True + + i = len(LIST_ITEM_PREFIX) + item = line[i:] + + # Replace **bold** text + item = re.sub(r'\*\*(.*?)\*\*', r'[B]\1[/B]', item) + + # Replace _italic_ text + item = re.sub(r'_(.*?)_', r'[I]\1[/I]', item) + + # Replace `code` text + item = re.sub(r'`(.*?)`', r'[FONT=Courier New]\1[/FONT]', item) + + # Replace [links](url) + item = re.sub(r'\[([^\]]+)]\(([^)]+)\)', r'[URL=\2]\1[/URL]', item) + + print(f'[*]{item}') + continue + + if len(line) > 0: + print(line) + + if listing: + print('[/LIST]') + + +def output_as_curseforge_html(lines): + """ + CurseForge uses regular HTML for file update descriptions, which makes it + fairly easy to convert from Markdown. Angled brackets need to be replaced + with their HTML entity equivalents, but other than that it's very similar + to the Spigot BBCode conversion. + """ + listing = False + + for line in lines: + line = line.strip() + + if line.startswith(VERSION_PREFIX): + i = len(VERSION_PREFIX) + version = line[i:] + print(f'

{version}

') + continue + + if line.startswith(SECTION_PREFIX): + if listing: + print('') + listing = False + + i = len(SECTION_PREFIX) + section = line[i:] + print(f'

{section}:

') + continue + + if line.startswith(LIST_ITEM_PREFIX): + if not listing: + print('') + + +def output_raw(lines): + [print(line.strip()) for line in lines] + + +if __name__ == '__main__': + main()