Skip to content

Commit 7ea3813

Browse files
committed
Documentation SEO improvements:
- Switch from Sphinx's `viewcode` to `linkcode` extension. This stops publishing another copy of PyRTL's source, which was confusing search engines. Instead, we link to the canonical source on GitHub. - Set `html_baseurl` so we define the HTML meta `canonical` tag. This helps search engines identify the latest version of each documentation page as the canonical reference, instead of choosing older versions.
1 parent 59bea7d commit 7ea3813

2 files changed

Lines changed: 143 additions & 3 deletions

File tree

docs/conf.py

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@
1010
# add these directories to sys.path here. If the directory is relative to the
1111
# documentation root, use os.path.abspath to make it absolute, like shown here.
1212
#
13+
import inspect
1314
import os
15+
import subprocess
1416
import sys
1517

18+
import pyrtl
19+
1620
sys.path.insert(0, os.path.abspath(".."))
1721

1822

1923
# -- Project information -----------------------------------------------------
2024

2125
project = "PyRTL"
22-
copyright = "2025, Timothy Sherwood"
26+
copyright = "2026, Timothy Sherwood"
2327
author = "Timothy Sherwood"
2428

2529
# -- General configuration ---------------------------------------------------
@@ -33,7 +37,7 @@
3337
"sphinx.ext.autodoc",
3438
"sphinx.ext.inheritance_diagram",
3539
"sphinx.ext.intersphinx",
36-
"sphinx.ext.viewcode",
40+
"sphinx.ext.linkcode",
3741
"sphinx_autodoc_typehints",
3842
"sphinx_copybutton",
3943
]
@@ -68,6 +72,7 @@
6872
# The theme to use for HTML and HTML Help pages. See the documentation for
6973
# a list of builtin themes.
7074
#
75+
html_baseurl = "https://pyrtl.readthedocs.io/en/latest"
7176
html_theme = "furo"
7277
html_theme_options = {
7378
"sidebar_hide_name": True,
@@ -96,3 +101,138 @@
96101
inheritance_graph_attrs = {
97102
"bgcolor": "aliceblue",
98103
}
104+
105+
# -- linkcode_resolve --------------------------------------------------------
106+
107+
# linkcode generates GitHub "[source]" links for documentation. linkcode_resolve returns
108+
# a GitHub link for a documented item (class, method, object, etc).
109+
#
110+
# Based on https://gist.github.com/rainbowphysics/505e35a7a1e9545d5a6cde22f6ca9558, with
111+
# some hacks to support PyRTL's top-level globals `conditional_assignment` and
112+
# `otherwise`.
113+
114+
# Link to your GitHub repo here:
115+
REPO_LINK = "https://github.com/UCSBarchlab/PyRTL"
116+
# Specify main branch
117+
MAIN_BRANCH = "development"
118+
119+
120+
def run_git_command(cmd: str) -> str | None:
121+
try:
122+
# Run command and get the output
123+
output: str = subprocess.check_output(cmd.split()).strip().decode("utf-8")
124+
125+
# In case command failed, return None
126+
if output.startswith("fatal:"):
127+
return None
128+
129+
# Return the raw command output
130+
return output
131+
except subprocess.CalledProcessError:
132+
return None
133+
134+
135+
# lock to current commit number
136+
head_commit = run_git_command("git log -n1 --pretty=%H")
137+
if head_commit is not None:
138+
linkcode_revision = head_commit
139+
140+
# if we are on main's HEAD, use main as reference instead
141+
main_head_commit = run_git_command(
142+
f"git log --first-parent {MAIN_BRANCH} -n1 --pretty=%H"
143+
)
144+
if head_commit == main_head_commit:
145+
linkcode_revision = MAIN_BRANCH
146+
147+
# if we have a tag, use tag as reference
148+
tag = run_git_command(f"git describe --exact-match --tags {linkcode_revision}")
149+
if tag is not None:
150+
linkcode_revision = tag
151+
152+
else:
153+
# If for some reason git command didn't work then default to main branch
154+
linkcode_revision = MAIN_BRANCH
155+
156+
157+
def get_line_range(obj):
158+
source, lineno = inspect.getsourcelines(obj)
159+
return lineno, lineno + len(source) - 1
160+
161+
162+
def get_link_info(
163+
modname: str, fullname: str
164+
) -> tuple[str, tuple[int, int]] | tuple[str, None]:
165+
# Fallback in case git repo is missing or malformed, or another error occurs
166+
fallback = modname.replace(".", "/")
167+
168+
# Get module based on module name
169+
module = sys.modules.get(modname)
170+
if module is None:
171+
return fallback, None
172+
173+
repo_main_folder = run_git_command("git rev-parse --show-toplevel")
174+
if repo_main_folder is None:
175+
return fallback, None
176+
177+
parent_obj = None
178+
obj = module
179+
for part in fullname.split("."):
180+
next_obj = getattr(obj, part, None)
181+
if next_obj is None:
182+
parent_obj = obj
183+
obj = part
184+
else:
185+
parent_obj = obj
186+
obj = next_obj
187+
188+
if isinstance(obj, property):
189+
obj = obj.fget
190+
191+
try:
192+
src_file = inspect.getsourcefile(obj)
193+
except TypeError:
194+
src_file = inspect.getsourcefile(parent_obj)
195+
196+
# Special cases for `pyrtl.conditional_assignment` and `pyrtl.otherwise`.
197+
conditional_assignment = False
198+
if obj is pyrtl.conditional_assignment or obj is pyrtl.otherwise:
199+
conditional_assignment = True
200+
parent_obj = pyrtl.conditional
201+
src_file = inspect.getsourcefile(parent_obj)
202+
203+
filepath = os.path.relpath(src_file, repo_main_folder)
204+
205+
try:
206+
source, lineno = inspect.getsourcelines(obj)
207+
linestart, linestop = lineno, lineno + len(source) - 1
208+
except OSError:
209+
return filepath, None
210+
except TypeError:
211+
source, lineno = inspect.getsourcelines(parent_obj)
212+
found_in_source = False
213+
for idx, line in enumerate(source):
214+
if line.lstrip().startswith(fullname.split(".")[-1]):
215+
linestart = lineno + idx
216+
if conditional_assignment:
217+
linestart = linestart + 1
218+
linestop = linestart
219+
found_in_source = True
220+
break
221+
222+
if not found_in_source:
223+
return filepath, None
224+
225+
return filepath, (linestart, linestop)
226+
227+
228+
def linkcode_resolve(domain, info):
229+
if domain != "py" or not info["module"]:
230+
return None
231+
232+
filepath, linenos = get_link_info(info["module"], info["fullname"])
233+
result = f"{REPO_LINK}/blob/{linkcode_revision}/{filepath}"
234+
if linenos is not None:
235+
linestart, linestop = linenos
236+
result += f"#L{linestart}-L{linestop}"
237+
238+
return result

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.. meta::
2-
:google-site-verification: GwI57TNdOrpPLp4ZORJicRtCIkUzt9lS472n3MRTMfs
2+
:google-site-verification: sO_rsKD1QKb6nFywsuLnRDiz8Ekep-jVNpBDMm65wQc
33

44
=====
55
PYRTL

0 commit comments

Comments
 (0)