Merge pull request #4235 from anarcat/manpage-links

add link to manpages in HTML builder
This commit is contained in:
Takeshi KOMIYA 2018-01-13 14:28:11 +09:00 committed by GitHub
commit 7e365d97ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 77 additions and 4 deletions

View File

@ -293,6 +293,24 @@ General configuration
.. versionadded:: 1.3
.. confval:: manpages_url
A URL to cross-reference :rst:role:`manpage` directives. If this is
defined to ``https://manpages.debian.org/{path}``, the
:literal:`:manpage:`man(1)`` role will like to
<https://manpages.debian.org/man(1)>. The patterns available are:
* ``page`` - the manual page (``man``)
* ``section`` - the manual section (``1``)
* ``path`` - the original manual page and section specified (``man(1)``)
This also supports manpages specified as ``man.1``.
.. note:: This currently affects only HTML writers but could be
expanded in the future.
.. versionadded:: 1.7
.. confval:: nitpicky
If true, Sphinx will warn about *all* references where the target cannot be

View File

@ -355,7 +355,8 @@ in a different style:
.. rst:role:: manpage
A reference to a Unix manual page including the section,
e.g. ``:manpage:`ls(1)```.
e.g. ``:manpage:`ls(1)```. Creates a hyperlink to an external site
rendering the manpage if :confval:`manpages_url` is defined.
.. rst:role:: menuselection

View File

@ -125,6 +125,7 @@ class Config(object):
primary_domain = ('py', 'env', [NoneType]),
needs_sphinx = (None, None, string_classes),
needs_extensions = ({}, None),
manpages_url = (None, 'env'),
nitpicky = (False, None),
nitpick_ignore = ([], None),
numfig = (False, 'env'),

View File

@ -23,7 +23,7 @@ from sphinx.transforms import (
ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences,
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, SortIds,
AutoNumbering, AutoIndexUpgrader, FilterSystemMessages,
UnreferencedFootnotesDetector, SphinxSmartQuotes
UnreferencedFootnotesDetector, SphinxSmartQuotes, ManpageLink
)
from sphinx.transforms.compact_bullet_list import RefOnlyBulletListTransform
from sphinx.transforms.i18n import (
@ -80,7 +80,7 @@ class SphinxStandaloneReader(SphinxBaseReader):
Locale, CitationReferences, DefaultSubstitutions, MoveModuleTargets,
HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds,
RemoveTranslatableInline, PreserveTranslatableMessages, FilterSystemMessages,
RefOnlyBulletListTransform, UnreferencedFootnotesDetector
RefOnlyBulletListTransform, UnreferencedFootnotesDetector, ManpageLink
] # type: List[Transform]
def __init__(self, app, *args, **kwargs):
@ -110,7 +110,7 @@ class SphinxI18nReader(SphinxBaseReader):
DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks,
AutoNumbering, SortIds, RemoveTranslatableInline,
FilterSystemMessages, RefOnlyBulletListTransform,
UnreferencedFootnotesDetector]
UnreferencedFootnotesDetector, ManpageLink]
def set_lineno_for_reporter(self, lineno):
# type: (int) -> None

View File

@ -9,6 +9,8 @@
:license: BSD, see LICENSE for details.
"""
import re
from docutils import nodes
from docutils.transforms import Transform, Transformer
from docutils.transforms.parts import ContentsFilter
@ -348,3 +350,21 @@ class SphinxSmartQuotes(SmartQuotes):
for txtnode in txtnodes:
notsmartquotable = not is_smartquotable(txtnode)
yield (texttype[notsmartquotable], txtnode.astext())
class ManpageLink(SphinxTransform):
"""Find manpage section numbers and names"""
default_priority = 999
def apply(self):
for node in self.document.traverse(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*)?\)?)$' # noqa
info = {'path': manpage,
'page': manpage,
'section': ''}
r = re.match(pattern, manpage)
if r:
info = r.groupdict()
node.attributes.update(info)

View File

@ -79,6 +79,7 @@ class HTMLTranslator(BaseTranslator):
self.highlightopts = builder.config.highlight_options
self.highlightlinenothreshold = sys.maxsize
self.docnames = [builder.current_docname] # for singlehtml builder
self.manpages_url = builder.config.manpages_url
self.protect_literal_text = 0
self.permalink_text = builder.config.html_add_permalinks
# support backwards-compatible setting to a bool
@ -816,9 +817,14 @@ class HTMLTranslator(BaseTranslator):
def visit_manpage(self, node):
# type: (nodes.Node) -> 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):
# type: (nodes.Node) -> None
if self.manpages_url:
self.depart_reference(node)
self.depart_literal_emphasis(node)
# overwritten to add even/odd classes

View File

@ -49,6 +49,7 @@ class HTML5Translator(BaseTranslator):
self.highlightopts = builder.config.highlight_options
self.highlightlinenothreshold = sys.maxsize
self.docnames = [builder.current_docname] # for singlehtml builder
self.manpages_url = builder.config.manpages_url
self.protect_literal_text = 0
self.permalink_text = builder.config.html_add_permalinks
# support backwards-compatible setting to a bool
@ -758,9 +759,14 @@ class HTML5Translator(BaseTranslator):
def visit_manpage(self, node):
# type: (nodes.Node) -> None
self.visit_literal_emphasis(node)
if self.manpages_url:
node['refuri'] = self.manpages_url.format(**dict(node))
self.visit_reference(node)
def depart_manpage(self, node):
# type: (nodes.Node) -> None
if self.manpages_url:
self.depart_reference(node)
self.depart_literal_emphasis(node)
# overwritten to add even/odd classes

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
master_doc = 'index'
html_theme = 'classic'
exclude_patterns = ['_build']

View File

@ -0,0 +1,3 @@
* :manpage:`man(1)`
* :manpage:`ls.1`
* :manpage:`sphinx`

View File

@ -1243,3 +1243,16 @@ def test_html_sidebar(app, status, warning):
assert '<h3>Related Topics</h3>' not in result
assert '<h3>This Page</h3>' not in result
assert '<h3>Quick search</h3>' not in result
@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}'})
@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)