Allow custom targets in the manpage role (#11825)

Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
This commit is contained in:
Nicolas Peugnet
2024-01-17 02:09:18 +01:00
committed by GitHub
parent 95fb0e5e57
commit 59cf4d4c35
6 changed files with 48 additions and 36 deletions

View File

@@ -175,6 +175,10 @@ different style:
``:manpage:`ls(1)``` displays :manpage:`ls(1)`. Creates a hyperlink to an
external site rendering the manpage if :confval:`manpages_url` is defined.
.. versionchanged:: 7.3
Allow specifying a target with ``<>``, like hyperlinks.
For example, ``:manpage:`blah <ls(1)>``` displays :manpage:`blah <ls(1)>`.
.. rst:role:: menuselection
Menu selections should be marked using the ``menuselection`` role. This is

View File

@@ -31,7 +31,6 @@ generic_docroles = {
'kbd': nodes.literal,
'mailheader': addnodes.literal_emphasis,
'makevar': addnodes.literal_strong,
'manpage': addnodes.manpage,
'mimetype': addnodes.literal_emphasis,
'newsgroup': addnodes.literal_emphasis,
'program': addnodes.literal_strong, # XXX should be an x-ref
@@ -342,6 +341,29 @@ class Abbreviation(SphinxRole):
return [nodes.abbreviation(self.rawtext, text, **options)], []
class Manpage(ReferenceRole):
_manpage_re = re.compile(r'^(?P<path>(?P<page>.+)[(.](?P<section>[1-9]\w*)?\)?)$')
def run(self) -> tuple[list[Node], list[system_message]]:
manpage = ws_re.sub(' ', self.target)
if m := self._manpage_re.match(manpage):
info = m.groupdict()
else:
info = {'path': manpage, 'page': manpage, 'section': ''}
inner: nodes.Node
text = self.title[1:] if self.disabled else self.title
if not self.disabled and self.config.manpages_url:
uri = self.config.manpages_url.format(**info)
inner = nodes.reference('', text, classes=[self.name], refuri=uri)
else:
inner = nodes.Text(text)
node = addnodes.manpage(self.rawtext, '', inner,
classes=[self.name], **info)
return [node], []
# Sphinx provides the `code-block` directive for highlighting code blocks.
# Docutils provides the `code` role which in theory can be used similarly by
# defining a custom role for a given programming language:
@@ -408,6 +430,7 @@ specific_docroles: dict[str, RoleFunction] = {
'file': EmphasizedLiteral(),
'samp': EmphasizedLiteral(),
'abbr': Abbreviation(),
'manpage': Manpage(),
}

View File

@@ -397,24 +397,6 @@ class DoctreeReadEvent(SphinxTransform):
self.app.emit('doctree-read', self.document)
class ManpageLink(SphinxTransform):
"""Find manpage section numbers and names"""
default_priority = 999
def apply(self, **kwargs: Any) -> None:
for node in self.document.findall(addnodes.manpage):
manpage = ' '.join(str(x) for x in node.children if isinstance(x, nodes.Text))
pattern = r'^(?P<path>(?P<page>.+)[\(\.](?P<section>[1-9]\w*)?\)?)$'
info = {'path': manpage,
'page': manpage,
'section': ''}
r = re.match(pattern, manpage)
if r:
info = r.groupdict()
node.attributes.update(info)
class GlossarySorter(SphinxTransform):
"""Sort glossaries that have the ``sorted`` flag."""
@@ -520,7 +502,6 @@ def setup(app: Sphinx) -> dict[str, Any]:
app.add_transform(UnreferencedFootnotesDetector)
app.add_transform(SphinxSmartQuotes)
app.add_transform(DoctreeReadEvent)
app.add_transform(ManpageLink)
app.add_transform(GlossarySorter)
app.add_transform(ReorderConsecutiveTargetAndIndexNodes)

View File

@@ -846,13 +846,8 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
def visit_manpage(self, node: Element) -> None:
self.visit_literal_emphasis(node)
if self.manpages_url:
node['refuri'] = self.manpages_url.format(**node.attributes)
self.visit_reference(node)
def depart_manpage(self, node: Element) -> None:
if self.manpages_url:
self.depart_reference(node)
self.depart_literal_emphasis(node)
# overwritten to add even/odd classes

View File

@@ -1,3 +1,5 @@
* :manpage:`man(1)`
* :manpage:`ls.1`
* :manpage:`sphinx`
* :manpage:`mailx(1) <bsd-mailx/mailx.1>`
* :manpage:`!man(1)`

View File

@@ -1492,18 +1492,25 @@ def test_html_sidebar(app, status, warning):
assert ctx['sidebars'] == []
@pytest.mark.parametrize(("fname", "expect"), flat_dict({
'index.html': [(".//em/a[@href='https://example.com/man.1']", "", True),
(".//em/a[@href='https://example.com/ls.1']", "", True),
(".//em/a[@href='https://example.com/sphinx.']", "", True)],
@pytest.mark.sphinx('html', testroot='manpage_url',
confoverrides={'manpages_url': 'https://example.com/{page}.{section}'})
def test_html_manpage(app, cached_etree_parse):
app.build(force_all=True)
}))
@pytest.mark.sphinx('html', testroot='manpage_url', confoverrides={
'manpages_url': 'https://example.com/{page}.{section}'})
@pytest.mark.test_params(shared_result='test_build_html_manpage_url')
def test_html_manpage(app, cached_etree_parse, fname, expect):
app.build()
check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect)
content = (app.outdir / 'index.html').read_text(encoding='utf8')
assert ('<em class="manpage">'
'<a class="manpage reference external" href="https://example.com/man.1">man(1)</a>'
'</em>') in content
assert ('<em class="manpage">'
'<a class="manpage reference external" href="https://example.com/ls.1">ls.1</a>'
'</em>') in content
assert ('<em class="manpage">'
'<a class="manpage reference external" href="https://example.com/sphinx.">sphinx</a>'
'</em>') in content
assert ('<em class="manpage">'
'<a class="manpage reference external" href="https://example.com/bsd-mailx/mailx.1">mailx(1)</a>'
'</em>') in content
assert '<em class="manpage">man(1)</em>' in content
@pytest.mark.sphinx('html', testroot='toctree-glob',