Skip to content

Tooling: add git hooks to help maintain codebase #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -114,4 +114,4 @@ venv.bak/
dmypy.json

# Pyre type checker
.pyre/
.pyre/
52 changes: 52 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
exclude: ".venv|__pycache__|tests/dev/|tests/fixtures/"
fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-added-large-files
args: ["--maxkb=500"]
- id: check-ast
- id: check-builtin-literals
- id: check-case-conflict
- id: check-toml
- id: check-yaml
- id: detect-private-key
- id: end-of-file-fixer
- id: fix-byte-order-marker
- id: fix-encoding-pragma
args: [--remove]
- id: name-tests-test
args: [--pytest-test-first]
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]

- repo: https://github.com/asottile/pyupgrade
rev: v3.10.1
hooks:
- id: pyupgrade
args:
- "--py38-plus"

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.0.287"
hooks:
- id: ruff
args: ["--fix-only", "--target-version=py38"]

- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: black
args: ["--target-version=py38"]

- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
args: ["--profile", "black", "--filter-files"]

ci:
autoupdate_schedule: quarterly
skip: []
submodules: false
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
2 changes: 1 addition & 1 deletion mkdocs_git_committers_plugin_2/exclude.py
Original file line number Diff line number Diff line change
@@ -2,8 +2,8 @@
Module to assist exclude certain files being processed by plugin.
Inspired by https://github.com/apenwarr/mkdocs-exclude
"""
import os
import fnmatch
import os
from typing import List


147 changes: 86 additions & 61 deletions mkdocs_git_committers_plugin_2/plugin.py
Original file line number Diff line number Diff line change
@@ -1,77 +1,78 @@
import os
import sys
import json
import logging
from pprint import pprint
import os
import re
import time
from datetime import datetime
from timeit import default_timer as timer
from datetime import datetime, timedelta

from mkdocs import utils as mkdocs_utils
from mkdocs.config import config_options, Config
import requests
from bs4 import BeautifulSoup as bs
from git import Commit, Repo
from mkdocs.config import config_options
from mkdocs.plugins import BasePlugin

from git import Repo, Commit
import requests, json
from requests.exceptions import HTTPError
import time
import hashlib
import re
from bs4 import BeautifulSoup as bs

from mkdocs_git_committers_plugin_2.exclude import exclude

LOG = logging.getLogger("mkdocs.plugins." + __name__)

class GitCommittersPlugin(BasePlugin):

class GitCommittersPlugin(BasePlugin):
config_scheme = (
('enterprise_hostname', config_options.Type(str, default='')),
('repository', config_options.Type(str, default='')),
('branch', config_options.Type(str, default='master')),
('docs_path', config_options.Type(str, default='docs/')),
('enabled', config_options.Type(bool, default=True)),
('cache_dir', config_options.Type(str, default='.cache/plugin/git-committers')),
("enterprise_hostname", config_options.Type(str, default="")),
("repository", config_options.Type(str, default="")),
("branch", config_options.Type(str, default="master")),
("docs_path", config_options.Type(str, default="docs/")),
("enabled", config_options.Type(bool, default=True)),
("cache_dir", config_options.Type(str, default=".cache/plugin/git-committers")),
("exclude", config_options.Type(list, default=[])),
)

def __init__(self):
self.total_time = 0
self.branch = 'master'
self.branch = "master"
self.enabled = True
self.authors = dict()
self.cache_page_authors = dict()
self.exclude = list()
self.cache_date = ''
self.authors = {}
self.cache_page_authors = {}
self.exclude = []
self.cache_date = ""

def on_config(self, config):
self.enabled = self.config['enabled']
self.enabled = self.config["enabled"]
if not self.enabled:
LOG.info("git-committers plugin DISABLED")
return config

LOG.info("git-committers plugin ENABLED")

if not self.config['repository']:
if not self.config["repository"]:
LOG.error("git-committers plugin: repository not specified")
return config
if self.config['enterprise_hostname'] and self.config['enterprise_hostname'] != '':
self.githuburl = "https://" + self.config['enterprise_hostname'] + "/"
if (
self.config["enterprise_hostname"]
and self.config["enterprise_hostname"] != ""
):
self.githuburl = "https://" + self.config["enterprise_hostname"] + "/"
else:
self.githuburl = "https://github.com/"
self.localrepo = Repo(".")
self.branch = self.config['branch']
self.excluded_pages = self.config['exclude']
self.branch = self.config["branch"]
self.excluded_pages = self.config["exclude"]
return config

def list_contributors(self, path):
if exclude(path.lstrip(self.config['docs_path']), self.excluded_pages):
if exclude(path.lstrip(self.config["docs_path"]), self.excluded_pages):
return None, None

last_commit_date = ""
path = path.replace("\\", "/")
for c in Commit.iter_items(self.localrepo, self.localrepo.head, path):
if not last_commit_date:
# Use the last commit and get the date
last_commit_date = time.strftime("%Y-%m-%d", time.gmtime(c.authored_date))
last_commit_date = time.strftime(
"%Y-%m-%d", time.gmtime(c.authored_date)
)

# File not committed yet
if last_commit_date == "":
@@ -80,68 +81,92 @@ def list_contributors(self, path):

# Try to leverage the cache
if path in self.cache_page_authors:
if self.cache_date and time.strptime(last_commit_date, "%Y-%m-%d") < time.strptime(self.cache_date, "%Y-%m-%d"):
return self.cache_page_authors[path]['authors'], self.cache_page_authors[path]['last_commit_date']

url_contribs = self.githuburl + self.config['repository'] + "/contributors-list/" + self.config['branch'] + "/" + path
if self.cache_date and time.strptime(
last_commit_date, "%Y-%m-%d"
) < time.strptime(self.cache_date, "%Y-%m-%d"):
return (
self.cache_page_authors[path]["authors"],
self.cache_page_authors[path]["last_commit_date"],
)

url_contribs = (
self.githuburl
+ self.config["repository"]
+ "/contributors-list/"
+ self.config["branch"]
+ "/"
+ path
)
LOG.info("git-committers: fetching contributors for " + path)
LOG.debug(" from " + url_contribs)
authors=[]
authors = []
try:
response = requests.get(url_contribs)
response.raise_for_status()
except HTTPError as http_err:
LOG.error(f'git-committers: HTTP error occurred: {http_err}\n(404 is normal if file is not on GitHub yet or Git submodule)')
LOG.error(
f"git-committers: HTTP error occurred: {http_err}\n(404 is normal if file is not on GitHub yet or Git submodule)"
)
except Exception as err:
LOG.error(f'git-committers: Other error occurred: {err}')
LOG.error(f"git-committers: Other error occurred: {err}")
else:
html = response.text
# Parse the HTML
soup = bs(html, "lxml")
lis = soup.find_all('li')
lis = soup.find_all("li")
for li in lis:
a_tags = li.find_all('a')
login = a_tags[0]['href'].replace("/", "")
a_tags = li.find_all("a")
login = a_tags[0]["href"].replace("/", "")
url = self.githuburl + login
name = login
img_tags = li.find_all('img')
avatar = img_tags[0]['src']
avatar = re.sub(r'\?.*$', '', avatar)
authors.append({'login':login, 'name': name, 'url': url, 'avatar': avatar})
img_tags = li.find_all("img")
avatar = img_tags[0]["src"]
avatar = re.sub(r"\?.*$", "", avatar)
authors.append(
{"login": login, "name": name, "url": url, "avatar": avatar}
)
# Update global cache_page_authors
self.cache_page_authors[path] = {'last_commit_date': last_commit_date, 'authors': authors}
self.cache_page_authors[path] = {
"last_commit_date": last_commit_date,
"authors": authors,
}

return authors, last_commit_date

def on_page_context(self, context, page, config, nav):
context['committers'] = []
context["committers"] = []
if not self.enabled:
return context
start = timer()
git_path = self.config['docs_path'] + page.file.src_path
git_path = self.config["docs_path"] + page.file.src_path
authors, last_commit_date = self.list_contributors(git_path)
if authors:
context['committers'] = authors
context["committers"] = authors
if last_commit_date:
context['last_commit_date'] = last_commit_date
context["last_commit_date"] = last_commit_date
end = timer()
self.total_time += (end - start)
self.total_time += end - start

return context

def on_post_build(self, config):
LOG.info("git-committers: saving page authors cache file")
json_data = json.dumps({'cache_date': datetime.now().strftime("%Y-%m-%d"), 'page_authors': self.cache_page_authors})
os.makedirs(self.config['cache_dir'], exist_ok=True)
f = open(self.config['cache_dir'] + "/page-authors.json", "w")
json_data = json.dumps(
{
"cache_date": datetime.now().strftime("%Y-%m-%d"),
"page_authors": self.cache_page_authors,
}
)
os.makedirs(self.config["cache_dir"], exist_ok=True)
f = open(self.config["cache_dir"] + "/page-authors.json", "w")
f.write(json_data)
f.close()

def on_pre_build(self, config):
if os.path.exists(self.config['cache_dir'] + "/page-authors.json"):
if os.path.exists(self.config["cache_dir"] + "/page-authors.json"):
LOG.info("git-committers: found page authors cache file - loading it")
f = open(self.config['cache_dir'] + "/page-authors.json", "r")
f = open(self.config["cache_dir"] + "/page-authors.json")
cache = json.loads(f.read())
self.cache_date = cache['cache_date']
self.cache_page_authors = cache['page_authors']
self.cache_date = cache["cache_date"]
self.cache_page_authors = cache["page_authors"]
f.close()
48 changes: 25 additions & 23 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from pathlib import Path
from typing import Union

from setuptools import setup, find_packages
from setuptools import find_packages, setup

# The directory containing this file
HERE = Path(__file__).parent

# The text of the README file
README = (HERE / "README.md").read_text()


def load_requirements(requirements_files: Union[Path, list[Path]]) -> list:
"""Helper to load requirements list from a path or a list of paths.

@@ -36,35 +37,36 @@ def load_requirements(requirements_files: Union[Path, list[Path]]) -> list:

return out_requirements


setup(
name='mkdocs-git-committers-plugin-2',
version='1.2.0',
description='An MkDocs plugin to create a list of contributors on the page. The git-committers plugin will seed the template context with a list of github committers and other useful GIT info such as last modified date',
name="mkdocs-git-committers-plugin-2",
version="1.2.0",
description="An MkDocs plugin to create a list of contributors on the page. The git-committers plugin will seed the template context with a list of github committers and other useful GIT info such as last modified date",
long_description=README,
long_description_content_type="text/markdown",
keywords='mkdocs pdf github',
url='https://github.com/ojacques/mkdocs-git-committers-plugin-2/',
author='Byrne Reese, Olivier Jacques',
author_email='byrne@majordojo.com, ojacques2@gmail.com',
license='MIT',
python_requires='>=3.8,<4',
keywords="mkdocs pdf github",
url="https://github.com/ojacques/mkdocs-git-committers-plugin-2/",
author="Byrne Reese, Olivier Jacques",
author_email="byrne@majordojo.com, ojacques2@gmail.com",
license="MIT",
python_requires=">=3.8,<4",
install_requires=load_requirements(HERE / "requirements.txt"),
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Intended Audience :: Information Technology',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11'
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
packages=find_packages(),
entry_points={
'mkdocs.plugins': [
'git-committers = mkdocs_git_committers_plugin_2.plugin:GitCommittersPlugin'
"mkdocs.plugins": [
"git-committers = mkdocs_git_committers_plugin_2.plugin:GitCommittersPlugin"
]
}
},
)