Repository: hynek/doc2dash

Issue:
Add support for DashDocSetDefaultFTSEnabled / DashDocSetFTSNotSupported
https://kapeli.com/docsets#fts

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12fe351..6391a1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Support for linking to [docset playgrounds](https://kapeli.com/docsets#docsetPlaygrounds) using the `--playground-url` option.
   [#201](https://github.com/hynek/doc2dash/pull/201)
 
+- Control over [full-text search](https://kapeli.com/docsets#fts) using the `--full-text-search=(on|off|forbidden)` option.
+  [#202](https://github.com/hynek/doc2dash/pull/202)
+
 
 ### Fixed
 
diff --git a/src/doc2dash/__main__.py b/src/doc2dash/__main__.py
index e457c6a..d826674 100644
--- a/src/doc2dash/__main__.py
+++ b/src/doc2dash/__main__.py
@@ -18,10 +18,8 @@ from typing import Any
 
 import click
 
-from doc2dash import docsets
-from doc2dash.convert import convert_docs
-
-from . import parsers
+from . import docsets, parsers
+from .convert import convert_docs
 from .output import create_log_config, error_console
 from .parsers.types import Parser
 
@@ -145,6 +143,13 @@ IMPORTABLE = ImportableType()
     "doc2dash.parsers.intersphinx.InterSphinxParser). Default behavior "
     "is to auto-detect documentation type.",
 )
+@click.option(
+    "--full-text-search",
+    type=docsets.FullTextSearch,
+    default=docsets.FullTextSearch.OFF,
+    help="Whether full-text search should be 'on' or 'off by default. "
+    "Or whether it's 'forbidden' to switch it on by the user at all.",
+)
 @click.version_option(version=metadata.version("doc2dash"))
 def main(
     source: Path,
@@ -160,8 +165,9 @@ def main(
     index_page: Path | None,
     enable_js: bool,
     online_redirect_url: str | None,
-    parser_type: type[Parser] | None,
     playground_url: str | None,
+    parser_type: type[Parser] | None,
+    full_text_search: docsets.FullTextSearch,
 ) -> None:
     """
     Convert docs from SOURCE to Dash's docset format.
@@ -227,6 +233,7 @@ def main(
         playground_url,
         icon,
         icon_2x,
+        full_text_search,
     )
 
     parser = parser_type(docset.docs)
diff --git a/src/doc2dash/docsets.py b/src/doc2dash/docsets.py
index 5763dbc..e157d1e 100644
--- a/src/doc2dash/docsets.py
+++ b/src/doc2dash/docsets.py
@@ -4,6 +4,8 @@
 
 from __future__ import annotations
 
+from enum import Enum
+
 import os
 import plistlib
 import shutil
@@ -30,6 +32,12 @@ class DocSet:
         return self.path / "Contents" / "Resources" / "Documents"
 
 
+class FullTextSearch(Enum):
+    ON = "on"
+    OFF = "off"
+    FORBIDDEN = "forbidden"
+
+
 def prepare_docset(
     source: Path,
     dest: Path,
@@ -40,6 +48,7 @@ def prepare_docset(
     playground_url: str | None,
     icon: Path | None,
     icon_2x: Path | None,
+    full_text_search: FullTextSearch,
 ) -> DocSet:
     """
     Create boilerplate files & directories and copy vanilla docs inside.
@@ -74,6 +83,10 @@ def prepare_docset(
         plist_cfg["DashDocSetFallbackURL"] = online_redirect_url
     if playground_url is not None:
         plist_cfg["DashDocSetPlayURL"] = playground_url
+    if full_text_search is FullTextSearch.FORBIDDEN:
+        plist_cfg["DashDocSetFTSNotSupported"] = True
+    if full_text_search is FullTextSearch.ON:
+        plist_cfg["DashDocSetDefaultFTSEnabled"] = True
 
     write_plist(plist_cfg, plist_path)
 


Test patch:
diff --git a/tests/test_docsets.py b/tests/test_docsets.py
index 572419e..9ad0fee 100644
--- a/tests/test_docsets.py
+++ b/tests/test_docsets.py
@@ -31,6 +31,7 @@ class TestPrepareDocset:
             playground_url=None,
             icon=None,
             icon_2x=None,
+            full_text_search=docsets.FullTextSearch.OFF,
         )
 
         m_ct.assert_called_once_with(
@@ -76,6 +77,7 @@ class TestPrepareDocset:
             playground_url=None,
             icon=None,
             icon_2x=None,
+            full_text_search=docsets.FullTextSearch.OFF,
         )
 
         p = docsets.read_plist(docset.plist)
@@ -110,6 +112,7 @@ class TestPrepareDocset:
             playground_url=None,
             icon=None,
             icon_2x=None,
+            full_text_search=docsets.FullTextSearch.OFF,
         )
 
         p = docsets.read_plist(docset.plist)
@@ -144,6 +147,7 @@ class TestPrepareDocset:
             playground_url=None,
             icon=None,
             icon_2x=None,
+            full_text_search=docsets.FullTextSearch.OFF,
         )
 
         p = docsets.read_plist(docset.plist)
@@ -179,6 +183,7 @@ class TestPrepareDocset:
             playground_url="https://repl.it/F9J7/1",
             icon=None,
             icon_2x=None,
+            full_text_search=docsets.FullTextSearch.OFF,
         )
 
         p = docsets.read_plist(docset.plist)
@@ -195,6 +200,56 @@ class TestPrepareDocset:
             "DashDocSetPlayURL": "https://repl.it/F9J7/1",
         }
 
+    def test_full_text_search_on(self, tmp_path, sphinx_built):
+        """
+        If full text search is enabled, it's added to the plist.
+        """
+        icon = Path("tests") / "logo.png"
+        dest = tmp_path / "bar"
+
+        docset = docsets.prepare_docset(
+            sphinx_built,
+            dest,
+            name="foo",
+            index_page=None,
+            enable_js=False,
+            online_redirect_url=None,
+            playground_url=None,
+            icon=None,
+            icon_2x=icon,
+            full_text_search=docsets.FullTextSearch.ON,
+        )
+
+        p = docsets.read_plist(docset.plist)
+
+        assert p["DashDocSetDefaultFTSEnabled"] is True
+        assert "DashDocSetFTSNotSupported" not in p
+
+    def test_full_text_search_forbidden(self, tmp_path, sphinx_built):
+        """
+        If full text search is forbidden, it's added to the plist.
+        """
+        icon = Path("tests") / "logo.png"
+        dest = tmp_path / "bar"
+
+        docset = docsets.prepare_docset(
+            sphinx_built,
+            dest,
+            name="foo",
+            index_page=None,
+            enable_js=False,
+            online_redirect_url=None,
+            playground_url=None,
+            icon=None,
+            icon_2x=icon,
+            full_text_search=docsets.FullTextSearch.FORBIDDEN,
+        )
+
+        p = docsets.read_plist(docset.plist)
+
+        assert p["DashDocSetFTSNotSupported"] is True
+        assert "DashDocSetDefaultFTSEnabled" not in p
+
     def test_with_icon(self, tmp_path, sphinx_built):
         """
         If an icon is passed, it's copied to the root of the docset.
@@ -212,6 +267,7 @@ class TestPrepareDocset:
             playground_url=None,
             icon=icon,
             icon_2x=None,
+            full_text_search=docsets.FullTextSearch.OFF,
         )
 
         assert (Path(dest) / "icon.png").exists()
@@ -233,6 +289,7 @@ class TestPrepareDocset:
             playground_url=None,
             icon=None,
             icon_2x=icon,
+            full_text_search=docsets.FullTextSearch.OFF,
         )
 
         assert (Path(dest) / "icon@2x.png").exists()
diff --git a/tests/test_main.py b/tests/test_main.py
index 783f0e7..15c1805 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -201,6 +201,7 @@ def test_normal_flow(monkeypatch, tmp_path, runner):
         playground_url,
         icon,
         icon_2x,
+        full_text_search,
     ):
         os.mkdir(dest)
         db_conn = sqlite3.connect(":memory:")