From d8b491bc33aa35cf6c3f6f0daf10e14e285af624 Mon Sep 17 00:00:00 2001 From: Daniel Fichtinger Date: Sat, 21 Jun 2025 20:06:52 -0400 Subject: [PATCH] add preview server --- pyproject.toml | 3 ++ src/zona/cli.py | 10 ++++++- src/zona/markdown.py | 1 + src/zona/server.py | 13 ++++++++ uv.lock | 71 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/zona/server.py diff --git a/pyproject.toml b/pyproject.toml index 49efd12..4f4db28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,9 @@ dependencies = [ "jinja2>=3.1.6", "marko[codehilite]>=2.1.4", "python-frontmatter>=1.1.0", + "starlette>=0.47.1", "typer>=0.16.0", + "uvicorn>=0.34.3", ] [project.scripts] @@ -42,6 +44,7 @@ typeCheckingMode = "recommended" reportExplicitAny = false reportAny = false reportUnusedCallResult = false +reportCallInDefaultInitializer = false allowedUntypedLibraries = ["frontmatter"] [tool.ruff] diff --git a/src/zona/cli.py b/src/zona/cli.py index 43b64f1..ac483ea 100644 --- a/src/zona/cli.py +++ b/src/zona/cli.py @@ -1,6 +1,6 @@ import typer from pathlib import Path -from zona import builder +from zona import builder, server app = typer.Typer() @@ -15,6 +15,14 @@ def build(in_dir: str | None = None, out_dir: str | None = None): builder.build(Path(in_dir), items) +@app.command() +def serve(dir: Path = Path("public")): + if not dir.exists(): + raise typer.BadParameter(f"Directory {dir} does not exist.") + typer.echo(f"Serving files from {dir}") + server.serve(dir) + + @app.command() def init(): # init will populate the current directory with diff --git a/src/zona/markdown.py b/src/zona/markdown.py index d70d947..c16a8c7 100644 --- a/src/zona/markdown.py +++ b/src/zona/markdown.py @@ -9,6 +9,7 @@ class ZonaRenderer(HTMLRenderer): @override def render_link(self, element: Link): href = element.dest + assert isinstance(href, str) if href.endswith(".md") and not href.startswith("http"): href = href[:-3] + ".html" body: Any = self.render_children(element) diff --git a/src/zona/server.py b/src/zona/server.py new file mode 100644 index 0000000..c6a2cd2 --- /dev/null +++ b/src/zona/server.py @@ -0,0 +1,13 @@ +from starlette.applications import Starlette +from starlette.staticfiles import StaticFiles +import typer +import uvicorn +from pathlib import Path + + +def serve(dir: Path, host: str = "localhost", port: int = 8000): + app = Starlette() + app.mount("/", StaticFiles(directory=dir, html=True), name="zona") + + typer.echo(f"Preview server running at http://{host}:{port}") + uvicorn.run(app, host=host, port=port, log_level="warning") diff --git a/uv.lock b/uv.lock index 7ffb15c..cad0892 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,20 @@ version = 1 revision = 2 requires-python = ">=3.12" +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + [[package]] name = "basedpyright" version = "1.29.4" @@ -44,6 +58,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/35/386550fd60316d1e37eccdda609b074113298f23cef5bddb2049823fe666/dacite-1.9.2-py3-none-any.whl", hash = "sha256:053f7c3f5128ca2e9aceb66892b1a3c8936d02c686e707bee96e19deef4bc4a0", size = 16600, upload-time = "2025-02-05T09:27:24.345Z" }, ] +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + [[package]] name = "iniconfig" version = "2.1.0" @@ -282,6 +314,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "starlette" +version = "0.47.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/69/662169fdb92fb96ec3eaee218cf540a629d629c86d7993d9651226a6789b/starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b", size = 2583072, upload-time = "2025-06-21T04:03:17.337Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/95/38ef0cd7fa11eaba6a99b3c4f5ac948d8bc6ff199aabd327a29cc000840c/starlette-0.47.1-py3-none-any.whl", hash = "sha256:5e11c9f5c7c3f24959edbf2dffdc01bba860228acf657129467d8a7468591527", size = 72747, upload-time = "2025-06-21T04:03:15.705Z" }, +] + [[package]] name = "typer" version = "0.16.0" @@ -306,6 +360,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, ] +[[package]] +name = "uvicorn" +version = "0.34.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" }, +] + [[package]] name = "zona" version = "0.1.0" @@ -315,7 +382,9 @@ dependencies = [ { name = "jinja2" }, { name = "marko", extra = ["codehilite"] }, { name = "python-frontmatter" }, + { name = "starlette" }, { name = "typer" }, + { name = "uvicorn" }, ] [package.dev-dependencies] @@ -331,7 +400,9 @@ requires-dist = [ { name = "jinja2", specifier = ">=3.1.6" }, { name = "marko", extras = ["codehilite"], specifier = ">=2.1.4" }, { name = "python-frontmatter", specifier = ">=1.1.0" }, + { name = "starlette", specifier = ">=0.47.1" }, { name = "typer", specifier = ">=0.16.0" }, + { name = "uvicorn", specifier = ">=0.34.3" }, ] [package.metadata.requires-dev]