Skip to content

Commit 5bf1b30

Browse files
authored
better GitHub links in documentation (#156)
* tentative fix * fix slash * include branch logic based on RTD version * small improvement * omit GH link element if not building development or master * change github link text to reflect the destination branch * link changelog page to changelog GH folder instead of the template rst file * bugfix
1 parent 09cea8a commit 5bf1b30

File tree

2 files changed

+144
-1
lines changed

2 files changed

+144
-1
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{#
2+
3+
Modify the "Edit on Github" links to handle auto-generated pages in the
4+
API reference listings. The GH links that sphinx generates by default make
5+
the assumption that an HTML file comes from an RST file with the same
6+
filepath, which isn't the case for autogenerated files.
7+
8+
We need to generate the target URL differently based on the type
9+
of page. We use the built-in `pagename` variable to determine what
10+
kind of page this is. `pagename` is the path at the end of the
11+
URL, without the extension. For instance,
12+
https://rdtools.rtfd.io/en/latest/generated/rdtools.soiling.soiling_srr.html
13+
will have pagename = "generated/rdtools.soiling.soiling_srr".
14+
15+
Note: make_github_url is defined in conf.py
16+
#}
17+
18+
{% extends "!breadcrumbs.html" %}
19+
{% block breadcrumbs_aside %}
20+
{# Get the appropriate GH link based on this page's name: #}
21+
{% set link_info = make_github_url(pagename) %}
22+
23+
{# Create the HTML element with our custom GH link, unless it couldn't
24+
be determined. Note that None is lowercase in templates. #}
25+
{% if link_info is not none %}
26+
<li class="wy-breadcrumbs-aside">
27+
<a href="{{ link_info['url'] }}" class="fa fa-github">
28+
{{ link_info['text'] }}
29+
</a>
30+
</li>
31+
{% endif %}
32+
{% endblock %}

docs/sphinx/source/conf.py

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# prefer local rdtools folder to one installed in a venv or site-packages:
1616
import os
1717
import sys
18+
import inspect
1819
sys.path.insert(0, os.path.abspath('../../..'))
1920

2021

@@ -83,4 +84,114 @@
8384
master_doc = 'index'
8485
# A workaround for the responsive tables always having annoying scrollbars.
8586
def setup(app):
86-
app.add_stylesheet("no_scrollbars.css")
87+
app.add_stylesheet("no_scrollbars.css")
88+
89+
90+
# %% helper functions for intelligent "View on Github" linking
91+
# based on
92+
# https://gist.github.com/flying-sheep/b65875c0ce965fbdd1d9e5d0b9851ef1
93+
94+
def get_obj_module(qualname):
95+
"""
96+
Get a module/class/attribute and its original module by qualname.
97+
Useful for looking up the original location when a function is imported
98+
into an __init__.py
99+
100+
Examples
101+
--------
102+
>>> func, mod = get_obj_module("rdtools.degradation_ols")
103+
>>> mod.__name__
104+
'rdtools.degradation'
105+
"""
106+
modname = qualname
107+
classname = None
108+
attrname = None
109+
while modname not in sys.modules:
110+
attrname = classname
111+
modname, classname = modname.rsplit('.', 1)
112+
113+
# retrieve object and find original module name
114+
if classname:
115+
cls = getattr(sys.modules[modname], classname)
116+
modname = cls.__module__
117+
obj = getattr(cls, attrname) if attrname else cls
118+
else:
119+
obj = None
120+
121+
return obj, sys.modules[modname]
122+
123+
124+
def get_linenos(obj):
125+
"""Get an object’s line numbers in its source code file"""
126+
try:
127+
lines, start = inspect.getsourcelines(obj)
128+
except TypeError: # obj is an attribute or None
129+
return None, None
130+
else:
131+
return start, start + len(lines) - 1
132+
133+
134+
def make_github_url(pagename):
135+
"""
136+
Generate the appropriate GH link for a given docs page. This function
137+
is intended for use in sphinx template files.
138+
The target URL is built differently based on the type of page. Sphinx
139+
provides templates with a built-in `pagename` variable that is the path
140+
at the end of the URL, without the extension. For instance,
141+
https://rdtools.rtfd.io/en/latest/generated/rdtools.soiling.soiling_srr.html
142+
will have pagename = "generated/rdtools.soiling.soiling_srr".
143+
144+
Returns None if not building development or master.
145+
"""
146+
147+
# RTD automatically sets READTHEDOCS_VERSION to the version being built.
148+
rtd_version = os.environ.get('READTHEDOCS_VERSION', None)
149+
version_map = {
150+
'stable': 'master',
151+
'latest': 'development',
152+
}
153+
try:
154+
branch = version_map[rtd_version]
155+
except KeyError:
156+
# for other builds (PRs, local builds etc), it's unclear where to link
157+
return None
158+
159+
URL_BASE = "https://github.com/nrel/rdtools/blob/{}/".format(branch)
160+
161+
# is it an API autogen page?
162+
if pagename.startswith("generated/"):
163+
# pagename looks like "generated/rdtools.degradation.degradation_ols"
164+
qualname = pagename.split("/")[-1]
165+
obj, module = get_obj_module(qualname)
166+
path = module.__name__.replace(".", "/") + ".py"
167+
target_url = URL_BASE + path
168+
# add line numbers if possible:
169+
start, end = get_linenos(obj)
170+
if start and end:
171+
target_url += '#L{}-L{}'.format(start, end)
172+
173+
# is it the example notebook?
174+
elif pagename == "example":
175+
target_url = URL_BASE + "docs/degradation_and_soiling_example.ipynb"
176+
177+
# is the the changelog page?
178+
elif pagename == "changelog":
179+
target_url = URL_BASE + "docs/sphinx/source/changelog"
180+
181+
# Just a normal source RST page
182+
else:
183+
target_url = URL_BASE + "docs/sphinx/source/" + pagename + ".rst"
184+
185+
display_text = "View on github/" + branch
186+
link_info = {
187+
'url': target_url,
188+
'text': display_text
189+
}
190+
return link_info
191+
192+
193+
# variables to pass into the HTML templating engine; these are accessible from
194+
# _templates/breadcrumbs.html
195+
html_context = {
196+
'make_github_url': make_github_url,
197+
}

0 commit comments

Comments
 (0)