diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index eeca858ca..300fc9c19 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -23,7 +23,7 @@ from sphinx.roles import XRefRole from sphinx.locale import l_, _ from sphinx.domains import Domain, ObjType from sphinx.directives import ObjectDescription -from sphinx.util import ws_re, logging +from sphinx.util import ws_re, logging, docname_join from sphinx.util.nodes import clean_astext, make_refnode if False: @@ -465,6 +465,7 @@ class StandardDomain(Domain): searchprio=-1), 'envvar': ObjType(l_('environment variable'), 'envvar'), 'cmdoption': ObjType(l_('program option'), 'option'), + 'doc': ObjType(l_('document'), 'doc', searchprio=-1) } # type: Dict[unicode, ObjType] directives = { @@ -491,6 +492,8 @@ class StandardDomain(Domain): warn_dangling=True), # links to labels, without a different title 'keyword': XRefRole(warn_dangling=True), + # links to documents + 'doc': XRefRole(warn_dangling=True, innernodeclass=nodes.inline), } # type: Dict[unicode, Union[RoleFunction, XRefRole]] initial_data = { @@ -515,6 +518,7 @@ class StandardDomain(Domain): 'the label must precede a section header)', 'numref': 'undefined label: %(target)s', 'keyword': 'unknown keyword: %(target)s', + 'doc': 'unknown document: %(target)s', 'option': 'unknown option: %(target)s', 'citation': 'citation not found: %(target)s', } @@ -650,6 +654,8 @@ class StandardDomain(Domain): resolver = self._resolve_numref_xref elif typ == 'keyword': resolver = self._resolve_keyword_xref + elif typ == 'doc': + resolver = self._resolve_doc_xref elif typ == 'option': resolver = self._resolve_option_xref elif typ == 'citation': @@ -747,6 +753,22 @@ class StandardDomain(Domain): return make_refnode(builder, fromdocname, docname, labelid, contnode) + def _resolve_doc_xref(self, env, fromdocname, builder, typ, target, node, contnode): + # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA + # directly reference to document by source name; can be absolute or relative + refdoc = node.get('refdoc', fromdocname) + docname = docname_join(refdoc, node['reftarget']) + if docname not in env.all_docs: + return None + else: + if node['refexplicit']: + # reference with explicit title + caption = node.astext() + else: + caption = clean_astext(env.titles[docname]) + innernode = nodes.inline(caption, caption, classes=['doc']) + return make_refnode(builder, fromdocname, docname, None, innernode) + def _resolve_option_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA progname = node.get('std:program') diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 17fafe8d2..2140193ef 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -34,9 +34,8 @@ from docutils.frontend import OptionParser from sphinx import addnodes from sphinx.io import SphinxStandaloneReader, SphinxDummyWriter, SphinxFileInput from sphinx.util import logging -from sphinx.util import get_matching_docs, docname_join, FilenameUniqDict, status_iterator -from sphinx.util.nodes import clean_astext, WarningStream, is_translatable, \ - process_only_nodes +from sphinx.util import get_matching_docs, FilenameUniqDict, status_iterator +from sphinx.util.nodes import WarningStream, is_translatable, process_only_nodes from sphinx.util.osutil import SEP, ensuredir from sphinx.util.i18n import find_catalog_files from sphinx.util.console import bold # type: ignore @@ -923,8 +922,6 @@ class BuildEnvironment(object): # really hardwired reference types elif typ == 'any': newnode = self._resolve_any_reference(builder, refdoc, node, contnode) - elif typ == 'doc': - newnode = self._resolve_doc_reference(builder, refdoc, node, contnode) # no new node found? try the missing-reference event if newnode is None: newnode = builder.app.emit_firstresult( @@ -960,8 +957,6 @@ class BuildEnvironment(object): return if domain and typ in domain.dangling_warnings: msg = domain.dangling_warnings[typ] - elif typ == 'doc': - msg = 'unknown document: %(target)s' elif node.get('refdomain', 'std') not in ('', 'std'): msg = '%s:%s reference target not found: %%(target)s' % \ (node['refdomain'], typ) @@ -970,31 +965,14 @@ class BuildEnvironment(object): logger.warning(msg % {'target': target}, location=node, type='ref', subtype=typ) - def _resolve_doc_reference(self, builder, refdoc, node, contnode): - # type: (Builder, unicode, nodes.Node, nodes.Node) -> nodes.Node - # directly reference to document by source name; - # can be absolute or relative - docname = docname_join(refdoc, node['reftarget']) - if docname in self.all_docs: - if node['refexplicit']: - # reference with explicit title - caption = node.astext() - else: - caption = clean_astext(self.titles[docname]) - innernode = nodes.inline(caption, caption) - innernode['classes'].append('doc') - newnode = nodes.reference('', '', internal=True) - newnode['refuri'] = builder.get_relative_uri(refdoc, docname) - newnode.append(innernode) - return newnode - def _resolve_any_reference(self, builder, refdoc, node, contnode): # type: (Builder, unicode, nodes.Node, nodes.Node) -> nodes.Node """Resolve reference generated by the "any" role.""" target = node['reftarget'] results = [] # type: List[Tuple[unicode, nodes.Node]] # first, try resolving as :doc: - doc_ref = self._resolve_doc_reference(builder, refdoc, node, contnode) + doc_ref = self.domains['std'].resolve_xref(self, refdoc, builder, 'doc', + target, node, contnode) if doc_ref: results.append(('doc', doc_ref)) # next, do the standard domain (makes this a priority) diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 884778cbc..d1be888ba 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -338,9 +338,6 @@ def missing_reference(app, env, node, contnode): for domain in env.domains.values() for objtype in domain.object_types] domain = None - elif node['reftype'] == 'doc': - domain = 'std' # special case - objtypes = ['std:doc'] else: domain = node.get('refdomain') if not domain: diff --git a/sphinx/roles.py b/sphinx/roles.py index 6723b0122..32894a8e7 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -323,8 +323,6 @@ def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): specific_docroles = { # links to download references 'download': XRefRole(nodeclass=addnodes.download_reference), - # links to documents - 'doc': XRefRole(warn_dangling=True, innernodeclass=nodes.inline), # links to anything 'any': AnyXRefRole(warn_dangling=True), diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index f5091df99..17584e7c9 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -325,11 +325,14 @@ def make_refnode(builder, fromdocname, todocname, targetid, child, title=None): # type: (Builder, unicode, unicode, unicode, nodes.Node, unicode) -> nodes.reference """Shortcut to create a reference node.""" node = nodes.reference('', '', internal=True) - if fromdocname == todocname: + if fromdocname == todocname and targetid: node['refid'] = targetid else: - node['refuri'] = (builder.get_relative_uri(fromdocname, todocname) + - '#' + targetid) + if targetid: + node['refuri'] = (builder.get_relative_uri(fromdocname, todocname) + + '#' + targetid) + else: + node['refuri'] = builder.get_relative_uri(fromdocname, todocname) if title: node['reftitle'] = title node.append(child) diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index 934b8a2bd..4d028c151 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -43,6 +43,7 @@ module2 py:module 0 foo.html#module-$ - module1.func py:function 1 sub/foo.html#$ - CFunc c:function 2 cfunc.html#CFunc - a term std:term -1 glossary.html#term-a-term - +docname std:doc -1 docname.html - a term including:colon std:term -1 glossary.html#term-a-term-including-colon - '''.encode('utf-8')) @@ -212,6 +213,10 @@ def test_missing_reference(tempdir, app, status, warning): rn = reference_check('py', 'mod', 'py3krelparent:module1', 'foo', refdoc='sub/dir/test') assert rn['refuri'] == '../../../../py3k/foo.html#module-module1' + # check refs of standard domain + rn = reference_check('std', 'doc', 'docname', 'docname') + assert rn['refuri'] == 'https://docs.python.org/docname.html' + def test_load_mappings_warnings(tempdir, app, status, warning): """