From 52c3fdc97a2dd4afada32aac7e5bea2dba9af49d Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Wed, 10 Dec 2025 10:35:49 +0100 Subject: [PATCH] docs: CONTRIBUTORS.md added --- .github/scripts/update_contributors.py | 258 ++++++++++++++++++++++ .github/workflows/update-contributors.yml | 93 ++++++++ CONTRIBUTORS.md | 102 +++++++++ 3 files changed, 453 insertions(+) create mode 100644 .github/scripts/update_contributors.py create mode 100644 .github/workflows/update-contributors.yml create mode 100644 CONTRIBUTORS.md diff --git a/.github/scripts/update_contributors.py b/.github/scripts/update_contributors.py new file mode 100644 index 000000000..4ee5644a3 --- /dev/null +++ b/.github/scripts/update_contributors.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +""" +Update CONTRIBUTORS.md with current GitHub contributors +This script fetches contributor information from GitHub API and updates the contributors list +""" + +import json +import re +import sys +from pathlib import Path +from typing import Dict, List, Optional +import subprocess + +try: + import requests +except ImportError: + print("Error: requests library not found. Install with: pip install requests") + sys.exit(1) + + +class ContributorUpdater: + """Manages updating the contributors list""" + + def __init__(self, repo_owner: str, repo_name: str, token: Optional[str] = None): + self.repo_owner = repo_owner + self.repo_name = repo_name + self.token = token + self.headers = {} + if token: + self.headers["Authorization"] = f"token {token}" + + def fetch_contributors(self) -> List[Dict]: + """Fetch contributors from GitHub API""" + url = f"https://api.github.com/repos/{self.repo_owner}/{self.repo_name}/contributors" + + all_contributors = [] + page = 1 + + while True: + params = {"page": page, "per_page": 100} + response = requests.get(url, headers=self.headers, params=params) + + if response.status_code != 200: + print(f"Error fetching contributors: {response.status_code}") + print(response.text) + break + + contributors = response.json() + if not contributors: + break + + all_contributors.extend(contributors) + page += 1 + + return all_contributors + + def get_contributor_details(self, username: str) -> Dict: + """Get detailed information about a contributor""" + url = f"https://api.github.com/users/{username}" + response = requests.get(url, headers=self.headers) + + if response.status_code == 200: + return response.json() + return {} + + def categorize_contributors( + self, contributors: List[Dict] + ) -> Dict[str, List[Dict]]: + """Categorize contributors by contribution level""" + # Core team members (manually maintained) + core_team = {"mpusz", "JohelEGP", "chiphogg"} + core_team_lower = {name.lower() for name in core_team} + + # Categorize by contribution count + major_contributors = [] # 50+ contributions + regular_contributors = [] # 10-49 contributions + occasional_contributors = [] # 1-9 contributions + + for contributor in contributors: + username = contributor["login"] + contributions = contributor["contributions"] + + # Skip core team members (handled separately) + if username.lower() in core_team_lower: + continue + + # Skip bots + if contributor.get("type") == "Bot": + continue + + if contributions >= 50: + major_contributors.append(contributor) + elif contributions >= 10: + regular_contributors.append(contributor) + else: + occasional_contributors.append(contributor) + + return { + "major": major_contributors, + "regular": regular_contributors, + "occasional": occasional_contributors, + } + + def generate_contributor_section( + self, contributors: List[Dict], include_contributions: bool = True + ) -> str: + """Generate markdown for a list of contributors""" + if not contributors: + return "*No contributors in this category yet.*\n" + + lines = [] + for contributor in contributors: + username = contributor["login"] + profile_url = contributor["html_url"] + contributions = contributor["contributions"] + + if include_contributions: + line = ( + f"- **[{username}]({profile_url})** ({contributions} contributions)" + ) + else: + line = f"- **[{username}]({profile_url})**" + + lines.append(line) + + return "\n".join(lines) + "\n" + + def update_contributors_file(self, contributors: List[Dict]): + """Update the CONTRIBUTORS.md file""" + contributors_file = Path("CONTRIBUTORS.md") + + if not contributors_file.exists(): + print("CONTRIBUTORS.md not found!") + return + + # Read current content + content = contributors_file.read_text() + + # Categorize contributors + categorized = self.categorize_contributors(contributors) + + # Generate new contributor sections + major_section = self.generate_contributor_section(categorized["major"]) + regular_section = self.generate_contributor_section(categorized["regular"]) + occasional_section = self.generate_contributor_section( + categorized["occasional"], include_contributions=False + ) + + # Generate statistics + total_contributors = len(contributors) + total_contributions = sum(c["contributions"] for c in contributors) + + stats_section = f"""## Statistics + +- **Total Contributors**: {total_contributors} +- **Total Contributions**: {total_contributions} +- **Major Contributors** (50+ contributions): {len(categorized['major'])} +- **Regular Contributors** (10-49 contributions): {len(categorized['regular'])} +- **Occasional Contributors** (1-9 contributions): {len(categorized['occasional'])} + +_Last updated: {self.get_current_date()}_ +""" + + # Update the contributors section + # Look for the CONTRIBUTORS_START/END markers + start_marker = "" + end_marker = "" + + if start_marker in content and end_marker in content: + # Replace the content between markers + before = content.split(start_marker)[0] + after = content.split(end_marker)[1] + + new_content = f"""{before}{start_marker} + +{stats_section} + +### Major Contributors + +_50+ contributions_ + +{major_section} + +### Regular Contributors + +_10-49 contributions_ + +{regular_section} + +### All Contributors + +_Everyone who has contributed to mp-units_ + +{occasional_section} + +{end_marker}{after}""" + + contributors_file.write_text(new_content) + print(f"Updated CONTRIBUTORS.md with {total_contributors} contributors") + else: + print("Could not find contributor markers in CONTRIBUTORS.md") + + def get_current_date(self) -> str: + """Get current date in a readable format""" + from datetime import datetime + + return datetime.now().strftime("%Y-%m-%d") + + +def get_github_token() -> Optional[str]: + """Try to get GitHub token from various sources""" + import os + + # Try environment variable + token = os.getenv("GITHUB_TOKEN") + if token: + return token + + # Try git config + try: + result = subprocess.run( + ["git", "config", "--get", "github.token"], capture_output=True, text=True + ) + if result.returncode == 0: + return result.stdout.strip() + except: + pass + + return None + + +def main(): + """Main function""" + # Get GitHub token (optional but recommended to avoid rate limits) + token = get_github_token() + if not token: + print("Warning: No GitHub token found. You may hit API rate limits.") + print("Set GITHUB_TOKEN environment variable for better performance.") + + # Initialize updater + updater = ContributorUpdater("mpusz", "units", token) + + try: + # Fetch contributors + print("Fetching contributors from GitHub...") + contributors = updater.fetch_contributors() + print(f"Found {len(contributors)} contributors") + + # Update contributors file + updater.update_contributors_file(contributors) + + except Exception as e: + print(f"Error updating contributors: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml new file mode 100644 index 000000000..dc3b6659e --- /dev/null +++ b/.github/workflows/update-contributors.yml @@ -0,0 +1,93 @@ +name: Update Contributors + +on: + schedule: + # Run monthly on the 1st at 00:00 UTC + - cron: "0 0 1 * *" + workflow_dispatch: + # Allow manual triggering + push: + paths: + - ".github/scripts/update_contributors.py" + branches: + - master + +permissions: + contents: write + pull-requests: write + +jobs: + update-contributors: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install requests + + - name: Update contributors list + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + python .github/scripts/update_contributors.py + + - name: Check for changes + id: verify-changed-files + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "changed=true" >> $GITHUB_OUTPUT + else + echo "changed=false" >> $GITHUB_OUTPUT + fi + + - name: Create Pull Request + if: steps.verify-changed-files.outputs.changed == 'true' + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "docs: update contributors list" + title: "Update Contributors List" + body: | + ## 👥 Contributors List Update + + This pull request automatically updates the contributors list with the latest information from GitHub. + + ### Changes + + - Updated contributor statistics + - Refreshed contributor categories based on contribution count + - Added new contributors since last update + + ### Review Notes + + - This is an automated update generated monthly + - Please review for any formatting issues + - Manual adjustments to categories can be made if needed + + Generated by GitHub Actions on: ${{ github.run_id }} + branch: update-contributors + delete-branch: true + base: master + + - name: Generate summary + if: always() + run: | + echo "## 👥 Contributors Update Summary" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.verify-changed-files.outputs.changed }}" == "true" ]; then + echo "✅ Contributors list updated and PR created" >> $GITHUB_STEP_SUMMARY + echo "📊 Check the created pull request for details" >> $GITHUB_STEP_SUMMARY + else + echo "ℹ️ No changes detected in contributors list" >> $GITHUB_STEP_SUMMARY + echo "📈 All contributors are up to date" >> $GITHUB_STEP_SUMMARY + fi diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 000000000..a3f13438f --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,102 @@ +# Contributors + +We are grateful to all the people who have contributed to **mp-units**! This project +wouldn't be possible without the dedication and hard work of our amazing community. + +## Core Team + +### Project Lead + +- **[Mateusz Pusz](https://github.com/mpusz)** - Original creator, project lead, and + primary maintainer + - Standards work: Leading C++29 standardization efforts + - Architecture: Core library design and implementation + - Community: Project vision and direction + +### Core Maintainers + +- **[Johel Ernesto Guerrero Peña](https://github.com/JohelEGP)** - Core maintainer + - API design and implementation + - Code review and quality assurance + - API Reference + +- **[Chip Hogg](https://github.com/chiphogg)** - Core maintainer + - Mathematical foundations and algorithms + - Performance optimizations + - Standards committee contributions + +## Community Contributors + + + + + +## Special Recognition + +### First-time Contributors + +We especially appreciate our first-time contributors who help make **mp-units** +more accessible and robust: + +- Thank you to everyone who has submitted their first PR! +- Special thanks to contributors who improve documentation and examples +- Recognition for bug reports that lead to important fixes + +### Community Champions + +- **Educational Content**: Contributors who create tutorials, blog posts, and educational materials +- **Issue Triage**: Community members who help with issue management and user support +- **Standards Advocacy**: People promoting **mp-units** in the C++ standards community +- **Adoption**: Organizations and individuals field-testing the library + +## Contribution Types + +We recognize various forms of contribution: + +### Code Contributions + +- 🐛 **Bug Fixes**: Identifying and fixing issues +- ✨ **Features**: New functionality and enhancements +- ⚡ **Performance**: Optimizations and improvements +- 🔧 **Infrastructure**: Build system, CI/CD, tooling + +### Non-Code Contributions + +- 📚 **Documentation**: Guides, examples, API documentation +- 🎓 **Education**: Tutorials, talks, blog posts +- 🔍 **Testing**: Bug reports, test cases, quality assurance +- 💬 **Community**: Support, mentoring, discussions +- 🎨 **Design**: User experience, visual design, branding +- 🌍 **Outreach**: Promotion, adoption, feedback collection + +## Contributor Guidelines + +### How to Contribute + +Please see our [Contributing Guide](CONTRIBUTING.md) for detailed information on: +- Setting up the development environment +- Coding standards and conventions +- Pull request process +- Testing requirements + +### Code of Conduct + +All contributors are expected to follow our [Code of Conduct](CODE_OF_CONDUCT.md). +We are committed to providing a welcoming and inclusive environment for everyone. + +## Thank You! 🙏 + +Every contribution, no matter how small, makes **mp-units** better. Whether you've: +- Fixed a typo in documentation +- Reported a bug +- Suggested an improvement +- Implemented a new feature +- Helped another user +- Shared the project with others + +**You are part of the mp-units community, and we appreciate you!** + +--- + +_Want to see your name here? Check out our [Contributing Guide](CONTRIBUTING.md) +and join our amazing community of contributors!_