diff --git a/src/docbuddy/cli.py b/src/docbuddy/cli.py index 0cb2f8e..874efd5 100644 --- a/src/docbuddy/cli.py +++ b/src/docbuddy/cli.py @@ -4,11 +4,16 @@ import argparse import functools import http.server +import pathlib import sys import threading import time import webbrowser -from importlib.resources import files + + +def _pkg_dir() -> pathlib.Path: + """Return the directory that contains standalone.html and static/.""" + return pathlib.Path(__file__).parent def main(): @@ -34,21 +39,23 @@ def main(): args = parser.parse_args() - # Locate packaged assets via importlib.resources (works for both editable - # and normal pip installs; no os.chdir() needed). - pkg_ref = files("docbuddy") - standalone_ref = pkg_ref.joinpath("standalone.html") + # Locate the package directory using __file__ – this is the most reliable + # way to find the installed package assets regardless of Python version, + # install method (editable, wheel, sdist), or platform. + pkg_dir = _pkg_dir() + standalone_path = pkg_dir / "standalone.html" - if not standalone_ref.is_file(): + if not standalone_path.is_file(): print( - f"Error: Could not find 'standalone.html' in the docbuddy package ({pkg_ref})", + f"Error: Could not find 'standalone.html' in the docbuddy package ({pkg_dir})", file=sys.stderr, ) sys.exit(1) # Serve only the package directory – not the whole repo/site-packages root. - pkg_dir = str(pkg_ref) - handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=pkg_dir) + handler = functools.partial( + http.server.SimpleHTTPRequestHandler, directory=str(pkg_dir) + ) url = f"http://{args.host}:{args.port}/standalone.html" diff --git a/tests/test_plugin.py b/tests/test_plugin.py index f437110..1a0ca2a 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1803,21 +1803,25 @@ def test_standalone_page_has_docbuddy_plugin(): def test_cli_standalone_html_is_packaged(): """Verify standalone.html is shipped inside the docbuddy package.""" - from importlib.resources import files + import pathlib - pkg_ref = files("docbuddy") - standalone_ref = pkg_ref.joinpath("standalone.html") + import docbuddy + + pkg_dir = pathlib.Path(docbuddy.__file__).parent + standalone_path = pkg_dir / "standalone.html" assert ( - standalone_ref.is_file() + standalone_path.is_file() ), "standalone.html must be present in the installed package" def test_cli_standalone_html_uses_local_static_path(): """standalone.html must load JS from ./static (not from the repo root path).""" - from importlib.resources import files + import pathlib + + import docbuddy - pkg_ref = files("docbuddy") - html = pkg_ref.joinpath("standalone.html").read_text(encoding="utf-8") + pkg_dir = pathlib.Path(docbuddy.__file__).parent + html = (pkg_dir / "standalone.html").read_text(encoding="utf-8") assert ( "./static" in html ), "standalone.html should reference './static' for local assets" @@ -1826,23 +1830,16 @@ def test_cli_standalone_html_uses_local_static_path(): ), "standalone.html must not reference the repo-layout path ../src/docbuddy/static" -def test_cli_main_exits_on_missing_standalone(monkeypatch): +def test_cli_main_exits_on_missing_standalone(monkeypatch, tmp_path): """main() must exit with a clear message when standalone.html is missing.""" import sys - from unittest.mock import MagicMock import pytest import docbuddy.cli as cli_module - # Patch `files` as it is imported in cli.py (must patch the name in that module) - fake_ref = MagicMock() - fake_ref.__str__ = lambda _: "/fake/pkg" - fake_file = MagicMock() - fake_file.is_file.return_value = False - fake_ref.joinpath.return_value = fake_file - - monkeypatch.setattr(cli_module, "files", lambda _pkg: fake_ref) + # Point _pkg_dir() at a temporary directory with no standalone.html + monkeypatch.setattr(cli_module, "_pkg_dir", lambda: tmp_path) monkeypatch.setattr(sys, "argv", ["docbuddy"]) with pytest.raises(SystemExit) as exc_info: @@ -1850,7 +1847,7 @@ def test_cli_main_exits_on_missing_standalone(monkeypatch): assert exc_info.value.code == 1 -def test_cli_uses_directory_not_chdir(monkeypatch): +def test_cli_uses_directory_not_chdir(monkeypatch, tmp_path): """main() must not call os.chdir; it must pass directory= to the HTTP handler.""" import functools import sys @@ -1858,14 +1855,9 @@ def test_cli_uses_directory_not_chdir(monkeypatch): import docbuddy.cli as cli_module - # Stub out standalone.html lookup so it succeeds - fake_ref = MagicMock() - fake_ref.__str__ = lambda _: "/fake/pkg" - fake_file = MagicMock() - fake_file.is_file.return_value = True - fake_ref.joinpath.return_value = fake_file - - monkeypatch.setattr(cli_module, "files", lambda _pkg: fake_ref) + # Create a fake standalone.html so the existence check passes + (tmp_path / "standalone.html").write_text("") + monkeypatch.setattr(cli_module, "_pkg_dir", lambda: tmp_path) monkeypatch.setattr(sys, "argv", ["docbuddy"]) # Capture the handler passed to HTTPServer to verify directory= is set @@ -1895,3 +1887,7 @@ def fake_http_server(addr, handler): assert ( "directory" in handler.keywords ), "handler must have directory= keyword argument" + # directory must point to the package dir (tmp_path in this test) + assert handler.keywords["directory"] == str( + tmp_path + ), "directory= must be the package directory"