From 548cdc858a2c80b1900eaab21b2f01628494419f Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 4 May 2008 16:57:15 +0000 Subject: [PATCH] Add globbing-style toctree entries. --- CHANGES | 6 +++- doc/concepts.rst | 28 +++++++++++++++- sphinx/directives.py | 51 +++++++++++++++++++---------- sphinx/static/sphinxdoc.css | 4 +++ sphinx/util/__init__.py | 64 ++++++++++++++++++++++++++++++++++++- 5 files changed, 134 insertions(+), 19 deletions(-) diff --git a/CHANGES b/CHANGES index 96289e966..46ed307c7 100644 --- a/CHANGES +++ b/CHANGES @@ -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 diff --git a/doc/concepts.rst b/doc/concepts.rst index 91df97521..2b1355940 100644 --- a/doc/concepts.rst +++ b/doc/concepts.rst @@ -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. diff --git a/sphinx/directives.py b/sphinx/directives.py index da0a8af54..06cb63637 100644 --- a/sphinx/directives.py +++ b/sphinx/directives.py @@ -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 "). - 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 "). + 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) diff --git a/sphinx/static/sphinxdoc.css b/sphinx/static/sphinxdoc.css index 6dddd299e..aae20e72b 100644 --- a/sphinx/static/sphinxdoc.css +++ b/sphinx/static/sphinxdoc.css @@ -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; diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 762179b0b..5100ca35c 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -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)