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.
This commit is contained in:
parent
9de5eb278d
commit
f6d01b8b67
25
docs/sphinx_selective_exclude/LICENSE
Normal file
25
docs/sphinx_selective_exclude/LICENSE
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2016 by the sphinx_selective_exclude authors.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
138
docs/sphinx_selective_exclude/README.md
Normal file
138
docs/sphinx_selective_exclude/README.md
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
Sphinx eager ".. only::" directive and other selective rendition extensions
|
||||||
|
===========================================================================
|
||||||
|
|
||||||
|
Project home page: https://github.com/pfalcon/sphinx_selective_exclude
|
||||||
|
|
||||||
|
The implementation of ".. only::" directive in Sphinx documentation
|
||||||
|
generation tool is known to violate principles of least user surprise
|
||||||
|
and user expectations in general. Instead of excluding content early
|
||||||
|
in the pipeline (pre-processor style), Sphinx defers exclusion until
|
||||||
|
output phase, and what's the worst, various stages processing ignore
|
||||||
|
"only" blocks and their exclusion status, so they may leak unexpected
|
||||||
|
information into ToC, indexes, etc.
|
||||||
|
|
||||||
|
There's multiple issues submitted upstream on this matter:
|
||||||
|
|
||||||
|
* https://github.com/sphinx-doc/sphinx/issues/2150
|
||||||
|
* https://github.com/sphinx-doc/sphinx/issues/1717
|
||||||
|
* https://github.com/sphinx-doc/sphinx/issues/1488
|
||||||
|
* etc.
|
||||||
|
|
||||||
|
They are largely ignored by Sphinx maintainers.
|
||||||
|
|
||||||
|
This projects tries to rectify situation on users' side. It actually
|
||||||
|
changes the way Sphinx processes "only" directive, but does this
|
||||||
|
without forking the project, and instead is made as a standard
|
||||||
|
Sphinx extension, which a user may add to their documentation config.
|
||||||
|
Unlike normal extensions, extensions provided in this package
|
||||||
|
monkey-patch Sphinx core to work in a way expected by users.
|
||||||
|
|
||||||
|
eager_only
|
||||||
|
----------
|
||||||
|
|
||||||
|
The core extension provided by the package is called `eager_only` and
|
||||||
|
is based on the idea by Andrea Cassioli (see bugreports above) to
|
||||||
|
process "only" directive as soon as possible during parsing phase.
|
||||||
|
This approach has some drawbacks, like producing warnings like
|
||||||
|
"WARNING: document isn't included in any toctree" if "only" is used
|
||||||
|
to shape up a toctree, or the fact that changing a documentation
|
||||||
|
builder (html/latex/etc.) will almost certainly require complete
|
||||||
|
rebuild of documentation. But these are relatively minor issues
|
||||||
|
comparing to completely broken way "only" works in upstream Sphinx.
|
||||||
|
|
||||||
|
modindex_exclude
|
||||||
|
----------------
|
||||||
|
|
||||||
|
"only" directive allows for fine-grained conditional exclusion, but
|
||||||
|
sometimes you may want to exclude entire module(s) at once. Even if
|
||||||
|
you wrap an entire module description in "only" directive, like:
|
||||||
|
|
||||||
|
.. only: option1
|
||||||
|
.. module:: my_module
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
You will still have an HTML page generated, albeit empty. It may also
|
||||||
|
go into indexes, so will be discoverable by users, leading to less
|
||||||
|
than ideal experience. `modindex_exclude` extension is design to
|
||||||
|
resolve this issue, by making sure that any reference of a module
|
||||||
|
is excluded from Python module index ("modindex"), as well as
|
||||||
|
general cross-reference index ("genindex"). In the latter case,
|
||||||
|
any symbol belong to a module will be excluded. Unlike `eager_only`
|
||||||
|
extension which appear to have issued with "latexpdf" builder,
|
||||||
|
`modindex_exclude` is useful for PDF, and allows to get cleaner
|
||||||
|
index for PDF, just the same as for HTML.
|
||||||
|
|
||||||
|
search_auto_exclude
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Even if you exclude soem documents from toctree:: using only::
|
||||||
|
directive, they will be indexed for full-text search, so user may
|
||||||
|
find them and get confused. This plugin follows very simple idea
|
||||||
|
that if you didn't include some documents in the toctree, then
|
||||||
|
you didn't want them to be accessible (e.g. for a particular
|
||||||
|
configuration), and so will make sure they aren't indexed either.
|
||||||
|
|
||||||
|
This extension depends on `eager_only` and won't work without it.
|
||||||
|
Note that Sphinx will issue warnings, as usual, for any documents
|
||||||
|
not included in a toctree. This is considered a feature, and gives
|
||||||
|
you a chance to check that document exclusions are indeed right
|
||||||
|
for a particular configuration you build (and not that you forgot
|
||||||
|
to add something to a toctree).
|
||||||
|
|
||||||
|
Summary
|
||||||
|
-------
|
||||||
|
|
||||||
|
Based on the above, sphinx_selective_exclude offers extension to let
|
||||||
|
you:
|
||||||
|
|
||||||
|
* Make "only::" directive work in an expected, intuitive manner, using
|
||||||
|
`eager_only` extension.
|
||||||
|
* However, if you apply only:: to toctree::, excluded documents will
|
||||||
|
still be available via full-text search, so you need to use
|
||||||
|
`search_auto_exclude` for that to work as expected.
|
||||||
|
* Similar to search, indexes may also require special treatment, hence
|
||||||
|
there's the `modindex_exclude` extension.
|
||||||
|
|
||||||
|
Most likely, you will want to use all 3 extensions together - if you
|
||||||
|
really want build subsets of docimentation covering sufficiently different
|
||||||
|
configurations from a single doctree. However, if one of them is enough
|
||||||
|
to cover your usecase, that's OK to (and why they were separated into
|
||||||
|
3 extensions, to follow KISS and "least surprise" principles and to
|
||||||
|
not make people deal with things they aren't interested in). In this case,
|
||||||
|
however remember there're other extensions, if you later hit a usecase
|
||||||
|
when they're needed.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
To use these extensions, add https://github.com/pfalcon/sphinx_selective_exclude
|
||||||
|
as a git submodule to your project, in documentation folder (where
|
||||||
|
Sphinx conf.py is located). Alternatively, commit sphinx_selective_exclude
|
||||||
|
directory instead of making it a submodule (you will need to pick up
|
||||||
|
any project updates manually then).
|
||||||
|
|
||||||
|
Add following lines to "extensions" settings in your conf.py (you
|
||||||
|
likely already have some standard Sphinx extensions enabled):
|
||||||
|
|
||||||
|
extensions = [
|
||||||
|
...
|
||||||
|
'sphinx_selective_exclude.eager_only',
|
||||||
|
'sphinx_selective_exclude.search_auto_exclude',
|
||||||
|
'sphinx_selective_exclude.modindex_exclude',
|
||||||
|
]
|
||||||
|
|
||||||
|
As discussed above, you may enable all extensions, or one by one.
|
||||||
|
|
||||||
|
Please note that to make sure these extensions work well and avoid producing
|
||||||
|
output docs with artifacts, it is IMPERATIVE to remove cached doctree if
|
||||||
|
you rebuild documentation with another builder (i.e. with different output
|
||||||
|
format). Also, to stay on safe side, it's recommended to remove old doctree
|
||||||
|
anyway before generating production-ready documentation for publishing. To
|
||||||
|
do that, run something like:
|
||||||
|
|
||||||
|
rm -rf _build/doctrees/
|
||||||
|
|
||||||
|
A typical artificat when not following these simple rules is that content
|
||||||
|
of some sections may be missing. If you face anything like that, just
|
||||||
|
remember what's written above and remove cached doctrees.
|
0
docs/sphinx_selective_exclude/__init__.py
Normal file
0
docs/sphinx_selective_exclude/__init__.py
Normal file
45
docs/sphinx_selective_exclude/eager_only.py
Normal file
45
docs/sphinx_selective_exclude/eager_only.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#
|
||||||
|
# This is a Sphinx documentation tool extension which makes .only::
|
||||||
|
# directives be eagerly processed early in the parsing stage. This
|
||||||
|
# makes sure that content in .only:: blocks gets actually excluded
|
||||||
|
# as a typical user expects, instead of bits of information in
|
||||||
|
# these blocks leaking to documentation in various ways (e.g.,
|
||||||
|
# indexes containing entries for functions which are actually in
|
||||||
|
# .only:: blocks and thus excluded from documentation, etc.)
|
||||||
|
# Note that with this extension, you may need to completely
|
||||||
|
# rebuild a doctree when switching builders (i.e. completely
|
||||||
|
# remove _build/doctree dir between generation of HTML vs PDF
|
||||||
|
# documentation).
|
||||||
|
#
|
||||||
|
# This extension works by monkey-patching Sphinx core, so potentially
|
||||||
|
# may not work with untested Sphinx versions. It tested to work with
|
||||||
|
# 1.2.2 and 1.4.2
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016 Paul Sokolovsky
|
||||||
|
# Based on idea by Andrea Cassioli:
|
||||||
|
# https://github.com/sphinx-doc/sphinx/issues/2150#issuecomment-171912290
|
||||||
|
# Licensed under the terms of BSD license, see LICENSE file.
|
||||||
|
#
|
||||||
|
import sphinx
|
||||||
|
from docutils.parsers.rst import directives
|
||||||
|
|
||||||
|
|
||||||
|
class EagerOnly(sphinx.directives.other.Only):
|
||||||
|
|
||||||
|
def run(self, *args):
|
||||||
|
# Evaluate the condition eagerly, and if false return no nodes right away
|
||||||
|
env = self.state.document.settings.env
|
||||||
|
env.app.builder.tags.add('TRUE')
|
||||||
|
#print(repr(self.arguments[0]))
|
||||||
|
if not env.app.builder.tags.eval_condition(self.arguments[0]):
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Otherwise, do the usual processing
|
||||||
|
nodes = super(EagerOnly, self).run()
|
||||||
|
if len(nodes) == 1:
|
||||||
|
nodes[0]['expr'] = 'TRUE'
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
directives.register_directive('only', EagerOnly)
|
75
docs/sphinx_selective_exclude/modindex_exclude.py
Normal file
75
docs/sphinx_selective_exclude/modindex_exclude.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#
|
||||||
|
# This is a Sphinx documentation tool extension which allows to
|
||||||
|
# exclude some Python modules from the generated indexes. Modules
|
||||||
|
# are excluded both from "modindex" and "genindex" index tables
|
||||||
|
# (in the latter case, all members of a module are exlcuded).
|
||||||
|
# To control exclusion, set "modindex_exclude" variable in Sphinx
|
||||||
|
# conf.py to the list of modules to exclude. Note: these should be
|
||||||
|
# modules (as defined by py:module directive, not just raw filenames).
|
||||||
|
# This extension works by monkey-patching Sphinx core, so potentially
|
||||||
|
# may not work with untested Sphinx versions. It tested to work with
|
||||||
|
# 1.2.2 and 1.4.2
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016 Paul Sokolovsky
|
||||||
|
# Licensed under the terms of BSD license, see LICENSE file.
|
||||||
|
#
|
||||||
|
import sphinx
|
||||||
|
|
||||||
|
|
||||||
|
#org_PythonModuleIndex_generate = None
|
||||||
|
org_PyObject_add_target_and_index = None
|
||||||
|
org_PyModule_run = None
|
||||||
|
|
||||||
|
EXCLUDES = {}
|
||||||
|
|
||||||
|
# No longer used, PyModule_run() monkey-patch does all the job
|
||||||
|
def PythonModuleIndex_generate(self, docnames=None):
|
||||||
|
docnames = []
|
||||||
|
excludes = self.domain.env.config['modindex_exclude']
|
||||||
|
for modname, (docname, synopsis, platforms, deprecated) in self.domain.data['modules'].items():
|
||||||
|
#print(docname)
|
||||||
|
if modname not in excludes:
|
||||||
|
docnames.append(docname)
|
||||||
|
|
||||||
|
return org_PythonModuleIndex_generate(self, docnames)
|
||||||
|
|
||||||
|
|
||||||
|
def PyObject_add_target_and_index(self, name_cls, sig, signode):
|
||||||
|
if hasattr(self.env, "ref_context"):
|
||||||
|
# Sphinx 1.4
|
||||||
|
ref_context = self.env.ref_context
|
||||||
|
else:
|
||||||
|
# Sphinx 1.2
|
||||||
|
ref_context = self.env.temp_data
|
||||||
|
modname = self.options.get(
|
||||||
|
'module', ref_context.get('py:module'))
|
||||||
|
#print("*", modname, name_cls)
|
||||||
|
if modname in self.env.config['modindex_exclude']:
|
||||||
|
return None
|
||||||
|
return org_PyObject_add_target_and_index(self, name_cls, sig, signode)
|
||||||
|
|
||||||
|
|
||||||
|
def PyModule_run(self):
|
||||||
|
env = self.state.document.settings.env
|
||||||
|
modname = self.arguments[0].strip()
|
||||||
|
excl = env.config['modindex_exclude']
|
||||||
|
if modname in excl:
|
||||||
|
self.options['noindex'] = True
|
||||||
|
EXCLUDES.setdefault(modname, []).append(env.docname)
|
||||||
|
return org_PyModule_run(self)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
app.add_config_value('modindex_exclude', [], 'html')
|
||||||
|
|
||||||
|
# global org_PythonModuleIndex_generate
|
||||||
|
# org_PythonModuleIndex_generate = sphinx.domains.python.PythonModuleIndex.generate
|
||||||
|
# sphinx.domains.python.PythonModuleIndex.generate = PythonModuleIndex_generate
|
||||||
|
|
||||||
|
global org_PyObject_add_target_and_index
|
||||||
|
org_PyObject_add_target_and_index = sphinx.domains.python.PyObject.add_target_and_index
|
||||||
|
sphinx.domains.python.PyObject.add_target_and_index = PyObject_add_target_and_index
|
||||||
|
|
||||||
|
global org_PyModule_run
|
||||||
|
org_PyModule_run = sphinx.domains.python.PyModule.run
|
||||||
|
sphinx.domains.python.PyModule.run = PyModule_run
|
34
docs/sphinx_selective_exclude/search_auto_exclude.py
Normal file
34
docs/sphinx_selective_exclude/search_auto_exclude.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#
|
||||||
|
# This is a Sphinx documentation tool extension which allows to
|
||||||
|
# automatically exclude from full-text search index document
|
||||||
|
# which are not referenced via toctree::. It's intended to be
|
||||||
|
# used with toctrees conditional on only:: directive, with the
|
||||||
|
# idea being that if you didn't include it in the ToC, you don't
|
||||||
|
# want the docs being findable by search either (for example,
|
||||||
|
# because these docs contain information not pertinent to a
|
||||||
|
# particular product configuration).
|
||||||
|
#
|
||||||
|
# This extension depends on "eager_only" extension and won't work
|
||||||
|
# without it.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016 Paul Sokolovsky
|
||||||
|
# Licensed under the terms of BSD license, see LICENSE file.
|
||||||
|
#
|
||||||
|
import sphinx
|
||||||
|
|
||||||
|
|
||||||
|
org_StandaloneHTMLBuilder_index_page = None
|
||||||
|
|
||||||
|
|
||||||
|
def StandaloneHTMLBuilder_index_page(self, pagename, doctree, title):
|
||||||
|
if pagename not in self.env.files_to_rebuild:
|
||||||
|
if pagename != self.env.config.master_doc and 'orphan' not in self.env.metadata[pagename]:
|
||||||
|
print("Excluding %s from full-text index because it's not referenced in ToC" % pagename)
|
||||||
|
return
|
||||||
|
return org_StandaloneHTMLBuilder_index_page(self, pagename, doctree, title)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
global org_StandaloneHTMLBuilder_index_page
|
||||||
|
org_StandaloneHTMLBuilder_index_page = sphinx.builders.html.StandaloneHTMLBuilder.index_page
|
||||||
|
sphinx.builders.html.StandaloneHTMLBuilder.index_page = StandaloneHTMLBuilder_index_page
|
Loading…
x
Reference in New Issue
Block a user