blob: 952e0b0a0523a91e998b1cf1a62fbf5d7a44d704 [file]
#!/usr/bin/env -S uv run --script
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# /// script
# dependencies = ["httpx"]
# ///
import httpx
import sys
from datetime import datetime, timedelta
from collections import defaultdict
def get_date_range():
today = datetime.now()
last_month_start = (today.replace(day=1) - timedelta(days=1)).replace(day=1)
last_month_end = today.replace(day=1) - timedelta(days=1)
return last_month_start, last_month_end
def find_committers(md=False):
people_data = httpx.get("https://projects.apache.org/json/foundation/people.json").json()
ldap_data = httpx.get("https://whimsy.apache.org/public/public_ldap_people.json").json()
last_month_start, last_month_end = get_date_range()
new_committers = defaultdict(list)
for person_id, person_info in people_data.items():
if person_id not in ldap_data["people"]:
continue
created = datetime.strptime(ldap_data["people"][person_id]["createTimestamp"], "%Y%m%d%H%M%SZ")
if last_month_start <= created <= last_month_end:
for group in person_info.get("groups", []):
if not group.endswith("-pmc") and group not in ["apldap", "incubator"]:
new_committers[group].append({
"name": person_info.get("name", person_id),
"id": person_id,
"date": created.strftime("%Y-%m-%d")
})
if new_committers:
total = sum(len(c) for c in new_committers.values())
if md:
print(f"## New Committers\n")
print(f"In {last_month_start.strftime('%B, %Y')}, {len(new_committers)} projects elected a total of {total} committers.\n")
for project in sorted(new_committers.keys()):
print(f"### {project.upper()}\n")
for committer in new_committers[project]:
print(f"- {committer['name']} ({committer['id']}) — {committer['date']}")
print()
else:
print(f"In {last_month_start.strftime('%B, %Y')}, {len(new_committers)} projects elected a total of {total} committers\n")
for project in sorted(new_committers.keys()):
print(f"{project.upper()}:")
for committer in new_committers[project]:
print(f" - {committer['name']} ({committer['id']}) on {committer['date']}")
print()
else:
print(f"No new committers in {last_month_start.strftime('%B %Y')}")
def find_pmc(md=False):
committee_data = httpx.get("https://whimsy.apache.org/public/committee-info.json").json()
committees_data = httpx.get("https://projects.apache.org/json/foundation/committees.json").json()
last_month_start, last_month_end = get_date_range()
reporting_month = last_month_start.strftime("%Y-%m")
new_pmc_members = defaultdict(list)
new_projects = set()
# Identify projects established in the reporting month
for committee in committees_data:
if committee.get("established") == reporting_month:
new_projects.add(committee.get("id", "").lower())
for project_id, project_info in committee_data.get("committees", {}).items():
if not project_info.get("pmc"):
continue
for member_id, member_info in project_info.get("roster", {}).items():
date_str = member_info.get("date")
if not date_str:
continue
added = datetime.strptime(date_str, "%Y-%m-%d")
if last_month_start <= added <= last_month_end:
new_pmc_members[project_id].append({
"name": member_info.get("name", member_id),
"id": member_id,
"date": date_str
})
if new_pmc_members:
total = sum(len(m) for m in new_pmc_members.values())
new_project_members = sum(len(m) for p, m in new_pmc_members.items() if p in new_projects)
summary = f"In {last_month_start.strftime('%B, %Y')}, {len(new_pmc_members)} projects elected a total of {total} PMC members"
if new_project_members > 0:
summary += f". {new_project_members} of those are part of newly-established projects"
if md:
print(f"## New PMC Members\n")
print(summary + ".\n")
for project in sorted(new_pmc_members.keys()):
label = project.upper()
if project in new_projects:
label += " 🎉 (New Project)"
print(f"### {label}\n")
for member in new_pmc_members[project]:
print(f"- {member['name']} ({member['id']}) — {member['date']}")
print()
else:
print(summary + "\n")
for project in sorted(new_pmc_members.keys()):
project_label = f"{project.upper()}"
if project in new_projects:
project_label += " 🎉 (New Project)"
print(f"{project_label}:")
for member in new_pmc_members[project]:
print(f" - {member['name']} ({member['id']}) on {member['date']}")
print()
else:
print(f"No new PMC members in {last_month_start.strftime('%B %Y')}")
def find_releases(md=False):
releases_data = httpx.get("https://projects.apache.org/json/foundation/releases.json").json()
last_month_start, last_month_end = get_date_range()
project_releases = defaultdict(list)
for project_id, releases in releases_data.items():
for release_name, release_date_str in releases.items():
release_date = datetime.strptime(release_date_str, "%Y-%m-%d")
if last_month_start <= release_date <= last_month_end:
project_releases[project_id].append({
"name": release_name,
"date": release_date_str
})
if project_releases:
total = sum(len(r) for r in project_releases.values())
if md:
print(f"## Releases\n")
print(f"In {last_month_start.strftime('%B, %Y')}, {len(project_releases)} projects made {total} releases.\n")
for project in sorted(project_releases.keys()):
print(f"### {project.upper()}\n")
for release in project_releases[project]:
print(f"- {release['name']} — {release['date']}")
print()
else:
print(f"In {last_month_start.strftime('%B, %Y')}, {len(project_releases)} projects made {total} releases\n")
for project in sorted(project_releases.keys()):
print(f"{project.upper()}:")
for release in project_releases[project]:
print(f" - {release['name']} on {release['date']}")
print()
else:
print(f"No releases made in {last_month_start.strftime('%B %Y')}")
if __name__ == "__main__":
args = sys.argv[1:]
if "-h" in args or "--help" in args:
print("Usage: asf_activity.py [OPTIONS]")
print("\nOptions:")
print(" committers Show committers added last month")
print(" pmc Show PMC members added last month")
print(" releases Show releases made last month")
print(" all Show all reports (default)")
print(" -m, --markdown Save output to a timestamped .md file")
print(" -h, --help Show this help message")
print("\nExamples:")
print(" asf_activity.py")
print(" asf_activity.py committers")
print(" asf_activity.py pmc releases --markdown")
sys.exit(0)
markdown = "-m" in args or "--markdown" in args
args = [a for a in args if a not in ("-m", "--markdown")]
outfile = None
if markdown:
filename = f"activity_{datetime.now().strftime('%Y_%m_%d')}.md"
outfile = open(filename, "w")
sys.stdout = outfile
if not args or "all" in args:
if markdown:
last_month_start, _ = get_date_range()
print(f"# ASF Activity — {last_month_start.strftime('%B %Y')}\n")
find_committers(md=markdown)
if not markdown:
print("\n" + "="*80 + "\n")
find_pmc(md=markdown)
if not markdown:
print("\n" + "="*80 + "\n")
find_releases(md=markdown)
else:
if "committers" in args:
find_committers(md=markdown)
if "pmc" in args:
find_pmc(md=markdown)
if "releases" in args:
find_releases(md=markdown)
if outfile:
sys.stdout = sys.__stdout__
outfile.close()
print(f"Output saved to {filename}")