-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Automate release process #2734
Open
tomc271
wants to merge
33
commits into
boutproject:v5.1.0-rc
Choose a base branch
from
tomc271:automate-release-process
base: v5.1.0-rc
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Automate release process #2734
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
4d9ddd5
Add script to bump version numbers. (Initial revision).
tomchapman 988753d
Add script to check for new authors. (Initial revision).
tomchapman 88f709e
Move scripts to bin directory
tomchapman 80d5a4f
Remove hard-coded paths.
tomchapman 7df3104
Make KNOWN_AUTHORS a dictionary, holding more information to help ide…
tomchapman 80129ae
Handle version numbers with multiple digits.
tomchapman 0111768
Add classes VersionNumber and ShortVersionNumber.
tomchapman f329b47
Run git log command to get the list of authors.
tomchapman 7ec28f4
Extracted function get_main_directory().
tomchapman b4db274
Also list emails for unrecognised authors.
tomchapman 048207d
Copy functions from other scripts:
tomchapman db5d413
Apply copied functions to this script.
tomchapman 696da06
Black formatting
tomchapman 9b8bc24
No need for ShortVersionNumber to inherit from VersionNumber
tomchapman 2d514f0
Use cwd argument to subprocess.run() to change directory (subprocess.…
tomchapman 5329cf4
Use subprocess check=True argument to check for error.
tomchapman c7051f2
Use subprocess text=True argument (then no need to decode() stdout).
tomchapman 8a27bfa
No need to catch real errors.
tomchapman f85ad2a
When catching KeyError, raise a new exception instead. Use an f-string.
tomchapman 19357aa
Use get_main_directory() function.
tomchapman 80b914c
Make VersionNumber a dataclass.
tomchapman cbb9823
Use str dunder method.
tomchapman 80eced1
Remove commented-out copied code.
tomchapman e9686f2
Add command line argument for the new version number.
tomchapman 6de3826
Corrected regex.
tomchapman a932ba0
Add command line description to update_version_number_in_file script.
tomchapman d52d622
Make ShortVersionNumber a dataclass.
tomchapman 19fe7ed
Maint: Make update version/citation scripts executable
ZedThree 5a8375c
Maint: Fix typo in next version regex
ZedThree a7f057e
Maint: Update list of known authors in citation script
ZedThree bc1b436
Maint: Don't print anything if no unrecognised authors
ZedThree a3fa61c
Add Tom Chapman to authors list
ZedThree 33632ad
Fix typo in author list
ZedThree File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
#!/usr/bin/env python3 | ||
import subprocess | ||
from pathlib import Path | ||
import os | ||
import yaml | ||
from unidecode import unidecode | ||
from typing import NamedTuple | ||
|
||
|
||
def get_main_directory(): | ||
return Path(os.path.abspath(__file__)).parent.parent | ||
|
||
|
||
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, | ||
) | ||
|
||
authors_string = output.stdout | ||
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 | ||
] | ||
return authors_with_emails | ||
|
||
|
||
def parse_cff_file(filename): | ||
with open(filename, "r", encoding="UTF-8") as stream: | ||
return yaml.safe_load(stream) | ||
|
||
|
||
def get_authors_from_cff_file(): | ||
filename = get_main_directory() / "CITATION.cff" | ||
file_contents = parse_cff_file(filename) | ||
try: | ||
return file_contents["authors"] | ||
except KeyError as key_error: | ||
raise ValueError(f"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(): | ||
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] | ||
|
||
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[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) | ||
|
||
|
||
class KnownAuthor(NamedTuple): | ||
family_names: str | ||
given_names: str | ||
|
||
|
||
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("Omotani", "John"), | ||
"BS": KnownAuthor("Brendan", "Shanahan"), | ||
} | ||
|
||
if __name__ == "__main__": | ||
authors_from_git = get_authors_from_git() | ||
existing_authors = get_authors_from_cff_file() | ||
update_citations() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, just noticed this. j-b-o is not John, I will ask @j-b-o whether she wants to be included in the authors list ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, sorry, thanks for catching this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.