mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Fix #8704: viewcode: anchors are generated in incremental build
The anchors for viewcode was generated in the reading phase only if supported builder is used. It causes anchors are missing on the incremental build after the build for non supported builder. This introduces `viewcode_anchor` node to insert the anchor even if non supported builders. They will be converted to the anchor tag in the resolving phase for supported builders. Or, they will be removed for non supported builders.
This commit is contained in:
parent
d5d072bc3a
commit
5260143afe
2
CHANGES
2
CHANGES
@ -10,6 +10,7 @@ Incompatible changes
|
|||||||
Deprecated
|
Deprecated
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
* pending_xref node for viewcode extension
|
||||||
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.broken``
|
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.broken``
|
||||||
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.good``
|
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.good``
|
||||||
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.redirected``
|
* ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.redirected``
|
||||||
@ -69,6 +70,7 @@ Bugs fixed
|
|||||||
* #8094: texinfo: image files on the different directory with document are not
|
* #8094: texinfo: image files on the different directory with document are not
|
||||||
copied
|
copied
|
||||||
* #8720: viewcode: module pages are generated for epub on incremental build
|
* #8720: viewcode: module pages are generated for epub on incremental build
|
||||||
|
* #8704: viewcode: anchors are generated in incremental build after singlehtml
|
||||||
* #8671: :confval:`highlight_options` is not working
|
* #8671: :confval:`highlight_options` is not working
|
||||||
* #8341: C, fix intersphinx lookup types for names in declarations.
|
* #8341: C, fix intersphinx lookup types for names in declarations.
|
||||||
* C, C++: in general fix intersphinx and role lookup types.
|
* C, C++: in general fix intersphinx and role lookup types.
|
||||||
|
@ -26,6 +26,11 @@ The following is a list of deprecated interfaces.
|
|||||||
- (will be) Removed
|
- (will be) Removed
|
||||||
- Alternatives
|
- Alternatives
|
||||||
|
|
||||||
|
* - pending_xref node for viewcode extension
|
||||||
|
- 3.5
|
||||||
|
- 5.0
|
||||||
|
- ``sphinx.ext.viewcode.viewcode_anchor``
|
||||||
|
|
||||||
* - ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.broken``
|
* - ``sphinx.builders.linkcheck.CheckExternalLinksBuilder.broken``
|
||||||
- 3.5
|
- 3.5
|
||||||
- 5.0
|
- 5.0
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
import posixpath
|
import posixpath
|
||||||
import traceback
|
import traceback
|
||||||
|
import warnings
|
||||||
from os import path
|
from os import path
|
||||||
from typing import Any, Dict, Generator, Iterable, Optional, Set, Tuple, cast
|
from typing import Any, Dict, Generator, Iterable, Optional, Set, Tuple, cast
|
||||||
|
|
||||||
@ -19,10 +20,13 @@ from docutils.nodes import Element, Node
|
|||||||
import sphinx
|
import sphinx
|
||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
from sphinx.application import Sphinx
|
from sphinx.application import Sphinx
|
||||||
|
from sphinx.builders import Builder
|
||||||
from sphinx.builders.html import StandaloneHTMLBuilder
|
from sphinx.builders.html import StandaloneHTMLBuilder
|
||||||
|
from sphinx.deprecation import RemovedInSphinx50Warning
|
||||||
from sphinx.environment import BuildEnvironment
|
from sphinx.environment import BuildEnvironment
|
||||||
from sphinx.locale import _, __
|
from sphinx.locale import _, __
|
||||||
from sphinx.pycode import ModuleAnalyzer
|
from sphinx.pycode import ModuleAnalyzer
|
||||||
|
from sphinx.transforms.post_transforms import SphinxPostTransform
|
||||||
from sphinx.util import get_full_modname, logging, status_iterator
|
from sphinx.util import get_full_modname, logging, status_iterator
|
||||||
from sphinx.util.nodes import make_refnode
|
from sphinx.util.nodes import make_refnode
|
||||||
|
|
||||||
@ -32,6 +36,15 @@ logger = logging.getLogger(__name__)
|
|||||||
OUTPUT_DIRNAME = '_modules'
|
OUTPUT_DIRNAME = '_modules'
|
||||||
|
|
||||||
|
|
||||||
|
class viewcode_anchor(Element):
|
||||||
|
"""Node for viewcode anchors.
|
||||||
|
|
||||||
|
This node will be processed in the resolving phase.
|
||||||
|
For viewcode supported builders, they will be all converted to the anchors.
|
||||||
|
For not supported builders, they will be removed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str]:
|
def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str]:
|
||||||
try:
|
try:
|
||||||
return get_full_modname(modname, attribute)
|
return get_full_modname(modname, attribute)
|
||||||
@ -50,14 +63,21 @@ def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_supported_builder(builder: Builder) -> bool:
|
||||||
|
if builder.format != 'html':
|
||||||
|
return False
|
||||||
|
elif builder.name == 'singlehtml':
|
||||||
|
return False
|
||||||
|
elif builder.name.startswith('epub') and not builder.config.viewcode_enable_epub:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def doctree_read(app: Sphinx, doctree: Node) -> None:
|
def doctree_read(app: Sphinx, doctree: Node) -> None:
|
||||||
env = app.builder.env
|
env = app.builder.env
|
||||||
if not hasattr(env, '_viewcode_modules'):
|
if not hasattr(env, '_viewcode_modules'):
|
||||||
env._viewcode_modules = {} # type: ignore
|
env._viewcode_modules = {} # type: ignore
|
||||||
if app.builder.name == "singlehtml":
|
|
||||||
return
|
|
||||||
if app.builder.name.startswith("epub") and not env.config.viewcode_enable_epub:
|
|
||||||
return
|
|
||||||
|
|
||||||
def has_tag(modname: str, fullname: str, docname: str, refname: str) -> bool:
|
def has_tag(modname: str, fullname: str, docname: str, refname: str) -> bool:
|
||||||
entry = env._viewcode_modules.get(modname, None) # type: ignore
|
entry = env._viewcode_modules.get(modname, None) # type: ignore
|
||||||
@ -115,12 +135,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None:
|
|||||||
continue
|
continue
|
||||||
names.add(fullname)
|
names.add(fullname)
|
||||||
pagename = posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))
|
pagename = posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))
|
||||||
inline = nodes.inline('', _('[source]'), classes=['viewcode-link'])
|
signode += viewcode_anchor(reftarget=pagename, refid=fullname, refdoc=env.docname)
|
||||||
onlynode = addnodes.only(expr='html')
|
|
||||||
onlynode += addnodes.pending_xref('', inline, reftype='viewcode', refdomain='std',
|
|
||||||
refexplicit=False, reftarget=pagename,
|
|
||||||
refid=fullname, refdoc=env.docname)
|
|
||||||
signode += onlynode
|
|
||||||
|
|
||||||
|
|
||||||
def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str],
|
def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str],
|
||||||
@ -134,10 +149,34 @@ def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str],
|
|||||||
env._viewcode_modules.update(other._viewcode_modules) # type: ignore
|
env._viewcode_modules.update(other._viewcode_modules) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class ViewcodeAnchorTransform(SphinxPostTransform):
|
||||||
|
"""Convert or remove viewcode_anchor nodes depends on builder."""
|
||||||
|
default_priority = 100
|
||||||
|
|
||||||
|
def run(self, **kwargs: Any) -> None:
|
||||||
|
if is_supported_builder(self.app.builder):
|
||||||
|
self.convert_viewcode_anchors()
|
||||||
|
else:
|
||||||
|
self.remove_viewcode_anchors()
|
||||||
|
|
||||||
|
def convert_viewcode_anchors(self) -> None:
|
||||||
|
for node in self.document.traverse(viewcode_anchor):
|
||||||
|
anchor = nodes.inline('', _('[source]'), classes=['viewcode-link'])
|
||||||
|
refnode = make_refnode(self.app.builder, node['refdoc'], node['reftarget'],
|
||||||
|
node['refid'], anchor)
|
||||||
|
node.replace_self(refnode)
|
||||||
|
|
||||||
|
def remove_viewcode_anchors(self) -> None:
|
||||||
|
for node in self.document.traverse(viewcode_anchor):
|
||||||
|
node.parent.remove(node)
|
||||||
|
|
||||||
|
|
||||||
def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node
|
def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node
|
||||||
) -> Optional[Node]:
|
) -> Optional[Node]:
|
||||||
# resolve our "viewcode" reference nodes -- they need special treatment
|
# resolve our "viewcode" reference nodes -- they need special treatment
|
||||||
if node['reftype'] == 'viewcode':
|
if node['reftype'] == 'viewcode':
|
||||||
|
warnings.warn('viewcode extension is no longer use pending_xref node. '
|
||||||
|
'Please update your extension.', RemovedInSphinx50Warning)
|
||||||
return make_refnode(app.builder, node['refdoc'], node['reftarget'],
|
return make_refnode(app.builder, node['refdoc'], node['reftarget'],
|
||||||
node['refid'], contnode)
|
node['refid'], contnode)
|
||||||
|
|
||||||
@ -182,9 +221,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non
|
|||||||
env = app.builder.env
|
env = app.builder.env
|
||||||
if not hasattr(env, '_viewcode_modules'):
|
if not hasattr(env, '_viewcode_modules'):
|
||||||
return
|
return
|
||||||
if app.builder.name == "singlehtml":
|
if not is_supported_builder(app.builder):
|
||||||
return
|
|
||||||
if app.builder.name.startswith("epub") and not env.config.viewcode_enable_epub:
|
|
||||||
return
|
return
|
||||||
highlighter = app.builder.highlighter # type: ignore
|
highlighter = app.builder.highlighter # type: ignore
|
||||||
urito = app.builder.get_relative_uri
|
urito = app.builder.get_relative_uri
|
||||||
@ -292,6 +329,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
|||||||
# app.add_config_value('viewcode_exclude_modules', [], 'env')
|
# app.add_config_value('viewcode_exclude_modules', [], 'env')
|
||||||
app.add_event('viewcode-find-source')
|
app.add_event('viewcode-find-source')
|
||||||
app.add_event('viewcode-follow-imported')
|
app.add_event('viewcode-follow-imported')
|
||||||
|
app.add_post_transform(ViewcodeAnchorTransform)
|
||||||
return {
|
return {
|
||||||
'version': sphinx.__display_version__,
|
'version': sphinx.__display_version__,
|
||||||
'env_version': 1,
|
'env_version': 1,
|
||||||
|
@ -55,6 +55,9 @@ def test_viewcode_epub_default(app, status, warning):
|
|||||||
|
|
||||||
assert not (app.outdir / '_modules/spam/mod1.xhtml').exists()
|
assert not (app.outdir / '_modules/spam/mod1.xhtml').exists()
|
||||||
|
|
||||||
|
result = (app.outdir / 'index.xhtml').read_text()
|
||||||
|
assert result.count('href="_modules/spam/mod1.xhtml#func1"') == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx('epub', testroot='ext-viewcode',
|
@pytest.mark.sphinx('epub', testroot='ext-viewcode',
|
||||||
confoverrides={'viewcode_enable_epub': True})
|
confoverrides={'viewcode_enable_epub': True})
|
||||||
@ -63,6 +66,9 @@ def test_viewcode_epub_enabled(app, status, warning):
|
|||||||
|
|
||||||
assert (app.outdir / '_modules/spam/mod1.xhtml').exists()
|
assert (app.outdir / '_modules/spam/mod1.xhtml').exists()
|
||||||
|
|
||||||
|
result = (app.outdir / 'index.xhtml').read_text()
|
||||||
|
assert result.count('href="_modules/spam/mod1.xhtml#func1"') == 2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(testroot='ext-viewcode', tags=['test_linkcode'])
|
@pytest.mark.sphinx(testroot='ext-viewcode', tags=['test_linkcode'])
|
||||||
def test_linkcode(app, status, warning):
|
def test_linkcode(app, status, warning):
|
||||||
|
Loading…
Reference in New Issue
Block a user