mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
add link to manpages in HTML builder
It is useful to have the HTML documentation builder actually link to real rendered versions of HTML manpages in its output. That way people can click on manpages to get the full documentation. There are a few services offering this online, so we do not explicitly enable one by default, but the Debian manpages repository has a lot of the manpages pre-rendered, so it is used as an example in the documentation. The parsing work is done by a transformer class that parses manpage objects and extract name/section elements. Those then can be used by writers to cross-reference to actual sites. An implementation is done in the two HTML writers, but could also apply to ePUB/PDF writers as well in the future. This is not enabled by default: the `manpages_url` configuration item needs to be enabled to point to the chosen site. The `page`, `section` and `path` parameters are expanded through Python string formatting in the URL on output. Unit tests are fairly limited, but should cover most common use-cases.
This commit is contained in:
parent
161f269202
commit
90f7c7ef3f
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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'),
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
5
tests/roots/test-manpage_url/conf.py
Normal file
5
tests/roots/test-manpage_url/conf.py
Normal file
@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
master_doc = 'index'
|
||||
html_theme = 'classic'
|
||||
exclude_patterns = ['_build']
|
3
tests/roots/test-manpage_url/index.rst
Normal file
3
tests/roots/test-manpage_url/index.rst
Normal file
@ -0,0 +1,3 @@
|
||||
* :manpage:`man(1)`
|
||||
* :manpage:`ls.1`
|
||||
* :manpage:`sphinx`
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user