11"""Provide a web server to browse the examples."""
2- from http .client import HTTPException
3- from pathlib import Path
2+ import contextlib
3+ from pathlib import PurePath
4+ from typing import AsyncContextManager
5+ from typing import Iterator
46
5- from bs4 import BeautifulSoup
67from starlette .applications import Starlette
78from starlette .requests import Request
89from starlette .responses import FileResponse
1213from starlette .templating import Jinja2Templates
1314from starlette .templating import _TemplateResponse
1415
16+ from psc .here import HERE
1517from psc .here import PYODIDE
1618from psc .here import PYSCRIPT
17- from psc .here import STATIC
19+ from psc .resources import Example
20+ from psc .resources import Resources
21+ from psc .resources import get_resources
1822
1923
20- HERE = Path (__file__ ).parent
2124templates = Jinja2Templates (directory = HERE / "templates" )
2225
2326
@@ -40,53 +43,34 @@ async def homepage(request: Request) -> _TemplateResponse:
4043 )
4144
4245
46+ async def examples (request : Request ) -> _TemplateResponse :
47+ """Handle the examples listing page."""
48+ these_examples : Iterator [Example ] = request .app .state .resources .examples .values ()
49+
50+ return templates .TemplateResponse (
51+ "examples.jinja2" ,
52+ dict (
53+ title = "Examples" ,
54+ examples = these_examples ,
55+ request = request ,
56+ ),
57+ )
58+
59+
4360async def example (request : Request ) -> _TemplateResponse :
4461 """Handle an example page."""
45- example_name = request .path_params ["example_name" ]
46- example_file = HERE / "examples" / example_name / "index.html"
47- example_content = example_file .read_text ()
48- soup = BeautifulSoup (example_content , "html5lib" )
49-
50- # Get the example title from the HTML file
51- title_node = soup .select_one ("title" )
52- title = title_node .text if title_node else ""
53-
54- # Assemble any extra head
55- extra_head_links = [
56- link .prettify ()
57- for link in soup .select ("head link" )
58- if not link .attrs ["href" ].endswith ("pyscript.css" )
59- and not link .attrs ["href" ].endswith ("favicon.png" )
60- ]
61- extra_head_scripts = [
62- script .prettify ()
63- for script in soup .select ("head script" )
64- if not script .attrs ["src" ].endswith ("pyscript.js" )
65- ]
66- extra_head_nodes = extra_head_links + extra_head_scripts
67- extra_head = "\n " .join (extra_head_nodes )
68-
69- # Assemble the main element
70- main_element = soup .select_one ("main" )
71- if main_element is None :
72- raise HTTPException ("Example file has no <main> element" )
73- main = f"<main>{ main_element .decode_contents ()} </main>"
74-
75- # Get any non-py-config PyScript nodes
76- pyscript_nodes = [
77- pyscript .prettify ()
78- for pyscript in soup .select ("body > *" )
79- if pyscript .name .startswith ("py-" ) and pyscript .name != "py-config"
80- ]
81- extra_pyscript = "\n " .join (pyscript_nodes )
62+ example_path = PurePath (request .path_params ["example_name" ])
63+ resources : Resources = request .app .state .resources
64+ this_example = resources .examples [example_path ]
8265
8366 return templates .TemplateResponse (
8467 "example.jinja2" ,
8568 dict (
86- title = title ,
87- extra_head = extra_head ,
88- main = main ,
89- extra_pyscript = extra_pyscript ,
69+ title = this_example .title ,
70+ subtitle = this_example .subtitle ,
71+ extra_head = this_example .extra_head ,
72+ main = this_example .main ,
73+ extra_pyscript = this_example .extra_pyscript ,
9074 request = request ,
9175 ),
9276 )
@@ -96,11 +80,26 @@ async def example(request: Request) -> _TemplateResponse:
9680 Route ("/" , homepage ),
9781 Route ("/index.html" , homepage ),
9882 Route ("/favicon.png" , favicon ),
83+ Route ("/examples/index.html" , examples ),
84+ Route ("/examples" , examples ),
9985 Route ("/examples/{example_name}/index.html" , example ),
86+ Route ("/examples/{example_name}/" , example ),
10087 Mount ("/examples" , StaticFiles (directory = HERE / "examples" )),
101- Mount ("/static" , StaticFiles (directory = STATIC )),
88+ Mount ("/static" , StaticFiles (directory = HERE / "static" )),
10289 Mount ("/pyscript" , StaticFiles (directory = PYSCRIPT )),
10390 Mount ("/pyodide" , StaticFiles (directory = PYODIDE )),
10491]
10592
106- app = Starlette (debug = True , routes = routes )
93+
94+ @contextlib .asynccontextmanager # type: ignore
95+ async def lifespan (a : Starlette ) -> AsyncContextManager : # type: ignore
96+ """Run the resources factory at startup and make available to views."""
97+ a .state .resources = get_resources ()
98+ yield
99+
100+
101+ app = Starlette (
102+ debug = True ,
103+ routes = routes ,
104+ lifespan = lifespan ,
105+ )
0 commit comments