Add globbing-style toctree entries.

This commit is contained in:
Georg Brandl 2008-05-04 16:57:15 +00:00
parent 407a0c47c1
commit 548cdc858a
5 changed files with 134 additions and 19 deletions

View File

@ -4,9 +4,11 @@ Changes in trunk
New features added
------------------
* The ``toctree`` directive now supports a ``glob`` option that allows
glob-style entries in the content.
* If the `pygments_style` config value contains a dot it's treated as the
import path of a custom Pygments style class.
* autodoc detects descriptors properly now
* A new config value, `exclude_dirs`, can be used to exclude whole
directories from the search for source files.
@ -36,6 +38,8 @@ Bugs fixed
and index file. Remove two remaining instances of hard-coded
"documentation".
* sphinx.ext.autodoc: descriptors are detected properly now.
* Lots of little fixes to the LaTeX output and style.
* Fix OpenSearch template and make template URL absolute. The

View File

@ -70,18 +70,36 @@ tables of contents. The ``toctree`` directive is the central element.
The second line above will link to the ``strings`` document, but will use the
title "All about strings" instead of the title of the ``strings`` document.
You can use "globbing" in toctree directives, by giving the ``glob`` flag
option. All entries are then matched against the list of available
documents, and matches are inserted into the list alphabetically. Example::
.. toctree::
:glob:
intro*
recipe/*
*
This includes first all documents whose names start with ``intro``, then all
documents in the ``recipe`` folder, then all remaining documents (except the
one containing the directive, of course.) [#]_
In the end, all documents in the :term:`source directory` (or subdirectories)
must occur in some ``toctree`` directive; Sphinx will emit a warning if it
finds a file that is not included, because that means that this file will not
be reachable through standard navigation. Use :confval:`unused_documents` to
explicitly exclude documents from this check, and :confval:`exclude_dirs` to
explicitly exclude documents from building, and :confval:`exclude_dirs` to
exclude whole directories.
The "master document" (selected by :confval:`master_doc`) is the "root" of
the TOC tree hierarchy. It can be used as the documentation's main page, or
as a "full table of contents" if you don't give a ``maxdepth`` option.
.. versionchanged:: 0.2.1
Added "globbing" option.
Special names
-------------
@ -110,3 +128,11 @@ The special document names (and pages generated for them) are:
Though only few such names are currently used by Sphinx, you should not create
documents or document-containing directories with such names. (Using ``_`` as
a prefix for a custom template directory is fine.)
.. rubric:: Footnotes
.. [#] A note on available globbing syntax: you can use the standard shell
constructs ``*``, ``?``, ``[...]`` and ``[!...]`` with the feature that
these all don't match slashes. A double star ``**`` can be used to match
any sequence of characters *including* slashes.

View File

@ -19,6 +19,7 @@ from docutils import nodes
from docutils.parsers.rst import directives
from sphinx import addnodes
from sphinx.util import patfilter
from sphinx.roles import caption_ref_re
from sphinx.util.compat import make_admonition
@ -650,28 +651,46 @@ def toctree_directive(name, arguments, options, content, lineno,
env = state.document.settings.env
suffix = env.config.source_suffix
dirname = posixpath.dirname(env.docname)
glob = 'glob' in options
ret = []
subnode = addnodes.toctree()
includefiles = []
includetitles = {}
for docname in content:
if not docname:
all_docnames = env.found_docs.copy()
# don't add the currently visited file in catch-all patterns
all_docnames.remove(env.docname)
for entry in content:
if not entry:
continue
# look for explicit titles and documents ("Some Title <document>").
m = caption_ref_re.match(docname)
if m:
docname = m.group(2)
includetitles[docname] = m.group(1)
# absolutize filenames, remove suffixes
if docname.endswith(suffix):
docname = docname[:-len(suffix)]
docname = posixpath.normpath(posixpath.join(dirname, docname))
if docname not in env.found_docs:
ret.append(state.document.reporter.warning(
'toctree references unknown document %r' % docname, line=lineno))
if not glob:
# look for explicit titles and documents ("Some Title <document>").
m = caption_ref_re.match(entry)
if m:
docname = m.group(2)
includetitles[docname] = m.group(1)
else:
docname = entry
# remove suffixes (backwards compatibility)
if docname.endswith(suffix):
docname = docname[:-len(suffix)]
# absolutize filenames
docname = posixpath.normpath(posixpath.join(dirname, docname))
if docname not in env.found_docs:
ret.append(state.document.reporter.warning(
'toctree references unknown document %r' % docname, line=lineno))
else:
includefiles.append(docname)
else:
includefiles.append(docname)
patname = posixpath.normpath(posixpath.join(dirname, entry))
docnames = sorted(patfilter(all_docnames, patname))
for docname in docnames:
all_docnames.remove(docname) # don't include it again
includefiles.append(docname)
if not docnames:
ret.append(state.document.reporter.warning(
'toctree glob pattern %r didn\'t match any documents' % entry,
line=lineno))
subnode['includefiles'] = includefiles
subnode['includetitles'] = includetitles
subnode['maxdepth'] = options.get('maxdepth', -1)
@ -679,7 +698,7 @@ def toctree_directive(name, arguments, options, content, lineno,
return ret
toctree_directive.content = 1
toctree_directive.options = {'maxdepth': int}
toctree_directive.options = {'maxdepth': int, 'glob': directives.flag}
directives.register_directive('toctree', toctree_directive)

View File

@ -242,6 +242,10 @@ p {
margin: 0.8em 0 0.5em 0;
}
p.rubric {
font-weight: bold;
}
h1 {
margin: 0;
padding: 0.7em 0 0.3em 0;

View File

@ -10,6 +10,7 @@
"""
import os
import re
import sys
import fnmatch
import tempfile
@ -158,7 +159,7 @@ def fmt_ex(ex):
def rpartition(s, t):
"""Similar to str.rpartition from 2.5."""
"""Similar to str.rpartition from 2.5, but doesn't return the separator."""
i = s.rfind(t)
if i != -1:
return s[:i], s[i+len(t):]
@ -187,3 +188,64 @@ def save_traceback():
os.write(fd, exc)
os.close(fd)
return path
def _translate_pattern(pat):
"""
Translate a shell-style glob pattern to a regular expression.
Adapted from the fnmatch module, but enhanced so that single stars don't
match slashes.
"""
i, n = 0, len(pat)
res = ''
while i < n:
c = pat[i]
i += 1
if c == '*':
if i < n and pat[i] == '*':
# double star matches slashes too
i += 1
res = res + '.*'
else:
# single star doesn't match slashes
res = res + '[^/]*'
elif c == '?':
# question mark doesn't match slashes too
res = res + '[^/]'
elif c == '[':
j = i
if j < n and pat[j] == '!':
j += 1
if j < n and pat[j] == ']':
j += 1
while j < n and pat[j] != ']':
j += 1
if j >= n:
res = res + '\\['
else:
stuff = pat[i:j].replace('\\', '\\\\')
i = j + 1
if stuff[0] == '!':
# negative pattern mustn't match slashes too
stuff = '^/' + stuff[1:]
elif stuff[0] == '^':
stuff = '\\' + stuff
res = '%s[%s]' % (res, stuff)
else:
res += re.escape(c)
return res + '$'
_pat_cache = {}
def patfilter(names, pat):
"""
Return the subset of the list NAMES that match PAT.
Adapted from fnmatch module.
"""
result = []
if not pat in _pat_cache:
_pat_cache[pat] = re.compile(_translate_pattern(pat))
match = _pat_cache[pat].match
return filter(match, names)