Skip to content

Commit 11c5fbb

Browse files
committed
Auto linking to GitHub source code from docs
1 parent b284c02 commit 11c5fbb

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

docs/source/conf.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
#
1313
import os
1414
import sys
15+
import inspect
16+
import subprocess
17+
from os.path import relpath, dirname
18+
19+
import gnss_lib_py
20+
1521
sys.path.insert(0, os.path.abspath('.'))
1622
sys.path.insert(0, os.path.abspath('../'))
1723
sys.path.insert(0, os.path.abspath('../../'))
@@ -40,6 +46,7 @@
4046
extensions = [
4147
'sphinx.ext.autodoc',
4248
'sphinx.ext.napoleon',
49+
'sphinx.ext.linkcode',
4350
'nbsphinx',
4451
'nbsphinx_link',
4552
'IPython.sphinxext.ipython_console_highlighting',
@@ -104,3 +111,113 @@
104111

105112
# document __init__ methods
106113
autoclass_content = 'both'
114+
115+
# Function to find URLs for the source code on GitHub for built docs
116+
117+
# The following code to find the head tag is taken from:
118+
# https://gist.github.com/nlgranger/55ff2e7ff10c280731348a16d569cb73
119+
120+
linkcode_revision = "main"
121+
try:
122+
# lock to commit number
123+
cmd = "git log -n1 --pretty=%H"
124+
head = subprocess.check_output(cmd.split()).strip().decode('utf-8')
125+
linkcode_revision = head
126+
127+
# if we are on main's HEAD, use main as reference
128+
cmd = "git log --first-parent main -n1 --pretty=%H"
129+
main = subprocess.check_output(cmd.split()).strip().decode('utf-8')
130+
if head == main:
131+
linkcode_revision = "main"
132+
133+
# if we have a tag, use tag as reference
134+
cmd = "git describe --exact-match --tags " + head
135+
tag = subprocess.check_output(cmd.split(" ")).strip().decode('utf-8')
136+
linkcode_revision = tag
137+
138+
except subprocess.CalledProcessError:
139+
pass
140+
141+
linkcode_url = "https://github.com/Stanford-NavLab/gnss_lib_py/blob/" \
142+
+ linkcode_revision + "/{filepath}#L{linestart}-L{linestop}"
143+
144+
145+
def linkcode_resolve(domain, info):
146+
"""Return GitHub link to Python file for docs.
147+
148+
This function does not return a link for non-Python objects.
149+
For Python objects, `domain == 'py'`, `info['module']` contains the
150+
name of the module containing the method being documented, and
151+
`info['fullname']` contains the name of the method.
152+
153+
Notes
154+
-----
155+
Based off the numpy implementation of linkcode_resolve:
156+
https://github.com/numpy/numpy/blob/2f375c0f9f19085684c9712d602d22a2b4cb4c8e/doc/source/conf.py#L443
157+
Retrieved on 1 Jul, 2023.
158+
"""
159+
if domain != 'py':
160+
return None
161+
162+
modname = info['module']
163+
fullname = info['fullname']
164+
submod = sys.modules.get(modname)
165+
if submod is None:
166+
return None
167+
# print('modname:', modname)
168+
# print('fullname:', fullname)
169+
# print(f"submod:{submod}")
170+
171+
obj = submod
172+
for part in fullname.split('.'):
173+
try:
174+
obj = getattr(obj, part)
175+
except Exception:
176+
return None
177+
178+
# strip decorators, which would resolve to the source of the decorator
179+
# possibly an upstream bug in getsourcefile, bpo-1764286
180+
try:
181+
unwrap = inspect.unwrap
182+
except AttributeError:
183+
pass
184+
else:
185+
obj = unwrap(obj)
186+
filepath = None
187+
lineno = None
188+
189+
if filepath is None:
190+
try:
191+
filepath = inspect.getsourcefile(obj)
192+
except Exception:
193+
filepath = None
194+
if not filepath:
195+
return None
196+
#NOTE: Re-export filtering turned off because
197+
# # Ignore re-exports as their source files are not within the gnss_lib_py repo
198+
# module = inspect.getmodule(obj)
199+
# if module is not None and not module.__name__.startswith("gnss_lib_py"):
200+
# return "no_module_not_gnss_lib_py"
201+
202+
try:
203+
source, lineno = inspect.getsourcelines(obj)
204+
except Exception:
205+
lineno = ""
206+
# The following line of code first finds the relative path from
207+
# the location of gnss_lib_py.__init__.py and then adds gnss_lib_py
208+
# to the beginning to give the path of that file from the root folder
209+
# of gnss_lib_py and the tests directory adjacent to it
210+
211+
filepath = relpath(filepath, dirname(gnss_lib_py.__file__))
212+
filepath = os.path.join('gnss_lib_py', filepath)
213+
214+
if lineno:
215+
linestart = lineno
216+
linestop = lineno + len(source) - 1
217+
else:
218+
linestart = ""
219+
linestop = ""
220+
221+
codelink = linkcode_url.format(
222+
filepath=filepath, linestart=linestart, linestop=linestop)
223+
return codelink

0 commit comments

Comments
 (0)