|
1 | 1 | """General helpers for the management of config parameters."""
|
2 | 2 |
|
| 3 | +import copy |
| 4 | +import os |
3 | 5 | import re
|
4 |
| -from typing import Any, Dict, Iterator |
| 6 | +from typing import Any, Dict, Iterator, List, Optional, Union |
5 | 7 |
|
6 | 8 | from docutils.nodes import Node
|
7 | 9 | from sphinx.application import Sphinx
|
8 |
| -from sphinx.util import logging |
| 10 | +from sphinx.util import logging, matching |
9 | 11 |
|
10 | 12 |
|
11 | 13 | def get_theme_options_dict(app: Sphinx) -> Dict[str, Any]:
|
@@ -58,3 +60,112 @@ def maybe_warn(app: Sphinx, msg, *args, **kwargs):
|
58 | 60 | should_warn = theme_options.get("surface_warnings", False)
|
59 | 61 | if should_warn:
|
60 | 62 | SPHINX_LOGGER.warning(msg, *args, **kwargs)
|
| 63 | + |
| 64 | + |
| 65 | +def set_secondary_sidebar_items( |
| 66 | + app: Sphinx, pagename: str, templatename: str, context, doctree |
| 67 | +) -> None: |
| 68 | + """Set the secondary sidebar items to render for the given pagename.""" |
| 69 | + if "theme_secondary_sidebar_items" in context: |
| 70 | + templates = context["theme_secondary_sidebar_items"] |
| 71 | + if isinstance(templates, dict): |
| 72 | + templates = _get_matching_sidebar_items(pagename, templates) |
| 73 | + |
| 74 | + context["secondary_sidebar_items"] = _update_and_remove_templates( |
| 75 | + app, |
| 76 | + context, |
| 77 | + templates, |
| 78 | + "theme_secondary_sidebar_items", |
| 79 | + ) |
| 80 | + |
| 81 | + |
| 82 | +def _update_and_remove_templates( |
| 83 | + app: Sphinx, |
| 84 | + context: Dict[str, Any], |
| 85 | + templates: Union[List, str], |
| 86 | + section: str, |
| 87 | + templates_skip_empty_check: Optional[List[str]] = None, |
| 88 | +) -> List[str]: |
| 89 | + """Update templates to include html suffix if needed; remove templates which render empty. |
| 90 | +
|
| 91 | + Args: |
| 92 | + app: Sphinx application passed to the html page context |
| 93 | + context: The html page context; dictionary of values passed to the templating engine |
| 94 | + templates: A list of template names, or a string of comma separated template names |
| 95 | + section: Name of the template section where the templates are to be rendered. Valid |
| 96 | + section names include any of the ``sphinx`` or ``html_theme_options`` that take templates |
| 97 | + or lists of templates as arguments, for example: ``theme_navbar_start``, |
| 98 | + ``theme_primary_sidebar_end``, ``theme_secondary_sidebar_items``, ``sidebars``, etc. For |
| 99 | + a complete list of valid section names, see the source for |
| 100 | + :py:func:`pydata_sphinx_theme.update_and_remove_templates` and |
| 101 | + :py:func:`pydata_sphinx_theme.utils.set_secondary_sidebar_items`, both of which call |
| 102 | + this function. |
| 103 | + templates_skip_empty_check: Names of any templates which should never be removed from the list |
| 104 | + of filtered templates returned by this function. These templates aren't checked if they |
| 105 | + render empty, which can save time if the template is slow to render. |
| 106 | +
|
| 107 | + Returns: |
| 108 | + A list of template names (including '.html' suffix) to render into the section |
| 109 | + """ |
| 110 | + if templates_skip_empty_check is None: |
| 111 | + templates_skip_empty_check = [] |
| 112 | + |
| 113 | + # Break apart `,` separated strings so we can use , in the defaults |
| 114 | + if isinstance(templates, str): |
| 115 | + templates = [template.strip() for template in templates.split(",")] |
| 116 | + |
| 117 | + # Add `.html` to templates with no suffix |
| 118 | + suffixed_templates = [] |
| 119 | + for template in templates: |
| 120 | + if os.path.splitext(template)[1]: |
| 121 | + suffixed_templates.append(template) |
| 122 | + else: |
| 123 | + suffixed_templates.append(f"{template}.html") |
| 124 | + |
| 125 | + ctx = copy.copy(context) |
| 126 | + ctx.update({section: suffixed_templates}) |
| 127 | + |
| 128 | + # Check whether the template renders to an empty string; remove if this is the case |
| 129 | + # Skip templates that are slow to render with templates_skip_empty_check |
| 130 | + filtered_templates = [] |
| 131 | + for template in suffixed_templates: |
| 132 | + if any(template.endswith(item) for item in templates_skip_empty_check): |
| 133 | + filtered_templates.append(template) |
| 134 | + else: |
| 135 | + rendered = app.builder.templates.render(template, ctx) |
| 136 | + if len(rendered.strip()) != 0: |
| 137 | + filtered_templates.append(template) |
| 138 | + |
| 139 | + return filtered_templates |
| 140 | + |
| 141 | + |
| 142 | +def _get_matching_sidebar_items( |
| 143 | + pagename: str, sidebars: Dict[str, List[str]] |
| 144 | +) -> List[str]: |
| 145 | + """Get the matching sidebar templates to render for the given pagename. |
| 146 | +
|
| 147 | + If a page matches more than one pattern, a warning is emitted, and the templates for the |
| 148 | + last matching pattern are used. |
| 149 | +
|
| 150 | + This function was adapted from sphinx.builders.html.StandaloneHTMLBuilder.add_sidebars. |
| 151 | + """ |
| 152 | + matched = None |
| 153 | + secondary_sidebar_items = [] |
| 154 | + for pattern, sidebar_items in sidebars.items(): |
| 155 | + if matching.patmatch(pagename, pattern): |
| 156 | + if matched and _has_wildcard(pattern) and _has_wildcard(matched): |
| 157 | + SPHINX_LOGGER.warning( |
| 158 | + f"Page {pagename} matches two wildcard patterns in secondary_sidebar_items: {matched} and {pattern}" |
| 159 | + ), |
| 160 | + |
| 161 | + matched = pattern |
| 162 | + secondary_sidebar_items = sidebar_items |
| 163 | + return secondary_sidebar_items |
| 164 | + |
| 165 | + |
| 166 | +def _has_wildcard(pattern: str) -> bool: |
| 167 | + """Check whether the pattern contains a wildcard. |
| 168 | +
|
| 169 | + Taken from sphinx.builders.StandaloneHTMLBuilder.add_sidebars. |
| 170 | + """ |
| 171 | + return any(char in pattern for char in "*?[") |
0 commit comments