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..dc29622 100644 --- a/sphinx_autobuild/__main__.py +++ b/sphinx_autobuild/__main__.py @@ -3,8 +3,10 @@ from __future__ import annotations import argparse +import asyncio import shlex import sys +import webbrowser 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,18 +82,29 @@ 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) 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...") + config = uvicorn.Config(app, host=host_name, port=port_num, log_level="warning") + server = uvicorn.Server(config) + + async def serve(): + server_task = asyncio.create_task(server.serve()) + while not server.started and not server.should_exit: + if server_task.done(): + break + await asyncio.sleep(0.1) + if args.open_browser and server.started: + webbrowser.open(f"http://{url_host}") + await server_task + try: - uvicorn.run(app, host=host_name, port=port_num, log_level="warning") + asyncio.run(serve()) except KeyboardInterrupt: show_message("Server ceasing operations. Cheerio!") @@ -218,10 +231,10 @@ def _add_autobuild_arguments(parser): ) group.add_argument( "--delay", - dest="delay", type=float, - default=5, - help="how long to wait before opening the browser", + default=0, + help="how long to wait before opening the browser (deprecated, no effect)", + **({"deprecated": True} if sys.version_info >= (3, 13) else {}), ) group.add_argument( "--watch", 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}")