Skip to content

Commit 2700ab2

Browse files
committed
btrfs-progs: docs: add document and label reference extensions
Sphinx/RST does not(?) have native support for cross references to labels in specific documents (doc, ref but not both at the same time), also allowing duplicate labels. We have web and manual pages and to share the same text there are common files included from each, defining labels. This leads to warnings and clicking the links ends up in the included document (like ch-volume-management-intro.rst) instead of the parent. As a last resort, implement own role that does the doc and ref in one go. A special directive is used to define a label that is expected to be duplicate (but otherwise it's an ordinary label) and this gets rid of the warning too. Signed-off-by: David Sterba <[email protected]>
1 parent d80dc96 commit 2700ab2

File tree

1 file changed

+78
-0
lines changed

1 file changed

+78
-0
lines changed

Documentation/conf.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
# sys.path.insert(0, os.path.abspath('.'))
1616

1717
import pathlib
18+
from docutils import nodes
19+
from docutils.utils import unescape
20+
from docutils.parsers.rst import Directive, directives
21+
from sphinx.util.nodes import split_explicit_title, set_source_info
1822

1923
# -- Project information -----------------------------------------------------
2024
project = 'BTRFS'
@@ -73,3 +77,77 @@
7377
]
7478

7579
extensions = [ 'sphinx_rtd_theme' ]
80+
81+
# Cross reference with document and label
82+
# Syntax: :docref`Title <rawdocname:label>`
83+
# Backends: html, man, others
84+
# - title is mandatory, for manual page backend will append "in rawdocname" if it's in another document
85+
# - label is not yet validated, can be duplicate in more documents
86+
# - rawdocname is without extension
87+
def role_docref(name, rawtext, text, lineno, inliner, options={}, content=[]):
88+
env = inliner.document.settings.env
89+
text = unescape(text)
90+
has_explicit_title, title, target = split_explicit_title(text)
91+
if not has_explicit_title:
92+
msg = inliner.reporter.error(f"docref requires title: {rawtext}", line=lineno)
93+
prb = inliner.problematic(rawtext, rawtext, msg)
94+
return [prb], [msg]
95+
96+
try:
97+
docname, label = target.split(':', 1)
98+
except ValueError:
99+
msg = inliner.reporter.error(f"invalid docref syntax {target}", line=lineno)
100+
prb = inliner.problematic(rawtext, rawtext, msg)
101+
return [prb], [msg]
102+
103+
# inliner.reporter.warning(f"DBG: docname={docname} label={label} env.docname={env.docname} title={title}")
104+
105+
# Validate doc
106+
if docname not in env.found_docs:
107+
docs = list(env.found_docs)
108+
msg = inliner.reporter.error(f"document not found {docname} (%s" % (docs), line=lineno)
109+
prb = inliner.problematic(rawtext, rawtext, msg)
110+
return [prb], [msg]
111+
112+
# TODO: validate label
113+
114+
suffix = ''
115+
if env.app.builder.name == 'html':
116+
suffix = '.html'
117+
elif env.app.builder.name == 'man':
118+
suffix = '//'
119+
120+
titlesuffix = ''
121+
if docname != env.docname:
122+
titlesuffix = f" (in {docname})"
123+
124+
try:
125+
ref_node = nodes.reference(rawtext, title + titlesuffix,
126+
refuri=f"{docname}{suffix}#{label}", **options)
127+
except ValueError:
128+
msg = inliner.reporter.error('invalid cross reference %r' % text, line=lineno)
129+
prb = inliner.problematic(rawtext, rawtext, msg)
130+
return [prb], [msg]
131+
return [ref_node], []
132+
133+
# Directive to define a label that can appear multiple time (e.g. from an included
134+
# document), no warnings
135+
# Must be used in connection with :docref: to link to the containing rather than
136+
# included document
137+
# Syntax: .. duplabel:: label-name
138+
# Backends: all
139+
class DupLabelDirective(Directive):
140+
required_arguments = 1
141+
142+
def run(self):
143+
label = self.arguments[0]
144+
target_node = nodes.target('', '', ids=[label])
145+
env = self.state.document.settings.env
146+
line_number = self.state.document.current_line
147+
env.domaindata['std']['labels'][label] = (env.docname, label, line_number)
148+
set_source_info(self, target_node)
149+
return [target_node]
150+
151+
def setup(app):
152+
app.add_role('docref', role_docref)
153+
app.add_directive('duplabel', DupLabelDirective)

0 commit comments

Comments
 (0)