diff --git a/README.rst b/README.rst index 190b9a1..1c982c2 100644 --- a/README.rst +++ b/README.rst @@ -64,7 +64,6 @@ which can seen by running ``sphinx-autobuild --help``: --ignore IGNORE glob expression for files to ignore, when watching for changes --no-initial skip the initial build --open-browser open the browser after building documentation - --delay DELAY how long to wait before opening the browser --watch DIR additional directories to watch --pre-build COMMAND additional command(s) to run prior to building the documentation --post-build COMMAND additional command(s) to run after building the documentation diff --git a/sphinx_autobuild/__main__.py b/sphinx_autobuild/__main__.py index b1f058f..88ce9a3 100644 --- a/sphinx_autobuild/__main__.py +++ b/sphinx_autobuild/__main__.py @@ -5,6 +5,8 @@ import argparse import shlex import sys +import webbrowser +from contextlib import asynccontextmanager from pathlib import Path import colorama @@ -22,7 +24,7 @@ from sphinx_autobuild.filter import IgnoreFilter from sphinx_autobuild.middleware import JavascriptInjectorMiddleware from sphinx_autobuild.server import RebuildServer -from sphinx_autobuild.utils import find_free_port, open_browser, show_message +from sphinx_autobuild.utils import find_free_port, show_message def main(argv=()): @@ -80,15 +82,15 @@ def main(argv=()): ] ignore_dirs = list(filter(None, ignore_dirs)) ignore_handler = IgnoreFilter(ignore_dirs, args.re_ignore) - app = _create_app(watch_dirs, ignore_handler, builder, serve_dir, url_host) + + app = _create_app( + watch_dirs, ignore_handler, builder, serve_dir, url_host, args.open_browser + ) if not args.no_initial_build: show_message("Starting initial build") builder(changed_paths=()) - if args.open_browser: - open_browser(url_host, args.delay) - show_message("Waiting to detect changes...") try: uvicorn.run(app, host=host_name, port=port_num, log_level="warning") @@ -96,16 +98,25 @@ def main(argv=()): show_message("Server ceasing operations. Cheerio!") -def _create_app(watch_dirs, ignore_handler, builder, out_dir, url_host): +def _create_app( + watch_dirs, ignore_handler, builder, out_dir, url_host, open_browser=False +): watcher = RebuildServer(watch_dirs, ignore_handler, change_callback=builder) + @asynccontextmanager + async def lifespan(app): + async with watcher.lifespan(app): + if open_browser: + webbrowser.open(f"http://{url_host}") + yield + return Starlette( routes=[ WebSocketRoute("/websocket-reload", watcher, name="reload"), Mount("/", app=StaticFiles(directory=out_dir, html=True), name="static"), ], middleware=[Middleware(JavascriptInjectorMiddleware, ws_url=url_host)], - lifespan=watcher.lifespan, + lifespan=lifespan, ) @@ -216,13 +227,7 @@ def _add_autobuild_arguments(parser): default=False, help="open the browser after building documentation", ) - group.add_argument( - "--delay", - dest="delay", - type=float, - default=5, - help="how long to wait before opening the browser", - ) + group.add_argument( "--watch", action="append", diff --git a/sphinx_autobuild/server.py b/sphinx_autobuild/server.py index c32b3f2..55ad079 100644 --- a/sphinx_autobuild/server.py +++ b/sphinx_autobuild/server.py @@ -2,7 +2,7 @@ import asyncio from concurrent.futures import ProcessPoolExecutor -from contextlib import AbstractAsyncContextManager, asynccontextmanager +from contextlib import asynccontextmanager from pathlib import Path from typing import TYPE_CHECKING @@ -10,20 +10,24 @@ from starlette.websockets import WebSocket if TYPE_CHECKING: - import os - from collections.abc import Callable, Sequence + from collections.abc import AsyncGenerator, Sequence + from os import PathLike + from typing import Protocol from starlette.types import Receive, Scope, Send from sphinx_autobuild.filter import IgnoreFilter + class ChangeCallback(Protocol): + def __call__(self, *, changed_paths: Sequence[Path]) -> None: ... + class RebuildServer: def __init__( self, - paths: list[os.PathLike[str]], + paths: list[PathLike[str]], ignore_filter: IgnoreFilter, - change_callback: Callable[[Sequence[Path]], None], + change_callback: ChangeCallback, ) -> None: self.paths = [Path(path).resolve(strict=True) for path in paths] self.ignore = ignore_filter @@ -32,7 +36,7 @@ def __init__( self.should_exit = asyncio.Event() @asynccontextmanager - async def lifespan(self, _app) -> AbstractAsyncContextManager[None]: + async def lifespan(self, _app) -> AsyncGenerator[None]: task = asyncio.create_task(self.main()) yield self.should_exit.set() diff --git a/sphinx_autobuild/utils.py b/sphinx_autobuild/utils.py index 16e19fb..e3fc9ed 100644 --- a/sphinx_autobuild/utils.py +++ b/sphinx_autobuild/utils.py @@ -4,9 +4,6 @@ import shlex import socket -import threading -import time -import webbrowser from colorama import Fore, Style @@ -22,16 +19,6 @@ def find_free_port(): return s.getsockname()[1] -def open_browser(url_host: str, delay: float) -> None: - def _opener(): - time.sleep(delay) - webbrowser.open(f"http://{url_host}") - - t = threading.Thread(target=_opener) - t.start() - t.join() - - def _log(text, *, colour): print(f"{Fore.GREEN}[sphinx-autobuild] {colour}{text}{Style.RESET_ALL}")