Skip to content

Commit f6d01b8

Browse files
committed
docs: Add sphinx_selective_exclude extension suite.
Designed specifically to workaround issues we were facing with generating multiple conditionalized output docsets from a single master doctree. Extensions were factored out into a separate project, based on the fact that many other Sphinx users experience similar or related problems: https://github.com/pfalcon/sphinx_selective_exclude Corresponds to the 182f4a8da57 upstream revision.
1 parent 9de5eb2 commit f6d01b8

6 files changed

Lines changed: 317 additions & 0 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Copyright (c) 2016 by the sphinx_selective_exclude authors.
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are
6+
met:
7+
8+
* Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
11+
* Redistributions in binary form must reproduce the above copyright
12+
notice, this list of conditions and the following disclaimer in the
13+
documentation and/or other materials provided with the distribution.
14+
15+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
Sphinx eager ".. only::" directive and other selective rendition extensions
2+
===========================================================================
3+
4+
Project home page: https://github.com/pfalcon/sphinx_selective_exclude
5+
6+
The implementation of ".. only::" directive in Sphinx documentation
7+
generation tool is known to violate principles of least user surprise
8+
and user expectations in general. Instead of excluding content early
9+
in the pipeline (pre-processor style), Sphinx defers exclusion until
10+
output phase, and what's the worst, various stages processing ignore
11+
"only" blocks and their exclusion status, so they may leak unexpected
12+
information into ToC, indexes, etc.
13+
14+
There's multiple issues submitted upstream on this matter:
15+
16+
* https://github.com/sphinx-doc/sphinx/issues/2150
17+
* https://github.com/sphinx-doc/sphinx/issues/1717
18+
* https://github.com/sphinx-doc/sphinx/issues/1488
19+
* etc.
20+
21+
They are largely ignored by Sphinx maintainers.
22+
23+
This projects tries to rectify situation on users' side. It actually
24+
changes the way Sphinx processes "only" directive, but does this
25+
without forking the project, and instead is made as a standard
26+
Sphinx extension, which a user may add to their documentation config.
27+
Unlike normal extensions, extensions provided in this package
28+
monkey-patch Sphinx core to work in a way expected by users.
29+
30+
eager_only
31+
----------
32+
33+
The core extension provided by the package is called `eager_only` and
34+
is based on the idea by Andrea Cassioli (see bugreports above) to
35+
process "only" directive as soon as possible during parsing phase.
36+
This approach has some drawbacks, like producing warnings like
37+
"WARNING: document isn't included in any toctree" if "only" is used
38+
to shape up a toctree, or the fact that changing a documentation
39+
builder (html/latex/etc.) will almost certainly require complete
40+
rebuild of documentation. But these are relatively minor issues
41+
comparing to completely broken way "only" works in upstream Sphinx.
42+
43+
modindex_exclude
44+
----------------
45+
46+
"only" directive allows for fine-grained conditional exclusion, but
47+
sometimes you may want to exclude entire module(s) at once. Even if
48+
you wrap an entire module description in "only" directive, like:
49+
50+
.. only: option1
51+
.. module:: my_module
52+
53+
...
54+
55+
You will still have an HTML page generated, albeit empty. It may also
56+
go into indexes, so will be discoverable by users, leading to less
57+
than ideal experience. `modindex_exclude` extension is design to
58+
resolve this issue, by making sure that any reference of a module
59+
is excluded from Python module index ("modindex"), as well as
60+
general cross-reference index ("genindex"). In the latter case,
61+
any symbol belong to a module will be excluded. Unlike `eager_only`
62+
extension which appear to have issued with "latexpdf" builder,
63+
`modindex_exclude` is useful for PDF, and allows to get cleaner
64+
index for PDF, just the same as for HTML.
65+
66+
search_auto_exclude
67+
-------------------
68+
69+
Even if you exclude soem documents from toctree:: using only::
70+
directive, they will be indexed for full-text search, so user may
71+
find them and get confused. This plugin follows very simple idea
72+
that if you didn't include some documents in the toctree, then
73+
you didn't want them to be accessible (e.g. for a particular
74+
configuration), and so will make sure they aren't indexed either.
75+
76+
This extension depends on `eager_only` and won't work without it.
77+
Note that Sphinx will issue warnings, as usual, for any documents
78+
not included in a toctree. This is considered a feature, and gives
79+
you a chance to check that document exclusions are indeed right
80+
for a particular configuration you build (and not that you forgot
81+
to add something to a toctree).
82+
83+
Summary
84+
-------
85+
86+
Based on the above, sphinx_selective_exclude offers extension to let
87+
you:
88+
89+
* Make "only::" directive work in an expected, intuitive manner, using
90+
`eager_only` extension.
91+
* However, if you apply only:: to toctree::, excluded documents will
92+
still be available via full-text search, so you need to use
93+
`search_auto_exclude` for that to work as expected.
94+
* Similar to search, indexes may also require special treatment, hence
95+
there's the `modindex_exclude` extension.
96+
97+
Most likely, you will want to use all 3 extensions together - if you
98+
really want build subsets of docimentation covering sufficiently different
99+
configurations from a single doctree. However, if one of them is enough
100+
to cover your usecase, that's OK to (and why they were separated into
101+
3 extensions, to follow KISS and "least surprise" principles and to
102+
not make people deal with things they aren't interested in). In this case,
103+
however remember there're other extensions, if you later hit a usecase
104+
when they're needed.
105+
106+
Usage
107+
-----
108+
109+
To use these extensions, add https://github.com/pfalcon/sphinx_selective_exclude
110+
as a git submodule to your project, in documentation folder (where
111+
Sphinx conf.py is located). Alternatively, commit sphinx_selective_exclude
112+
directory instead of making it a submodule (you will need to pick up
113+
any project updates manually then).
114+
115+
Add following lines to "extensions" settings in your conf.py (you
116+
likely already have some standard Sphinx extensions enabled):
117+
118+
extensions = [
119+
...
120+
'sphinx_selective_exclude.eager_only',
121+
'sphinx_selective_exclude.search_auto_exclude',
122+
'sphinx_selective_exclude.modindex_exclude',
123+
]
124+
125+
As discussed above, you may enable all extensions, or one by one.
126+
127+
Please note that to make sure these extensions work well and avoid producing
128+
output docs with artifacts, it is IMPERATIVE to remove cached doctree if
129+
you rebuild documentation with another builder (i.e. with different output
130+
format). Also, to stay on safe side, it's recommended to remove old doctree
131+
anyway before generating production-ready documentation for publishing. To
132+
do that, run something like:
133+
134+
rm -rf _build/doctrees/
135+
136+
A typical artificat when not following these simple rules is that content
137+
of some sections may be missing. If you face anything like that, just
138+
remember what's written above and remove cached doctrees.

docs/sphinx_selective_exclude/__init__.py

Whitespace-only changes.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#
2+
# This is a Sphinx documentation tool extension which makes .only::
3+
# directives be eagerly processed early in the parsing stage. This
4+
# makes sure that content in .only:: blocks gets actually excluded
5+
# as a typical user expects, instead of bits of information in
6+
# these blocks leaking to documentation in various ways (e.g.,
7+
# indexes containing entries for functions which are actually in
8+
# .only:: blocks and thus excluded from documentation, etc.)
9+
# Note that with this extension, you may need to completely
10+
# rebuild a doctree when switching builders (i.e. completely
11+
# remove _build/doctree dir between generation of HTML vs PDF
12+
# documentation).
13+
#
14+
# This extension works by monkey-patching Sphinx core, so potentially
15+
# may not work with untested Sphinx versions. It tested to work with
16+
# 1.2.2 and 1.4.2
17+
#
18+
# Copyright (c) 2016 Paul Sokolovsky
19+
# Based on idea by Andrea Cassioli:
20+
# https://github.com/sphinx-doc/sphinx/issues/2150#issuecomment-171912290
21+
# Licensed under the terms of BSD license, see LICENSE file.
22+
#
23+
import sphinx
24+
from docutils.parsers.rst import directives
25+
26+
27+
class EagerOnly(sphinx.directives.other.Only):
28+
29+
def run(self, *args):
30+
# Evaluate the condition eagerly, and if false return no nodes right away
31+
env = self.state.document.settings.env
32+
env.app.builder.tags.add('TRUE')
33+
#print(repr(self.arguments[0]))
34+
if not env.app.builder.tags.eval_condition(self.arguments[0]):
35+
return []
36+
37+
# Otherwise, do the usual processing
38+
nodes = super(EagerOnly, self).run()
39+
if len(nodes) == 1:
40+
nodes[0]['expr'] = 'TRUE'
41+
return nodes
42+
43+
44+
def setup(app):
45+
directives.register_directive('only', EagerOnly)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#
2+
# This is a Sphinx documentation tool extension which allows to
3+
# exclude some Python modules from the generated indexes. Modules
4+
# are excluded both from "modindex" and "genindex" index tables
5+
# (in the latter case, all members of a module are exlcuded).
6+
# To control exclusion, set "modindex_exclude" variable in Sphinx
7+
# conf.py to the list of modules to exclude. Note: these should be
8+
# modules (as defined by py:module directive, not just raw filenames).
9+
# This extension works by monkey-patching Sphinx core, so potentially
10+
# may not work with untested Sphinx versions. It tested to work with
11+
# 1.2.2 and 1.4.2
12+
#
13+
# Copyright (c) 2016 Paul Sokolovsky
14+
# Licensed under the terms of BSD license, see LICENSE file.
15+
#
16+
import sphinx
17+
18+
19+
#org_PythonModuleIndex_generate = None
20+
org_PyObject_add_target_and_index = None
21+
org_PyModule_run = None
22+
23+
EXCLUDES = {}
24+
25+
# No longer used, PyModule_run() monkey-patch does all the job
26+
def PythonModuleIndex_generate(self, docnames=None):
27+
docnames = []
28+
excludes = self.domain.env.config['modindex_exclude']
29+
for modname, (docname, synopsis, platforms, deprecated) in self.domain.data['modules'].items():
30+
#print(docname)
31+
if modname not in excludes:
32+
docnames.append(docname)
33+
34+
return org_PythonModuleIndex_generate(self, docnames)
35+
36+
37+
def PyObject_add_target_and_index(self, name_cls, sig, signode):
38+
if hasattr(self.env, "ref_context"):
39+
# Sphinx 1.4
40+
ref_context = self.env.ref_context
41+
else:
42+
# Sphinx 1.2
43+
ref_context = self.env.temp_data
44+
modname = self.options.get(
45+
'module', ref_context.get('py:module'))
46+
#print("*", modname, name_cls)
47+
if modname in self.env.config['modindex_exclude']:
48+
return None
49+
return org_PyObject_add_target_and_index(self, name_cls, sig, signode)
50+
51+
52+
def PyModule_run(self):
53+
env = self.state.document.settings.env
54+
modname = self.arguments[0].strip()
55+
excl = env.config['modindex_exclude']
56+
if modname in excl:
57+
self.options['noindex'] = True
58+
EXCLUDES.setdefault(modname, []).append(env.docname)
59+
return org_PyModule_run(self)
60+
61+
62+
def setup(app):
63+
app.add_config_value('modindex_exclude', [], 'html')
64+
65+
# global org_PythonModuleIndex_generate
66+
# org_PythonModuleIndex_generate = sphinx.domains.python.PythonModuleIndex.generate
67+
# sphinx.domains.python.PythonModuleIndex.generate = PythonModuleIndex_generate
68+
69+
global org_PyObject_add_target_and_index
70+
org_PyObject_add_target_and_index = sphinx.domains.python.PyObject.add_target_and_index
71+
sphinx.domains.python.PyObject.add_target_and_index = PyObject_add_target_and_index
72+
73+
global org_PyModule_run
74+
org_PyModule_run = sphinx.domains.python.PyModule.run
75+
sphinx.domains.python.PyModule.run = PyModule_run
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#
2+
# This is a Sphinx documentation tool extension which allows to
3+
# automatically exclude from full-text search index document
4+
# which are not referenced via toctree::. It's intended to be
5+
# used with toctrees conditional on only:: directive, with the
6+
# idea being that if you didn't include it in the ToC, you don't
7+
# want the docs being findable by search either (for example,
8+
# because these docs contain information not pertinent to a
9+
# particular product configuration).
10+
#
11+
# This extension depends on "eager_only" extension and won't work
12+
# without it.
13+
#
14+
# Copyright (c) 2016 Paul Sokolovsky
15+
# Licensed under the terms of BSD license, see LICENSE file.
16+
#
17+
import sphinx
18+
19+
20+
org_StandaloneHTMLBuilder_index_page = None
21+
22+
23+
def StandaloneHTMLBuilder_index_page(self, pagename, doctree, title):
24+
if pagename not in self.env.files_to_rebuild:
25+
if pagename != self.env.config.master_doc and 'orphan' not in self.env.metadata[pagename]:
26+
print("Excluding %s from full-text index because it's not referenced in ToC" % pagename)
27+
return
28+
return org_StandaloneHTMLBuilder_index_page(self, pagename, doctree, title)
29+
30+
31+
def setup(app):
32+
global org_StandaloneHTMLBuilder_index_page
33+
org_StandaloneHTMLBuilder_index_page = sphinx.builders.html.StandaloneHTMLBuilder.index_page
34+
sphinx.builders.html.StandaloneHTMLBuilder.index_page = StandaloneHTMLBuilder_index_page

0 commit comments

Comments
 (0)