mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch 'stable' into 1.7-release
This commit is contained in:
commit
af25fa123d
2
CHANGES
2
CHANGES
@ -204,6 +204,8 @@ Bugs fixed
|
|||||||
* #4438: math: math with labels with whitespace cause html error
|
* #4438: math: math with labels with whitespace cause html error
|
||||||
* #2437: make full reference for classes, aliased with "alias of"
|
* #2437: make full reference for classes, aliased with "alias of"
|
||||||
* #4434: pure numbers as link targets produce warning
|
* #4434: pure numbers as link targets produce warning
|
||||||
|
* #4477: Build fails after building specific files
|
||||||
|
* #4449: apidoc: include "empty" packages that contain modules
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
--------
|
--------
|
||||||
|
@ -54,9 +54,11 @@ from sphinx.environment.adapters.indexentries import IndexEntries
|
|||||||
|
|
||||||
if False:
|
if False:
|
||||||
# For type annotation
|
# For type annotation
|
||||||
from typing import Any, Dict, Iterable, Iterator, List, Type, Tuple, Union # NOQA
|
from typing import Any, Dict, IO, Iterable, Iterator, List, Type, Tuple, Union # NOQA
|
||||||
from sphinx.domains import Domain, Index # NOQA
|
|
||||||
from sphinx.application import Sphinx # NOQA
|
from sphinx.application import Sphinx # NOQA
|
||||||
|
from sphinx.config import Config # NOQA
|
||||||
|
from sphinx.domains import Domain, Index # NOQA
|
||||||
|
from sphinx.util.tags import Tags # NOQA
|
||||||
|
|
||||||
# Experimental HTML5 Writer
|
# Experimental HTML5 Writer
|
||||||
if is_html5_writer_available():
|
if is_html5_writer_available():
|
||||||
@ -147,6 +149,56 @@ class Stylesheet(text_type):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class BuildInfo(object):
|
||||||
|
"""buildinfo file manipulator.
|
||||||
|
|
||||||
|
HTMLBuilder and its family are storing their own envdata to ``.buildinfo``.
|
||||||
|
This class is a manipulator for the file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, f):
|
||||||
|
# type: (IO) -> BuildInfo
|
||||||
|
try:
|
||||||
|
lines = f.readlines()
|
||||||
|
assert lines[0].rstrip() == '# Sphinx build info version 1'
|
||||||
|
assert lines[2].startswith('config: ')
|
||||||
|
assert lines[3].startswith('tags: ')
|
||||||
|
|
||||||
|
build_info = BuildInfo()
|
||||||
|
build_info.config_hash = lines[2].split()[1].strip()
|
||||||
|
build_info.tags_hash = lines[3].split()[1].strip()
|
||||||
|
return build_info
|
||||||
|
except Exception as exc:
|
||||||
|
raise ValueError('build info file is broken: %r' % exc)
|
||||||
|
|
||||||
|
def __init__(self, config=None, tags=None):
|
||||||
|
# type: (Config, Tags) -> None
|
||||||
|
self.config_hash = u''
|
||||||
|
self.tags_hash = u''
|
||||||
|
|
||||||
|
if config:
|
||||||
|
values = dict((c.name, c.value) for c in config.filter('html'))
|
||||||
|
self.config_hash = get_stable_hash(values)
|
||||||
|
|
||||||
|
if tags:
|
||||||
|
self.tags_hash = get_stable_hash(sorted(tags))
|
||||||
|
|
||||||
|
def __eq__(self, other): # type: ignore
|
||||||
|
# type: (BuildInfo) -> bool
|
||||||
|
return (self.config_hash == other.config_hash and
|
||||||
|
self.tags_hash == other.tags_hash)
|
||||||
|
|
||||||
|
def dump(self, f):
|
||||||
|
# type: (IO) -> None
|
||||||
|
f.write('# Sphinx build info version 1\n'
|
||||||
|
'# This file hashes the configuration used when building these files.'
|
||||||
|
' When it is not found, a full rebuild will be done.\n'
|
||||||
|
'config: %s\n'
|
||||||
|
'tags: %s\n' %
|
||||||
|
(self.config_hash, self.tags_hash))
|
||||||
|
|
||||||
|
|
||||||
class StandaloneHTMLBuilder(Builder):
|
class StandaloneHTMLBuilder(Builder):
|
||||||
"""
|
"""
|
||||||
Builds standalone HTML docs.
|
Builds standalone HTML docs.
|
||||||
@ -191,9 +243,7 @@ class StandaloneHTMLBuilder(Builder):
|
|||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
# a hash of all config values that, if changed, cause a full rebuild
|
self.build_info = BuildInfo(self.config, self.tags)
|
||||||
self.config_hash = '' # type: unicode
|
|
||||||
self.tags_hash = '' # type: unicode
|
|
||||||
# basename of images directory
|
# basename of images directory
|
||||||
self.imagedir = '_images'
|
self.imagedir = '_images'
|
||||||
# section numbers for headings in the currently visited document
|
# section numbers for headings in the currently visited document
|
||||||
@ -274,32 +324,19 @@ class StandaloneHTMLBuilder(Builder):
|
|||||||
|
|
||||||
def get_outdated_docs(self):
|
def get_outdated_docs(self):
|
||||||
# type: () -> Iterator[unicode]
|
# type: () -> Iterator[unicode]
|
||||||
cfgdict = dict((confval.name, confval.value) for confval in self.config.filter('html'))
|
|
||||||
self.config_hash = get_stable_hash(cfgdict)
|
|
||||||
self.tags_hash = get_stable_hash(sorted(self.tags))
|
|
||||||
old_config_hash = old_tags_hash = ''
|
|
||||||
try:
|
try:
|
||||||
with open(path.join(self.outdir, '.buildinfo')) as fp:
|
with open(path.join(self.outdir, '.buildinfo')) as fp:
|
||||||
version = fp.readline()
|
buildinfo = BuildInfo.load(fp)
|
||||||
if version.rstrip() != '# Sphinx build info version 1':
|
|
||||||
raise ValueError
|
if self.build_info != buildinfo:
|
||||||
fp.readline() # skip commentary
|
|
||||||
cfg, old_config_hash = fp.readline().strip().split(': ')
|
|
||||||
if cfg != 'config':
|
|
||||||
raise ValueError
|
|
||||||
tag, old_tags_hash = fp.readline().strip().split(': ')
|
|
||||||
if tag != 'tags':
|
|
||||||
raise ValueError
|
|
||||||
except ValueError:
|
|
||||||
logger.warning('unsupported build info format in %r, building all',
|
|
||||||
path.join(self.outdir, '.buildinfo'))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if old_config_hash != self.config_hash or \
|
|
||||||
old_tags_hash != self.tags_hash:
|
|
||||||
for docname in self.env.found_docs:
|
for docname in self.env.found_docs:
|
||||||
yield docname
|
yield docname
|
||||||
return
|
return
|
||||||
|
except ValueError as exc:
|
||||||
|
logger.warning('Failed to read build info file: %r', exc)
|
||||||
|
except IOError:
|
||||||
|
# ignore errors on reading
|
||||||
|
pass
|
||||||
|
|
||||||
if self.templates:
|
if self.templates:
|
||||||
template_mtime = self.templates.newest_template_mtime()
|
template_mtime = self.templates.newest_template_mtime()
|
||||||
@ -777,14 +814,9 @@ class StandaloneHTMLBuilder(Builder):
|
|||||||
|
|
||||||
def write_buildinfo(self):
|
def write_buildinfo(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
# write build info file
|
|
||||||
try:
|
try:
|
||||||
with open(path.join(self.outdir, '.buildinfo'), 'w') as fp:
|
with open(path.join(self.outdir, '.buildinfo'), 'w') as fp:
|
||||||
fp.write('# Sphinx build info version 1\n'
|
self.build_info.dump(fp)
|
||||||
'# This file hashes the configuration used when building'
|
|
||||||
' these files. When it is not found, a full rebuild will'
|
|
||||||
' be done.\nconfig: %s\ntags: %s\n' %
|
|
||||||
(self.config_hash, self.tags_hash))
|
|
||||||
except IOError as exc:
|
except IOError as exc:
|
||||||
logger.warning('Failed to write build info file: %r', exc)
|
logger.warning('Failed to write build info file: %r', exc)
|
||||||
|
|
||||||
@ -1257,8 +1289,7 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder):
|
|||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
self.config_hash = ''
|
self.build_info = BuildInfo(self.config, self.tags)
|
||||||
self.tags_hash = ''
|
|
||||||
self.imagedir = '_images'
|
self.imagedir = '_images'
|
||||||
self.current_docname = None
|
self.current_docname = None
|
||||||
self.theme = None # no theme necessary
|
self.theme = None # no theme necessary
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import glob
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from os import path
|
from os import path
|
||||||
@ -194,6 +195,16 @@ def shall_skip(module, opts):
|
|||||||
|
|
||||||
# skip it if there is nothing (or just \n or \r\n) in the file
|
# skip it if there is nothing (or just \n or \r\n) in the file
|
||||||
if path.exists(module) and path.getsize(module) <= 2:
|
if path.exists(module) and path.getsize(module) <= 2:
|
||||||
|
skip = True
|
||||||
|
if os.path.basename(module) == '__init__.py':
|
||||||
|
pattern = path.join(path.dirname(module), '*.py')
|
||||||
|
# We only want to skip packages if they do not contain any
|
||||||
|
# .py files other than __init__.py.
|
||||||
|
other_modules = list(glob.glob(pattern))
|
||||||
|
other_modules.remove(module)
|
||||||
|
skip = not other_modules
|
||||||
|
|
||||||
|
if skip:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# skip if it has a "private" name and this is selected
|
# skip if it has a "private" name and this is selected
|
||||||
|
0
tests/roots/test-apidoc-pep420/a/b/e/__init__.py
Normal file
0
tests/roots/test-apidoc-pep420/a/b/e/__init__.py
Normal file
1
tests/roots/test-apidoc-pep420/a/b/e/f.py
Normal file
1
tests/roots/test-apidoc-pep420/a/b/e/f.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"Module f"
|
@ -67,6 +67,7 @@ def test_pep_0420_enabled(make_app, apidoc):
|
|||||||
outdir = apidoc.outdir
|
outdir = apidoc.outdir
|
||||||
assert (outdir / 'conf.py').isfile()
|
assert (outdir / 'conf.py').isfile()
|
||||||
assert (outdir / 'a.b.c.rst').isfile()
|
assert (outdir / 'a.b.c.rst').isfile()
|
||||||
|
assert (outdir / 'a.b.e.rst').isfile()
|
||||||
assert (outdir / 'a.b.x.rst').isfile()
|
assert (outdir / 'a.b.x.rst').isfile()
|
||||||
|
|
||||||
with open(outdir / 'a.b.c.rst') as f:
|
with open(outdir / 'a.b.c.rst') as f:
|
||||||
@ -74,6 +75,10 @@ def test_pep_0420_enabled(make_app, apidoc):
|
|||||||
assert "automodule:: a.b.c.d\n" in rst
|
assert "automodule:: a.b.c.d\n" in rst
|
||||||
assert "automodule:: a.b.c\n" in rst
|
assert "automodule:: a.b.c\n" in rst
|
||||||
|
|
||||||
|
with open(outdir / 'a.b.e.rst') as f:
|
||||||
|
rst = f.read()
|
||||||
|
assert "automodule:: a.b.e.f\n" in rst
|
||||||
|
|
||||||
with open(outdir / 'a.b.x.rst') as f:
|
with open(outdir / 'a.b.x.rst') as f:
|
||||||
rst = f.read()
|
rst = f.read()
|
||||||
assert "automodule:: a.b.x.y\n" in rst
|
assert "automodule:: a.b.x.y\n" in rst
|
||||||
@ -86,12 +91,67 @@ def test_pep_0420_enabled(make_app, apidoc):
|
|||||||
|
|
||||||
builddir = outdir / '_build' / 'text'
|
builddir = outdir / '_build' / 'text'
|
||||||
assert (builddir / 'a.b.c.txt').isfile()
|
assert (builddir / 'a.b.c.txt').isfile()
|
||||||
|
assert (builddir / 'a.b.e.txt').isfile()
|
||||||
assert (builddir / 'a.b.x.txt').isfile()
|
assert (builddir / 'a.b.x.txt').isfile()
|
||||||
|
|
||||||
with open(builddir / 'a.b.c.txt') as f:
|
with open(builddir / 'a.b.c.txt') as f:
|
||||||
txt = f.read()
|
txt = f.read()
|
||||||
assert "a.b.c package\n" in txt
|
assert "a.b.c package\n" in txt
|
||||||
|
|
||||||
|
with open(builddir / 'a.b.e.txt') as f:
|
||||||
|
txt = f.read()
|
||||||
|
assert "a.b.e.f module\n" in txt
|
||||||
|
|
||||||
|
with open(builddir / 'a.b.x.txt') as f:
|
||||||
|
txt = f.read()
|
||||||
|
assert "a.b.x namespace\n" in txt
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.apidoc(
|
||||||
|
coderoot='test-apidoc-pep420/a',
|
||||||
|
options=["--implicit-namespaces", "--separate"],
|
||||||
|
)
|
||||||
|
def test_pep_0420_enabled_separate(make_app, apidoc):
|
||||||
|
outdir = apidoc.outdir
|
||||||
|
assert (outdir / 'conf.py').isfile()
|
||||||
|
assert (outdir / 'a.b.c.rst').isfile()
|
||||||
|
assert (outdir / 'a.b.e.rst').isfile()
|
||||||
|
assert (outdir / 'a.b.e.f.rst').isfile()
|
||||||
|
assert (outdir / 'a.b.x.rst').isfile()
|
||||||
|
assert (outdir / 'a.b.x.y.rst').isfile()
|
||||||
|
|
||||||
|
with open(outdir / 'a.b.c.rst') as f:
|
||||||
|
rst = f.read()
|
||||||
|
assert ".. toctree::\n\n a.b.c.d\n" in rst
|
||||||
|
|
||||||
|
with open(outdir / 'a.b.e.rst') as f:
|
||||||
|
rst = f.read()
|
||||||
|
assert ".. toctree::\n\n a.b.e.f\n" in rst
|
||||||
|
|
||||||
|
with open(outdir / 'a.b.x.rst') as f:
|
||||||
|
rst = f.read()
|
||||||
|
assert ".. toctree::\n\n a.b.x.y\n" in rst
|
||||||
|
|
||||||
|
app = make_app('text', srcdir=outdir)
|
||||||
|
app.build()
|
||||||
|
print(app._status.getvalue())
|
||||||
|
print(app._warning.getvalue())
|
||||||
|
|
||||||
|
builddir = outdir / '_build' / 'text'
|
||||||
|
assert (builddir / 'a.b.c.txt').isfile()
|
||||||
|
assert (builddir / 'a.b.e.txt').isfile()
|
||||||
|
assert (builddir / 'a.b.e.f.txt').isfile()
|
||||||
|
assert (builddir / 'a.b.x.txt').isfile()
|
||||||
|
assert (builddir / 'a.b.x.y.txt').isfile()
|
||||||
|
|
||||||
|
with open(builddir / 'a.b.c.txt') as f:
|
||||||
|
txt = f.read()
|
||||||
|
assert "a.b.c package\n" in txt
|
||||||
|
|
||||||
|
with open(builddir / 'a.b.e.f.txt') as f:
|
||||||
|
txt = f.read()
|
||||||
|
assert "a.b.e.f module\n" in txt
|
||||||
|
|
||||||
with open(builddir / 'a.b.x.txt') as f:
|
with open(builddir / 'a.b.x.txt') as f:
|
||||||
txt = f.read()
|
txt = f.read()
|
||||||
assert "a.b.x namespace\n" in txt
|
assert "a.b.x namespace\n" in txt
|
||||||
|
Loading…
Reference in New Issue
Block a user