diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py
index 80f1327fd..697514718 100644
--- a/sphinx/builders/html.py
+++ b/sphinx/builders/html.py
@@ -31,6 +31,7 @@ from docutils.readers.doctree import Reader as DoctreeReader
from sphinx import package_dir, __display_version__
from sphinx.util import jsonimpl, logging, status_iterator
from sphinx.util.i18n import format_date
+from sphinx.util.inventory import InventoryFile
from sphinx.util.osutil import SEP, os_path, relative_uri, ensuredir, \
movefile, copyfile
from sphinx.util.nodes import inline_all_toctrees
@@ -896,33 +897,8 @@ class StandaloneHTMLBuilder(Builder):
def dump_inventory(self):
# type: () -> None
- def safe_name(string):
- return re.sub("\s+", " ", string)
-
logger.info(bold('dumping object inventory... '), nonl=True)
- with open(path.join(self.outdir, INVENTORY_FILENAME), 'wb') as f:
- f.write((u'# Sphinx inventory version 2\n'
- u'# Project: %s\n'
- u'# Version: %s\n'
- u'# The remainder of this file is compressed using zlib.\n'
- % (safe_name(self.config.project),
- safe_name(self.config.version))).encode('utf-8'))
- compressor = zlib.compressobj(9)
- for domainname, domain in sorted(self.env.domains.items()):
- for name, dispname, type, docname, anchor, prio in \
- sorted(domain.get_objects()):
- if anchor.endswith(name):
- # this can shorten the inventory by as much as 25%
- anchor = anchor[:-len(name)] + '$'
- uri = self.get_target_uri(docname)
- if anchor:
- uri += '#' + anchor
- if dispname == name:
- dispname = u'-'
- f.write(compressor.compress(
- (u'%s %s:%s %s %s %s\n' % (name, domainname, type,
- prio, uri, dispname)).encode('utf-8')))
- f.write(compressor.flush())
+ InventoryFile.dump(path.join(self.outdir, INVENTORY_FILENAME), self.env, self)
logger.info('done')
def dump_search_index(self):
diff --git a/sphinx/util/inventory.py b/sphinx/util/inventory.py
index 9c79f47ab..a74a74964 100644
--- a/sphinx/util/inventory.py
+++ b/sphinx/util/inventory.py
@@ -9,11 +9,14 @@
:license: BSD, see LICENSE for details.
"""
import re
+import os
import zlib
import codecs
from six import PY3
+from sphinx.util import logging
+
if False:
# For type annotation
from typing import Callable, Dict, IO, Iterator, Tuple # NOQA
@@ -27,6 +30,8 @@ if False:
BUFSIZE = 16 * 1024
UTF8StreamReader = codecs.lookup('utf-8')[2]
+logger = logging.getLogger(__name__)
+
class ZlibReader(object):
"""Compressed file reader."""
@@ -121,3 +126,35 @@ class InventoryFile(object):
invdata.setdefault(type, {})[name] = (projname, version,
location, dispname)
return invdata
+
+ @classmethod
+ def dump(cls, filename, env, builder):
+ # type: (unicode, BuildEnvironment, Builder) -> None
+ def escape(string):
+ # type: (unicode) -> unicode
+ return re.sub("\s+", " ", string).encode('utf-8')
+
+ with open(os.path.join(filename), 'wb') as f:
+ # header
+ f.write('# Sphinx inventory version 2\n')
+ f.write('# Project: %s\n' % escape(env.config.project))
+ f.write('# Version: %s\n' % escape(env.config.version))
+ f.write('# The remainder of this file is compressed using zlib.\n')
+
+ # body
+ compressor = zlib.compressobj(9)
+ for domainname, domain in sorted(env.domains.items()):
+ for name, dispname, typ, docname, anchor, prio in \
+ sorted(domain.get_objects()):
+ if anchor.endswith(name):
+ # this can shorten the inventory by as much as 25%
+ anchor = anchor[:-len(name)] + '$'
+ uri = builder.get_target_uri(docname)
+ if anchor:
+ uri += '#' + anchor
+ if dispname == name:
+ dispname = u'-'
+ entry = (u'%s %s:%s %s %s %s\n' %
+ (name, domainname, typ, prio, uri, dispname))
+ f.write(compressor.compress(entry.encode('utf-8')))
+ f.write(compressor.flush())
diff --git a/tests/test_build_html.py b/tests/test_build_html.py
index ab228c13c..68442bd3f 100644
--- a/tests/test_build_html.py
+++ b/tests/test_build_html.py
@@ -16,6 +16,8 @@ from itertools import cycle, chain
from six import PY3
from sphinx import __display_version__
+from sphinx.util.inventory import InventoryFile
+
from util import remove_unicode_literals, strip_escseq
import xml.etree.cElementTree as ElementTree
from html5lib import getTreeBuilder, HTMLParser
@@ -1149,3 +1151,29 @@ def test_html_entity(app):
content = (app.outdir / 'index.html').text()
for entity in re.findall(r'&([a-z]+);', content, re.M):
assert entity not in valid_entities
+
+
+@pytest.mark.sphinx('html', testroot='basic')
+def test_html_inventory(app):
+ app.builder.build_all()
+ with open(app.outdir / 'objects.inv') as f:
+ invdata = InventoryFile.load(f, 'http://example.com', os.path.join)
+ assert invdata.keys() == ['std:label', 'std:doc']
+ assert invdata['std:label'].keys() == ['modindex', 'genindex', 'search']
+ assert invdata['std:label']['modindex'] == ('Python',
+ '',
+ 'http://example.com/py-modindex.html',
+ 'Module Index')
+ assert invdata['std:label']['genindex'] == ('Python',
+ '',
+ 'http://example.com/genindex.html',
+ 'Index')
+ assert invdata['std:label']['search'] == ('Python',
+ '',
+ 'http://example.com/search.html',
+ 'Search Page')
+ assert invdata['std:doc'].keys() == ['index']
+ assert invdata['std:doc']['index'] == ('Python',
+ '',
+ 'http://example.com/index.html',
+ 'The basic Sphinx documentation for testing')