如何使用 intersphinx 正确编写对外部文档的交叉引用?

How to properly write cross-references to external documentation with intersphinx?

我正在尝试将对外部 API 的交叉引用添加到我的文档中,但我面临三种不同的行为。

我将 sphinx(1.3.1) 与 Python(2.7.3) 一起使用,我的 intersphinx 映射配置为:

{
'python': ('https://docs.python.org/2.7', None),
'numpy': ('http://docs.scipy.org/doc/numpy/', None),
'cv2' : ('http://docs.opencv.org/2.4/', None),
'h5py' : ('http://docs.h5py.org/en/latest/', None)
}

我可以毫不费力地使用 :class:`numpy.ndarray`:func:`numpy.array` 编写对 numpy API 的交叉引用,正如预期的那样,它给了我 numpy.ndarray.

但是,对于 h5py,我可以生成 link 的唯一方法是省略模块名称。例如,:class:`Group`(或 :class:`h5py:Group`)给我 Group,但 :class:`h5py.Group` 无法生成 link。

最后,我找不到一种方法来编写对 OpenCV 的有效交叉引用 API,其中 none 似乎有效:

:func:`cv2.convertScaleAbs`
:func:`cv2:cv2.convertScaleAbs`
:func:`cv2:convertScaleAbs`
:func:`convertScaleAbs`

如何正确编写对外部 API 的交叉引用,或配置 intersphinx,以生成 numpy 情况下的 link?

我再次尝试理解 objects.inv 文件的内容,希望这次我检查了 numpy 和 h5py,而不仅仅是 OpenCV 的一个。

如何读取 intersphinx 清单文件

尽管我找不到任何关于读取 object.inv 文件内容的有用信息,但使用 intersphinx 模块实际上非常简单。

from sphinx.ext import intersphinx
import warnings


def fetch_inventory(uri):
    """Read a Sphinx inventory file into a dictionary."""
    class MockConfig(object):
        intersphinx_timeout = None  # type: int
        tls_verify = False

    class MockApp(object):
        srcdir = ''
        config = MockConfig()

        def warn(self, msg):
            warnings.warn(msg)

    return intersphinx.fetch_inventory(MockApp(), '', uri)


uri = 'http://docs.python.org/2.7/objects.inv'

# Read inventory into a dictionary
inv = fetch_inventory(uri)
# Or just print it
intersphinx.debug(['', uri])

文件结构 (numpy)

检查 numpy 的后,您可以看到键是域:

[u'np-c:function',
 u'std:label',
 u'c:member',
 u'np:classmethod',
 u'np:data',
 u'py:class',
 u'np-c:member',
 u'c:var',
 u'np:class',
 u'np:function',
 u'py:module',
 u'np-c:macro',
 u'np:exception',
 u'py:method',
 u'np:method',
 u'np-c:var',
 u'py:exception',
 u'np:staticmethod',
 u'py:staticmethod',
 u'c:type',
 u'np-c:type',
 u'c:macro',
 u'c:function',
 u'np:module',
 u'py:data',
 u'np:attribute',
 u'std:term',
 u'py:function',
 u'py:classmethod',
 u'py:attribute']

您可以在查看特定域的内容时了解如何编写交叉引用。例如,py:class:

{u'numpy.DataSource': (u'NumPy',
  u'1.9',
  u'http://docs.scipy.org/doc/numpy/reference/generated/numpy.DataSource.html#numpy.DataSource',
  u'-'),
 u'numpy.MachAr': (u'NumPy',
  u'1.9',
  u'http://docs.scipy.org/doc/numpy/reference/generated/numpy.MachAr.html#numpy.MachAr',
  u'-'),
 u'numpy.broadcast': (u'NumPy',
  u'1.9',
  u'http://docs.scipy.org/doc/numpy/reference/generated/numpy.broadcast.html#numpy.broadcast',
  u'-'),
  ...}

所以在这里,:class:`numpy.DataSource` 将按预期工作。

h5py

对于 h5py,域是:

[u'py:attribute', u'std:label', u'py:method', u'py:function', u'py:class']

如果您查看 py:class 域:

{u'AttributeManager': (u'h5py',
  u'2.5',
  u'http://docs.h5py.org/en/latest/high/attr.html#AttributeManager',
  u'-'),
 u'Dataset': (u'h5py',
  u'2.5',
  u'http://docs.h5py.org/en/latest/high/dataset.html#Dataset',
  u'-'),
 u'ExternalLink': (u'h5py',
  u'2.5',
  u'http://docs.h5py.org/en/latest/high/group.html#ExternalLink',
  u'-'),
 ...}

这就是为什么我无法将其用作 numpy 引用的原因。所以格式化它们的好方法是 :class:`h5py:Dataset`.

OpenCV

OpenCV 的清单对象似乎格式不正确。我期望找到域的地方实际上有 902 个函数签名:

[u':',
 u'AdjusterAdapter::create(const',
 u'AdjusterAdapter::good()',
 u'AdjusterAdapter::tooFew(int',
 u'AdjusterAdapter::tooMany(int',
 u'Algorithm::create(const',
 u'Algorithm::getList(vector<string>&',
 u'Algorithm::name()',
 u'Algorithm::read(const',
 u'Algorithm::set(const'
 ...]

如果我们取第一个的值:

{u'Ptr<AdjusterAdapter>': (u'OpenCV',
  u'2.4',
  u'http://docs.opencv.org/2.4/detectorType)',
  u'ocv:function 1 modules/features2d/doc/common_interfaces_of_feature_detectors.html#$ -')}

我很确定用这个文件编写 OpenCV 交叉引用是不可能的...

结论

我认为 intersphinx 基于文档项目的内容以 标准 方式生成了 objects.inv,但似乎并非如此。 因此,编写交叉引用的正确方法似乎是 API 依赖的,并且应该检查特定的清单对象以实际查看可用的内容。

除了@gall 的详细回答,我发现 intersphinx 也可以 运行 作为一个模块:

python -m sphinx.ext.intersphinx 'http://python-eve.org/objects.inv'

这会输出格式正确的信息。供参考:https://github.com/sphinx-doc/sphinx/blob/master/sphinx/ext/intersphinx.py#L390

已接受的答案在新版本中不再有效 (1.5.x) ...

import requests
import posixpath
from sphinx.ext.intersphinx import read_inventory

uri = 'http://docs.python.org/2.7/'

r = requests.get(uri + 'objects.inv', stream=True)
r.raise_for_status()

inv = read_inventory(r.raw, uri, posixpath.join)

如何使用 OpenCV 2.4 (cv2) intersphinx

受@Gall 回答的启发,我想比较 OpenCV 和 numpy 清单文件的内容。我无法让 sphinx.ext.intersphinx.fetch_inventory 从 ipython 开始工作,但以下确实有效:

curl http://docs.opencv.org/2.4/objects.inv | tail -n +5 | zlib-flate -uncompress > cv2.inv
curl https://docs.scipy.org/doc/numpy/objects.inv | tail -n +5 | zlib-flate -uncompress > numpy.inv

numpy.inv 有这样的行:

numpy.ndarray py:class 1 reference/generated/numpy.ndarray.html#$ -

而 cv2.inv 有这样的行:

cv2.imread ocv:pyfunction 1 modules/highgui/doc/reading_and_writing_images_and_video.html#$ -

所以您可能 link 使用 :ocv:pyfunction:`cv2.imread` 而不是 :py:function:`cv2.imread` 访问 OpenCV 文档。不过 Sphinx 不喜欢它:

WARNING: Unknown interpreted text role "ocv:pyfunction".

一点谷歌搜索显示 OpenCV 项目有自己的 "ocv" sphinx 域:https://github.com/opencv/opencv/blob/2.4/doc/ocv.py——大概是因为他们需要记录 C、C++ 和 Python APIs同时。

要使用它,请将 ocv.py 保存在您的 Sphinx conf.py 旁边,然后修改您的 conf.py:

sys.path.insert(0, os.path.abspath('.'))
import ocv
extensions = [
    'ocv',
]
intersphinx_mapping = {
    'cv2': ('http://docs.opencv.org/2.4/', None),
}

在您的第一个文件中,您需要说 :ocv:pyfunc:`cv2.imread`(而不是 :ocv:pyfunction:)。

Sphinx 打印了一些警告 (unparseable C++ definition: u'cv2.imread'),但生成的 html 文档实际上看起来不错,有 link 到 http://docs.opencv.org/2.4/modules/highgui/doc/reading_and_writing_images_and_video.html#cv2.imread。您可以编辑 ocv.py 并删除打印该警告的行。

另一种检查 objects.inv 文件的方法是使用 sphobjinv 模块。

您可以搜索本地甚至远程库存文件(模糊匹配)。例如 scipy:

$ sphobjinv suggest -t 90 -u https://docs.scipy.org/doc/scipy/reference/objects.inv "signal.convolve2d"

Remote inventory found.

:py:function:`scipy.signal.convolve2d`
:std:doc:`generated/scipy.signal.convolve2d`

请注意,您可能需要使用 :py:func: 而不是 :py:function:(我很乐意知道原因)。

我是个顽固的傻瓜,我使用了 2to3Sphinx deprecated APIs chart to revive 所以它可以在 Python 3.5.

上与 Sphinx 2.3 一起使用

修复后的版本在这里:

https://gist.github.com/ssokolow/a230b27b7ea4a31f7fb40621e6461f9a

...我所做的快速版本是:

  1. 运行 2to3 -w ocv.py && rm ocv.py.bak
  2. 在 运行 Sphinx 之间来回循环,并将函数重命名为图表中的替换函数。我相信这些是我必须在此步骤中进行的唯一更改:
    1. Directive 现在必须从 docutils.parsers.rst
    2. 导入
    3. 将对 l_(...) 的调用替换为对 _(...) 的调用,并删除 l_ 导入。
  3. 将对 env.warn 的调用替换为对 log.warn 的调用,其中 log = sphinx.util.logging.getLogger(__name__).

然后,您只需将它与这个 intersphinx 定义配对,您就会得到一些足够新的东西以与大多数用例相关:

'cv2': ('https://docs.opencv.org/3.0-last-rst/', None)

为方便起见,我为别名 intersphinx 交叉引用做了一个小扩展。这很有用,因为当子模块中的对象从包的 __init__.py.

中导入时,对象清单有时会变得混乱

另见 https://github.com/sphinx-doc/sphinx/issues/5603

###
# Workaround of
# Intersphinx references to objects imported at package level can"t be mapped.
#
# See https://github.com/sphinx-doc/sphinx/issues/5603

intersphinx_aliases = {
    ("py:class", "click.core.Group"):
        ("py:class", "click.Group"),
    ("py:class", "click.core.Command"):
        ("py:class", "click.Command"),
}


def add_intersphinx_aliases_to_inv(app):
    from sphinx.ext.intersphinx import InventoryAdapter
    inventories = InventoryAdapter(app.builder.env)

    for alias, target in app.config.intersphinx_aliases.items():
        alias_domain, alias_name = alias
        target_domain, target_name = target
        try:
            found = inventories.main_inventory[target_domain][target_name]
            try:
                inventories.main_inventory[alias_domain][alias_name] = found
            except KeyError:
                print("could not add to inv")
                continue
        except KeyError:
            print("missed :(")
            continue


def setup(app):
    app.add_config_value("intersphinx_aliases", {}, "env")
    app.connect("builder-inited", add_intersphinx_aliases_to_inv)

要使用它,我将上面的代码粘贴到我的 conf.py 中,并将别名添加到 intersphinx_aliases 字典中。