Georg Brandl avatar Georg Brandl committed ebe4c7c

The ``html_sidebars`` config value can now contain patterns as keys, and the values can be lists that explicitly select which sidebar templates should be rendered.

That means that the builtin sidebar contents can be included only selectively.

Comments (0)

Files changed (15)

 Release 1.0 (in development)
 ============================
 
+* The ``html_sidebars`` config value can now contain patterns as
+  keys, and the values can be lists that explicitly select which
+  sidebar templates should be rendered.  That means that the builtin
+  sidebar contents can be included only selectively.
+
 * ``html_static_path`` can now contain single file entries.
 
 * The new universal config value ``exclude_patterns`` makes the
 html_index = 'index.html'
 
 # Custom sidebar templates, maps page names to templates.
-html_sidebars = {'index': 'indexsidebar.html'}
+html_sidebars = {'index': ['indexsidebar.html', 'searchbox.html']}
 
 # Additional templates that should be rendered to pages, maps page names to
 # templates.
 .. confval:: exclude_patterns
 
    A list of glob-style patterns that should be excluded when looking for source
-   files. [#]_ They are matched against the source file names relative to the
+   files. [1]_ They are matched against the source file names relative to the
    source directory, using slashes as directory separators on all platforms.
 
    Example patterns:
 .. confval:: html_sidebars
 
    Custom sidebar templates, must be a dictionary that maps document names to
-   template names.  Example::
+   template names.
+
+   The keys can contain glob-style patterns [1]_, in which case all matching
+   documents will get the specified sidebars.  (A warning is emitted when a
+   more than one glob-style pattern matches for any document.)
+
+   The values can be either lists or single strings.
+
+   * If a value is a list, it specifies the complete list of sidebar templates
+     to include.  If all or some of the default sidebars are to be included,
+     they must be put into this list as well.
+
+     The default sidebars (for documents that don't match any pattern) are:
+     ``['localtoc.html', 'relations.html', 'sourcelink.html',
+     'searchbox.html']``.
+
+   * If a value is a single string, it specifies a custom sidebar to be added
+     between the ``'sourcelink.html'`` and ``'searchbox.html'`` entries.  This
+     is for compatibility with Sphinx versions before 1.0.
+
+   Builtin sidebar templates that can be rendered are:
+
+   * **localtoc.html** -- a fine-grained table of contents of the current document
+   * **globaltoc.html** -- a coarse-grained table of contents for the whole
+     documentation set, collapsed
+   * **relations.html** -- two links to the previous and next documents
+   * **sourcelink.html** -- a link to the source of the current document, if
+     enabled in :confval:`html_show_sourcelink`
+   * **searchbox.html** -- the "quick search" box
+
+   Example::
 
       html_sidebars = {
-         'using/windows': 'windowssidebar.html'
+         '**': ['globaltoc.html', 'sourcelink.html', 'searchbox.html'],
+         'using/windows': ['windowssidebar.html', 'searchbox.html'],
       }
 
-   This will render the template ``windowssidebar.html`` within the sidebar of
-   the given document.
+   This will render the custom template ``windowssidebar.html`` and the quick
+   search box within the sidebar of the given document, and render the default
+   sidebars for all other pages (except that the local TOC is replaced by the
+   global TOC).
+
+   .. versionadded:: 1.0
+      The ability to use globbing keys and to specify multiple sidebars.
+
+   Note that this value only has no effect if the chosen theme does not possess
+   a sidebar, like the builtin **scrolls** and **haiku** themes.
 
 .. confval:: html_additional_pages
 
 
 .. rubric:: Footnotes
 
-.. [#] A note on available globbing syntax: you can use the standard shell
+.. [1] 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.

sphinx/builders/html.py

 from docutils.readers.doctree import Reader as DoctreeReader
 
 from sphinx import package_dir, __version__
-from sphinx.util import SEP, os_path, relative_uri, ensuredir, \
-    movefile, ustrftime, copy_static_entry, copyfile, compile_matchers
+from sphinx.util import SEP, os_path, relative_uri, ensuredir, patmatch, \
+    movefile, ustrftime, copy_static_entry, copyfile, compile_matchers, any
 from sphinx.errors import SphinxError
 from sphinx.search import js_index
 from sphinx.theming import Theme
     # Dito for this one.
     css_files = []
 
+    default_sidebars = ['localtoc.html', 'relations.html',
+                        'sourcelink.html', 'searchbox.html']
+
     # cached publisher object for snippets
     _publisher = None
 
     def get_outfilename(self, pagename):
         return path.join(self.outdir, os_path(pagename) + self.out_suffix)
 
+    def get_sidebars(self, pagename):
+        def has_wildcard(pattern):
+            return any(char in pattern for char in '*?[')
+        sidebars = None
+        matched = None
+        for pattern, patsidebars in self.config.html_sidebars.iteritems():
+            if patmatch(pagename, pattern):
+                if matched:
+                    if has_wildcard(pattern):
+                        # warn if both patterns contain wildcards
+                        if has_wildcard(matched):
+                            self.warn('page %s matches two patterns in '
+                                      'html_sidebars: %r and %r' %
+                                      (pagename, matched, pattern))
+                        # else the already matched pattern is more specific
+                        # than the present one, because it contains no wildcard
+                        continue
+                matched = pattern
+                sidebars = patsidebars
+        if sidebars is None:
+            sidebars = self.default_sidebars
+        elif isinstance(sidebars, basestring):
+            # 0.x compatible mode: insert custom sidebar before searchbox
+            sidebars = self.default_sidebars[:-1] + [sidebars] + \
+                       self.default_sidebars[-1:]
+        return sidebars
+
     # --------- these are overwritten by the serialization builder
 
     def get_target_uri(self, docname, typ=None):
             return uri
         ctx['pathto'] = pathto
         ctx['hasdoc'] = lambda name: name in self.env.all_docs
-        ctx['customsidebar'] = self.config.html_sidebars.get(pagename)
         ctx['encoding'] = encoding = self.config.html_output_encoding
         ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw)
+        ctx['sidebars'] = self.get_sidebars(pagename)
         ctx.update(addctx)
 
         self.app.emit('html-page-context', pagename, templatename,
     def handle_page(self, pagename, ctx, templatename='page.html',
                     outfilename=None, event_arg=None):
         ctx['current_page_name'] = pagename
-        sidebarfile = self.config.html_sidebars.get(pagename)
-        if sidebarfile:
-            ctx['customsidebar'] = sidebarfile
+        ctx['sidebars'] = self.get_sidebars(pagename)
 
         if not outfilename:
             outfilename = path.join(self.outdir,

sphinx/themes/basic/globaltoc.html

+{#
+    basic/globaltoc.html
+    ~~~~~~~~~~~~~~~~~~~~
+
+    Sphinx sidebar template: global table of contents.
+
+    :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{%- block sidebartoc %}
+{%- if display_toc %}
+  <h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
+  {{ toctree() }}
+{%- endif %}
+{%- endblock %}

sphinx/themes/basic/layout.html

             </a></p>
           {%- endif %}
           {%- endblock %}
-          {%- block sidebartoc %}
-          {%- if display_toc %}
-            <h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
-            {{ toc }}
-          {%- endif %}
-          {%- endblock %}
-          {%- block sidebarrel %}
-          {%- if prev %}
-            <h4>{{ _('Previous topic') }}</h4>
-            <p class="topless"><a href="{{ prev.link|e }}"
-                                  title="{{ _('previous chapter') }}">{{ prev.title }}</a></p>
-          {%- endif %}
-          {%- if next %}
-            <h4>{{ _('Next topic') }}</h4>
-            <p class="topless"><a href="{{ next.link|e }}"
-                                  title="{{ _('next chapter') }}">{{ next.title }}</a></p>
-          {%- endif %}
-          {%- endblock %}
-          {%- block sidebarsourcelink %}
-          {%- if show_source and has_source and sourcename %}
-            <h3>{{ _('This Page') }}</h3>
-            <ul class="this-page-menu">
-              <li><a href="{{ pathto('_sources/' + sourcename, true)|e }}"
-                     rel="nofollow">{{ _('Show Source') }}</a></li>
-            </ul>
-          {%- endif %}
-          {%- endblock %}
-          {%- if customsidebar %}
-          {% include customsidebar %}
-          {%- endif %}
-          {%- block sidebarsearch %}
-          {%- if pagename != "search" %}
-          <div id="searchbox" style="display: none">
-            <h3>{{ _('Quick search') }}</h3>
-              <form class="search" action="{{ pathto('search') }}" method="get">
-                <input type="text" name="q" size="18" />
-                <input type="submit" value="{{ _('Go') }}" />
-                <input type="hidden" name="check_keywords" value="yes" />
-                <input type="hidden" name="area" value="default" />
-              </form>
-              <p class="searchtip" style="font-size: 90%">
-              {{ _('Enter search terms or a module, class or function name.') }}
-              </p>
-          </div>
-          <script type="text/javascript">$('#searchbox').show(0);</script>
-          {%- endif %}
-          {%- endblock %}
+          {%- for sidebar in sidebars %}
+          {%- include sidebar %}
+          {%- endfor %}
         </div>
       </div>
       {%- endif %}{% endif %}

sphinx/themes/basic/localtoc.html

+{#
+    basic/localtoc.html
+    ~~~~~~~~~~~~~~~~~~~
+
+    Sphinx sidebar template: local table of contents.
+
+    :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{%- block sidebartoc %}
+{%- if display_toc %}
+  <h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
+  {{ toc }}
+{%- endif %}
+{%- endblock %}

sphinx/themes/basic/relations.html

+{#
+    basic/relations.html
+    ~~~~~~~~~~~~~~~~~~~~
+
+    Sphinx sidebar template: relation links.
+
+    :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{%- block sidebarrel %}
+{%- if prev %}
+  <h4>{{ _('Previous topic') }}</h4>
+  <p class="topless"><a href="{{ prev.link|e }}"
+                        title="{{ _('previous chapter') }}">{{ prev.title }}</a></p>
+{%- endif %}
+{%- if next %}
+  <h4>{{ _('Next topic') }}</h4>
+  <p class="topless"><a href="{{ next.link|e }}"
+                        title="{{ _('next chapter') }}">{{ next.title }}</a></p>
+{%- endif %}
+{%- endblock %}

sphinx/themes/basic/searchbox.html

+{#
+    basic/searchbox.html
+    ~~~~~~~~~~~~~~~~~~~~
+
+    Sphinx sidebar template: quick search box.
+
+    :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{%- block sidebarsearch %}
+{%- if pagename != "search" %}
+<div id="searchbox" style="display: none">
+  <h3>{{ _('Quick search') }}</h3>
+    <form class="search" action="{{ pathto('search') }}" method="get">
+      <input type="text" name="q" size="18" />
+      <input type="submit" value="{{ _('Go') }}" />
+      <input type="hidden" name="check_keywords" value="yes" />
+      <input type="hidden" name="area" value="default" />
+    </form>
+    <p class="searchtip" style="font-size: 90%">
+    {{ _('Enter search terms or a module, class or function name.') }}
+    </p>
+</div>
+<script type="text/javascript">$('#searchbox').show(0);</script>
+{%- endif %}
+{%- endblock %}

sphinx/themes/basic/sourcelink.html

+{#
+    basic/sourcelink.html
+    ~~~~~~~~~~~~~~~~~~~~~
+
+    Sphinx sidebar template: "show source" link.
+
+    :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{%- block sidebarsourcelink %}
+{%- if show_source and has_source and sourcename %}
+  <h3>{{ _('This Page') }}</h3>
+  <ul class="this-page-menu">
+    <li><a href="{{ pathto('_sources/' + sourcename, true)|e }}"
+           rel="nofollow">{{ _('Show Source') }}</a></li>
+  </ul>
+{%- endif %}
+{%- endblock %}

sphinx/util/__init__.py

 import os
 import re
 import sys
-import stat
 import time
 import errno
 import types
 
 _pat_cache = {}
 
+def patmatch(name, pat):
+    """
+    Return if name matches pat.  Adapted from fnmatch module.
+    """
+    if pat not in _pat_cache:
+        _pat_cache[pat] = re.compile(_translate_pattern(pat))
+    return _pat_cache[pat].match(name)
+
 def patfilter(names, pat):
     """
     Return the subset of the list NAMES that match PAT.
     return False, text, text
 
 
+try:
+    any = any
+except NameError:
+    def any(gen):
+        for i in gen:
+            if i:
+                return True
+        return False
+
 # monkey-patch Node.traverse to get more speed
 # traverse() is called so many times during a build that it saves
 # on average 20-25% overall build time!

tests/root/_templates/contentssb.html

+{# sidebar only for contents document #}
+<h4>Contents sidebar</h4>

tests/root/_templates/customsb.html

+{# custom sidebar template #}
+<h4>Custom sidebar</h4>

tests/root/conf.py

 html_theme = 'testtheme'
 html_theme_path = ['.']
 html_theme_options = {'testopt': 'testoverride'}
-
+html_sidebars = {'**': 'customsb.html', 'contents': 'contentssb.html'}
 html_style = 'default.css'
 html_static_path = ['_static', 'templated.css_t']
 html_last_updated_fmt = '%b %d, %Y'

tests/test_build_html.py

         ".//dl[@class='userdesc']": '',
         ".//dt[@id='userdescrole-myobj']": '',
         ".//a[@href='#userdescrole-myobj']": '',
+        # custom sidebar
+        ".//h4": 'Custom sidebar',
     },
     'contents.html': {
         ".//meta[@name='hc'][@content='hcval']": '',
         ".//title": 'Sphinx <Tests>',
         ".//div[@class='footer']": 'Georg Brandl & Team',
         ".//a[@href='http://python.org/']": '',
+        # custom sidebar only for contents
+        ".//h4": 'Contents sidebar',
     },
     'bom.html': {
         ".//title": " File with UTF-8 BOM",
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.