Skip to content

Commit 7524ba8

Browse files
kandersolarcwhanse
authored andcommitted
Fancy "view on github" links in documentation (pvlib#913)
* try out fancy GH linking * comment breadcrumbs.html * fix syntax * refactor GH link logic out of the template and into conf.py * typo
1 parent 00bda7a commit 7524ba8

File tree

2 files changed

+121
-1
lines changed

2 files changed

+121
-1
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{#
2+
3+
Modify the "Edit on Github" links to handle auto-generated pages in the
4+
example gallery and the API reference listings. The GH links that sphinx
5+
generates by default make the assumption that an HTML file comes from an RST
6+
file with the same 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://pvlib-python.rtfd.org/en/stable/auto_examples/plot_singlediode.html
13+
will have pagename = "auto_examples/plot_singlediode".
14+
15+
Note: make_github_url is defined in conf.py
16+
#}
17+
18+
{% extends "!breadcrumbs.html" %}
19+
{% block breadcrumbs_aside %}
20+
<li class="wy-breadcrumbs-aside">
21+
{# Get the appropriate GH link based on this page's name: #}
22+
{% set target_url = make_github_url(pagename) %}
23+
{# Create the HTML element with our custom GH link: #}
24+
<a href="{{ target_url }}" class="fa fa-github">View on Github</a>
25+
</li>
26+
{% endblock %}

docs/sphinx/source/conf.py

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
# for warning suppression
2222
import warnings
2323

24+
# for generating GH links with linenumbers
25+
import inspect
26+
2427

2528
class Mock(MagicMock):
2629
@classmethod
@@ -231,7 +234,6 @@ def setup(app):
231234
# Override footnote callout CSS to be normal text instead of superscript
232235
app.add_stylesheet("no_reference_superscript.css")
233236

234-
235237
# -- Options for LaTeX output ---------------------------------------------
236238

237239
latex_elements = {
@@ -345,3 +347,95 @@ def setup(app):
345347
warnings.filterwarnings("ignore", category=UserWarning,
346348
message='Matplotlib is currently using agg, which is a'
347349
' non-GUI backend, so cannot show the figure.')
350+
351+
# %% helper functions for intelligent "View on Github" linking
352+
# based on
353+
# https://gist.github.com/flying-sheep/b65875c0ce965fbdd1d9e5d0b9851ef1
354+
355+
356+
def get_obj_module(qualname):
357+
"""
358+
Get a module/class/attribute and its original module by qualname.
359+
Useful for looking up the original location when a function is imported
360+
into an __init__.py
361+
362+
Examples
363+
--------
364+
>>> func, mod = get_obj_module("pvlib.iotools.read_midc")
365+
>>> mod.__name__
366+
'pvlib.iotools.midc'
367+
"""
368+
modname = qualname
369+
classname = None
370+
attrname = None
371+
while modname not in sys.modules:
372+
attrname = classname
373+
modname, classname = modname.rsplit('.', 1)
374+
375+
# retrieve object and find original module name
376+
if classname:
377+
cls = getattr(sys.modules[modname], classname)
378+
modname = cls.__module__
379+
obj = getattr(cls, attrname) if attrname else cls
380+
else:
381+
obj = None
382+
383+
return obj, sys.modules[modname]
384+
385+
386+
def get_linenos(obj):
387+
"""Get an object’s line numbers in its source code file"""
388+
try:
389+
lines, start = inspect.getsourcelines(obj)
390+
except TypeError: # obj is an attribute or None
391+
return None, None
392+
else:
393+
return start, start + len(lines) - 1
394+
395+
396+
def make_github_url(pagename):
397+
"""
398+
Generate the appropriate GH link for a given docs page. This function
399+
is intended for use in sphinx template files.
400+
401+
The target URL is built differently based on the type of page. Sphinx
402+
provides templates with a built-in `pagename` variable that is the path
403+
at the end of the URL, without the extension. For instance,
404+
https://pvlib-python.rtfd.org/en/stable/auto_examples/plot_singlediode.html
405+
will have pagename = "auto_examples/plot_singlediode".
406+
"""
407+
408+
URL_BASE = "https://github.com/pvlib/pvlib-python/blob/master/"
409+
410+
# is it a gallery page?
411+
if any(d in pagename for d in sphinx_gallery_conf['gallery_dirs']):
412+
if pagename.split("/")[-1] == "index":
413+
example_file = "README.rst"
414+
else:
415+
example_file = pagename.split("/")[-1] + ".py"
416+
target_url = URL_BASE + "docs/examples/" + example_file
417+
418+
# is it an API autogen page?
419+
elif "generated" in pagename:
420+
# pagename looks like "generated/pvlib.location.Location"
421+
qualname = pagename.split("/")[-1]
422+
obj, module = get_obj_module(qualname)
423+
path = module.__name__.replace(".", "/") + ".py"
424+
target_url = URL_BASE + path
425+
# add line numbers if possible:
426+
start, end = get_linenos(obj)
427+
if start and end:
428+
target_url += '#L{}-L{}'.format(start, end)
429+
430+
# Just a normal source RST page
431+
else:
432+
target_url = URL_BASE + "docs/sphinx/source/" + pagename + ".rst"
433+
434+
return target_url
435+
436+
437+
# variables to pass into the HTML templating engine; these are accessible from
438+
# _templates/breadcrumbs.html
439+
html_context = {
440+
'make_github_url': make_github_url,
441+
}

0 commit comments

Comments
 (0)