From 9cd3c970d21cc1b60d52a4f38a679b7729c6e0a6 Mon Sep 17 00:00:00 2001 From: Nicholas Felt Date: Tue, 24 Oct 2023 13:34:50 -0700 Subject: [PATCH] Add template for GitHub Releases (#67) * docs: Add extension that will enable GitHub Pages use for doc hosting. * ci: Add template for generating release notes for GitHub. * docs: Update changelog with information on template. * ci: Update version used for semantic release --- .github/workflows/package-release.yml | 10 +++--- CHANGELOG.md | 4 +++ docs/conf.py | 1 + python_semantic_release_templates/.macros.j2 | 26 ++++++++++++++ .../.previous_release_notes_for_template.md | 0 .../.release_notes.md.j2 | 7 ++++ .../CHANGELOG.md.j2 | 32 ++++------------- scripts/check_unreleased_changelog_items.py | 34 ++++++++++++++----- 8 files changed, 74 insertions(+), 40 deletions(-) create mode 100644 python_semantic_release_templates/.macros.j2 create mode 100644 python_semantic_release_templates/.previous_release_notes_for_template.md create mode 100644 python_semantic_release_templates/.release_notes.md.j2 diff --git a/.github/workflows/package-release.yml b/.github/workflows/package-release.yml index e3ab332b..be68cd51 100644 --- a/.github/workflows/package-release.yml +++ b/.github/workflows/package-release.yml @@ -37,14 +37,12 @@ jobs: with: python-version: x check-latest: true - - name: Check for unreleased entries in the Changelog - run: python scripts/check_unreleased_changelog_items.py - - name: Copy Changelog to template directory + - name: Check for unreleased entries in the Changelog and copy files to templates run: | - cp CHANGELOG.md python_semantic_release_templates/.previous_changelog_for_template.md - git add python_semantic_release_templates/.previous_changelog_for_template.md + python scripts/check_unreleased_changelog_items.py + git add python_semantic_release_templates - name: Python Semantic Release - uses: python-semantic-release/python-semantic-release@v8.1.2 + uses: python-semantic-release/python-semantic-release@v8.3.0 id: release with: force: ${{ inputs.release_level }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 1debd3a6..6a79777f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ ______________________________________________________________________ Things to be included in the next release go here. +### Added + +- Added a template for rendering custom Release Notes for GitHub Releases + ______________________________________________________________________ ## v0.1.19 (2023-10-24) diff --git a/docs/conf.py b/docs/conf.py index 95781891..f127b676 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -45,6 +45,7 @@ "sphinx.ext.todo", "sphinx.ext.inheritance_diagram", "sphinx.ext.graphviz", + "sphinx.ext.githubpages", "sphinxcontrib.jquery", "sphinxcontrib.mermaid", "sphinx_togglebutton", diff --git a/python_semantic_release_templates/.macros.j2 b/python_semantic_release_templates/.macros.j2 new file mode 100644 index 00000000..1034a917 --- /dev/null +++ b/python_semantic_release_templates/.macros.j2 @@ -0,0 +1,26 @@ +{%- macro populate_variables() %} + {%- set latest_release_dict_entry = (context.history.released.items()|list)[0] %} + {%- set latest_version_number = latest_release_dict_entry[0].as_tag() %} + {%- set latest_version_date = latest_release_dict_entry[1].tagged_date.strftime("%Y-%m-%d") %} + {%- set recently_merged_prs = {} %} + {%- for type_, commits in latest_release_dict_entry[1]["elements"] | dictsort %} + {%- for commit in commits %} + {%- set pr_num = commit.commit.message.rstrip().rsplit("(#", 1)[-1].rsplit(")", 1)[0]|int %} + {%- if pr_num %} + {%- do recently_merged_prs.update({commit.commit.message.split("\n")[0].rsplit("(#", 1)[0]|safe: pr_num}) %} + {%- endif %} + {%- endfor %} + {%- endfor %} + + {%- if recently_merged_prs %} + {%- set merged_prs_text_list = ["\n\n### Merged Pull Requests\n\n"] %} + {%- for message, number in recently_merged_prs.items() %} + {%- do merged_prs_text_list.append("- " ~ message ~ "([#" + number|string ~ "](" ~ number|string|pull_request_url ~ "))\n") %} + {%- endfor %} + {%- set merged_prs_text = (merged_prs_text_list|join).rstrip() %} + {%- else %} + {%- set merged_prs_text = "" %} + {%- endif %} + {%- set output = (latest_version_number, latest_version_date, merged_prs_text) %} + {{- caller(output) }} +{%- endmacro %} diff --git a/python_semantic_release_templates/.previous_release_notes_for_template.md b/python_semantic_release_templates/.previous_release_notes_for_template.md new file mode 100644 index 00000000..e69de29b diff --git a/python_semantic_release_templates/.release_notes.md.j2 b/python_semantic_release_templates/.release_notes.md.j2 new file mode 100644 index 00000000..d92964a5 --- /dev/null +++ b/python_semantic_release_templates/.release_notes.md.j2 @@ -0,0 +1,7 @@ +{%- import ".macros.j2" as macros %} + +{%- call(output) macros.populate_variables() %} + {%- filter replace("Things to be included in the next release go here.", "# " + output[0] + " (" + output[1] + ")" + output[2])|replace("##", "#") %} + {%- include ".previous_release_notes_for_template.md" %} + {% endfilter %} +{%- endcall %} diff --git a/python_semantic_release_templates/CHANGELOG.md.j2 b/python_semantic_release_templates/CHANGELOG.md.j2 index f0d003d6..81c96ba8 100644 --- a/python_semantic_release_templates/CHANGELOG.md.j2 +++ b/python_semantic_release_templates/CHANGELOG.md.j2 @@ -1,27 +1,7 @@ -{%- set latest_release_dict_entry = (context.history.released.items()|list)[0] %} -{%- set latest_version_number = latest_release_dict_entry[0].as_tag() %} -{%- set latest_version_date = latest_release_dict_entry[1].tagged_date.strftime("%Y-%m-%d") %} -{%- set recently_merged_prs = {} %} -{%- for type_, commits in latest_release_dict_entry[1]["elements"] | dictsort %} - {%- for commit in commits %} - {%- set pr_num = commit.commit.message.rstrip().rsplit("(#", 1)[-1].rsplit(")", 1)[0]|int %} - {%- if pr_num %} - {%- do recently_merged_prs.update({commit.commit.message.split("\n")[0].rsplit("(#", 1)[0]|safe: pr_num}) %} - {%- endif %} - {%- endfor %} -{%- endfor %} +{%- import ".macros.j2" as macros %} -{%- if recently_merged_prs %} - {%- set merged_prs_text_list = ["\n\n### Merged Pull Requests\n\n"] %} - {%- for message, number in recently_merged_prs.items() %} - {%- do merged_prs_text_list.append("- " ~ message ~ "([#" + number|string ~ "](" ~ number|string|pull_request_url ~ "))\n") %} - {%- endfor %} - {%- set merged_prs_text = (merged_prs_text_list|join).rstrip() %} -{%- else %} - {%- set merged_prs_text = "" %} -{%- endif %} - - -{%- filter replace("Things to be included in the next release go here.", "Things to be included in the next release go here.\n\n______________________________________________________________________\n\n## " + latest_version_number + " (" + latest_version_date + ")" + merged_prs_text) %} - {%- include ".previous_changelog_for_template.md" %} -{% endfilter %} +{%- call(output) macros.populate_variables() %} + {%- filter replace("Things to be included in the next release go here.", "Things to be included in the next release go here.\n\n______________________________________________________________________\n\n## " + output[0] + " (" + output[1] + ")" + output[2]) %} + {%- include ".previous_changelog_for_template.md" %} + {% endfilter %} +{%- endcall %} diff --git a/scripts/check_unreleased_changelog_items.py b/scripts/check_unreleased_changelog_items.py index 9015dfcf..e1488af8 100644 --- a/scripts/check_unreleased_changelog_items.py +++ b/scripts/check_unreleased_changelog_items.py @@ -1,11 +1,25 @@ """This script will check for unreleased entries in the CHANGELOG.md file. It will exit with a non-zero exit code if there are no unreleased entries. + +It will also copy the necessary files into the template directory to properly render the CHANGELOG +and Release Notes. """ import pathlib import re +import shutil -CHANGELOG_FILENAME = "CHANGELOG.md" +CHANGELOG_FILEPATH = pathlib.Path(__file__).parent.parent / "CHANGELOG.md" +TEMPLATE_CHANGELOG_FILEPATH = ( + pathlib.Path(__file__).parent.parent + / "python_semantic_release_templates" + / ".previous_changelog_for_template.md" +) +TEMPLATE_RELEASE_NOTES_FILEPATH = ( + pathlib.Path(__file__).parent.parent + / "python_semantic_release_templates" + / ".previous_release_notes_for_template.md" +) def main() -> None: @@ -14,16 +28,17 @@ def main() -> None: Raises: SystemExit: Indicates no new entries were found. """ + release_notes_content = "" found_entries = False - with open( - pathlib.Path(__file__).parent.parent / CHANGELOG_FILENAME, encoding="utf-8" - ) as changelog_file: + with CHANGELOG_FILEPATH.open(mode="r", encoding="utf-8") as changelog_file: tracking_unreleased = False tracking_entries = False for line in changelog_file: if line.startswith("___"): tracking_unreleased = False tracking_entries = False + if tracking_unreleased: + release_notes_content += line if line.startswith("## Unreleased"): tracking_unreleased = True if tracking_unreleased and line.startswith( @@ -37,15 +52,18 @@ def main() -> None: ) ): tracking_entries = True - if tracking_entries: + if tracking_entries and not found_entries: found_entries = bool(re.match(r"^- \w+", line)) - if found_entries: - break if not found_entries: - msg = f"No unreleased entries were found in {CHANGELOG_FILENAME}." + msg = f"No unreleased entries were found in {CHANGELOG_FILEPATH}." raise SystemExit(msg) + # Copy the files to the correct location + shutil.copy(CHANGELOG_FILEPATH, TEMPLATE_CHANGELOG_FILEPATH) + with TEMPLATE_RELEASE_NOTES_FILEPATH.open("w", encoding="utf-8") as template_release_notes: + template_release_notes.write(release_notes_content.strip() + "\n") + if __name__ == "__main__": main()