Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions pdoc/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

from collections.abc import Callable
from collections.abc import Mapping
import functools
import html
import json
from pathlib import Path
Expand Down Expand Up @@ -124,6 +125,16 @@ def make_index(mod: pdoc.doc.Namespace, **extra):
return documents


@functools.cache
def node_executable() -> str | None:
if shutil.which("nodejs"):
return "nodejs"
elif shutil.which("node"):
return "node"
else:
return None


def precompile_index(documents: list[dict], compile_js: Path) -> str:
"""
This method tries to precompile the Elasticlunr.js search index by invoking `nodejs` or `node`.
Expand All @@ -136,12 +147,11 @@ def precompile_index(documents: list[dict], compile_js: Path) -> str:
"""
raw = json.dumps(documents)
try:
if shutil.which("nodejs"):
executable = "nodejs"
else:
executable = "node"
node = node_executable()
if node is None:
raise FileNotFoundError("No such file or directory: 'node'")
out = subprocess.check_output(
[executable, compile_js],
[node, compile_js],
input=raw.encode(),
cwd=Path(__file__).parent / "templates",
stderr=subprocess.STDOUT,
Expand Down
15 changes: 8 additions & 7 deletions test/test__pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ def test_no_pydantic(monkeypatch):
assert _pydantic.default_value(pdoc.doc.Module, "kind", "module") == "module"


def test_with_pydantic(monkeypatch):
class User(pydantic.BaseModel):
id: int
name: str = pydantic.Field(description="desc", default="Jane Doe")
class ExampleModel(pydantic.BaseModel):
id: int
name: str = pydantic.Field(description="desc", default="Jane Doe")


assert _pydantic.is_pydantic_model(User)
assert _pydantic.get_field_docstring(User, "name") == "desc"
assert _pydantic.default_value(User, "name", None) == "Jane Doe"
def test_with_pydantic(monkeypatch):
assert _pydantic.is_pydantic_model(ExampleModel)
assert _pydantic.get_field_docstring(ExampleModel, "name") == "desc"
assert _pydantic.default_value(ExampleModel, "name", None) == "Jane Doe"

assert not _pydantic.is_pydantic_model(pdoc.doc.Module)
assert _pydantic.get_field_docstring(pdoc.doc.Module, "kind") is None
Expand Down
39 changes: 27 additions & 12 deletions test/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@
here = Path(__file__).parent


def test_node_executable(monkeypatch):
monkeypatch.setattr(
shutil, "which", lambda x: "/usr/bin/nodejs" if x == "nodejs" else None
)
search.node_executable.cache_clear()
assert search.node_executable() == "nodejs"

monkeypatch.setattr(
shutil, "which", lambda x: "/usr/bin/node" if x == "node" else None
)
search.node_executable.cache_clear()
assert search.node_executable() == "node"

monkeypatch.setattr(shutil, "which", lambda _: None)
search.node_executable.cache_clear()
assert search.node_executable() is None


def test_precompile_index(monkeypatch, capsys):
docs = [
{
Expand All @@ -18,28 +36,25 @@ def test_precompile_index(monkeypatch, capsys):
"doc": "a" * 3 * 1024 * 1024, # we only warn if index size is meaningful.
}
]
raw = json.dumps(docs)
compile_js = here / ".." / "pdoc" / "templates" / "build-search-index.js"

monkeypatch.setattr(subprocess, "check_output", lambda *_, **__: '{"foo": 42}')
monkeypatch.setattr(search, "node_executable", lambda: "nodejs")
assert (
search.precompile_index(docs, compile_js)
== '{"foo": 42, "_isPrebuiltIndex": true}'
)

monkeypatch.setattr(shutil, "which", lambda _: "C:\\nodejs.exe")
assert (
search.precompile_index(docs, compile_js)
== '{"foo": 42, "_isPrebuiltIndex": true}'
)
monkeypatch.setattr(shutil, "which", lambda _: None)
assert (
search.precompile_index(docs, compile_js)
== '{"foo": 42, "_isPrebuiltIndex": true}'
)
monkeypatch.setattr(search, "node_executable", lambda: None)
assert search.precompile_index(docs, compile_js) == raw

def _raise(*_, **__):
raise subprocess.CalledProcessError(-1, ["cmd"], b"nodejs error")

monkeypatch.setattr(search, "node_executable", lambda: "node")
monkeypatch.setattr(subprocess, "check_output", _raise)
assert search.precompile_index(docs, compile_js) == json.dumps(docs)
assert "pdoc failed to precompile the search index" in capsys.readouterr().out
assert search.precompile_index(docs, compile_js) == raw
out = capsys.readouterr().out
assert "pdoc failed to precompile the search index" in out
assert "Node.js Output" in out
10 changes: 3 additions & 7 deletions test/test_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
from contextlib import ExitStack
import os
from pathlib import Path
import shutil
import sys
import tempfile
import warnings

import pytest

import pdoc.render
import pdoc.search

here = Path(__file__).parent.absolute()

Expand Down Expand Up @@ -175,6 +175,7 @@ def test_snapshots(snapshot: Snapshot, format: str, monkeypatch):
Compare pdoc's rendered output against stored snapshots.
"""
monkeypatch.chdir(snapshot_dir)
monkeypatch.setattr(pdoc.search, "node_executable", lambda: None)
if sys.version_info < snapshot.min_version:
pytest.skip(
f"Snapshot only works on Python {'.'.join(str(x) for x in snapshot.min_version)} and above."
Expand All @@ -189,12 +190,7 @@ def test_snapshots(snapshot: Snapshot, format: str, monkeypatch):

if __name__ == "__main__":
warnings.simplefilter("error")
if not shutil.which("nodejs") and not shutil.which("node"):
print(
"Snapshots include precompiled search indices, "
"but this system does not have Node.js installed to render them. Aborting."
)
sys.exit(1)
pdoc.search.node_executable = lambda: None # type: ignore
os.chdir(snapshot_dir)
skipped_some = False
for snapshot in snapshots:
Expand Down
Loading