Skip to content
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

add ability to add contributor information to html pages (#1254) #1299

Merged
merged 19 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
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: 2 additions & 0 deletions .github/workflows/ci.yaml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ jobs:
texlive-fonts-recommended

- name: Generate build files
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: |
make prepare

Expand Down
8 changes: 8 additions & 0 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ Open the HTML manual in a web brower for visual verification

firefox build/manual/build/html/index.html

### HTML pages with contributor information

To build the html pages that contains contributor information on the sidebar set a github access token to an environment variable called GH_TOKEN.

Note: If set this will increase the build time of the documentation by approximately a factor of 4.

setenv GH_TOKEN <access token>

## Repository content

These are the components that define the structure of NeXus data files
Expand Down
20 changes: 20 additions & 0 deletions dev_tools/docs/nxdl.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ..globals.errors import NXDLParseError
from ..globals.nxdl import NXDL_NAMESPACE
from ..globals.urls import REPO_URL
from ..utils.github import get_file_contributors_via_api
from ..utils.types import PathLike
from .anchor_list import AnchorRegistry

Expand Down Expand Up @@ -80,6 +81,7 @@ def _parse_nxdl_file(self, nxdl_file: Path):
self._print(
f".. auto-generated by {__name__} from the NXDL source {source} -- DO NOT EDIT"
)

self._print("")
self._print(".. index::")
self._print(f" ! {nxclass_name} ({self._listing_category})")
Expand All @@ -100,6 +102,24 @@ def _parse_nxdl_file(self, nxdl_file: Path):
else:
extends = f":ref:`{extends}`"

# add the contributors as variables to the rst file that will
nxdl_root = get_nxdl_root()
rel_path = str(nxdl_file.relative_to(nxdl_root))
rel_html = str(rel_path).replace(os.sep, "/")
contribs_dct = get_file_contributors_via_api("definitions", rel_html)
if contribs_dct is not None:
self._print("")
self._print("..")
self._print(" Contributors List")
for date_str, contrib_dct in contribs_dct.items():
date_str = date_str.split("T")[0]
name = contrib_dct["name"]
gh_login_nm = contrib_dct["commit_dct"]["committer"]["login"]
gh_avatar_url = contrib_dct["commit_dct"]["committer"]["avatar_url"]
self._print("")
s = "|".join([name, gh_login_nm, gh_avatar_url, date_str])
self._print(f" .. |contrib_name| replace:: {s}")

self._print("")
self._print("**Status**:\n")
self._print(f" {self._listing_category.strip()}, extends {extends}")
Expand Down
Empty file added dev_tools/ext/__init__.py
Empty file.
30 changes: 30 additions & 0 deletions dev_tools/ext/contrib_ext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import re

# a custom sphinx extension that is connected to the source-read hook for rst files,
# the purpose is to read all of the contributor information from the rst file and
# place it in a string variable that will be used in the sourcelink.html jinja template
# that has been over ridden and sits in the _templates directory to produce the
# contributor information on the for right sidebar of the html pages

variables_re = re.compile(r"\|(\w+)\| replace::\s(.+)")


def extract_contributor_vars(app, docname, source):
# Read the RST file content
content = source[0]

# Extract variables using regular expressions
variables = variables_re.findall(content)

# Create a dictionary to store the extracted variables
# this will create a list of single strings each of which contain the info about the contributor
extracted_variables = [var[1] for var in variables]
if "variables" not in app.config.html_context.keys():
app.config.html_context["variables"] = {}

# Add the extracted variables to the Jinja environment
app.config.html_context["variables"][docname] = extracted_variables


def setup(app):
app.connect("source-read", extract_contributor_vars)
92 changes: 92 additions & 0 deletions dev_tools/utils/github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import os

import requests


def format_author_name(nm):
"""
make sure all words in name start with a capital
"""
nms = nm.split(" ")
auth_nm = " ".join([n.capitalize() for n in nms])
return auth_nm


def get_github_profile_name(email):
"""
given an email addr return the github login name
"""
email = email.replace(" ", "")
nm = email.split("@")[0]
return nm


def get_file_contributors_via_api(repo_name, file_path):
"""
This function takes the repo name (ex:"definitions") and relative path to the nxdl
file (ex: "applications/NXmx.nxdl.xml") and using the github api it retrieves a dictionary
of committers for that file in descending date order.

In order to increase the capacity (rate) of use of the github API an access token is used if it exists
as an environment variable called GH_TOKEN, in the ci yaml file this is expected to be assigned from the secret
object like this
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}

With the access token the rate is 5000 times per hour and without it is 60

returns a sorted dict of unique contributors to a file, or None if no GH_TOKEN has been defined in os.environ
"""
have_token = False
if "GH_TOKEN" in os.environ.keys():
access_token = os.environ["GH_TOKEN"]
if len(access_token) > 0:
have_token = True
else:
# because the environment does not contain GH_TOKEN, assume the user wants to build the
# docs without contributor info
return None

contrib_skip_list = ["GitHub"]
url = f"https://api.github.com/repos/nexusformat/{repo_name}/commits"
params = {"path": file_path}
headers = {}
if have_token:
# Set the headers with the access token
headers = {
"Authorization": f"token {access_token}",
"Accept": "application/vnd.github.v3+json",
}

response = requests.get(url, params=params, headers=headers)
commits = response.json()
if response.status_code != 200:
# if its 403: the max rate per hour has been reached
raise Exception(
f"access_token={access_token}, {commits['message']},{commits['documentation_url']}"
)

contributor_names = set()
contribs_dct = {}
_email_lst = []
for commit_dct in commits:
if commit_dct["committer"] is not None:
contributor = commit_dct["commit"]["committer"]["name"]
RussBerg marked this conversation as resolved.
Show resolved Hide resolved
if contributor in contrib_skip_list:
continue
contributor_names.add(contributor)
if commit_dct["commit"]["committer"]["email"] not in _email_lst:
_email = commit_dct["commit"]["committer"]["email"]
_email_lst.append(_email)
contribs_dct[commit_dct["commit"]["committer"]["date"]] = {
"name": format_author_name(
commit_dct["commit"]["committer"]["name"]
),
"commit_dct": commit_dct,
}

# sort them so they are in descending order from newest to oldest
sorted_keys = sorted(contribs_dct.keys(), reverse=True)
sorted_dict = {key: contribs_dct[key] for key in sorted_keys}

return sorted_dict
45 changes: 45 additions & 0 deletions manual/source/_templates/sourcelink.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{#
basic/sourcelink.html
~~~~~~~~~~~~~~~~~~~~~

Sphinx sidebar template: "show source" link.

:copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
#}
{%- if show_source and has_source and sourcename %}
<div role="note" aria-label="source link">
<h3>{{ _('This Page') }}</h3>
<p>Have a Question? <a href="https://github.com/nexusformat/definitions/issues/new">Get help</a></p>
<ul class="this-page-menu">
<style>
.avatar-container {
display: flex;
align-items: center;
}

.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 5px;
}
</style>
{% set nx_class_nm = sourcename|replace(".rst.txt","") %}
{% if variables[nx_class_nm]|length > 0 %}
<p> Contributors </p>
{% else %}
{% endif %}
<div class="avatar-container">
{% for vars_string in variables[nx_class_nm] %}
{% set var_list = vars_string.split('|') %}
{% set tooltip_string = var_list[0] ~ " " ~ var_list[3] %}
<a href="https://github.com/{{ var_list[1] }}"
class=""
title="{{ tooltip_string }}"
data-hovercard-type="user" data-hovercard-url="/users/{{ var_list[1] }}/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self">
<img class="avatar circle" src="{{ var_list[2] }}" data-view-component="true" alt="GitHub Avatar">
{% endfor %}
</ul>
</div>
{%- endif %}
18 changes: 9 additions & 9 deletions manual/source/conf.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@

import sys, os, datetime


# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))

# add the abs path to the custom extension for collecting the contributor variables from the rst files
sys.path.insert(0, os.path.abspath('../../../dev_tools/ext'))

# -- Project information -----------------------------------------------------

Expand Down Expand Up @@ -47,7 +46,8 @@
'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
'sphinx.ext.todo',
'sphinx_tabs.tabs'
'sphinx_tabs.tabs',
'contrib_ext'
]

# Show `.. todo` directives in the output
Expand Down Expand Up @@ -80,10 +80,10 @@

html_sidebars = {
'**': [
'localtoc.html',
'relations.html',
'sourcelink.html',
'searchbox.html',
'localtoc.html',
'relations.html',
'sourcelink.html',
'searchbox.html',
'google_search.html'
],
}
Expand Down