1+ """
2+ Sphinx extension to display GitHub contributors.
3+ """
4+
5+ import os
6+ import json
7+ import requests
8+ from docutils import nodes
9+ from docutils .parsers .rst import Directive , directives
10+
11+
12+ class GithubContributorsDirective (Directive ):
13+ """
14+ Directive to display GitHub contributors.
15+
16+ Usage:
17+ .. github-contributors:: owner/repo
18+ :max: 10
19+ :columns: 4
20+ """
21+
22+ required_arguments = 1
23+ optional_arguments = 0
24+ option_spec = {
25+ 'max' : directives .positive_int ,
26+ 'columns' : directives .positive_int ,
27+ 'exclude' : directives .unchanged ,
28+ }
29+ has_content = False
30+
31+ def run (self ):
32+ # Parse arguments
33+ repo_path = self .arguments [0 ]
34+ max_contributors = self .options .get ('max' , 100 )
35+ columns = self .options .get ('columns' , 4 )
36+ exclude_logins = self .options .get ('exclude' , '' ).split (',' )
37+ exclude_logins = [login .strip () for login in exclude_logins if login .strip ()]
38+
39+ try :
40+ # Use GitHub API to get contributors
41+ api_url = f"https://api.github.com/repos/{ repo_path } /contributors?per_page={ max_contributors } "
42+
43+ # Check for GitHub token in environment variables
44+ github_token = os .environ .get ('GITHUB_TOKEN' , '' )
45+ headers = {}
46+ if github_token :
47+ headers ['Authorization' ] = f'token { github_token } '
48+
49+ response = requests .get (api_url , headers = headers )
50+ response .raise_for_status ()
51+ contributors = response .json ()
52+
53+ # Filter excluded contributors
54+ contributors = [c for c in contributors if c ['login' ] not in exclude_logins ]
55+
56+ # Create a container div for the contributors grid
57+ container = nodes .container (classes = ['contributors-grid' ])
58+ container .append (nodes .raw ('' ,
59+ f'<style>.contributors-grid {{ display: grid; grid-template-columns: repeat({ columns } , 1fr); gap: 20px; }}</style>' ,
60+ format = 'html' ))
61+
62+ # Add each contributor
63+ for contributor in contributors [:max_contributors ]:
64+ login = contributor ['login' ]
65+ avatar_url = contributor ['avatar_url' ]
66+ profile_url = contributor ['html_url' ]
67+ contributions = contributor ['contributions' ]
68+
69+ # Create a container for this contributor
70+ contributor_div = nodes .container (classes = ['contributor' ])
71+ contributor_div .append (nodes .raw ('' ,
72+ f'''
73+ <div style="text-align: center; margin-bottom: 10px;">
74+ <a href="{ profile_url } " target="_blank" style="text-decoration: none;">
75+ <img src="{ avatar_url } " alt="{ login } " style="width: 80px; height: 80px; border-radius: 50%; margin-bottom: 5px;" />
76+ <div>{ login } </div>
77+ <div style="font-size: 0.8em; color: #666;">{ contributions } commit{ "s" if contributions != 1 else "" } </div>
78+ </a>
79+ </div>
80+ ''' ,
81+ format = 'html' ))
82+
83+ container .append (contributor_div )
84+
85+ return [container ]
86+
87+ except Exception as e :
88+ warning_node = nodes .warning ()
89+ warning_node += nodes .paragraph (text = f"Error getting GitHub contributors: { e } " )
90+ return [warning_node ]
91+
92+
93+ def setup (app ):
94+ """
95+ Set up the Sphinx extension.
96+ """
97+ app .add_directive ('github-contributors' , GithubContributorsDirective )
98+
99+ return {
100+ 'version' : '0.1' ,
101+ 'parallel_read_safe' : True ,
102+ 'parallel_write_safe' : True ,
103+ }
0 commit comments