Skip to content

Commit 59cf4d4

Browse files
n-peugnetAA-Turner
andauthored
Allow custom targets in the manpage role (#11825)
Co-authored-by: Adam Turner <[email protected]>
1 parent 95fb0e5 commit 59cf4d4

File tree

6 files changed

+48
-36
lines changed

6 files changed

+48
-36
lines changed

doc/usage/restructuredtext/roles.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ different style:
175175
``:manpage:`ls(1)``` displays :manpage:`ls(1)`. Creates a hyperlink to an
176176
external site rendering the manpage if :confval:`manpages_url` is defined.
177177

178+
.. versionchanged:: 7.3
179+
Allow specifying a target with ``<>``, like hyperlinks.
180+
For example, ``:manpage:`blah <ls(1)>``` displays :manpage:`blah <ls(1)>`.
181+
178182
.. rst:role:: menuselection
179183
180184
Menu selections should be marked using the ``menuselection`` role. This is

sphinx/roles.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
'kbd': nodes.literal,
3232
'mailheader': addnodes.literal_emphasis,
3333
'makevar': addnodes.literal_strong,
34-
'manpage': addnodes.manpage,
3534
'mimetype': addnodes.literal_emphasis,
3635
'newsgroup': addnodes.literal_emphasis,
3736
'program': addnodes.literal_strong, # XXX should be an x-ref
@@ -342,6 +341,29 @@ def run(self) -> tuple[list[Node], list[system_message]]:
342341
return [nodes.abbreviation(self.rawtext, text, **options)], []
343342

344343

344+
class Manpage(ReferenceRole):
345+
_manpage_re = re.compile(r'^(?P<path>(?P<page>.+)[(.](?P<section>[1-9]\w*)?\)?)$')
346+
347+
def run(self) -> tuple[list[Node], list[system_message]]:
348+
manpage = ws_re.sub(' ', self.target)
349+
if m := self._manpage_re.match(manpage):
350+
info = m.groupdict()
351+
else:
352+
info = {'path': manpage, 'page': manpage, 'section': ''}
353+
354+
inner: nodes.Node
355+
text = self.title[1:] if self.disabled else self.title
356+
if not self.disabled and self.config.manpages_url:
357+
uri = self.config.manpages_url.format(**info)
358+
inner = nodes.reference('', text, classes=[self.name], refuri=uri)
359+
else:
360+
inner = nodes.Text(text)
361+
node = addnodes.manpage(self.rawtext, '', inner,
362+
classes=[self.name], **info)
363+
364+
return [node], []
365+
366+
345367
# Sphinx provides the `code-block` directive for highlighting code blocks.
346368
# Docutils provides the `code` role which in theory can be used similarly by
347369
# defining a custom role for a given programming language:
@@ -408,6 +430,7 @@ def code_role(name: str, rawtext: str, text: str, lineno: int,
408430
'file': EmphasizedLiteral(),
409431
'samp': EmphasizedLiteral(),
410432
'abbr': Abbreviation(),
433+
'manpage': Manpage(),
411434
}
412435

413436

sphinx/transforms/__init__.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -397,24 +397,6 @@ def apply(self, **kwargs: Any) -> None:
397397
self.app.emit('doctree-read', self.document)
398398

399399

400-
class ManpageLink(SphinxTransform):
401-
"""Find manpage section numbers and names"""
402-
403-
default_priority = 999
404-
405-
def apply(self, **kwargs: Any) -> None:
406-
for node in self.document.findall(addnodes.manpage):
407-
manpage = ' '.join(str(x) for x in node.children if isinstance(x, nodes.Text))
408-
pattern = r'^(?P<path>(?P<page>.+)[\(\.](?P<section>[1-9]\w*)?\)?)$'
409-
info = {'path': manpage,
410-
'page': manpage,
411-
'section': ''}
412-
r = re.match(pattern, manpage)
413-
if r:
414-
info = r.groupdict()
415-
node.attributes.update(info)
416-
417-
418400
class GlossarySorter(SphinxTransform):
419401
"""Sort glossaries that have the ``sorted`` flag."""
420402

@@ -520,7 +502,6 @@ def setup(app: Sphinx) -> dict[str, Any]:
520502
app.add_transform(UnreferencedFootnotesDetector)
521503
app.add_transform(SphinxSmartQuotes)
522504
app.add_transform(DoctreeReadEvent)
523-
app.add_transform(ManpageLink)
524505
app.add_transform(GlossarySorter)
525506
app.add_transform(ReorderConsecutiveTargetAndIndexNodes)
526507

sphinx/writers/html5.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -846,13 +846,8 @@ def depart_abbreviation(self, node: Element) -> None:
846846

847847
def visit_manpage(self, node: Element) -> None:
848848
self.visit_literal_emphasis(node)
849-
if self.manpages_url:
850-
node['refuri'] = self.manpages_url.format(**node.attributes)
851-
self.visit_reference(node)
852849

853850
def depart_manpage(self, node: Element) -> None:
854-
if self.manpages_url:
855-
self.depart_reference(node)
856851
self.depart_literal_emphasis(node)
857852

858853
# overwritten to add even/odd classes
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
* :manpage:`man(1)`
22
* :manpage:`ls.1`
33
* :manpage:`sphinx`
4+
* :manpage:`mailx(1) <bsd-mailx/mailx.1>`
5+
* :manpage:`!man(1)`

tests/test_build_html.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,18 +1492,25 @@ def test_html_sidebar(app, status, warning):
14921492
assert ctx['sidebars'] == []
14931493

14941494

1495-
@pytest.mark.parametrize(("fname", "expect"), flat_dict({
1496-
'index.html': [(".//em/a[@href='https://example.com/man.1']", "", True),
1497-
(".//em/a[@href='https://example.com/ls.1']", "", True),
1498-
(".//em/a[@href='https://example.com/sphinx.']", "", True)],
1495+
@pytest.mark.sphinx('html', testroot='manpage_url',
1496+
confoverrides={'manpages_url': 'https://example.com/{page}.{section}'})
1497+
def test_html_manpage(app, cached_etree_parse):
1498+
app.build(force_all=True)
14991499

1500-
}))
1501-
@pytest.mark.sphinx('html', testroot='manpage_url', confoverrides={
1502-
'manpages_url': 'https://example.com/{page}.{section}'})
1503-
@pytest.mark.test_params(shared_result='test_build_html_manpage_url')
1504-
def test_html_manpage(app, cached_etree_parse, fname, expect):
1505-
app.build()
1506-
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)
1500+
content = (app.outdir / 'index.html').read_text(encoding='utf8')
1501+
assert ('<em class="manpage">'
1502+
'<a class="manpage reference external" href="https://example.com/man.1">man(1)</a>'
1503+
'</em>') in content
1504+
assert ('<em class="manpage">'
1505+
'<a class="manpage reference external" href="https://example.com/ls.1">ls.1</a>'
1506+
'</em>') in content
1507+
assert ('<em class="manpage">'
1508+
'<a class="manpage reference external" href="https://example.com/sphinx.">sphinx</a>'
1509+
'</em>') in content
1510+
assert ('<em class="manpage">'
1511+
'<a class="manpage reference external" href="https://example.com/bsd-mailx/mailx.1">mailx(1)</a>'
1512+
'</em>') in content
1513+
assert '<em class="manpage">man(1)</em>' in content
15071514

15081515

15091516
@pytest.mark.sphinx('html', testroot='toctree-glob',

0 commit comments

Comments
 (0)