Skip to content

Commit eee5bb4

Browse files
committed
feat: add committer plugin
1 parent f5c4e16 commit eee5bb4

File tree

3 files changed

+155
-1
lines changed

3 files changed

+155
-1
lines changed

hooks/committer.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import fnmatch
2+
import json
3+
import os
4+
import random
5+
from datetime import datetime
6+
from typing import List
7+
8+
import requests
9+
10+
11+
def exclude(src_path: str, globs: List[str]) -> bool:
12+
"""
13+
Determine if a src_path should be excluded.
14+
Supports globs (e.g. folder/* or *.md).
15+
Credits: code adapted from
16+
https://github.com/timvink/mkdocs-git-authors-plugin/blob/master/mkdocs_git_authors_plugin/exclude.py
17+
Args:
18+
src_path (src): Path of file
19+
globs (list): list of globs
20+
Returns:
21+
(bool): whether src_path should be excluded
22+
"""
23+
assert isinstance(src_path, str)
24+
assert isinstance(globs, list)
25+
26+
for g in globs:
27+
if fnmatch.fnmatchcase(src_path, g):
28+
return True
29+
30+
# Windows reports filenames as eg. a\\b\\c instead of a/b/c.
31+
# To make the same globs/regexes match filenames on Windows and
32+
# other OSes, let's try matching against converted filenames.
33+
# On the other hand, Unix actually allows filenames to contain
34+
# literal \\ characters (although it is rare), so we won't
35+
# always convert them. We only convert if os.sep reports
36+
# something unusual. Conversely, some future mkdocs might
37+
# report Windows filenames using / separators regardless of
38+
# os.sep, so we *always* test with / above.
39+
if os.sep != "/":
40+
src_path_fix = src_path.replace(os.sep, "/")
41+
if fnmatch.fnmatchcase(src_path_fix, g):
42+
return True
43+
return False
44+
45+
46+
def get_header() -> dict:
47+
if "MKDOCS_API_KEYS" in os.environ:
48+
keys = os.environ["MKDOCS_API_KEYS"].strip().split(",")
49+
return {"Authorization": "token " + str(random.choice(keys)).strip()}
50+
return {}
51+
52+
53+
class CommitterPlugin:
54+
def __init__(self):
55+
self.cache_dir = ".cache/plugin/git-committers"
56+
self.cache_file = f"{self.cache_dir}/page-authors.json"
57+
self.cache_page_authors = {}
58+
self.cache_date = ""
59+
self.excluded_pages = []
60+
self.last_request_return_code = 0
61+
62+
@staticmethod
63+
def get_request_url(edit_url: str) -> str:
64+
path = requests.utils.quote(
65+
edit_url.replace("https://github.com/doocs/leetcode/edit/main", "").replace(
66+
"%20", " "
67+
)
68+
)
69+
return f"https://api.github.com/repos/doocs/leetcode/commits?path={path}&sha=main&per_page=100"
70+
71+
def on_pre_build(self, config):
72+
if os.path.exists(self.cache_file):
73+
with open(self.cache_file, "r") as f:
74+
cache = f.read()
75+
self.cache_date = cache["cache_date"]
76+
self.cache_page_authors = cache["page_authors"]
77+
78+
def on_post_build(self, config):
79+
json_data = json.dumps(
80+
{
81+
"cache_date": datetime.now().strftime("%Y-%m-%d"),
82+
"page_authors": self.cache_page_authors,
83+
}
84+
)
85+
os.makedirs(self.cache_dir, exist_ok=True)
86+
f = open(self.cache_file, "w")
87+
f.write(json_data)
88+
f.close()
89+
90+
def get_contributors_to_file(self, path: str) -> List[dict]:
91+
# We already got a 401 (unauthorized) or 403 (rate limit) error, so we don't try again
92+
if self.last_request_return_code in [401, 403]:
93+
print("Got a 401 or 403 error, not trying again")
94+
return []
95+
96+
authors = []
97+
r = requests.get(url=path, headers=get_header())
98+
self.last_request_return_code = r.status_code
99+
if r.status_code == 200:
100+
# Get login, url and avatar for each author. Ensure no duplicates.
101+
res = r.json()
102+
for commit in res:
103+
if (
104+
commit["author"]
105+
and commit["author"]["login"]
106+
and commit["author"]["login"]
107+
not in [author["login"] for author in authors]
108+
):
109+
authors.append(
110+
{
111+
"login": commit["author"]["login"],
112+
"name": commit["author"]["login"],
113+
"url": commit["author"]["html_url"],
114+
"avatar": commit["author"]["avatar_url"],
115+
}
116+
)
117+
return authors
118+
return []
119+
120+
def list_contributors(self, path: str) -> List[dict]:
121+
path = path.replace("\\", "/")
122+
authors = self.get_contributors_to_file(path)
123+
self.cache_page_authors[path] = {"authors": authors}
124+
return authors
125+
126+
def on_page_context(self, context, page, config, nav):
127+
if not page.edit_url:
128+
return context
129+
context["committers"] = []
130+
if exclude(page.file.src_path, self.excluded_pages):
131+
return context
132+
path = self.get_request_url(page.edit_url)
133+
authors = self.list_contributors(path)
134+
if authors:
135+
context["committers"] = authors
136+
context["committers_source"] = "github"
137+
return context
138+
139+
140+
plugin = CommitterPlugin()
141+
142+
143+
def on_pre_build(config):
144+
plugin.on_pre_build(config)
145+
146+
147+
def on_post_build(config):
148+
plugin.on_post_build(config)
149+
150+
151+
def on_page_context(context, page, config, nav):
152+
return plugin.on_page_context(context, page, config, nav)

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ plugins:
8383
hooks:
8484
- hooks/edit_url.py
8585
- hooks/tags.py
86+
- hooks/committer.py
8687

8788
markdown_extensions:
8889
- pymdownx.superfences

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ mkdocs-git-committers-plugin-2==2.3.0
66
mkdocs-material==9.5.13
77
mkdocs-glightbox==0.3.7
88
jieba==0.42.1
9-
beautifulsoup4==4.12.0
9+
beautifulsoup4==4.12.0
10+
gitpython==3.1.24

0 commit comments

Comments
 (0)