From 2f797bcd5796f279c8c93a27b612c5850f5600a3 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 11 Jul 2023 11:02:42 +0100 Subject: [PATCH 01/40] Add script to bump version numbers. (Initial revision). [skip ci] --- .../update_version_number_in_files.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tools/pylib/_boutpp_build/update_version_number_in_files.py diff --git a/tools/pylib/_boutpp_build/update_version_number_in_files.py b/tools/pylib/_boutpp_build/update_version_number_in_files.py new file mode 100644 index 0000000000..9497690a2e --- /dev/null +++ b/tools/pylib/_boutpp_build/update_version_number_in_files.py @@ -0,0 +1,53 @@ +from pathlib import Path +import re + + +def get_full_filepath(filepath): + + return Path(r"C:\git\BOUT-dev") / filepath + + +def update_version_number_in_file(relative_filepath, pattern, new_version_number): + + def get_replacement(match): + return match[0].replace(match[1], new_version_number) + + full_filepath = get_full_filepath(relative_filepath) + with open(full_filepath, "r", encoding='UTF-8') as file: + file_contents = file.read() + updated_text = re.sub(pattern, get_replacement, file_contents, flags=re.MULTILINE) + with open(full_filepath, "w", encoding='UTF-8') as file: + file.write(updated_text) + + +def bump_version_numbers(new_version_number): + + short_version_number = new_version_number[0:3] + new_minor_version_number = str(int(new_version_number[2]) + 1) + bout_next_version_number = re.sub(r"(?<=\d\.)\d(?=\.\d)", new_minor_version_number, new_version_number) + + update_version_number_in_file("configure.ac", + r"^AC_INIT\(\[BOUT\+\+\],\[(\d\.\d\.\d)\]", new_version_number) + update_version_number_in_file("CITATION.cff", + r"^version: (\d\.\d\.\d)", new_version_number) + update_version_number_in_file("manual/sphinx/conf.py", + r"^version = \"(\d\.\d)\"", short_version_number) + update_version_number_in_file("manual/sphinx/conf.py", + r"^release = \"(\d\.\d\.\d)\"", new_version_number) + update_version_number_in_file("manual/doxygen/Doxyfile_readthedocs", + r"^PROJECT_NUMBER = (\d\.\d\.\d)", new_version_number) + update_version_number_in_file("manual/doxygen/Doxyfile", + r"^PROJECT_NUMBER = (\d\.\d\.\d)", new_version_number) + update_version_number_in_file("CMakeLists.txt", + r"^set\(_bout_previous_version \"v(\d\.\d\.\d)\"\)", new_version_number) + update_version_number_in_file("CMakeLists.txt", + r"^set\(_bout_next_version \"(\d\.\d\.\d)\"\)", bout_next_version_number) + update_version_number_in_file("tools/pylib/_boutpp_build/backend.py", + r"_bout_previous_version = \"v(\d\.\d\.\d)\"", new_version_number) + update_version_number_in_file("tools/pylib/_boutpp_build/backend.py", + r"_bout_next_version = \"v(\d\.\d\.\d)\"", bout_next_version_number) + + +if __name__ == '__main__': + + bump_version_numbers(new_version_number="6.1.0") From 3a5bda2bd3445ec3f50df0a50bef0d5b81fa6c72 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 11 Jul 2023 11:03:08 +0100 Subject: [PATCH 02/40] Add script to check for new authors. (Initial revision). [skip ci] --- tools/pylib/_boutpp_build/update_citations.py | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 tools/pylib/_boutpp_build/update_citations.py diff --git a/tools/pylib/_boutpp_build/update_citations.py b/tools/pylib/_boutpp_build/update_citations.py new file mode 100644 index 0000000000..2e276a8c5c --- /dev/null +++ b/tools/pylib/_boutpp_build/update_citations.py @@ -0,0 +1,166 @@ +from pathlib import Path +from unidecode import unidecode +import yaml + +authors_from_git = ["A Allen", "Aaron Fisher", "Adam Dempsey", "Andrew Allen", "arkabokshi", "Ben Dudson", "bendudson", + "Benjamin Dudson", "Brendan", "Brendan Shanahan", "Brett Friedman", "brey", "BS", "bshanahan", + "Chenhao Ma", "Chris MacMackin", "D Dickinson", "David", "David Bold", "David Dickinson", + "David Schwörer", "dependabot[bot]", "Dmitry Feliksovich Meyerson", "Dmitry Meyerson", "dschwoerer", + "Eric Medwedeff", "Erik Grinaker", "Fabio Riva", "George Breyiannis", "GitHub Merge Button", + "github-actions[bot]", "hahahasan", "Haruki Seto", "Haruki SETO", "holger", "Holger Jones", + "Hong Zhang", "Ilon Joseph", "Ilon Joseph - x31405", "Jarrod Leddy", "JB Leddy", "j-b-o", + "Jed Brown", "Jens Madsen", "John Omotani", "johnomotani", "jonesholger", "Joseph Parker", + "Joshua Sauppe", "Kab Seok Kang", "kangkabseok", "Kevin Savage", "Licheng Wang", "loeiten", + "Luke Easy", "M Leconte", "M V Umansky", "Marta Estarellas", "Matt Thomas", "Maxim Umansky", + "Maxim Umansky - x26041", "Michael Løiten", "Michael Loiten Magnussen", "Minwoo Kim", + "Nicholas Walkden", "Nick Walkden", "nick-walkden", "Olivier Izacard", "Pengwei Xi", "Peter Hill", + "Peter Naylor", "Qin, Yining", "Sajidah Ahmed", "Sanat Tiwari", "Sean Farley", "seanfarley", + "Seto Haruki", "Simon Myers", "Tianyang Xia", "Toby James", "tomc271", "Tongnyeol Rhee", + "Xiang Liu", "Xinliang Xu", "Xueqiao Xu", "Yining Qin", "ZedThree", "Zhanhui Wang"] + + +def parse_cff_file(filename): + with open(filename, "r", encoding='UTF-8') as stream: + try: + return yaml.safe_load(stream) + except yaml.YAMLError as exc: + print(exc) + + +def get_authors_from_cff_file(): + filename = Path(r"C:\git\BOUT-dev") / "CITATION.cff" + file_contents = parse_cff_file(filename) + try: + return file_contents["authors"] + except KeyError as key_error: + print("Failed to find section:", key_error, "in", filename) + + +class ExistingAuthorNames: + + def __init__(self, existing_authors): + self._existing_author_names = \ + [(unidecode(a.get("given-names")), unidecode(a.get("family-names"))) for a in existing_authors] + + def last_name_matches_surname_and_first_name_or_first_letter_matches_given_name(self, last_name, first_name): + matches = [n for n in self._existing_author_names if + n[1].casefold() == last_name.casefold()] # Last name matches surname + + for match in matches: + if match[0].casefold() == first_name.casefold(): # The given name also matches author first name + return True + if match[0][0].casefold() == first_name[0].casefold(): # The first initial matches author first name + return True + + def first_name_matches_surname_and_last_name_matches_given_name(self, first_name, last_name): + matches = [n for n in self._existing_author_names if + n[1].casefold() == first_name.casefold()] # First name matches surname + + for match in matches: + if match[0].casefold() == last_name.casefold(): # The given name also matches author last name + return True + + def surname_matches_whole_author_name(self, author): + + surname_matches = [n for n in self._existing_author_names if n[1].casefold() == author.casefold()] + if len(surname_matches) > 0: + return True + + def given_name_matches_matches_whole_author_name(self, author): + given_name_matches = [n for n in self._existing_author_names if n[0].casefold() == author.casefold()] + if len(given_name_matches) > 0: + return True + + def combined_name_matches_whole_author_name(self, author): + + combined_name_matches = [n for n in self._existing_author_names if + (n[0] + n[1]).casefold() == author.casefold()] + if len(combined_name_matches) > 0: + return True + + def combined_name_reversed_matches(self, author): + + combined_name_reversed_matches = [n for n in self._existing_author_names if + (n[1] + n[0]).casefold() == author.casefold()] + if len(combined_name_reversed_matches) > 0: + return True + + def author_name_is_first_initial_and_surname_concatenated(self, author): + first_character = author[0] + remaining_characters = author[1:] + matches = [n for n in self._existing_author_names if + n[1].casefold() == remaining_characters.casefold()] # Second part of name matches surname + for match in matches: + if match[0][0].casefold() == first_character.casefold(): # The first initial matches author first name + return True + + +def author_found_in_existing_authors(author, existing_authors): + existing_author_names = ExistingAuthorNames(existing_authors) + + names = author.split() + first_name = unidecode(names[0].replace(",", "")) + last_name = unidecode(names[-1]) + + if existing_author_names.last_name_matches_surname_and_first_name_or_first_letter_matches_given_name( + last_name, first_name): + return True + + if existing_author_names.first_name_matches_surname_and_last_name_matches_given_name(first_name, last_name): + return True + + if existing_author_names.surname_matches_whole_author_name(author): + return True + + if existing_author_names.given_name_matches_matches_whole_author_name(author): + return True + + if existing_author_names.combined_name_matches_whole_author_name(author): + return True + + if existing_author_names.combined_name_reversed_matches(author): + return True + + if existing_author_names.author_name_is_first_initial_and_surname_concatenated(author): + return True + + return False + + +def update_citations(): + + existing_authors = get_authors_from_cff_file() + + nonhuman_authors = [a for a in authors_from_git if "github" in a.casefold() or "dependabot" in a.casefold()] + + known_authors = [a for a in authors_from_git if a in KNOWN_AUTHORS] + + human_authors = [a for a in authors_from_git if a not in nonhuman_authors] + + authors_to_search_for = [a for a in human_authors if a not in known_authors] + + unrecognised_authors = [a for a in authors_to_search_for if + not author_found_in_existing_authors(a, existing_authors)] + + print("The following authors were not recognised. Add to citations?") + for author in unrecognised_authors: + print(author) + + +KNOWN_AUTHORS = {"bendudson", + "brey", + "David Schwörer", + "dschwoerer", + "hahahasan", + "Ilon Joseph - x31405", + "kangkabseok", + "loeiten", + "Michael Loiten Magnussen", + "Maxim Umansky - x26041", + "nick-walkden", + "ZedThree", + # "tomc271" + } + +if __name__ == '__main__': + update_citations() From ab8deca87d81c39fdb776382d4dd58aa1e1b4a3d Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 12 Jul 2023 10:02:00 +0100 Subject: [PATCH 03/40] Move scripts to bin directory --- {tools/pylib/_boutpp_build => bin}/update_citations.py | 0 .../pylib/_boutpp_build => bin}/update_version_number_in_files.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {tools/pylib/_boutpp_build => bin}/update_citations.py (100%) rename {tools/pylib/_boutpp_build => bin}/update_version_number_in_files.py (100%) diff --git a/tools/pylib/_boutpp_build/update_citations.py b/bin/update_citations.py similarity index 100% rename from tools/pylib/_boutpp_build/update_citations.py rename to bin/update_citations.py diff --git a/tools/pylib/_boutpp_build/update_version_number_in_files.py b/bin/update_version_number_in_files.py similarity index 100% rename from tools/pylib/_boutpp_build/update_version_number_in_files.py rename to bin/update_version_number_in_files.py From e397570db400831880d9feb9d2404cde9fa1f778 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 12 Jul 2023 10:24:45 +0100 Subject: [PATCH 04/40] Remove hard-coded paths. --- bin/update_citations.py | 6 ++++-- bin/update_version_number_in_files.py | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index 2e276a8c5c..a9936aaed9 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -1,6 +1,7 @@ from pathlib import Path -from unidecode import unidecode +import os import yaml +from unidecode import unidecode authors_from_git = ["A Allen", "Aaron Fisher", "Adam Dempsey", "Andrew Allen", "arkabokshi", "Ben Dudson", "bendudson", "Benjamin Dudson", "Brendan", "Brendan Shanahan", "Brett Friedman", "brey", "BS", "bshanahan", @@ -28,7 +29,8 @@ def parse_cff_file(filename): def get_authors_from_cff_file(): - filename = Path(r"C:\git\BOUT-dev") / "CITATION.cff" + main_directory = Path(os.path.abspath(__file__)).parent.parent + filename = Path(main_directory) / "CITATION.cff" file_contents = parse_cff_file(filename) try: return file_contents["authors"] diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 9497690a2e..261e21108d 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -1,10 +1,12 @@ from pathlib import Path +import os import re def get_full_filepath(filepath): - return Path(r"C:\git\BOUT-dev") / filepath + main_directory = Path(os.path.abspath(__file__)).parent.parent + return Path(main_directory) / filepath def update_version_number_in_file(relative_filepath, pattern, new_version_number): From 039f117f9451a58e776eb6a37c27e7cdf2d13a2f Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 12 Jul 2023 10:28:04 +0100 Subject: [PATCH 05/40] Make KNOWN_AUTHORS a dictionary, holding more information to help identify the known authors. --- bin/update_citations.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index a9936aaed9..0c7c606660 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -2,6 +2,7 @@ import os import yaml from unidecode import unidecode +from typing import NamedTuple authors_from_git = ["A Allen", "Aaron Fisher", "Adam Dempsey", "Andrew Allen", "arkabokshi", "Ben Dudson", "bendudson", "Benjamin Dudson", "Brendan", "Brendan Shanahan", "Brett Friedman", "brey", "BS", "bshanahan", @@ -149,19 +150,24 @@ def update_citations(): print(author) -KNOWN_AUTHORS = {"bendudson", - "brey", - "David Schwörer", - "dschwoerer", - "hahahasan", - "Ilon Joseph - x31405", - "kangkabseok", - "loeiten", - "Michael Loiten Magnussen", - "Maxim Umansky - x26041", - "nick-walkden", - "ZedThree", - # "tomc271" +class KnownAuthor(NamedTuple): + family_names: str + given_names: str + + +KNOWN_AUTHORS = {"bendudson": KnownAuthor("Dodson", "Benjamin"), + "brey": KnownAuthor("Breyiannis", "George"), + "David Schwörer": KnownAuthor("Bold", "David"), + "dschwoerer": KnownAuthor("Bold", "David"), + "hahahasan": KnownAuthor("Muhammed", "Hasan"), + "Ilon Joseph - x31405": KnownAuthor("Joseph", "Ilon"), + "kangkabseok": KnownAuthor("Kang", "Kab Seok"), + "loeiten": KnownAuthor("Løiten", "Michael"), + "Michael Loiten Magnussen": KnownAuthor("Løiten", "Michael"), + "Maxim Umansky - x26041": KnownAuthor("Umansky", "Maxim"), + "nick-walkden": KnownAuthor("Walkden", "Nicholas"), + "ZedThree": KnownAuthor("Hill", "Peter") + # "tomc271": KnownAuthor("Chapman", "Tom") } if __name__ == '__main__': From 8d0e0f84aae72697a676b0908fdb3112a4c81208 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 12 Jul 2023 10:33:58 +0100 Subject: [PATCH 06/40] Handle version numbers with multiple digits. --- bin/update_version_number_in_files.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 261e21108d..8234fc9f7c 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -26,28 +26,28 @@ def bump_version_numbers(new_version_number): short_version_number = new_version_number[0:3] new_minor_version_number = str(int(new_version_number[2]) + 1) - bout_next_version_number = re.sub(r"(?<=\d\.)\d(?=\.\d)", new_minor_version_number, new_version_number) + bout_next_version_number = re.sub(r"(?<=\d\.)\d+(?=\.\d+)", new_minor_version_number, new_version_number) update_version_number_in_file("configure.ac", - r"^AC_INIT\(\[BOUT\+\+\],\[(\d\.\d\.\d)\]", new_version_number) + r"^AC_INIT\(\[BOUT\+\+\],\[(\d+\.\d+\.\d+)\]", new_version_number) update_version_number_in_file("CITATION.cff", - r"^version: (\d\.\d\.\d)", new_version_number) + r"^version: (\d+\.\d+\.\d+)", new_version_number) update_version_number_in_file("manual/sphinx/conf.py", - r"^version = \"(\d\.\d)\"", short_version_number) + r"^version = \"(\d+\.\d+)\"", short_version_number) update_version_number_in_file("manual/sphinx/conf.py", - r"^release = \"(\d\.\d\.\d)\"", new_version_number) + r"^release = \"(\d+\.\d+\.\d+)\"", new_version_number) update_version_number_in_file("manual/doxygen/Doxyfile_readthedocs", - r"^PROJECT_NUMBER = (\d\.\d\.\d)", new_version_number) + r"^PROJECT_NUMBER = (\d+\.\d+\.\d+)", new_version_number) update_version_number_in_file("manual/doxygen/Doxyfile", - r"^PROJECT_NUMBER = (\d\.\d\.\d)", new_version_number) + r"^PROJECT_NUMBER = (\d+\.\d+\.\d+)", new_version_number) update_version_number_in_file("CMakeLists.txt", - r"^set\(_bout_previous_version \"v(\d\.\d\.\d)\"\)", new_version_number) + r"^set\(_bout_previous_version \"v(\d+\.\d+\.\d+)\"\)", new_version_number) update_version_number_in_file("CMakeLists.txt", - r"^set\(_bout_next_version \"(\d\.\d\.\d)\"\)", bout_next_version_number) + r"^set\(_bout_next_version \"(\d+\.\d+\.\d+)\"\)", bout_next_version_number) update_version_number_in_file("tools/pylib/_boutpp_build/backend.py", - r"_bout_previous_version = \"v(\d\.\d\.\d)\"", new_version_number) + r"_bout_previous_version = \"v(\d+\.\d+\.\d+)\"", new_version_number) update_version_number_in_file("tools/pylib/_boutpp_build/backend.py", - r"_bout_next_version = \"v(\d\.\d\.\d)\"", bout_next_version_number) + r"_bout_next_version = \"v(\d+\.\d+\.\d+)\"", bout_next_version_number) if __name__ == '__main__': From a248468d330dd288728a3c75f1753b40ce2f493b Mon Sep 17 00:00:00 2001 From: tomc271 Date: Wed, 12 Jul 2023 11:12:05 +0100 Subject: [PATCH 07/40] Add classes VersionNumber and ShortVersionNumber. --- bin/update_version_number_in_files.py | 37 +++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 8234fc9f7c..24ba8bd7b6 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -12,7 +12,7 @@ def get_full_filepath(filepath): def update_version_number_in_file(relative_filepath, pattern, new_version_number): def get_replacement(match): - return match[0].replace(match[1], new_version_number) + return match[0].replace(match[1], new_version_number.as_string()) full_filepath = get_full_filepath(relative_filepath) with open(full_filepath, "r", encoding='UTF-8') as file: @@ -24,9 +24,10 @@ def get_replacement(match): def bump_version_numbers(new_version_number): - short_version_number = new_version_number[0:3] - new_minor_version_number = str(int(new_version_number[2]) + 1) - bout_next_version_number = re.sub(r"(?<=\d\.)\d+(?=\.\d+)", new_minor_version_number, new_version_number) + short_version_number = ShortVersionNumber(new_version_number.major_version, new_version_number.minor_version) + bout_next_version_number = VersionNumber(new_version_number.major_version, + new_version_number.minor_version + 1, + new_version_number.patch_version) update_version_number_in_file("configure.ac", r"^AC_INIT\(\[BOUT\+\+\],\[(\d+\.\d+\.\d+)\]", new_version_number) @@ -50,6 +51,32 @@ def bump_version_numbers(new_version_number): r"_bout_next_version = \"v(\d+\.\d+\.\d+)\"", bout_next_version_number) +class VersionNumber: + + major_version: int + minor_version: int + patch_version: int + + def __init__(self, major_version, minor_version, patch_version): + self.major_version = major_version + self.minor_version = minor_version + self.patch_version = patch_version + + def as_string(self): + return "%d.%d.%d" % (self.major_version, self.minor_version, self.patch_version) + + +class ShortVersionNumber(VersionNumber): + + def __init__(self, major_version, minor_version): + self.major_version = major_version + self.minor_version = minor_version + self.patch_version = None + + def as_string(self): + return "%d.%d" % (self.major_version, self.minor_version) + + if __name__ == '__main__': - bump_version_numbers(new_version_number="6.1.0") + bump_version_numbers(new_version_number=VersionNumber(63, 15, 12)) From 9a502e1d28a567f344066471ac56bfcb2e7a1131 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 13 Jul 2023 17:08:07 +0100 Subject: [PATCH 08/40] Run git log command to get the list of authors. --- bin/update_citations.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index 0c7c606660..a25eae04f9 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -1,24 +1,27 @@ +import subprocess from pathlib import Path import os import yaml from unidecode import unidecode from typing import NamedTuple -authors_from_git = ["A Allen", "Aaron Fisher", "Adam Dempsey", "Andrew Allen", "arkabokshi", "Ben Dudson", "bendudson", - "Benjamin Dudson", "Brendan", "Brendan Shanahan", "Brett Friedman", "brey", "BS", "bshanahan", - "Chenhao Ma", "Chris MacMackin", "D Dickinson", "David", "David Bold", "David Dickinson", - "David Schwörer", "dependabot[bot]", "Dmitry Feliksovich Meyerson", "Dmitry Meyerson", "dschwoerer", - "Eric Medwedeff", "Erik Grinaker", "Fabio Riva", "George Breyiannis", "GitHub Merge Button", - "github-actions[bot]", "hahahasan", "Haruki Seto", "Haruki SETO", "holger", "Holger Jones", - "Hong Zhang", "Ilon Joseph", "Ilon Joseph - x31405", "Jarrod Leddy", "JB Leddy", "j-b-o", - "Jed Brown", "Jens Madsen", "John Omotani", "johnomotani", "jonesholger", "Joseph Parker", - "Joshua Sauppe", "Kab Seok Kang", "kangkabseok", "Kevin Savage", "Licheng Wang", "loeiten", - "Luke Easy", "M Leconte", "M V Umansky", "Marta Estarellas", "Matt Thomas", "Maxim Umansky", - "Maxim Umansky - x26041", "Michael Løiten", "Michael Loiten Magnussen", "Minwoo Kim", - "Nicholas Walkden", "Nick Walkden", "nick-walkden", "Olivier Izacard", "Pengwei Xi", "Peter Hill", - "Peter Naylor", "Qin, Yining", "Sajidah Ahmed", "Sanat Tiwari", "Sean Farley", "seanfarley", - "Seto Haruki", "Simon Myers", "Tianyang Xia", "Toby James", "tomc271", "Tongnyeol Rhee", - "Xiang Liu", "Xinliang Xu", "Xueqiao Xu", "Yining Qin", "ZedThree", "Zhanhui Wang"] + +def get_authors_from_git(): + + main_directory = Path(os.path.abspath(__file__)).parent.parent + subprocess.run(["cd", main_directory], shell=True) + + output = subprocess.run(["git", "log", "--format='%aN'"], capture_output=True) + if output.stderr: + return output.stderr + + authors_string = output.stdout.decode() + authors_list = authors_string.split('\n') + authors_without_quotes = [a.strip("'") for a in authors_list] + + distinct_authors = set(authors_without_quotes) + distinct_authors_list_without_initial_empty_string = list(distinct_authors)[1:] + return distinct_authors_list_without_initial_empty_string def parse_cff_file(filename): @@ -132,8 +135,6 @@ def author_found_in_existing_authors(author, existing_authors): def update_citations(): - existing_authors = get_authors_from_cff_file() - nonhuman_authors = [a for a in authors_from_git if "github" in a.casefold() or "dependabot" in a.casefold()] known_authors = [a for a in authors_from_git if a in KNOWN_AUTHORS] @@ -171,4 +172,7 @@ class KnownAuthor(NamedTuple): } if __name__ == '__main__': + + authors_from_git = get_authors_from_git() + existing_authors = get_authors_from_cff_file() update_citations() From bec2572a5be72d637b571895ad8c232c443dfc3d Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 13 Jul 2023 17:11:37 +0100 Subject: [PATCH 09/40] Extracted function get_main_directory(). --- bin/update_citations.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index a25eae04f9..c4f5eed142 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -6,9 +6,13 @@ from typing import NamedTuple +def get_main_directory(): + return Path(os.path.abspath(__file__)).parent.parent + + def get_authors_from_git(): - main_directory = Path(os.path.abspath(__file__)).parent.parent + main_directory = get_main_directory() subprocess.run(["cd", main_directory], shell=True) output = subprocess.run(["git", "log", "--format='%aN'"], capture_output=True) From bb6f4e15fc1a59e5aeb90ae27953e0b53df5b23e Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 13 Jul 2023 17:23:51 +0100 Subject: [PATCH 10/40] Also list emails for unrecognised authors. [skip ci] --- bin/update_citations.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index c4f5eed142..99f3e2c11c 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -15,7 +15,7 @@ def get_authors_from_git(): main_directory = get_main_directory() subprocess.run(["cd", main_directory], shell=True) - output = subprocess.run(["git", "log", "--format='%aN'"], capture_output=True) + output = subprocess.run(["git", "log", "--format='%aN %aE'"], capture_output=True) if output.stderr: return output.stderr @@ -24,8 +24,9 @@ def get_authors_from_git(): authors_without_quotes = [a.strip("'") for a in authors_list] distinct_authors = set(authors_without_quotes) - distinct_authors_list_without_initial_empty_string = list(distinct_authors)[1:] - return distinct_authors_list_without_initial_empty_string + distinct_authors_list_without_empty_strings = [a for a in distinct_authors if a] + authors_with_emails = [a.rsplit(maxsplit=1) for a in distinct_authors_list_without_empty_strings] + return authors_with_emails def parse_cff_file(filename): @@ -106,6 +107,7 @@ def author_name_is_first_initial_and_surname_concatenated(self, author): def author_found_in_existing_authors(author, existing_authors): + existing_author_names = ExistingAuthorNames(existing_authors) names = author.split() @@ -139,16 +141,16 @@ def author_found_in_existing_authors(author, existing_authors): def update_citations(): - nonhuman_authors = [a for a in authors_from_git if "github" in a.casefold() or "dependabot" in a.casefold()] + nonhuman_authors = [a for a in authors_from_git if "github" in a[0].casefold() or "dependabot" in a[0].casefold()] - known_authors = [a for a in authors_from_git if a in KNOWN_AUTHORS] + known_authors = [a for a in authors_from_git if a[0] in KNOWN_AUTHORS] human_authors = [a for a in authors_from_git if a not in nonhuman_authors] authors_to_search_for = [a for a in human_authors if a not in known_authors] unrecognised_authors = [a for a in authors_to_search_for if - not author_found_in_existing_authors(a, existing_authors)] + not author_found_in_existing_authors(a[0], existing_authors)] print("The following authors were not recognised. Add to citations?") for author in unrecognised_authors: From 105845417f0c09a45ee0aa5bad960ad6115f03ef Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 10 Aug 2023 10:03:46 +0100 Subject: [PATCH 11/40] Copy functions from other scripts: apply_fixes(replacements, source) yes_or_no(question) create_patch(filename, original, modified) [unfinished] --- bin/update_version_number_in_files.py | 130 +++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 24ba8bd7b6..5ef8da844b 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -1,4 +1,8 @@ from pathlib import Path +import argparse +import difflib +import copy +import textwrap import os import re @@ -77,6 +81,130 @@ def as_string(self): return "%d.%d" % (self.major_version, self.minor_version) +def apply_fixes(replacements, source): + + raise NotImplementedError + # """Apply all fixes in this module""" + # modified = copy.deepcopy(source) + # + # for replacement in replacements: + # if replacement["new"] is None: + # print( + # "'%s' has been removed, please delete from your code" + # % replacement["old"] + # ) + # continue + # + # modified = fix_include_version_header( + # replacement["old"], replacement["headers"], modified + # ) + # if replacement["macro"] and replacement["always_defined"]: + # modified = fix_always_defined_macros( + # replacement["old"], replacement["new"], modified + # ) + # elif replacement["always_defined"]: + # modified = fix_ifdefs(replacement["old"], modified) + # modified = fix_replacement(replacement["old"], replacement["new"], modified) + # + # return modified + + +def yes_or_no(question): + """Convert user input from yes/no variations to True/False""" + while True: + reply = input(question + " [y/N] ").lower().strip() + if not reply or reply[0] == "n": + return False + if reply[0] == "y": + return True + + +def create_patch(filename, original, modified): + """Create a unified diff between original and modified""" + + patch = "\n".join( + difflib.unified_diff( + original.splitlines(), + modified.splitlines(), + fromfile=filename, + tofile=filename, + lineterm="", + ) + ) + + return patch + + if __name__ == '__main__': - bump_version_numbers(new_version_number=VersionNumber(63, 15, 12)) + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.dedent( + """\ + Fix macro defines for BOUT++ v4 -> v5 + + Please note that this is only slightly better than dumb text replacement. It + will fix the following: + + * replacement of macros with variables or new names + * inclusion of correct headers for new variables + * removal of #if(n)def/#endif blocks that do simple checks for the old + macro, keeping the appriopriate part, if replaced by a variable + * change '#if(n)def' for '#if (!)' if the replacment is always defined + + It will try not to replace quoted macro names, but may + still replace them in strings or comments. + + Please check the diff output carefully! + """ + ), + ) + + parser.add_argument("files", action="store", nargs="+", help="Input files") + parser.add_argument( + "--force", "-f", action="store_true", help="Make changes without asking" + ) + parser.add_argument( + "--quiet", "-q", action="store_true", help="Don't print patches" + ) + parser.add_argument( + "--patch-only", "-p", action="store_true", help="Print the patches and exit" + ) + + args = parser.parse_args() + + if args.force and args.patch_only: + raise ValueError("Incompatible options: --force and --patch") + + for filename in args.files: + with open(filename, "r") as f: + contents = f.read() + original = copy.deepcopy(contents) + + replacements = bump_version_numbers(new_version_number=VersionNumber(63, 15, 12)) + modified = apply_fixes(replacements, contents) + patch = create_patch(filename, original, modified) + + if args.patch_only: + print(patch) + continue + + if not patch: + if not args.quiet: + print("No changes to make to {}".format(filename)) + continue + + if not args.quiet: + print("\n******************************************") + print("Changes to {}\n".format(filename)) + print(patch) + print("\n******************************************") + + if args.force: + make_change = True + else: + make_change = yes_or_no("Make changes to {}?".format(filename)) + + if make_change: + with open(filename, "w") as f: + f.write(modified) From 120af431ac09fefd1394a26ea67068109d7daded Mon Sep 17 00:00:00 2001 From: tomc271 Date: Thu, 10 Aug 2023 16:14:53 +0100 Subject: [PATCH 12/40] Apply copied functions to this script. --- bin/update_version_number_in_files.py | 108 +++++++++++++------------- 1 file changed, 52 insertions(+), 56 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 5ef8da844b..7a570000a6 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -15,15 +15,39 @@ def get_full_filepath(filepath): def update_version_number_in_file(relative_filepath, pattern, new_version_number): - def get_replacement(match): - return match[0].replace(match[1], new_version_number.as_string()) - full_filepath = get_full_filepath(relative_filepath) + with open(full_filepath, "r", encoding='UTF-8') as file: + file_contents = file.read() - updated_text = re.sub(pattern, get_replacement, file_contents, flags=re.MULTILINE) - with open(full_filepath, "w", encoding='UTF-8') as file: - file.write(updated_text) + original = copy.deepcopy(file_contents) + + modified = apply_fixes(pattern, new_version_number, file_contents) + patch = create_patch(str(full_filepath), original, modified) + + if args.patch_only: + print(patch) + return + + if not patch: + if not args.quiet: + print("No changes to make to {}".format(full_filepath)) + return + + if not args.quiet: + print("\n******************************************") + print("Changes to {}\n".format(full_filepath)) + print(patch) + print("\n******************************************") + + if args.force: + make_change = True + else: + make_change = yes_or_no("Make changes to {}?".format(full_filepath)) + + if make_change: + with open(full_filepath, "w", encoding='UTF-8') as file: + file.write(modified) def bump_version_numbers(new_version_number): @@ -81,9 +105,27 @@ def as_string(self): return "%d.%d" % (self.major_version, self.minor_version) -def apply_fixes(replacements, source): +def apply_fixes(pattern, new_version_number, source): + """Apply the various fixes for each factory to source. Returns + modified source + + Parameters + ---------- + pattern + Regex pattern to apply for replacement + new_version_number + New version number to use in replacement + source + Text to update + """ + + def get_replacement(match): + return match[0].replace(match[1], new_version_number.as_string()) + + modified = re.sub(pattern, get_replacement, source, flags=re.MULTILINE) + + return modified - raise NotImplementedError # """Apply all fixes in this module""" # modified = copy.deepcopy(source) # @@ -141,26 +183,11 @@ def create_patch(filename, original, modified): formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent( """\ - Fix macro defines for BOUT++ v4 -> v5 - - Please note that this is only slightly better than dumb text replacement. It - will fix the following: - - * replacement of macros with variables or new names - * inclusion of correct headers for new variables - * removal of #if(n)def/#endif blocks that do simple checks for the old - macro, keeping the appriopriate part, if replaced by a variable - * change '#if(n)def' for '#if (!)' if the replacment is always defined - - It will try not to replace quoted macro names, but may - still replace them in strings or comments. - - Please check the diff output carefully! + TODO: Description here... """ ), ) - parser.add_argument("files", action="store", nargs="+", help="Input files") parser.add_argument( "--force", "-f", action="store_true", help="Make changes without asking" ) @@ -176,35 +203,4 @@ def create_patch(filename, original, modified): if args.force and args.patch_only: raise ValueError("Incompatible options: --force and --patch") - for filename in args.files: - with open(filename, "r") as f: - contents = f.read() - original = copy.deepcopy(contents) - - replacements = bump_version_numbers(new_version_number=VersionNumber(63, 15, 12)) - modified = apply_fixes(replacements, contents) - patch = create_patch(filename, original, modified) - - if args.patch_only: - print(patch) - continue - - if not patch: - if not args.quiet: - print("No changes to make to {}".format(filename)) - continue - - if not args.quiet: - print("\n******************************************") - print("Changes to {}\n".format(filename)) - print(patch) - print("\n******************************************") - - if args.force: - make_change = True - else: - make_change = yes_or_no("Make changes to {}?".format(filename)) - - if make_change: - with open(filename, "w") as f: - f.write(modified) + bump_version_numbers(new_version_number=VersionNumber(63, 15, 12)) \ No newline at end of file From 1c28f3118e7e01dbd1ac8d79c9f11e46d1eaf597 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 13:10:36 +0100 Subject: [PATCH 13/40] Black formatting [skip ci] --- bin/update_citations.py | 152 +++++++++++++++++--------- bin/update_version_number_in_files.py | 90 +++++++++------ 2 files changed, 154 insertions(+), 88 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index 99f3e2c11c..232a1fb48c 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -11,7 +11,6 @@ def get_main_directory(): def get_authors_from_git(): - main_directory = get_main_directory() subprocess.run(["cd", main_directory], shell=True) @@ -20,17 +19,19 @@ def get_authors_from_git(): return output.stderr authors_string = output.stdout.decode() - authors_list = authors_string.split('\n') + authors_list = authors_string.split("\n") authors_without_quotes = [a.strip("'") for a in authors_list] distinct_authors = set(authors_without_quotes) distinct_authors_list_without_empty_strings = [a for a in distinct_authors if a] - authors_with_emails = [a.rsplit(maxsplit=1) for a in distinct_authors_list_without_empty_strings] + authors_with_emails = [ + a.rsplit(maxsplit=1) for a in distinct_authors_list_without_empty_strings + ] return authors_with_emails def parse_cff_file(filename): - with open(filename, "r", encoding='UTF-8') as stream: + with open(filename, "r", encoding="UTF-8") as stream: try: return yaml.safe_load(stream) except yaml.YAMLError as exc: @@ -48,66 +49,98 @@ def get_authors_from_cff_file(): class ExistingAuthorNames: - def __init__(self, existing_authors): - self._existing_author_names = \ - [(unidecode(a.get("given-names")), unidecode(a.get("family-names"))) for a in existing_authors] - - def last_name_matches_surname_and_first_name_or_first_letter_matches_given_name(self, last_name, first_name): - matches = [n for n in self._existing_author_names if - n[1].casefold() == last_name.casefold()] # Last name matches surname + self._existing_author_names = [ + (unidecode(a.get("given-names")), unidecode(a.get("family-names"))) + for a in existing_authors + ] + + def last_name_matches_surname_and_first_name_or_first_letter_matches_given_name( + self, last_name, first_name + ): + matches = [ + n + for n in self._existing_author_names + if n[1].casefold() == last_name.casefold() + ] # Last name matches surname for match in matches: - if match[0].casefold() == first_name.casefold(): # The given name also matches author first name + if ( + match[0].casefold() == first_name.casefold() + ): # The given name also matches author first name return True - if match[0][0].casefold() == first_name[0].casefold(): # The first initial matches author first name + if ( + match[0][0].casefold() == first_name[0].casefold() + ): # The first initial matches author first name return True - def first_name_matches_surname_and_last_name_matches_given_name(self, first_name, last_name): - matches = [n for n in self._existing_author_names if - n[1].casefold() == first_name.casefold()] # First name matches surname + def first_name_matches_surname_and_last_name_matches_given_name( + self, first_name, last_name + ): + matches = [ + n + for n in self._existing_author_names + if n[1].casefold() == first_name.casefold() + ] # First name matches surname for match in matches: - if match[0].casefold() == last_name.casefold(): # The given name also matches author last name + if ( + match[0].casefold() == last_name.casefold() + ): # The given name also matches author last name return True def surname_matches_whole_author_name(self, author): - - surname_matches = [n for n in self._existing_author_names if n[1].casefold() == author.casefold()] + surname_matches = [ + n + for n in self._existing_author_names + if n[1].casefold() == author.casefold() + ] if len(surname_matches) > 0: return True def given_name_matches_matches_whole_author_name(self, author): - given_name_matches = [n for n in self._existing_author_names if n[0].casefold() == author.casefold()] + given_name_matches = [ + n + for n in self._existing_author_names + if n[0].casefold() == author.casefold() + ] if len(given_name_matches) > 0: return True def combined_name_matches_whole_author_name(self, author): - - combined_name_matches = [n for n in self._existing_author_names if - (n[0] + n[1]).casefold() == author.casefold()] + combined_name_matches = [ + n + for n in self._existing_author_names + if (n[0] + n[1]).casefold() == author.casefold() + ] if len(combined_name_matches) > 0: return True def combined_name_reversed_matches(self, author): - - combined_name_reversed_matches = [n for n in self._existing_author_names if - (n[1] + n[0]).casefold() == author.casefold()] + combined_name_reversed_matches = [ + n + for n in self._existing_author_names + if (n[1] + n[0]).casefold() == author.casefold() + ] if len(combined_name_reversed_matches) > 0: return True def author_name_is_first_initial_and_surname_concatenated(self, author): first_character = author[0] remaining_characters = author[1:] - matches = [n for n in self._existing_author_names if - n[1].casefold() == remaining_characters.casefold()] # Second part of name matches surname + matches = [ + n + for n in self._existing_author_names + if n[1].casefold() == remaining_characters.casefold() + ] # Second part of name matches surname for match in matches: - if match[0][0].casefold() == first_character.casefold(): # The first initial matches author first name + if ( + match[0][0].casefold() == first_character.casefold() + ): # The first initial matches author first name return True def author_found_in_existing_authors(author, existing_authors): - existing_author_names = ExistingAuthorNames(existing_authors) names = author.split() @@ -115,10 +148,13 @@ def author_found_in_existing_authors(author, existing_authors): last_name = unidecode(names[-1]) if existing_author_names.last_name_matches_surname_and_first_name_or_first_letter_matches_given_name( - last_name, first_name): + last_name, first_name + ): return True - if existing_author_names.first_name_matches_surname_and_last_name_matches_given_name(first_name, last_name): + if existing_author_names.first_name_matches_surname_and_last_name_matches_given_name( + first_name, last_name + ): return True if existing_author_names.surname_matches_whole_author_name(author): @@ -133,15 +169,20 @@ def author_found_in_existing_authors(author, existing_authors): if existing_author_names.combined_name_reversed_matches(author): return True - if existing_author_names.author_name_is_first_initial_and_surname_concatenated(author): + if existing_author_names.author_name_is_first_initial_and_surname_concatenated( + author + ): return True return False def update_citations(): - - nonhuman_authors = [a for a in authors_from_git if "github" in a[0].casefold() or "dependabot" in a[0].casefold()] + nonhuman_authors = [ + a + for a in authors_from_git + if "github" in a[0].casefold() or "dependabot" in a[0].casefold() + ] known_authors = [a for a in authors_from_git if a[0] in KNOWN_AUTHORS] @@ -149,8 +190,11 @@ def update_citations(): authors_to_search_for = [a for a in human_authors if a not in known_authors] - unrecognised_authors = [a for a in authors_to_search_for if - not author_found_in_existing_authors(a[0], existing_authors)] + unrecognised_authors = [ + a + for a in authors_to_search_for + if not author_found_in_existing_authors(a[0], existing_authors) + ] print("The following authors were not recognised. Add to citations?") for author in unrecognised_authors: @@ -162,23 +206,23 @@ class KnownAuthor(NamedTuple): given_names: str -KNOWN_AUTHORS = {"bendudson": KnownAuthor("Dodson", "Benjamin"), - "brey": KnownAuthor("Breyiannis", "George"), - "David Schwörer": KnownAuthor("Bold", "David"), - "dschwoerer": KnownAuthor("Bold", "David"), - "hahahasan": KnownAuthor("Muhammed", "Hasan"), - "Ilon Joseph - x31405": KnownAuthor("Joseph", "Ilon"), - "kangkabseok": KnownAuthor("Kang", "Kab Seok"), - "loeiten": KnownAuthor("Løiten", "Michael"), - "Michael Loiten Magnussen": KnownAuthor("Løiten", "Michael"), - "Maxim Umansky - x26041": KnownAuthor("Umansky", "Maxim"), - "nick-walkden": KnownAuthor("Walkden", "Nicholas"), - "ZedThree": KnownAuthor("Hill", "Peter") - # "tomc271": KnownAuthor("Chapman", "Tom") - } - -if __name__ == '__main__': - +KNOWN_AUTHORS = { + "bendudson": KnownAuthor("Dodson", "Benjamin"), + "brey": KnownAuthor("Breyiannis", "George"), + "David Schwörer": KnownAuthor("Bold", "David"), + "dschwoerer": KnownAuthor("Bold", "David"), + "hahahasan": KnownAuthor("Muhammed", "Hasan"), + "Ilon Joseph - x31405": KnownAuthor("Joseph", "Ilon"), + "kangkabseok": KnownAuthor("Kang", "Kab Seok"), + "loeiten": KnownAuthor("Løiten", "Michael"), + "Michael Loiten Magnussen": KnownAuthor("Løiten", "Michael"), + "Maxim Umansky - x26041": KnownAuthor("Umansky", "Maxim"), + "nick-walkden": KnownAuthor("Walkden", "Nicholas"), + "ZedThree": KnownAuthor("Hill", "Peter") + # "tomc271": KnownAuthor("Chapman", "Tom") +} + +if __name__ == "__main__": authors_from_git = get_authors_from_git() existing_authors = get_authors_from_cff_file() update_citations() diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 7a570000a6..a7e92884d5 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -8,17 +8,14 @@ def get_full_filepath(filepath): - main_directory = Path(os.path.abspath(__file__)).parent.parent return Path(main_directory) / filepath def update_version_number_in_file(relative_filepath, pattern, new_version_number): - full_filepath = get_full_filepath(relative_filepath) - with open(full_filepath, "r", encoding='UTF-8') as file: - + with open(full_filepath, "r", encoding="UTF-8") as file: file_contents = file.read() original = copy.deepcopy(file_contents) @@ -46,41 +43,67 @@ def update_version_number_in_file(relative_filepath, pattern, new_version_number make_change = yes_or_no("Make changes to {}?".format(full_filepath)) if make_change: - with open(full_filepath, "w", encoding='UTF-8') as file: + with open(full_filepath, "w", encoding="UTF-8") as file: file.write(modified) def bump_version_numbers(new_version_number): + short_version_number = ShortVersionNumber( + new_version_number.major_version, new_version_number.minor_version + ) + bout_next_version_number = VersionNumber( + new_version_number.major_version, + new_version_number.minor_version + 1, + new_version_number.patch_version, + ) - short_version_number = ShortVersionNumber(new_version_number.major_version, new_version_number.minor_version) - bout_next_version_number = VersionNumber(new_version_number.major_version, - new_version_number.minor_version + 1, - new_version_number.patch_version) - - update_version_number_in_file("configure.ac", - r"^AC_INIT\(\[BOUT\+\+\],\[(\d+\.\d+\.\d+)\]", new_version_number) - update_version_number_in_file("CITATION.cff", - r"^version: (\d+\.\d+\.\d+)", new_version_number) - update_version_number_in_file("manual/sphinx/conf.py", - r"^version = \"(\d+\.\d+)\"", short_version_number) - update_version_number_in_file("manual/sphinx/conf.py", - r"^release = \"(\d+\.\d+\.\d+)\"", new_version_number) - update_version_number_in_file("manual/doxygen/Doxyfile_readthedocs", - r"^PROJECT_NUMBER = (\d+\.\d+\.\d+)", new_version_number) - update_version_number_in_file("manual/doxygen/Doxyfile", - r"^PROJECT_NUMBER = (\d+\.\d+\.\d+)", new_version_number) - update_version_number_in_file("CMakeLists.txt", - r"^set\(_bout_previous_version \"v(\d+\.\d+\.\d+)\"\)", new_version_number) - update_version_number_in_file("CMakeLists.txt", - r"^set\(_bout_next_version \"(\d+\.\d+\.\d+)\"\)", bout_next_version_number) - update_version_number_in_file("tools/pylib/_boutpp_build/backend.py", - r"_bout_previous_version = \"v(\d+\.\d+\.\d+)\"", new_version_number) - update_version_number_in_file("tools/pylib/_boutpp_build/backend.py", - r"_bout_next_version = \"v(\d+\.\d+\.\d+)\"", bout_next_version_number) + update_version_number_in_file( + "configure.ac", + r"^AC_INIT\(\[BOUT\+\+\],\[(\d+\.\d+\.\d+)\]", + new_version_number, + ) + update_version_number_in_file( + "CITATION.cff", r"^version: (\d+\.\d+\.\d+)", new_version_number + ) + update_version_number_in_file( + "manual/sphinx/conf.py", r"^version = \"(\d+\.\d+)\"", short_version_number + ) + update_version_number_in_file( + "manual/sphinx/conf.py", r"^release = \"(\d+\.\d+\.\d+)\"", new_version_number + ) + update_version_number_in_file( + "manual/doxygen/Doxyfile_readthedocs", + r"^PROJECT_NUMBER = (\d+\.\d+\.\d+)", + new_version_number, + ) + update_version_number_in_file( + "manual/doxygen/Doxyfile", + r"^PROJECT_NUMBER = (\d+\.\d+\.\d+)", + new_version_number, + ) + update_version_number_in_file( + "CMakeLists.txt", + r"^set\(_bout_previous_version \"v(\d+\.\d+\.\d+)\"\)", + new_version_number, + ) + update_version_number_in_file( + "CMakeLists.txt", + r"^set\(_bout_next_version \"(\d+\.\d+\.\d+)\"\)", + bout_next_version_number, + ) + update_version_number_in_file( + "tools/pylib/_boutpp_build/backend.py", + r"_bout_previous_version = \"v(\d+\.\d+\.\d+)\"", + new_version_number, + ) + update_version_number_in_file( + "tools/pylib/_boutpp_build/backend.py", + r"_bout_next_version = \"v(\d+\.\d+\.\d+)\"", + bout_next_version_number, + ) class VersionNumber: - major_version: int minor_version: int patch_version: int @@ -177,8 +200,7 @@ def create_patch(filename, original, modified): return patch -if __name__ == '__main__': - +if __name__ == "__main__": parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent( @@ -203,4 +225,4 @@ def create_patch(filename, original, modified): if args.force and args.patch_only: raise ValueError("Incompatible options: --force and --patch") - bump_version_numbers(new_version_number=VersionNumber(63, 15, 12)) \ No newline at end of file + bump_version_numbers(new_version_number=VersionNumber(63, 15, 12)) From 6c91918e30017c65915ba8173e5c71df170ef637 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 13:11:43 +0100 Subject: [PATCH 14/40] No need for ShortVersionNumber to inherit from VersionNumber --- bin/update_version_number_in_files.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index a7e92884d5..4309e42955 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -117,12 +117,10 @@ def as_string(self): return "%d.%d.%d" % (self.major_version, self.minor_version, self.patch_version) -class ShortVersionNumber(VersionNumber): - +class ShortVersionNumber: def __init__(self, major_version, minor_version): self.major_version = major_version self.minor_version = minor_version - self.patch_version = None def as_string(self): return "%d.%d" % (self.major_version, self.minor_version) From 170b1dcf479f23f762cc6c7461530c4b1865c6a8 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 17:38:55 +0100 Subject: [PATCH 15/40] Use cwd argument to subprocess.run() to change directory (subprocess.run("cd",... does nothing as it's a separate process). [skip ci] --- bin/update_citations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index 232a1fb48c..993a617a04 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -12,9 +12,9 @@ def get_main_directory(): def get_authors_from_git(): main_directory = get_main_directory() - subprocess.run(["cd", main_directory], shell=True) - - output = subprocess.run(["git", "log", "--format='%aN %aE'"], capture_output=True) + output = subprocess.run( + ["git", "log", "--format='%aN %aE'"], capture_output=True, cwd=main_directory + ) if output.stderr: return output.stderr From 6a8897db720f8a5670043c7f2b1ddef634eb7aad Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 17:40:26 +0100 Subject: [PATCH 16/40] Use subprocess check=True argument to check for error. [skip ci] --- bin/update_citations.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index 993a617a04..49e56c012e 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -13,10 +13,8 @@ def get_main_directory(): def get_authors_from_git(): main_directory = get_main_directory() output = subprocess.run( - ["git", "log", "--format='%aN %aE'"], capture_output=True, cwd=main_directory + ["git", "log", "--format='%aN %aE'"], capture_output=True, cwd=main_directory, check=True ) - if output.stderr: - return output.stderr authors_string = output.stdout.decode() authors_list = authors_string.split("\n") From 5d686090b34f257319c6461048d7c0d491aff6fa Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 17:42:24 +0100 Subject: [PATCH 17/40] Use subprocess text=True argument (then no need to decode() stdout). [skip ci] --- bin/update_citations.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index 49e56c012e..63403d5128 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -13,10 +13,14 @@ def get_main_directory(): def get_authors_from_git(): main_directory = get_main_directory() output = subprocess.run( - ["git", "log", "--format='%aN %aE'"], capture_output=True, cwd=main_directory, check=True + ["git", "log", "--format='%aN %aE'"], + capture_output=True, + cwd=main_directory, + check=True, + text=True, ) - authors_string = output.stdout.decode() + authors_string = output.stdout authors_list = authors_string.split("\n") authors_without_quotes = [a.strip("'") for a in authors_list] @@ -31,7 +35,7 @@ def get_authors_from_git(): def parse_cff_file(filename): with open(filename, "r", encoding="UTF-8") as stream: try: - return yaml.safe_load(stream) + return yaml.safe_load(stream) except yaml.YAMLError as exc: print(exc) From 02784898bf1d05dbaf3611216a4c0bffcb65ff39 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 17:48:59 +0100 Subject: [PATCH 18/40] No need to catch real errors. [skip ci] --- bin/update_citations.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index 63403d5128..9c6464ad96 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -34,10 +34,7 @@ def get_authors_from_git(): def parse_cff_file(filename): with open(filename, "r", encoding="UTF-8") as stream: - try: return yaml.safe_load(stream) - except yaml.YAMLError as exc: - print(exc) def get_authors_from_cff_file(): From 72c0637d8ebcd07ae9f1a0988279e4bae5626071 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 17:56:51 +0100 Subject: [PATCH 19/40] When catching KeyError, raise a new exception instead. Use an f-string. [skip ci] --- bin/update_citations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index 9c6464ad96..470a23e3a5 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -44,7 +44,7 @@ def get_authors_from_cff_file(): try: return file_contents["authors"] except KeyError as key_error: - print("Failed to find section:", key_error, "in", filename) + raise ValueError(f"Failed to find section:{key_error} in {filename}") class ExistingAuthorNames: From ae072d821664317c149a77e4607e3b4a92915c1b Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 18:01:41 +0100 Subject: [PATCH 20/40] Use get_main_directory() function. [skip ci] --- bin/update_citations.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index 470a23e3a5..a672eac343 100644 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -38,8 +38,7 @@ def parse_cff_file(filename): def get_authors_from_cff_file(): - main_directory = Path(os.path.abspath(__file__)).parent.parent - filename = Path(main_directory) / "CITATION.cff" + filename = get_main_directory() / "CITATION.cff" file_contents = parse_cff_file(filename) try: return file_contents["authors"] From d10dbe86cdc1ab52b3a8bbbeb0766d6bb5444da3 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 18:04:29 +0100 Subject: [PATCH 21/40] Make VersionNumber a dataclass. [skip ci] --- bin/update_version_number_in_files.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 4309e42955..578e75d089 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from pathlib import Path import argparse import difflib @@ -103,16 +104,12 @@ def bump_version_numbers(new_version_number): ) +@dataclass class VersionNumber: major_version: int minor_version: int patch_version: int - def __init__(self, major_version, minor_version, patch_version): - self.major_version = major_version - self.minor_version = minor_version - self.patch_version = patch_version - def as_string(self): return "%d.%d.%d" % (self.major_version, self.minor_version, self.patch_version) From 65d42bc9589014e4a66ff7b3c855056a6e88d0b8 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 18:11:18 +0100 Subject: [PATCH 22/40] Use str dunder method. [skip ci] --- bin/update_version_number_in_files.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 578e75d089..2820f82d92 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -110,7 +110,7 @@ class VersionNumber: minor_version: int patch_version: int - def as_string(self): + def __str__(self): return "%d.%d.%d" % (self.major_version, self.minor_version, self.patch_version) @@ -119,7 +119,7 @@ def __init__(self, major_version, minor_version): self.major_version = major_version self.minor_version = minor_version - def as_string(self): + def __str__(self): return "%d.%d" % (self.major_version, self.minor_version) @@ -138,7 +138,7 @@ def apply_fixes(pattern, new_version_number, source): """ def get_replacement(match): - return match[0].replace(match[1], new_version_number.as_string()) + return match[0].replace(match[1], str(new_version_number)) modified = re.sub(pattern, get_replacement, source, flags=re.MULTILINE) From 9570f9f940670c3caa1ed73c97e05ddcef3f265e Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 18:15:09 +0100 Subject: [PATCH 23/40] Remove commented-out copied code. [skip ci] --- bin/update_version_number_in_files.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 2820f82d92..94c15caba4 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -144,30 +144,6 @@ def get_replacement(match): return modified - # """Apply all fixes in this module""" - # modified = copy.deepcopy(source) - # - # for replacement in replacements: - # if replacement["new"] is None: - # print( - # "'%s' has been removed, please delete from your code" - # % replacement["old"] - # ) - # continue - # - # modified = fix_include_version_header( - # replacement["old"], replacement["headers"], modified - # ) - # if replacement["macro"] and replacement["always_defined"]: - # modified = fix_always_defined_macros( - # replacement["old"], replacement["new"], modified - # ) - # elif replacement["always_defined"]: - # modified = fix_ifdefs(replacement["old"], modified) - # modified = fix_replacement(replacement["old"], replacement["new"], modified) - # - # return modified - def yes_or_no(question): """Convert user input from yes/no variations to True/False""" From 6e5f6fa82ef1664817cfecf2e9e540759fde010d Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 19:08:43 +0100 Subject: [PATCH 24/40] Add command line argument for the new version number. [skip ci] --- bin/update_version_number_in_files.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 94c15caba4..1cb8e948f3 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -190,10 +190,11 @@ def create_patch(filename, original, modified): parser.add_argument( "--patch-only", "-p", action="store_true", help="Print the patches and exit" ) + parser.add_argument("new_version", help="Specify the new version number") args = parser.parse_args() if args.force and args.patch_only: raise ValueError("Incompatible options: --force and --patch") - - bump_version_numbers(new_version_number=VersionNumber(63, 15, 12)) + major, minor, patch = map(int, args.new_version.split(".")) + bump_version_numbers(new_version_number=VersionNumber(major, minor, patch)) From 871d94615ddb99d70d858a244cdb10b54458d21e Mon Sep 17 00:00:00 2001 From: tomc271 Date: Mon, 21 Aug 2023 19:09:21 +0100 Subject: [PATCH 25/40] Corrected regex. [skip ci] --- bin/update_version_number_in_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 1cb8e948f3..ef0eaf3eb4 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -99,7 +99,7 @@ def bump_version_numbers(new_version_number): ) update_version_number_in_file( "tools/pylib/_boutpp_build/backend.py", - r"_bout_next_version = \"v(\d+\.\d+\.\d+)\"", + r"_bout_next_version = \"(\d+\.\d+\.\d+)\"", bout_next_version_number, ) From e779059a802da896d28b1289ccbc147d16ff9735 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 22 Aug 2023 13:18:40 +0100 Subject: [PATCH 26/40] Add command line description to update_version_number_in_file script. [skip ci] --- bin/update_version_number_in_files.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index ef0eaf3eb4..c2299f781b 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -176,7 +176,18 @@ def create_patch(filename, original, modified): formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent( """\ - TODO: Description here... + Update the software version number to the specified version, + to be given in the form major.minor.patch, + e.g. 5.10.3 + + Where the 3rd ('patch') part of the version is omitted, + only the 'major' and 'minor' parts will be used, + e.g. 5.10.3 -> 5.10 + + For the 'bout-next' version number, + the 'minor' version number of the provided version will be incremented by 1, + e.g. 5.10.3 -> 5.11.3 + """ ), ) From d03141266f1bdf889e3a3684e16e419f86001961 Mon Sep 17 00:00:00 2001 From: tomc271 Date: Tue, 22 Aug 2023 13:36:55 +0100 Subject: [PATCH 27/40] Make ShortVersionNumber a dataclass. [skip ci] --- bin/update_version_number_in_files.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index c2299f781b..e414df14b0 100644 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -114,10 +114,10 @@ def __str__(self): return "%d.%d.%d" % (self.major_version, self.minor_version, self.patch_version) +@dataclass class ShortVersionNumber: - def __init__(self, major_version, minor_version): - self.major_version = major_version - self.minor_version = minor_version + major_version: int + minor_version: int def __str__(self): return "%d.%d" % (self.major_version, self.minor_version) From 8fe1dfe697e23ff9f001d81b618d85603f7cca09 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Wed, 20 Sep 2023 14:18:19 +0100 Subject: [PATCH 28/40] Maint: Make update version/citation scripts executable [skip ci] --- bin/update_citations.py | 1 + bin/update_version_number_in_files.py | 1 + 2 files changed, 2 insertions(+) mode change 100644 => 100755 bin/update_citations.py mode change 100644 => 100755 bin/update_version_number_in_files.py diff --git a/bin/update_citations.py b/bin/update_citations.py old mode 100644 new mode 100755 index a672eac343..56ae990bf9 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import subprocess from pathlib import Path import os diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py old mode 100644 new mode 100755 index e414df14b0..7d184af0bf --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 from dataclasses import dataclass from pathlib import Path import argparse From a8ae191cef1e9f3a0aa04a7cd6f7ca6414f949f9 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Wed, 20 Sep 2023 14:19:20 +0100 Subject: [PATCH 29/40] Maint: Fix typo in next version regex --- bin/update_version_number_in_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 7d184af0bf..de187c413a 100755 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -100,7 +100,7 @@ def bump_version_numbers(new_version_number): ) update_version_number_in_file( "tools/pylib/_boutpp_build/backend.py", - r"_bout_next_version = \"(\d+\.\d+\.\d+)\"", + r"_bout_next_version = \"v(\d+\.\d+\.\d+)\"", bout_next_version_number, ) From 0ef56ad7f00f9e92ee510b8dfcf562318ff36010 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Wed, 20 Sep 2023 14:38:39 +0100 Subject: [PATCH 30/40] Maint: Update list of known authors in citation script --- bin/update_citations.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index 56ae990bf9..d85bcffb15 100755 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -217,8 +217,10 @@ class KnownAuthor(NamedTuple): "Michael Loiten Magnussen": KnownAuthor("Løiten", "Michael"), "Maxim Umansky - x26041": KnownAuthor("Umansky", "Maxim"), "nick-walkden": KnownAuthor("Walkden", "Nicholas"), - "ZedThree": KnownAuthor("Hill", "Peter") - # "tomc271": KnownAuthor("Chapman", "Tom") + "ZedThree": KnownAuthor("Hill", "Peter"), + "tomc271": KnownAuthor("Chapman", "Tom"), + "j-b-o": KnownAuthor("Omotani", "John"), + "BS": KnownAuthor("Brendan", "Shanahan"), } if __name__ == "__main__": From 4a02f4ceff2a47117f64d879f09d6207f2ec5eda Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Wed, 20 Sep 2023 14:39:19 +0100 Subject: [PATCH 31/40] Maint: Don't print anything if no unrecognised authors --- bin/update_citations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/update_citations.py b/bin/update_citations.py index d85bcffb15..607227775d 100755 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -195,6 +195,9 @@ def update_citations(): if not author_found_in_existing_authors(a[0], existing_authors) ] + if not unrecognised_authors: + return + print("The following authors were not recognised. Add to citations?") for author in unrecognised_authors: print(author) From 8a3b237ba6126d452bb7869fd5476a185211bbf1 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Wed, 20 Sep 2023 15:25:33 +0100 Subject: [PATCH 32/40] Fix typo in author list [skip ci] Co-authored-by: David Bold --- bin/update_citations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index 607227775d..ed358f7592 100755 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -209,7 +209,7 @@ class KnownAuthor(NamedTuple): KNOWN_AUTHORS = { - "bendudson": KnownAuthor("Dodson", "Benjamin"), + "bendudson": KnownAuthor("Dudson", "Benjamin"), "brey": KnownAuthor("Breyiannis", "George"), "David Schwörer": KnownAuthor("Bold", "David"), "dschwoerer": KnownAuthor("Bold", "David"), From 356bf9b6020d6da67e58d2e59f99f80d203b5591 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 12 Sep 2024 09:30:29 +0100 Subject: [PATCH 33/40] Fix author alias in citation update tool --- bin/update_citations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index ed358f7592..f92ee098f9 100755 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -222,7 +222,7 @@ class KnownAuthor(NamedTuple): "nick-walkden": KnownAuthor("Walkden", "Nicholas"), "ZedThree": KnownAuthor("Hill", "Peter"), "tomc271": KnownAuthor("Chapman", "Tom"), - "j-b-o": KnownAuthor("Omotani", "John"), + "j-b-o": KnownAuthor("Bold", "Jessica"), "BS": KnownAuthor("Brendan", "Shanahan"), } From d86492475da1a7899052997ca1191816d8d10303 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 12 Sep 2024 09:36:32 +0100 Subject: [PATCH 34/40] Add list of packages for maintenance tasks --- requirements_maint.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 requirements_maint.txt diff --git a/requirements_maint.txt b/requirements_maint.txt new file mode 100644 index 0000000000..07d0734789 --- /dev/null +++ b/requirements_maint.txt @@ -0,0 +1,3 @@ +pygithub~=2.4 +PyYAML~=6.0 +Unidecode~=1.3 From 240965a8ef5f33a28e3b81f239375c95a5c7ac52 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 12 Sep 2024 13:31:08 +0100 Subject: [PATCH 35/40] Fix some formatting in citation updater --- bin/update_citations.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index f92ee098f9..cbaed08176 100755 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -39,12 +39,11 @@ def parse_cff_file(filename): def get_authors_from_cff_file(): - filename = get_main_directory() / "CITATION.cff" - file_contents = parse_cff_file(filename) + file_contents = parse_cff_file() try: return file_contents["authors"] except KeyError as key_error: - raise ValueError(f"Failed to find section:{key_error} in {filename}") + raise ValueError(f"Failed to find section:{key_error} in {CITATION_FILE}") class ExistingAuthorNames: @@ -57,35 +56,34 @@ def __init__(self, existing_authors): def last_name_matches_surname_and_first_name_or_first_letter_matches_given_name( self, last_name, first_name ): + # Last name matches surname matches = [ n for n in self._existing_author_names if n[1].casefold() == last_name.casefold() - ] # Last name matches surname + ] for match in matches: - if ( - match[0].casefold() == first_name.casefold() - ): # The given name also matches author first name + # The given name also matches author first name + if match[0].casefold() == first_name.casefold(): return True - if ( - match[0][0].casefold() == first_name[0].casefold() - ): # The first initial matches author first name + # The first initial matches author first name + if match[0][0].casefold() == first_name[0].casefold(): return True def first_name_matches_surname_and_last_name_matches_given_name( self, first_name, last_name ): + # First name matches surname matches = [ n for n in self._existing_author_names if n[1].casefold() == first_name.casefold() - ] # First name matches surname + ] + # The given name also matches author last name for match in matches: - if ( - match[0].casefold() == last_name.casefold() - ): # The given name also matches author last name + if match[0].casefold() == last_name.casefold(): return True def surname_matches_whole_author_name(self, author): @@ -127,15 +125,15 @@ def combined_name_reversed_matches(self, author): def author_name_is_first_initial_and_surname_concatenated(self, author): first_character = author[0] remaining_characters = author[1:] + # Second part of name matches surname matches = [ n for n in self._existing_author_names if n[1].casefold() == remaining_characters.casefold() - ] # Second part of name matches surname + ] for match in matches: - if ( - match[0][0].casefold() == first_character.casefold() - ): # The first initial matches author first name + # The first initial matches author first name + if match[0][0].casefold() == first_character.casefold(): return True From 37bb0e8cecafdd00583df8133c73037a6174c7de Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 12 Sep 2024 13:32:42 +0100 Subject: [PATCH 36/40] Make citation updater patch citation file --- bin/update_citations.py | 139 +++++++++++++++++++++++++--------------- requirements_maint.txt | 2 +- 2 files changed, 89 insertions(+), 52 deletions(-) diff --git a/bin/update_citations.py b/bin/update_citations.py index cbaed08176..c7771a3611 100755 --- a/bin/update_citations.py +++ b/bin/update_citations.py @@ -1,22 +1,46 @@ #!/usr/bin/env python3 +import argparse import subprocess +from collections import defaultdict +from dataclasses import dataclass from pathlib import Path -import os -import yaml + +from ruamel.yaml import YAML from unidecode import unidecode -from typing import NamedTuple -def get_main_directory(): - return Path(os.path.abspath(__file__)).parent.parent +@dataclass +class KnownAuthor: + family_names: str + given_names: str + + +TOP_DIR = Path(__file__).parent.parent +CITATION_FILE = TOP_DIR / "CITATION.cff" +NONHUMAN_AUTHORS = ("github", "dependabot") +KNOWN_AUTHORS = { + "bendudson": KnownAuthor("Dudson", "Benjamin"), + "brey": KnownAuthor("Breyiannis", "George"), + "David Schwörer": KnownAuthor("Bold", "David"), + "dschwoerer": KnownAuthor("Bold", "David"), + "hahahasan": KnownAuthor("Muhammed", "Hasan"), + "Ilon Joseph - x31405": KnownAuthor("Joseph", "Ilon"), + "kangkabseok": KnownAuthor("Kang", "Kab Seok"), + "loeiten": KnownAuthor("Løiten", "Michael"), + "Michael Loiten Magnussen": KnownAuthor("Løiten", "Michael"), + "Maxim Umansky - x26041": KnownAuthor("Umansky", "Maxim"), + "nick-walkden": KnownAuthor("Walkden", "Nicholas"), + "ZedThree": KnownAuthor("Hill", "Peter"), + "tomc271": KnownAuthor("Chapman", "Tom"), + "j-b-o": KnownAuthor("Bold", "Jessica"), + "BS": KnownAuthor("Brendan", "Shanahan"), +} def get_authors_from_git(): - main_directory = get_main_directory() output = subprocess.run( ["git", "log", "--format='%aN %aE'"], capture_output=True, - cwd=main_directory, check=True, text=True, ) @@ -26,16 +50,20 @@ def get_authors_from_git(): authors_without_quotes = [a.strip("'") for a in authors_list] distinct_authors = set(authors_without_quotes) - distinct_authors_list_without_empty_strings = [a for a in distinct_authors if a] - authors_with_emails = [ - a.rsplit(maxsplit=1) for a in distinct_authors_list_without_empty_strings + distinct_authors_list_without_empty_strings = [ + a.rsplit(maxsplit=1) for a in distinct_authors if a ] + authors_with_emails = defaultdict(list) + for author, email in distinct_authors_list_without_empty_strings: + authors_with_emails[author].append(email) + return authors_with_emails -def parse_cff_file(filename): - with open(filename, "r", encoding="UTF-8") as stream: - return yaml.safe_load(stream) +def parse_cff_file(): + yaml = YAML(typ="rt") + with open(CITATION_FILE, "r", encoding="UTF-8") as stream: + return yaml.load(stream) def get_authors_from_cff_file(): @@ -174,57 +202,66 @@ def author_found_in_existing_authors(author, existing_authors): return False -def update_citations(): - nonhuman_authors = [ - a - for a in authors_from_git - if "github" in a[0].casefold() or "dependabot" in a[0].casefold() - ] +def is_nonhuman(author: str) -> bool: + lower_case_author = author.casefold() + return any(nonhuman in lower_case_author for nonhuman in NONHUMAN_AUTHORS) - known_authors = [a for a in authors_from_git if a[0] in KNOWN_AUTHORS] - human_authors = [a for a in authors_from_git if a not in nonhuman_authors] +def update_citations(): + authors_from_git = get_authors_from_git() + existing_authors = get_authors_from_cff_file() - authors_to_search_for = [a for a in human_authors if a not in known_authors] + known_authors = [a for a in authors_from_git if a in KNOWN_AUTHORS] + human_authors = {a: e for a, e in authors_from_git.items() if not is_nonhuman(a)} + authors_to_search_for = { + a: e for a, e in human_authors.items() if a not in known_authors + } - unrecognised_authors = [ - a - for a in authors_to_search_for - if not author_found_in_existing_authors(a[0], existing_authors) - ] + unrecognised_authors = { + a: e + for a, e in authors_to_search_for.items() + if not author_found_in_existing_authors(a, existing_authors) + } if not unrecognised_authors: return - print("The following authors were not recognised. Add to citations?") + for author, email in unrecognised_authors.items(): + print(f"{author} (email(s): {email})") + + while True: + reply = ( + input("\nThe above authors were not recognised. Add to citations? [y/N] ") + .lower() + .strip() + ) + if not reply or reply[0] == "n": + return + if reply[0] == "y": + break + + new_authors = [] for author in unrecognised_authors: - print(author) + first_name, last_name = author.rsplit(maxsplit=1) + new_authors.append({"family-names": last_name, "given-names": first_name}) + print(f"Adding new authors:\n{new_authors}") -class KnownAuthor(NamedTuple): - family_names: str - given_names: str + yaml_file = parse_cff_file() + yaml_file["authors"].extend(new_authors) + # Try to preserve indentation and quotes as much as possible + yaml = YAML() + yaml.indent(mapping=2, sequence=4, offset=2) + yaml.preserve_quotes = True + with open(CITATION_FILE, "w", encoding="UTF-8") as f: + yaml.dump(yaml_file, f) -KNOWN_AUTHORS = { - "bendudson": KnownAuthor("Dudson", "Benjamin"), - "brey": KnownAuthor("Breyiannis", "George"), - "David Schwörer": KnownAuthor("Bold", "David"), - "dschwoerer": KnownAuthor("Bold", "David"), - "hahahasan": KnownAuthor("Muhammed", "Hasan"), - "Ilon Joseph - x31405": KnownAuthor("Joseph", "Ilon"), - "kangkabseok": KnownAuthor("Kang", "Kab Seok"), - "loeiten": KnownAuthor("Løiten", "Michael"), - "Michael Loiten Magnussen": KnownAuthor("Løiten", "Michael"), - "Maxim Umansky - x26041": KnownAuthor("Umansky", "Maxim"), - "nick-walkden": KnownAuthor("Walkden", "Nicholas"), - "ZedThree": KnownAuthor("Hill", "Peter"), - "tomc271": KnownAuthor("Chapman", "Tom"), - "j-b-o": KnownAuthor("Bold", "Jessica"), - "BS": KnownAuthor("Brendan", "Shanahan"), -} if __name__ == "__main__": - authors_from_git = get_authors_from_git() - existing_authors = get_authors_from_cff_file() + parser = argparse.ArgumentParser( + description="Update CITATIONS.cff based on git authors" + ) + parser.parse_args() + update_citations() diff --git a/requirements_maint.txt b/requirements_maint.txt index 07d0734789..9f4ddc3699 100644 --- a/requirements_maint.txt +++ b/requirements_maint.txt @@ -1,3 +1,3 @@ pygithub~=2.4 -PyYAML~=6.0 +ruamel-yaml~=0.18 Unidecode~=1.3 From 6a1a9979e0e3310c47c1f558231c10175837b73d Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 12 Sep 2024 13:50:56 +0100 Subject: [PATCH 37/40] Remove extra "v" from previous version regexp --- bin/update_version_number_in_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index de187c413a..9b5d94e780 100755 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -85,7 +85,7 @@ def bump_version_numbers(new_version_number): ) update_version_number_in_file( "CMakeLists.txt", - r"^set\(_bout_previous_version \"v(\d+\.\d+\.\d+)\"\)", + r"^set\(_bout_previous_version \"(\d+\.\d+\.\d+)\"\)", new_version_number, ) update_version_number_in_file( From 86a10a109ec7bc17ef1bc8e13e86f7b299082dbf Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 12 Sep 2024 13:52:04 +0100 Subject: [PATCH 38/40] Replace short version class with property --- bin/update_version_number_in_files.py | 38 +++++++++++---------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index 9b5d94e780..e20ceb5687 100755 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -9,6 +9,20 @@ import re +@dataclass +class VersionNumber: + major: int + minor: int + patch: int + + def __str__(self): + return f"{self.major}.{self.minor}.{self.patch}" + + @property + def short(self) -> str: + return f"{self.major}.{self.minor}" + + def get_full_filepath(filepath): main_directory = Path(os.path.abspath(__file__)).parent.parent return Path(main_directory) / filepath @@ -50,9 +64,6 @@ def update_version_number_in_file(relative_filepath, pattern, new_version_number def bump_version_numbers(new_version_number): - short_version_number = ShortVersionNumber( - new_version_number.major_version, new_version_number.minor_version - ) bout_next_version_number = VersionNumber( new_version_number.major_version, new_version_number.minor_version + 1, @@ -68,7 +79,7 @@ def bump_version_numbers(new_version_number): "CITATION.cff", r"^version: (\d+\.\d+\.\d+)", new_version_number ) update_version_number_in_file( - "manual/sphinx/conf.py", r"^version = \"(\d+\.\d+)\"", short_version_number + "manual/sphinx/conf.py", r"^version = \"(\d+\.\d+)\"", new_version_number.short ) update_version_number_in_file( "manual/sphinx/conf.py", r"^release = \"(\d+\.\d+\.\d+)\"", new_version_number @@ -105,25 +116,6 @@ def bump_version_numbers(new_version_number): ) -@dataclass -class VersionNumber: - major_version: int - minor_version: int - patch_version: int - - def __str__(self): - return "%d.%d.%d" % (self.major_version, self.minor_version, self.patch_version) - - -@dataclass -class ShortVersionNumber: - major_version: int - minor_version: int - - def __str__(self): - return "%d.%d" % (self.major_version, self.minor_version) - - def apply_fixes(pattern, new_version_number, source): """Apply the various fixes for each factory to source. Returns modified source From ebdfd7ddf2bd8003f0704467643e96b64b01d880 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 12 Sep 2024 13:52:29 +0100 Subject: [PATCH 39/40] Add optional next version number argument to version updater script --- bin/update_version_number_in_files.py | 32 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index e20ceb5687..dd0dd17227 100755 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -63,13 +63,9 @@ def update_version_number_in_file(relative_filepath, pattern, new_version_number file.write(modified) -def bump_version_numbers(new_version_number): - bout_next_version_number = VersionNumber( - new_version_number.major_version, - new_version_number.minor_version + 1, - new_version_number.patch_version, - ) - +def bump_version_numbers( + new_version_number: VersionNumber, next_version_number: VersionNumber +): update_version_number_in_file( "configure.ac", r"^AC_INIT\(\[BOUT\+\+\],\[(\d+\.\d+\.\d+)\]", @@ -102,7 +98,7 @@ def bump_version_numbers(new_version_number): update_version_number_in_file( "CMakeLists.txt", r"^set\(_bout_next_version \"(\d+\.\d+\.\d+)\"\)", - bout_next_version_number, + next_version_number, ) update_version_number_in_file( "tools/pylib/_boutpp_build/backend.py", @@ -112,7 +108,7 @@ def bump_version_numbers(new_version_number): update_version_number_in_file( "tools/pylib/_boutpp_build/backend.py", r"_bout_next_version = \"v(\d+\.\d+\.\d+)\"", - bout_next_version_number, + next_version_number, ) @@ -194,11 +190,23 @@ def create_patch(filename, original, modified): parser.add_argument( "--patch-only", "-p", action="store_true", help="Print the patches and exit" ) - parser.add_argument("new_version", help="Specify the new version number") + parser.add_argument("new_version", help="New (current) version number") + parser.add_argument( + "next_version", help="Next version number", nargs="?", default=None + ) args = parser.parse_args() if args.force and args.patch_only: raise ValueError("Incompatible options: --force and --patch") - major, minor, patch = map(int, args.new_version.split(".")) - bump_version_numbers(new_version_number=VersionNumber(major, minor, patch)) + + new_version = VersionNumber(*map(int, args.new_version.split("."))) + next_version = ( + VersionNumber(new_version.major, new_version.minor + 1, 0) + if args.next_version is None + else VersionNumber(*map(int, args.next_version.split("."))) + ) + + bump_version_numbers( + new_version_number=new_version, next_version_number=next_version + ) From 15e2e7e06847a1ad62755f3ec5b29bf2cca94843 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Thu, 12 Sep 2024 13:53:39 +0100 Subject: [PATCH 40/40] Make some regexes a little more robust --- bin/update_version_number_in_files.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/update_version_number_in_files.py b/bin/update_version_number_in_files.py index dd0dd17227..85df930aea 100755 --- a/bin/update_version_number_in_files.py +++ b/bin/update_version_number_in_files.py @@ -82,12 +82,12 @@ def bump_version_numbers( ) update_version_number_in_file( "manual/doxygen/Doxyfile_readthedocs", - r"^PROJECT_NUMBER = (\d+\.\d+\.\d+)", + r"^PROJECT_NUMBER\s*=\s*(\d+\.\d+\.\d+)", new_version_number, ) update_version_number_in_file( "manual/doxygen/Doxyfile", - r"^PROJECT_NUMBER = (\d+\.\d+\.\d+)", + r"^PROJECT_NUMBER\s*=\s*(\d+\.\d+\.\d+)", new_version_number, ) update_version_number_in_file(