Skip to content

Commit

Permalink
HTML search: allow configuration of the search index filename
Browse files Browse the repository at this point in the history
  • Loading branch information
jayaddison committed Jul 26, 2024
1 parent ace5744 commit 79f6959
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 3 deletions.
7 changes: 7 additions & 0 deletions doc/usage/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2129,6 +2129,13 @@ and also make use of these options.
.. versionadded:: 1.2

.. confval:: html_search_filename
:type: :code-py:`str`
:default: :code-py:`searchindex.js`

Controls the filename of the JavaScript search index that will be
written in HTML output.

.. confval:: html_scaled_image_link
:type: :code-py:`bool`
:default: :code-py:`True`
Expand Down
23 changes: 21 additions & 2 deletions sphinx/builders/html/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ class StandaloneHTMLBuilder(Builder):
'image/gif', 'image/jpeg']
supported_remote_images = True
supported_data_uri_images = True
searchindex_filename = 'searchindex.js'
add_permalinks = True
allow_sharp_as_current_path = True
embedded = False # for things like HTML help or Qt help: suppresses sidebar
Expand Down Expand Up @@ -249,6 +248,8 @@ def init(self) -> None:

self.use_index = self.get_builder_config('use_index', 'html')

self.searchindex_filename = self.get_builder_config('search_filename', 'html')

def create_build_info(self) -> BuildInfo:
return BuildInfo(self.config, self.tags, frozenset({'html'}))

Expand Down Expand Up @@ -716,7 +717,8 @@ def gen_additional_pages(self) -> None:
# the search page
if self.search:
logger.info('search ', nonl=True)
self.handle_page('search', {}, 'search.html')
searchcontext = {'search_index': self.searchindex_filename}
self.handle_page('search', searchcontext, 'search.html')

# the opensearch xml file
if self.config.html_use_opensearch and self.search:
Expand Down Expand Up @@ -1320,6 +1322,21 @@ def validate_html_favicon(app: Sphinx, config: Config) -> None:
config.html_favicon = None


def validate_html_search_filename(app: Sphinx, config: Config) -> None:
"""Check html_search_filename setting."""
if config.html_search_filename:
if isurl(config.html_search_filename):
logger.warning(__('html_search_filename must not be a URL'))
config.html_search_filename = 'searchindex.js'
if not config.html_search_filename.endswith('.js'):
logger.warning(__('html_search_filename must have a .js suffix'))
config.html_search_filename = 'searchindex.js'
search_path = path.normpath(path.join(app.outdir, config.html_search_filename))
if path.commonpath((app.outdir, search_path)) != path.normpath(app.outdir):
logger.warning(__('html_search_filename must be within the output directory'))
config.html_search_filename = 'searchindex.js'


def error_on_html_sidebars_string_values(app: Sphinx, config: Config) -> None:
"""Support removed in Sphinx 2."""
errors = {}
Expand Down Expand Up @@ -1387,6 +1404,7 @@ def setup(app: Sphinx) -> ExtensionMetadata:
app.add_config_value('html_search_language', None, 'html', str)
app.add_config_value('html_search_options', {}, 'html')
app.add_config_value('html_search_scorer', '', '')
app.add_config_value('html_search_filename', 'searchindex.js', 'html', str)
app.add_config_value('html_scaled_image_link', True, 'html')
app.add_config_value('html_baseurl', '', 'html')
# removal is indefinitely on hold (ref: https://github.com/sphinx-doc/sphinx/issues/10265)
Expand All @@ -1406,6 +1424,7 @@ def setup(app: Sphinx) -> ExtensionMetadata:
app.connect('config-inited', validate_html_static_path, priority=800)
app.connect('config-inited', validate_html_logo, priority=800)
app.connect('config-inited', validate_html_favicon, priority=800)
app.connect('config-inited', validate_html_search_filename, priority=800)
app.connect('config-inited', error_on_html_sidebars_string_values, priority=800)
app.connect('config-inited', error_on_html_4, priority=800)
app.connect('builder-inited', validate_math_renderer)
Expand Down
2 changes: 1 addition & 1 deletion sphinx/themes/basic/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<script src="{{ pathto('_static/language_data.js', 1) }}"></script>
{%- endblock %}
{% block extrahead %}
<script src="{{ pathto('searchindex.js', 1) }}" defer="defer"></script>
<script src="{{ pathto(search_index, 1) }}" defer="defer"></script>
<meta name="robots" content="noindex" />
{{ super() }}
{% endblock %}
Expand Down
33 changes: 33 additions & 0 deletions tests/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,39 @@ def test_search_index_is_deterministic(app):
assert_is_sorted(index, '')


@pytest.mark.sphinx(testroot='search',
confoverrides={'html_search_filename': '_static/searchindex.js'})
def test_search_index_alternate_filename(app):
app.build(force_all=True)
assert (app.outdir / '_static' / 'searchindex.js').exists()


@pytest.mark.sphinx(testroot='search',
confoverrides={'html_search_filename': 'http://example.invalid/searchindex.js'})
def test_search_index_url_filename_rejected(app):
app.build(force_all=True)
assert (app.outdir / 'searchindex.js').exists() # default searchindex.js location
assert "must not be a URL" in app.warning.getvalue()


@pytest.mark.sphinx(testroot='search',
confoverrides={'html_search_filename': 'searchindex.html'})
def test_search_index_non_js_filename_rejected(app):
app.build(force_all=True)
assert not (app.outdir / 'searchindex.html').exists()
assert (app.outdir / 'searchindex.js').exists() # default searchindex.js location
assert "must have a .js suffix" in app.warning.getvalue()


@pytest.mark.sphinx(testroot='search',
confoverrides={'html_search_filename': '../disallowed.js'})
def test_search_index_filename_outside_outdir_rejected(app):
app.build(force_all=True)
assert not (app.outdir / '..' / 'disallowed.js').exists()
assert (app.outdir / 'searchindex.js').exists() # default searchindex.js location
assert "must be within the output directory" in app.warning.getvalue()


def is_title_tuple_type(item: list[int | str]):
"""
In the search index, titles inside .alltitles are stored as a tuple of
Expand Down

0 comments on commit 79f6959

Please sign in to comment.