2020-08-03 10:57:44 +03:00
|
|
|
import json
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
import requests
|
|
|
|
from jinja2 import Template
|
|
|
|
|
|
|
|
CURRENT_FILE = Path(__file__)
|
|
|
|
ROOT = CURRENT_FILE.parents[1]
|
|
|
|
BOT_LOGINS = ["pyup-bot"]
|
|
|
|
|
2020-08-10 20:30:36 +03:00
|
|
|
# Jinja template for CONTRIBUTORS.md
|
2020-08-10 19:42:01 +03:00
|
|
|
CONTRIBUTORS_TEMPLATE = """
|
|
|
|
# Contributors
|
|
|
|
|
|
|
|
## Core Developers
|
|
|
|
|
|
|
|
These contributors have commit flags for the repository, and are able to
|
|
|
|
accept and merge pull requests.
|
|
|
|
|
|
|
|
<table>
|
|
|
|
<tr>
|
|
|
|
<th>Name</th>
|
|
|
|
<th>Github</th>
|
|
|
|
<th>Twitter</th>
|
|
|
|
</tr>
|
|
|
|
{%- for contributor in core_contributors %}
|
|
|
|
<tr>
|
|
|
|
<td>{{ contributor.name }}</td>
|
|
|
|
<td>
|
|
|
|
<a href="https://github.com/{{ contributor.github_login }}">{{ contributor.github_login }}</a>
|
|
|
|
</td>
|
|
|
|
<td>{{ contributor.twitter_username }}</td>
|
|
|
|
</tr>
|
|
|
|
{%- endfor %}
|
|
|
|
</table>
|
|
|
|
|
|
|
|
*Audrey is also the creator of Cookiecutter. Audrey and Daniel are on
|
|
|
|
the Cookiecutter core team.*
|
|
|
|
|
|
|
|
## Other Contributors
|
|
|
|
|
|
|
|
Listed in alphabetical order.
|
|
|
|
|
2020-08-03 10:57:44 +03:00
|
|
|
<table>
|
|
|
|
<tr>
|
|
|
|
<th>Name</th>
|
|
|
|
<th>Github</th>
|
|
|
|
<th>Twitter</th>
|
|
|
|
</tr>
|
2020-08-10 19:42:01 +03:00
|
|
|
{%- for contributor in other_contributors %}
|
2020-08-03 10:57:44 +03:00
|
|
|
<tr>
|
|
|
|
<td>{{ contributor.name }}</td>
|
|
|
|
<td>
|
|
|
|
<a href="https://github.com/{{ contributor.github_login }}">{{ contributor.github_login }}</a>
|
|
|
|
</td>
|
|
|
|
<td>{{ contributor.twitter_username }}</td>
|
|
|
|
</tr>
|
|
|
|
{%- endfor %}
|
|
|
|
</table>
|
2020-08-10 19:42:01 +03:00
|
|
|
|
|
|
|
### Special Thanks
|
|
|
|
|
|
|
|
The following haven't provided code directly, but have provided
|
|
|
|
guidance and advice.
|
|
|
|
|
|
|
|
- Jannis Leidel
|
|
|
|
- Nate Aune
|
|
|
|
- Barry Morrison
|
2020-08-03 10:57:44 +03:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
2020-08-10 20:30:36 +03:00
|
|
|
"""
|
|
|
|
Script entry point.
|
|
|
|
|
|
|
|
1. Fetch recent contribtors from the Github API
|
|
|
|
2. Add missing ones to the JSON file
|
|
|
|
3. Generate Markdown from JSON file
|
|
|
|
"""
|
|
|
|
# Use Github API to fetch recent authors rather than
|
|
|
|
# git CLI because we need to know their GH username
|
2020-08-03 10:57:44 +03:00
|
|
|
gh = GitHub()
|
|
|
|
recent_authors = set(gh.iter_recent_authors())
|
2020-08-10 20:13:42 +03:00
|
|
|
|
2020-08-10 20:30:36 +03:00
|
|
|
# Add missing users to the JSON file
|
2020-08-03 10:57:44 +03:00
|
|
|
contrib_file = ContributorsJSONFile()
|
|
|
|
for username in recent_authors:
|
2020-08-10 20:13:42 +03:00
|
|
|
print(f"Checking if {username} should be added")
|
2020-08-10 19:42:01 +03:00
|
|
|
if username not in contrib_file and username not in BOT_LOGINS:
|
2020-08-03 10:57:44 +03:00
|
|
|
user_data = gh.fetch_user_info(username)
|
|
|
|
contrib_file.add_contributor(user_data)
|
2020-08-10 20:13:42 +03:00
|
|
|
print(f"Added {username} to contributors")
|
2020-08-03 10:57:44 +03:00
|
|
|
contrib_file.save()
|
|
|
|
|
2020-08-10 20:30:36 +03:00
|
|
|
# Generate MD file from JSON file
|
2020-08-10 19:42:01 +03:00
|
|
|
write_md_file(contrib_file.content)
|
2020-08-03 10:57:44 +03:00
|
|
|
|
|
|
|
|
|
|
|
class GitHub:
|
2020-08-10 20:30:36 +03:00
|
|
|
"""Small wrapper around Github REST API."""
|
|
|
|
|
2020-08-03 10:57:44 +03:00
|
|
|
base_url = "https://api.github.com"
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
self.session = requests.Session()
|
|
|
|
|
|
|
|
def request(self, endpoint):
|
|
|
|
response = self.session.get(f"{self.base_url}{endpoint}")
|
|
|
|
response.raise_for_status()
|
|
|
|
return response.json()
|
|
|
|
|
|
|
|
def iter_recent_authors(self):
|
|
|
|
commits = self.request("/repos/pydanny/cookiecutter-django/commits")
|
|
|
|
for commit in commits:
|
|
|
|
login = commit["author"]["login"]
|
|
|
|
if login not in BOT_LOGINS:
|
|
|
|
yield login
|
|
|
|
|
|
|
|
def fetch_user_info(self, username):
|
|
|
|
return self.request(f"/users/{username}")
|
|
|
|
|
|
|
|
|
|
|
|
class ContributorsJSONFile:
|
2020-08-10 20:30:36 +03:00
|
|
|
"""Helper to interact with the JSON file."""
|
|
|
|
|
2020-08-03 10:57:44 +03:00
|
|
|
file_path = ROOT / ".github" / "contributors.json"
|
|
|
|
content = None
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
2020-08-10 20:30:36 +03:00
|
|
|
"""Read initial content."""
|
2020-08-10 19:42:01 +03:00
|
|
|
self.content = json.loads(self.file_path.read_text())
|
2020-08-03 10:57:44 +03:00
|
|
|
|
|
|
|
def __contains__(self, github_login: str):
|
2020-08-10 20:30:36 +03:00
|
|
|
"""Provide a nice API to do: `username in file`."""
|
2020-08-03 10:57:44 +03:00
|
|
|
return any(github_login == contrib["github_login"] for contrib in self.content)
|
|
|
|
|
|
|
|
def add_contributor(self, user_data):
|
2020-08-10 20:30:36 +03:00
|
|
|
"""Append the contributor data we care about at the end."""
|
2020-08-03 10:57:44 +03:00
|
|
|
contributor_data = {
|
|
|
|
"name": user_data["name"],
|
|
|
|
"github_login": user_data["login"],
|
|
|
|
"twitter_username": user_data["twitter_username"],
|
|
|
|
}
|
2020-08-10 20:30:36 +03:00
|
|
|
self.content.append(contributor_data)
|
2020-08-03 10:57:44 +03:00
|
|
|
|
|
|
|
def save(self):
|
2020-08-10 20:30:36 +03:00
|
|
|
"""Write the file to disk with indentation."""
|
2020-08-10 19:46:34 +03:00
|
|
|
text_content = json.dumps(self.content, indent=2, ensure_ascii=False)
|
|
|
|
self.file_path.write_text(text_content)
|
2020-08-03 10:57:44 +03:00
|
|
|
|
|
|
|
|
2020-08-10 19:42:01 +03:00
|
|
|
def write_md_file(contributors):
|
2020-08-10 20:30:36 +03:00
|
|
|
"""Generate markdown file from Jinja template."""
|
2020-08-10 19:42:01 +03:00
|
|
|
template = Template(CONTRIBUTORS_TEMPLATE, autoescape=True)
|
2020-08-10 19:46:34 +03:00
|
|
|
core_contributors = [c for c in contributors if c.get("is_core", False)]
|
|
|
|
other_contributors = (c for c in contributors if not c.get("is_core", False))
|
2020-08-10 20:18:09 +03:00
|
|
|
other_contributors = sorted(other_contributors, key=lambda c: c["name"].lower())
|
2020-08-10 19:46:34 +03:00
|
|
|
content = template.render(
|
|
|
|
core_contributors=core_contributors, other_contributors=other_contributors
|
2020-08-10 19:42:01 +03:00
|
|
|
)
|
2020-08-03 10:57:44 +03:00
|
|
|
|
2020-08-10 19:42:01 +03:00
|
|
|
file_path = ROOT / "CONTRIBUTORS.md"
|
|
|
|
file_path.write_text(content)
|
2020-08-03 10:57:44 +03:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2020-08-10 19:42:01 +03:00
|
|
|
main()
|