Skip to content

Commit 54b211c

Browse files
Revathyvenugopal162pyansys-ci-botjorgepiloto
authored
feat: add static search (#487)
Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: Jorge Martínez <[email protected]>
1 parent bd04fb7 commit 54b211c

File tree

12 files changed

+466
-403
lines changed

12 files changed

+466
-403
lines changed

doc/changelog.d/487.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
feat: add static search

doc/source/conf.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
ansys_favicon,
1616
ansys_logo_white,
1717
ansys_logo_white_cropped,
18-
convert_version_to_pymeilisearch,
1918
generate_404,
2019
get_version_match,
2120
latex,
@@ -60,12 +59,6 @@
6059
"json_url": f"https://{cname}/versions.json",
6160
"version_match": get_version_match(__version__),
6261
},
63-
"use_meilisearch": {
64-
"api_key": os.getenv("MEILISEARCH_PUBLIC_API_KEY", ""),
65-
"index_uids": {
66-
f"ansys-sphinx-theme-v{convert_version_to_pymeilisearch(__version__)}": "ansys-sphinx-theme", # noqa: E501
67-
},
68-
},
6962
"ansys_sphinx_theme_autoapi": {
7063
"project": project,
7164
"directory": "src/ansys_sphinx_theme/examples",
@@ -74,6 +67,10 @@
7467
"package_depth": 1,
7568
},
7669
"logo": "ansys",
70+
"static_search": {
71+
"threshold": 0.5,
72+
"ignoreLocation": True,
73+
},
7774
}
7875

7976

doc/source/user-guide/options.rst

Lines changed: 25 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -108,92 +108,51 @@ If you want to hide all icons, use the ``show_icons`` Boolean variable:
108108
...
109109
}
110110
111-
Use MeiliSearch
112-
----------------
113-
114-
MeiliSearch is an open source search engine that allows developers to
115-
easily integrate search functionality into their applications.
116-
117-
To use MeiliSearch in your documentation, in the ``conf.py`` file,
118-
a child dictionary named ``use_meilisearch``is added to the ``html_theme_options``
119-
dictionary.
120-
121-
This dictionary contains these keys, in the order given:
122-
123-
#. ``host``: Host name of your MeiliSearch instance. If no value is provided,
124-
the default public host for PyAnsys is used: ``https://backend.search.pyansys.com``
125-
on port ``7700``. If added security is needed, you can use the ``os.getenv()`` function
126-
to set the instance using an environment variable.
111+
Static search options
112+
----------------------
127113

128-
#. ``api_key``: API key for your MeiliSearch instance. If no value is provided,
129-
the default public API key for PyAnsys is used. If added security is needed,
130-
you can use the ``os.getenv()`` function to set the key using an environment
131-
variable.
114+
The Ansys Sphinx theme supports static search options to customize the search experience.
132115

133-
#. ``index_uids``: Dictionary that provides the mapping between the unique
134-
identifier (UID) of an index and its corresponding user-friendly name.
135-
Each key-value pair in the dictionary represents an index, with the key
136-
being the index UID and the value being the index name. The index UID
137-
points to an index on the server.
116+
The static search bar is created using ``Fuse.js``. You can provide all options supported by ``Fuse.js`` through the ``static_search`` dictionary in the ``html_theme_options``.
138117

139-
Here is an example of how to configure MeiliSearch for use in the ``conf.py`` file:
118+
Additional options include:
140119

141-
.. code-block:: python
142-
143-
import os
120+
1. ``keys``: List of keys to search in the documents. Default are ``["title", "text"]``.
121+
2. ``threshold``: The minimum score a search result must have to be included in the results. Default is ``0.5``.
122+
3. ``ignoreLocation``: Whether to ignore the location of the search term in the document. Default is ``False``. Ignoring the location can increase the search speed for large documents.
123+
4. ``limit``: The maximum number of search results to display. Default is ``10``.
124+
5. ``min_chars_for_search``: The minimum number of characters required to start the search. Default is ``1``.
144125

145-
use_meilisearch = {
146-
"host": os.getenv("MEILISEARCH_HOST_NAME", ""),
147-
"api_key": os.getenv("MEILISEARCH_API_KEY", ""),
148-
"index_uids": {
149-
"index-uid of current project": "index name to display",
150-
"another-index-uid": "index name to display",
151-
},
152-
}
126+
.. note::
153127

128+
All other options are available in the `Fuse.js documentation <https://fusejs.io/api/options.html>`_.
154129

155-
If your project features multiple documentation versions, it's crucial to adapt the
156-
``index_uids`` mapping to accommodate different versions. To ensure seamless search
157-
integration across versions, use the following format to dynamically generate
158-
version-specific index ``UIDs``:
130+
Here is an example of how to add the ``static_search`` dictionary to the ``html_theme_options`` dictionary:
159131

160132
.. code-block:: python
161133
162-
from ansys_sphinx_theme import convert_version_to_pymeilisearch
163-
164-
use_meilisearch = {
165-
"api_key": os.getenv("MEILISEARCH_PUBLIC_API_KEY", ""),
166-
"index_uids": {
167-
f"ansys-sphinx-theme-v{convert_version_to_pymeilisearch(__version__)}": "ansys-sphinx-theme",
134+
html_theme_options = {
135+
"static_search": {
136+
"threshold": 0.5,
137+
"limit": 10,
138+
"min_chars_for_search": 1,
168139
},
169140
}
170141
171142
172-
Here is an example configuration of how to configure MeiliSearch in the ``conf.py`` file
173-
for the Ansys Sphinx Theme:
174-
175-
.. code-block:: python
143+
.. note::
176144

177-
import os
145+
Serve locally your documentation using the ``python -m http.server -d /path/to/docs/html/`` to have a live-preview of your search results. This method is compliant with the `CORS policy <https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS>`_ and allows to load the generated resource files containing the indices of your documentation.
146+
The search bar does not work if you open the HTML files directly in the browser.
178147

179-
html_theme_options = {
180-
"use_meilisearch": {
181-
"index_uids": {
182-
"ansys-sphinx-theme-sphinx-docs": "ansys-sphinx-theme",
183-
"pyansys-docs-all-public": "PyAnsys",
184-
},
185-
},
186-
}
148+
To open the documentation in a local server, run the following command in the directory where the HTML files are located:
187149

150+
.. code-block:: bash
188151
189-
With these options set, MeiliSearch is available for performing searches of
190-
your documentation.
152+
python -m http.server
191153
192-
.. note::
154+
Then, open the browser and go to ``http://localhost:8000``.
193155

194-
If you do not set the ``use_meilisearch`` dictionary, the
195-
Ansys Sphinx Theme uses the default search functionality
196-
inherited from the PyData Sphinx Theme.
197156

198157
Cheat sheets
199158
------------

src/ansys_sphinx_theme/__init__.py

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
from ansys_sphinx_theme.extension.linkcode import DOMAIN_KEYS, sphinx_linkcode_resolve
3636
from ansys_sphinx_theme.latex import generate_404 # noqa: F401
37+
from ansys_sphinx_theme.search import create_search_index, update_search_config
3738

3839
try:
3940
import importlib.metadata as importlib_metadata
@@ -125,27 +126,6 @@ def get_version_match(semver: str) -> str:
125126
return ".".join([major, minor])
126127

127128

128-
def convert_version_to_pymeilisearch(semver: str) -> str:
129-
"""Convert a semantic version number to pymeilisearch-compatible format.
130-
131-
This function evaluates the given semantic version number and returns a
132-
version number that is compatible with `pymeilisearch`, where dots are
133-
replaced with hyphens.
134-
135-
Parameters
136-
----------
137-
semver : str
138-
Semantic version number in the form of a string.
139-
140-
Returns
141-
-------
142-
str
143-
pymeilisearch-compatible version number.
144-
"""
145-
version = get_version_match(semver).replace(".", "-")
146-
return version
147-
148-
149129
def setup_default_html_theme_options(app):
150130
"""Set up the default configuration for the HTML options.
151131
@@ -534,6 +514,21 @@ def build_quarto_cheatsheet(app: Sphinx):
534514
app.config.html_theme_options["cheatsheet"]["thumbnail"] = f"{output_dir}/{output_png}"
535515

536516

517+
def check_for_depreciated_theme_options(app: Sphinx):
518+
"""Check for depreciated theme options.
519+
520+
Parameters
521+
----------
522+
app : sphinx.application.Sphinx
523+
Application instance for rendering the documentation.
524+
"""
525+
theme_options = app.config.html_theme_options
526+
if "use_meilisearch" in theme_options:
527+
raise DeprecationWarning(
528+
"The 'use_meilisearch' option is deprecated. Remove the option from your configuration file." # noqa: E501
529+
)
530+
531+
537532
def setup(app: Sphinx) -> Dict:
538533
"""Connect to the Sphinx theme app.
539534
@@ -556,6 +551,8 @@ def setup(app: Sphinx) -> Dict:
556551
# Add default HTML configuration
557552
setup_default_html_theme_options(app)
558553

554+
update_search_config(app)
555+
559556
# Verify that the main CSS file exists
560557
if not CSS_PATH.exists():
561558
raise FileNotFoundError(f"Unable to locate ansys-sphinx theme at {CSS_PATH.absolute()}")
@@ -567,10 +564,12 @@ def setup(app: Sphinx) -> Dict:
567564
app.add_css_file("https://www.nerdfonts.com/assets/css/webfont.css")
568565
app.connect("builder-inited", configure_theme_logo)
569566
app.connect("builder-inited", build_quarto_cheatsheet)
567+
app.connect("builder-inited", check_for_depreciated_theme_options)
570568
app.connect("html-page-context", update_footer_theme)
571569
app.connect("html-page-context", fix_edit_html_page_context)
572570
app.connect("html-page-context", add_cheat_sheet)
573571
app.connect("build-finished", replace_html_tag)
572+
app.connect("build-finished", create_search_index)
574573
return {
575574
"version": __version__,
576575
"parallel_read_safe": True,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
"""Initiate the search."""
23+
24+
from sphinx.application import Sphinx
25+
26+
from ansys_sphinx_theme.search.fuse_search import create_search_index
27+
28+
29+
def update_search_config(app: Sphinx) -> None:
30+
"""Update the search configuration.
31+
32+
Parameters
33+
----------
34+
app : Sphinx
35+
Sphinx application.
36+
"""
37+
theme_static_options = app.config.html_theme_options.get("static_search", {})
38+
theme_static_options["keys"] = ["title", "text"]
39+
theme_static_options["threshold"] = theme_static_options.get("threshold", 0.5)
40+
theme_static_options["shouldSort"] = theme_static_options.get("shouldSort", "True")
41+
theme_static_options["ignoreLocation"] = theme_static_options.get("ignoreLocation", "False")
42+
theme_static_options["useExtendedSearch"] = theme_static_options.get(
43+
"useExtendedSearch", "True"
44+
)
45+
theme_static_options["limit"] = theme_static_options.get("limit", 10)
46+
theme_static_options["min_chars_for_search"] = theme_static_options.get(
47+
"min_chars_for_search", 1
48+
)
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
"""Module to fuse search."""
24+
25+
import json
26+
import re
27+
28+
from docutils import nodes
29+
30+
31+
class SearchIndex:
32+
"""Class to get search index."""
33+
34+
def __init__(self, doc_name, app):
35+
"""Initialize the class.
36+
37+
Parameters
38+
----------
39+
doc_name : str
40+
Document name.
41+
app : Sphinx
42+
Sphinx application.
43+
"""
44+
self._doc_name = doc_name
45+
self.doc_path = f"{self._doc_name}.html"
46+
self.doc_title = app.env.titles[self._doc_name].astext()
47+
self._doc_tree = app.env.get_doctree(self._doc_name)
48+
self.sections = []
49+
50+
def iterate_through_docs(self):
51+
"""Iterate through the document."""
52+
for node in self._doc_tree.traverse(nodes.section):
53+
self.section_title = node[0].astext()
54+
self.section_text = "\n".join(
55+
n.astext()
56+
for node_type in [nodes.paragraph, nodes.literal_block]
57+
for n in node.traverse(node_type)
58+
)
59+
self.section_anchor_id = _title_to_anchor(self.section_title)
60+
self.sections.append(
61+
{
62+
"section_title": self.section_title,
63+
"section_text": self.section_text,
64+
"section_anchor_id": self.section_anchor_id,
65+
}
66+
)
67+
68+
@property
69+
def indices(self):
70+
"""Get search index."""
71+
for sections in self.sections:
72+
search_index = {
73+
"objectID": self._doc_name,
74+
"href": f"{self.doc_path}#{sections['section_anchor_id']}",
75+
"title": self.doc_title,
76+
"section": sections["section_title"],
77+
"text": sections["section_text"],
78+
}
79+
yield search_index
80+
81+
82+
def _title_to_anchor(title: str) -> str:
83+
"""Convert title to anchor."""
84+
return re.sub(r"[^\w\s-]", "", title.lower().strip().replace(" ", "-"))
85+
86+
87+
def create_search_index(app, exception):
88+
"""Create search index for the document in build finished.
89+
90+
Parameters
91+
----------
92+
app : Sphinx
93+
Sphinx application.
94+
exception : Any
95+
Exception.
96+
"""
97+
if exception:
98+
return
99+
100+
if not app.config.html_theme_options.get("static_search", {}):
101+
return
102+
103+
all_docs = app.env.found_docs
104+
search_index_list = []
105+
106+
for document in all_docs:
107+
search_index = SearchIndex(document, app)
108+
search_index.iterate_through_docs()
109+
search_index_list.extend(search_index.indices)
110+
111+
search_index = app.builder.outdir / "search.json"
112+
with search_index.open("w", encoding="utf-8") as index_file:
113+
json.dump(search_index_list, index_file, ensure_ascii=False, indent=4)

0 commit comments

Comments
 (0)