:py:`path` Path. Equivalent to :py:`key.split('.')`.
:py:`url` URL to the entry documentation,
formatted with `custom URL formatters`_.
+ :py:`css_classes` List of CSS classes to add to the
+ :html:`<a>` tag. Internal entries
+ usually have :py:`['m-doc']` while
+ exteral have :py:`['m-doc-external']`.
=================== =======================================
=================== ===========================================================
- Flat link: :ref-flat:`os.path.join()`
- Link using a default role: `str.partition()`
+When used with the Python doc theme, the :rst:`:ref` can be used also for
+linking to internal types, while external types, classes and enums are also
+linked to from all signatures.
+
.. note-success::
For linking to Doxygen documentation, a similar functionality is provided
enum_entry.object = enum_
enum_entry.path = path
enum_entry.url = '{}#{}'.format(parent_url, state.config['ID_FORMATTER'](EntryType.ENUM, path[-1:]))
+ enum_entry.css_classes = ['m-doc']
enum_entry.values = []
if issubclass(enum_, enum.Enum):
entry.type = EntryType.ENUM_VALUE
entry.path = subpath
entry.url = '{}#{}'.format(parent_url, state.config['ID_FORMATTER'](EntryType.ENUM_VALUE, subpath[-2:]))
+ entry.css_classes = ['m-doc']
state.name_map['.'.join(subpath)] = entry
elif state.config['PYBIND11_COMPATIBILITY']:
entry.type = EntryType.ENUM_VALUE
entry.path = subpath
entry.url = '{}#{}'.format(parent_url, state.config['ID_FORMATTER'](EntryType.ENUM_VALUE, subpath[-2:]))
+ entry.css_classes = ['m-doc']
state.name_map['.'.join(subpath)] = entry
# Add itself to the name map
class_entry.type = EntryType.CLASS
class_entry.object = class_
class_entry.path = path
+ class_entry.css_classes = ['m-doc']
class_entry.url = state.config['URL_FORMATTER'](EntryType.CLASS, path)[1]
class_entry.members = []
entry.object = object
entry.path = subpath
entry.url = '{}#{}'.format(class_entry.url, state.config['ID_FORMATTER'](type, subpath[-1:]))
+ entry.css_classes = ['m-doc']
state.name_map['.'.join(subpath)] = entry
class_entry.members += [name]
module_entry.type = EntryType.MODULE
module_entry.object = module
module_entry.path = path
+ module_entry.css_classes = ['m-doc']
module_entry.url = state.config['URL_FORMATTER'](EntryType.MODULE, path)[1]
module_entry.members = []
entry.object = object
entry.path = subpath
entry.url = '{}#{}'.format(module_entry.url, state.config['ID_FORMATTER'](type, subpath[-1:]))
+ entry.css_classes = ['m-doc']
state.name_map['.'.join(subpath)] = entry
module_entry.members += [name]
entry.object = object
entry.path = subpath
entry.url = '{}#{}'.format(module_entry.url, state.config['ID_FORMATTER'](type, subpath[-1:]))
+ entry.css_classes = ['m-doc']
state.name_map['.'.join(subpath)] = entry
module_entry.members += [name]
relative_name = make_relative_name(state, referrer_path, name)
entry = state.name_map[name]
- return '<a href="{}" class="m-doc">{}</a>'.format(entry.url, relative_name)
+ return '<a href="{}" class="{}">{}</a>'.format(entry.url, ' '.join(entry.css_classes), relative_name)
_pybind_name_rx = re.compile('[a-zA-Z0-9_]*')
_pybind_arg_name_rx = re.compile('[*a-zA-Z0-9_]+')
# side effect of the render is entry.summary (and entry.name for pages)
# being filled.
for entry in state.name_map.values():
+ # If there is no object, the entry is an external reference. Skip
+ # those. Can't do `not entry.object` because that gives ValueError
+ # for numpy ("use a.any() or a.all()")
+ if hasattr(entry, 'object') and entry.object is None: continue
+
if entry.type == EntryType.MODULE:
render_module(state, entry.path, entry.object, env)
elif entry.type == EntryType.CLASS:
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>Type links | My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ Type links
+ </h1>
+<p>External links with correct class: <a class="m-doc-external" href="https://docs.python.org/3/library/typing.html#typing.Tuple">typing.Tuple</a> or <a class="m-doc-external" href="https://docs.python.org/3/library/stdtypes.html#str">str</a> (tested
+extensively in the <code>m.sphinx</code> tests).</p>
+<p>Internal links with <code>m-doc</code> implicit:</p>
+<ul>
+<li>Module: <a class="m-doc" href="inspect_type_links.html">inspect_type_links</a></li>
+<li>Class: <a class="m-doc" href="inspect_type_links.first.Foo.html">inspect_type_links.first.Foo</a></li>
+<li>Function: <a class="m-doc" href="inspect_type_links.first.Foo.html#reference_inner">inspect_type_links.first.Foo.reference_inner()</a><ul>
+<li>without <code>()</code>: <a class="m-doc" href="inspect_type_links.first.Foo.html#reference_inner">inspect_type_links.first.Foo.reference_inner()</a></li>
+</ul>
+</li>
+<li>Property: <a class="m-doc" href="inspect_type_links.second.Foo.html#type_property">inspect_type_links.second.Foo.type_property</a></li>
+<li>Enum: <a class="m-doc" href="inspect_type_links.second.html#Enum">inspect_type_links.second.Enum</a></li>
+<li>Enum value: <a class="m-doc" href="inspect_type_links.second.html#Enum-SECOND">inspect_type_links.second.Enum.SECOND</a></li>
+<li>Data: <a class="m-doc" href="inspect_type_links.second.html#TYPE_DATA">inspect_type_links.second.TYPE_DATA</a></li>
+</ul>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+Type links
+##########
+
+External links with correct class: :ref:`typing.Tuple` or :ref:`str` (tested
+extensively in the ``m.sphinx`` tests).
+
+Internal links with ``m-doc`` implicit:
+
+- Module: :ref:`inspect_type_links`
+- Class: :ref:`inspect_type_links.first.Foo`
+- Function: :ref:`inspect_type_links.first.Foo.reference_inner()`
+
+ - without ``()``: :ref:`inspect_type_links.first.Foo.reference_inner`
+
+- Property: :ref:`inspect_type_links.second.Foo.type_property`
+- Enum: :ref:`inspect_type_links.second.Enum`
+- Enum value: :ref:`inspect_type_links.second.Enum.SECOND`
+- Data: :ref:`inspect_type_links.second.TYPE_DATA`
<h2><a href="#enums">Enums</a></h2>
<dl class="m-doc">
<dt id="Enum">
- <span class="m-doc-wrap-bumper">class <a href="#Enum" class="m-doc-self">Enum</a>(enum.Enum): </span><span class="m-doc-wrap"><a href="#Enum-FIRST" class="m-doc-self" id="Enum-FIRST">FIRST</a> = 1
+ <span class="m-doc-wrap-bumper">class <a href="#Enum" class="m-doc-self">Enum</a>(<a href="https://docs.python.org/3/library/enum.html#enum.Enum" class="m-doc-external">enum.Enum</a>): </span><span class="m-doc-wrap"><a href="#Enum-FIRST" class="m-doc-self" id="Enum-FIRST">FIRST</a> = 1
<a href="#Enum-SECOND" class="m-doc-self" id="Enum-SECOND">SECOND</a> = 2</span>
</dt>
<dd>An enum</dd>
class TypeLinks(BaseInspectTestCase):
def test(self):
- self.run_python()
+ self.run_python({
+ 'PLUGINS': ['m.sphinx'],
+ 'INPUT_PAGES': ['index.rst'],
+ 'M_SPHINX_INVENTORIES': [
+ ('../../../doc/documentation/python.inv', 'https://docs.python.org/3/', [], ['m-doc-external'])]
+ })
+
+ self.assertEqual(*self.actual_expected_contents('index.html'))
+
self.assertEqual(*self.actual_expected_contents('inspect_type_links.first.html'))
self.assertEqual(*self.actual_expected_contents('inspect_type_links.first.Foo.html'))
self.assertEqual(*self.actual_expected_contents('inspect_type_links.first.Foo.Foo.html'))
import logging
import os
import re
+from types import SimpleNamespace as Empty
from typing import Dict
from urllib.parse import urljoin
import zlib
# and unknown domains such as c++ for now as I'm unsure about potential
# name clashes.
if not found:
- for type in ['py:exception', 'py:attribute', 'py:method', 'py:data', 'py:module', 'py:function', 'py:class', 'py:classmethod', 'py:staticmethod', 'c:var', 'c:type', 'c:function', 'c:member', 'c:macro']:
+ for type in [
+ 'py:exception', 'py:attribute', 'py:method', 'py:data', 'py:module', 'py:function', 'py:class', 'py:classmethod', 'py:staticmethod',
+ 'c:var', 'c:type', 'c:function', 'c:member', 'c:macro',
+ # TODO: those apparently don't exist:
+ 'py:enum', 'py:enumvalue'
+ ]:
if type in intersphinx_inventory and prefixed in intersphinx_inventory[type]:
found = type, intersphinx_inventory[type][prefixed]
node = nodes.literal(rawtext, target, **_options)
return [node], []
-def register_mcss(mcss_settings, module_doc_contents, class_doc_contents, enum_doc_contents, function_doc_contents, property_doc_contents, data_doc_contents, **kwargs):
+def merge_inventories(name_map, **kwargs):
+ global intersphinx_inventory
+
+ # Create inventory entries from the name_map
+ internal_inventory = {}
+ for path_str, entry in name_map.items():
+ EntryType = type(entry.type) # so we don't need to import the enum
+ if entry.type == EntryType.MODULE:
+ type_string = 'py:module'
+ elif entry.type == EntryType.CLASS:
+ type_string = 'py:class'
+ elif entry.type == EntryType.FUNCTION:
+ # TODO: properly distinguish between 'py:function',
+ # 'py:classmethod', 'py:staticmethod', 'py:method'
+ type_string = 'py:function'
+ elif entry.type == EntryType.OVERLOADED_FUNCTION:
+ # TODO: what about the other overloads?
+ type_string = 'py:function'
+ elif entry.type == EntryType.PROPERTY:
+ # datetime.date.year is decorated with @property and listed as a
+ # py:attribute, so that's probably it
+ type_string = 'py:attribute'
+ elif entry.type == EntryType.ENUM:
+ type_string = 'py:enum' # this desn't exist in Sphinx
+ elif entry.type == EntryType.ENUM_VALUE:
+ type_string = 'py:enumvalue' # these don't exist in Sphinx
+ elif entry.type == EntryType.DATA:
+ type_string = 'py:data'
+ elif entry.type == EntryType.PAGE:
+ type_string = 'std:doc'
+ else:
+ # TODO: what to do with these? allow linking to them? disambiguate
+ # or prefix the names somehow?
+ assert entry.type == EntryType.SPECIAL, entry.type
+ continue
+
+ # Mark those with m-doc (as internal)
+ internal_inventory.setdefault(type_string, {})[path_str] = (entry.url, '-', ['m-doc'])
+
+ # Add class / enum / enum value inventory entries to the name map for type
+ # cross-linking
+ for type_, type_string in [
+ # TODO: this will blow up if the above loop is never entered (which is
+ # unlikely) as EntryType is defined there
+ (EntryType.CLASS, 'py:class'),
+ (EntryType.DATA, 'py:data'), # typing.Tuple or typing.Any is data
+ # Those are custom to m.css, not in Sphinx
+ (EntryType.ENUM, 'py:enum'),
+ (EntryType.ENUM_VALUE, 'py:enumvalue'),
+ ]:
+ if type_string in intersphinx_inventory:
+ for path, value in intersphinx_inventory[type_string].items():
+ url, _, css_classes = value
+ entry = Empty()
+ entry.type = type_
+ entry.object = None
+ entry.path = path.split('.')
+ entry.css_classes = css_classes
+ entry.url = url
+ name_map[path] = entry
+
+ # Add stuff from the name map to our inventory
+ for type_, data_internal in internal_inventory.items():
+ data = intersphinx_inventory.setdefault(type_, {})
+ for path, value in data_internal.items():
+ assert path not in data
+ data[path] = value
+
+def register_mcss(mcss_settings, module_doc_contents, class_doc_contents, enum_doc_contents, function_doc_contents, property_doc_contents, data_doc_contents, hooks_post_crawl, **kwargs):
global module_doc_output, class_doc_output, enum_doc_output, function_doc_output, property_doc_output, data_doc_output
module_doc_output = module_doc_contents
class_doc_output = class_doc_contents
rst.roles.register_local_role('ref', ref)
+ hooks_post_crawl += [merge_inventories]
+
def _pelican_configure(pelicanobj):
# For backwards compatibility, the input directory is pelican's CWD
parse_intersphinx_inventories(input=os.getcwd(),