|
10 | 10 | # add these directories to sys.path here. If the directory is relative to the |
11 | 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. |
12 | 12 | # |
| 13 | +import inspect |
13 | 14 | import os |
14 | 15 | import re |
15 | 16 | import sys |
| 17 | +from os.path import dirname, relpath |
16 | 18 |
|
17 | 19 | import git |
18 | 20 |
|
|
46 | 48 | 'sphinx.ext.autodoc', |
47 | 49 | 'sphinx.ext.autosummary', |
48 | 50 | 'sphinx.ext.napoleon', |
| 51 | + 'sphinx.ext.linkcode', |
49 | 52 | ] |
50 | 53 |
|
51 | 54 | # Add any paths that contain templates here, relative to this directory. |
|
157 | 160 | settings_source_path = str(root) |
158 | 161 | settings_repo_url = "https://github.com/openedx/openedx-events" |
159 | 162 | settings_repo_version = openedx_event_version |
| 163 | +openedxevents_repo_url = settings_repo_url |
| 164 | + |
| 165 | +# Linkcode Extension Configuration |
| 166 | + |
| 167 | +REPO_URL = "https://github.com/openedx/openedx-events/blob/main" |
| 168 | + |
| 169 | +def linkcode_resolve(domain: str, info: dict[str, str]) -> str | None: |
| 170 | + """ |
| 171 | + Resolves source code links for Python objects in Sphinx documentation. |
| 172 | + This function is based on the `linkcode_resolve` function in the SciPy project. |
| 173 | +
|
| 174 | + Args: |
| 175 | + domain (str): The language domain of the object. Only processes Python objects ('py') |
| 176 | + info (dict[str, str]): Dictionary containing information about the object to link. |
| 177 | + Must contain: |
| 178 | + - 'module': Name of the module containing the object |
| 179 | + - 'fullname': Complete name of the object including its path |
| 180 | +
|
| 181 | + Returns: |
| 182 | + str | None: URL to the source code on GitHub with specific line numbers, |
| 183 | + or None if the link cannot be resolved |
| 184 | + """ |
| 185 | + if domain != "py": |
| 186 | + return None |
| 187 | + |
| 188 | + modname = info["module"] |
| 189 | + fullname = info["fullname"] |
| 190 | + |
| 191 | + submod = sys.modules.get(modname) |
| 192 | + if submod is None: |
| 193 | + return None |
| 194 | + |
| 195 | + obj = submod |
| 196 | + for part in fullname.split("."): |
| 197 | + try: |
| 198 | + obj = getattr(obj, part) |
| 199 | + except Exception: |
| 200 | + return None |
| 201 | + |
| 202 | + # Use the original function object if it is wrapped. |
| 203 | + while hasattr(obj, "__wrapped__"): |
| 204 | + obj = obj.__wrapped__ |
| 205 | + |
| 206 | + # Get the file path where the object is defined |
| 207 | + try: |
| 208 | + # Try to get the file path of the object directly |
| 209 | + file_path = inspect.getsourcefile(obj) |
| 210 | + except Exception: |
| 211 | + try: |
| 212 | + # If that fails, try to get the file path of the module where the object is defined |
| 213 | + file_path = inspect.getsourcefile(sys.modules[obj.__module__]) |
| 214 | + except Exception: |
| 215 | + # If both attempts fail, set file_path to None |
| 216 | + file_path = None |
| 217 | + if not file_path: |
| 218 | + return None |
| 219 | + |
| 220 | + try: |
| 221 | + source, start_line = inspect.getsourcelines(obj) |
| 222 | + except Exception: |
| 223 | + start_line = None |
| 224 | + |
| 225 | + if start_line: |
| 226 | + linespec = f"#L{start_line}-L{start_line + len(source) - 1}" |
| 227 | + else: |
| 228 | + linespec = "" |
| 229 | + |
| 230 | + import openedx_events |
| 231 | + |
| 232 | + start_dir = os.path.abspath(os.path.join(dirname(openedx_events.__file__), "..")) |
| 233 | + file_path = relpath(file_path, start=start_dir).replace(os.path.sep, "/") |
| 234 | + |
| 235 | + return f"{REPO_URL}/{file_path}{linespec}" |
0 commit comments