Skip to content

Commit

Permalink
Merge pull request #1299 from nexusformat/1254-add-list-of-contributo…
Browse files Browse the repository at this point in the history
…rs-to-appdef

add ability to add contributor information to html pages (#1254)
  • Loading branch information
RussBerg committed Jun 26, 2023
2 parents 2635a44 + 439b390 commit 747d240
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 9 deletions.
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"]
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

0 comments on commit 747d240

Please sign in to comment.