From 4a8cbf2b51b569929aecd549d41d3e9e11dd6771 Mon Sep 17 00:00:00 2001 From: Demetris Roumis Date: Tue, 26 Dec 2023 17:16:09 -0500 Subject: [PATCH] link from reference API to source code on GitHub (#289) --- nbsite/shared_conf.py | 68 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/nbsite/shared_conf.py b/nbsite/shared_conf.py index d3cf3aae..5ecefa4b 100644 --- a/nbsite/shared_conf.py +++ b/nbsite/shared_conf.py @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- import datetime +import importlib +import inspect import os import pathlib as _pathlib +import subprocess +import sys import nbsite as _nbsite @@ -47,7 +51,7 @@ def remove_mystnb_static(app): 'sphinx.ext.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', + 'sphinx.ext.linkcode', 'sphinx.ext.inheritance_diagram', 'sphinx_copybutton', ] @@ -127,3 +131,65 @@ def remove_mystnb_static(app): # Ignores: skipping unknown output mime type: application/vnd.holoviews_exec.v0+json "mystnb.unknown_mime_type" ] + +GIT_ROOT_CMD = "git rev-parse --show-toplevel" +GITHUB_BASE_URL = "https://github.com/holoviz/" + +def get_module_object(modname, fullname): + """Retrieve the Python object based on its module and fullname.""" + submod = sys.modules.get(modname) + if submod is None: + return None + obj = submod + for part in fullname.split("."): + obj = getattr(obj, part, None) + if obj is None: + return None + return obj + +def get_source_info(obj): + """Get the source file and line numbers for a Python object.""" + try: + fn = inspect.getsourcefile(inspect.unwrap(obj)) + source, lineno = inspect.getsourcelines(obj) + except (TypeError, OSError): + return None, None, None + return fn, source, lineno + +def get_repo_dir(): + """Get the root directory of the current Git repository.""" + try: + output = subprocess.check_output(GIT_ROOT_CMD.split(" ")).strip().decode('utf-8') + return _pathlib.Path(output).name + except subprocess.CalledProcessError: + raise RuntimeError("Unable to determine the repository directory") + +def linkcode_resolve(domain, info): + """Resolve the link to the source code for a given Python object in documentation.""" + if domain != "py": + return None + + modname = info["module"] + fullname = info["fullname"] + + obj = get_module_object(modname, fullname) + if obj is None: + return None + + fn, source, lineno = get_source_info(obj) + if not fn: + return None + + linespec = f"#L{lineno}-L{lineno + len(source) - 1}" if lineno else "" + + package = get_repo_dir() + + ppath = importlib.__import__(package).__file__ + pver = importlib.__import__(package).__version__ + + fn = os.path.relpath(fn, start=os.path.dirname(ppath)) + + if "+" in pver: + return f"{GITHUB_BASE_URL}{package}/blob/main/{package}/{fn}{linespec}" + else: + return f"{GITHUB_BASE_URL}{package}/blob/v{pver}/{package}/{fn}{linespec}"