1
1
"""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
4
6
5
- from bs4 import BeautifulSoup
6
7
from starlette .applications import Starlette
7
8
from starlette .requests import Request
8
9
from starlette .responses import FileResponse
12
13
from starlette .templating import Jinja2Templates
13
14
from starlette .templating import _TemplateResponse
14
15
16
+ from psc .here import HERE
15
17
from psc .here import PYODIDE
16
18
from 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
18
22
19
23
20
- HERE = Path (__file__ ).parent
21
24
templates = Jinja2Templates (directory = HERE / "templates" )
22
25
23
26
@@ -40,53 +43,34 @@ async def homepage(request: Request) -> _TemplateResponse:
40
43
)
41
44
42
45
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
+
43
60
async def example (request : Request ) -> _TemplateResponse :
44
61
"""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 ]
82
65
83
66
return templates .TemplateResponse (
84
67
"example.jinja2" ,
85
68
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 ,
90
74
request = request ,
91
75
),
92
76
)
@@ -96,11 +80,26 @@ async def example(request: Request) -> _TemplateResponse:
96
80
Route ("/" , homepage ),
97
81
Route ("/index.html" , homepage ),
98
82
Route ("/favicon.png" , favicon ),
83
+ Route ("/examples/index.html" , examples ),
84
+ Route ("/examples" , examples ),
99
85
Route ("/examples/{example_name}/index.html" , example ),
86
+ Route ("/examples/{example_name}/" , example ),
100
87
Mount ("/examples" , StaticFiles (directory = HERE / "examples" )),
101
- Mount ("/static" , StaticFiles (directory = STATIC )),
88
+ Mount ("/static" , StaticFiles (directory = HERE / "static" )),
102
89
Mount ("/pyscript" , StaticFiles (directory = PYSCRIPT )),
103
90
Mount ("/pyodide" , StaticFiles (directory = PYODIDE )),
104
91
]
105
92
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