Skip to content

Commit 73c2d80

Browse files
authored
Merge pull request #68 from pyscript/pe-linked-files
Show the code for examples
2 parents 22768b8 + b1c2889 commit 73c2d80

File tree

13 files changed

+301
-17
lines changed

13 files changed

+301
-17
lines changed

src/psc/app.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,25 @@ async def example(request: Request) -> _TemplateResponse:
132132
)
133133

134134

135+
async def example_code(request: Request) -> _TemplateResponse:
136+
"""Handle the linked files for the code example."""
137+
example_name = request.path_params["example_name"]
138+
resources: Resources = request.app.state.resources
139+
this_example = resources.examples[example_name]
140+
root_path = "../../.."
141+
142+
return templates.TemplateResponse(
143+
"example_code.jinja2",
144+
dict(
145+
title=f"{this_example.title} Code",
146+
extra_head=this_example.extra_head,
147+
request=request,
148+
root_path=root_path,
149+
linked_files=this_example.linked_files,
150+
),
151+
)
152+
153+
135154
async def content_page(request: Request) -> _TemplateResponse:
136155
"""Handle a content page."""
137156
page_name = request.path_params["page_name"]
@@ -159,6 +178,7 @@ async def content_page(request: Request) -> _TemplateResponse:
159178
Route("/authors", authors),
160179
Route("/authors/{author_name}.html", author),
161180
Route("/gallery/examples/{example_name}/index.html", example),
181+
Route("/gallery/examples/{example_name}/code.html", example_code),
162182
Route("/gallery/examples/{example_name}/", example),
163183
Route("/pages/{page_name}.html", content_page),
164184
Mount("/gallery", StaticFiles(directory=HERE / "gallery")),
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
---
22
title: xkcd Antigravity
33
subtitle: We can fly!
4+
linked_files:
5+
- antigravity.py
46
---
57
Based on the [xkcd antigravity](https://xkcd.com/353/)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
h1 {
2+
font-weight: normal;
23
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
---
22
title: Hello World
33
subtitle: The classic hello world, but in Python -- in a browser!
4+
linked_files:
5+
- hello_world.css
6+
- hello_world.js
47
---
58
The *body* description.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
---
22
title: Hello World Python
33
subtitle: The hello world example, but in a .py file.
4+
linked_files:
5+
- hello_world.py
6+
- hello_world.css
7+
- hello_world.js
48
---
59
The *body* description.

src/psc/gallery/examples/interest_calculator/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22
title: Compound Interest Calculator
33
subtitle: Enter some numbers, get some numbers.
44
author: meg-1
5+
linked_files:
6+
- calculator.py
7+
- styles.css
58
---
69
The *body* description.

src/psc/resources.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,28 @@ class Resource:
8181
extra_head: str = ""
8282

8383

84+
linked_file_mapping = dict(
85+
py="python",
86+
css="css",
87+
html="html",
88+
js="javascript",
89+
)
90+
91+
92+
@dataclass
93+
class LinkedFile:
94+
"""A source file on disk that gets attached to an example."""
95+
96+
path: Path
97+
language: str = field(init=False)
98+
body: str = field(init=False)
99+
100+
def __post_init__(self) -> None:
101+
"""Read the file contents into the body."""
102+
self.language = linked_file_mapping[self.path.suffix[1:]]
103+
self.body = self.path.read_text()
104+
105+
84106
@dataclass
85107
class Example(Resource):
86108
"""Create an example from an HTML location on disk.
@@ -91,9 +113,10 @@ class Example(Resource):
91113
Meaning, HERE / "examples" / name / "index.html".
92114
"""
93115

94-
subtitle: str = ""
95-
description: str = ""
96-
author: str | None = None
116+
subtitle: str = field(init=False)
117+
description: str = field(init=False)
118+
author: str = field(init=False)
119+
linked_files: list[LinkedFile] = field(default_factory=list)
97120

98121
def __post_init__(self) -> None:
99122
"""Extract most of the data from the HTML file."""
@@ -107,13 +130,22 @@ def __post_init__(self) -> None:
107130
self.description = str(md.render(md_fm.content))
108131

109132
# Main, extra head example's HTML file.
110-
index_html_file = HERE / "gallery/examples" / self.name / "index.html"
133+
this_example_path = HERE / "gallery/examples" / self.name
134+
index_html_file = this_example_path / "index.html"
111135
if not index_html_file.exists(): # pragma: nocover
112136
raise ValueError(f"No example at {self.name}")
113-
soup = BeautifulSoup(index_html_file.read_text(), "html5lib")
137+
index_html_text = index_html_file.read_text()
138+
soup = BeautifulSoup(index_html_text, "html5lib")
114139
self.extra_head = get_head_nodes(soup)
115140
self.body = get_body_content(soup)
116141

142+
# Process any linked files
143+
linked_paths = [*["index.html"], *md_fm.get("linked_files", [])]
144+
for linked_name in linked_paths:
145+
linked_path = this_example_path / linked_name
146+
linked_file = LinkedFile(path=linked_path)
147+
self.linked_files.append(linked_file)
148+
117149

118150
@dataclass
119151
class Author(Resource):

src/psc/static/prism.css

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/* PrismJS 1.29.0
2+
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+python&plugins=custom-class+toolbar+copy-to-clipboard+download-button */
3+
code[class*=language-], pre[class*=language-] {
4+
color: #000;
5+
background: 0 0;
6+
text-shadow: 0 1px #fff;
7+
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
8+
font-size: 1em;
9+
text-align: left;
10+
white-space: pre;
11+
word-spacing: normal;
12+
word-break: normal;
13+
word-wrap: normal;
14+
line-height: 1.5;
15+
-moz-tab-size: 4;
16+
-o-tab-size: 4;
17+
tab-size: 4;
18+
-webkit-hyphens: none;
19+
-moz-hyphens: none;
20+
-ms-hyphens: none;
21+
hyphens: none
22+
}
23+
24+
code[class*=language-] ::-moz-selection, code[class*=language-]::-moz-selection, pre[class*=language-] ::-moz-selection, pre[class*=language-]::-moz-selection {
25+
text-shadow: none;
26+
background: #b3d4fc
27+
}
28+
29+
code[class*=language-] ::selection, code[class*=language-]::selection, pre[class*=language-] ::selection, pre[class*=language-]::selection {
30+
text-shadow: none;
31+
background: #b3d4fc
32+
}
33+
34+
@media print {
35+
code[class*=language-], pre[class*=language-] {
36+
text-shadow: none
37+
}
38+
}
39+
40+
pre[class*=language-] {
41+
padding: 1em;
42+
margin: .5em 0;
43+
overflow: auto
44+
}
45+
46+
:not(pre) > code[class*=language-], pre[class*=language-] {
47+
background: #f5f2f0
48+
}
49+
50+
:not(pre) > code[class*=language-] {
51+
padding: .1em;
52+
border-radius: .3em;
53+
white-space: normal
54+
}
55+
56+
.prism--token.prism--cdata, .prism--token.prism--comment, .prism--token.prism--doctype, .prism--token.prism--prolog {
57+
color: #708090
58+
}
59+
60+
.prism--token.prism--punctuation {
61+
color: #999
62+
}
63+
64+
.prism--token.prism--namespace {
65+
opacity: .7
66+
}
67+
68+
.prism--token.prism--boolean, .prism--token.prism--constant, .prism--token.prism--deleted, .prism--token.prism--number, .prism--token.prism--property, .prism--token.prism--symbol, .prism--token.prism--tag {
69+
color: #905
70+
}
71+
72+
.prism--token.prism--attr-name, .prism--token.prism--builtin, .prism--token.prism--char, .prism--token.prism--inserted, .prism--token.prism--selector, .prism--token.prism--string {
73+
color: #690
74+
}
75+
76+
.language-css .prism--token.prism--string, .style .prism--token.prism--string, .prism--token.prism--entity, .prism--token.prism--operator, .prism--token.prism--url {
77+
color: #9a6e3a;
78+
background: hsla(0, 0%, 100%, .5)
79+
}
80+
81+
.prism--token.prism--atrule, .prism--token.prism--attr-value, .prism--token.prism--keyword {
82+
color: #07a
83+
}
84+
85+
.prism--token.prism--class-name, .prism--token.prism--function {
86+
color: #dd4a68
87+
}
88+
89+
.prism--token.prism--important, .prism--token.prism--regex, .prism--token.prism--variable {
90+
color: #e90
91+
}
92+
93+
.prism--token.prism--bold, .prism--token.prism--important {
94+
font-weight: 700
95+
}
96+
97+
.prism--token.prism--italic {
98+
font-style: italic
99+
}
100+
101+
.prism--token.prism--entity {
102+
cursor: help
103+
}
104+
105+
div.code-toolbar {
106+
position: relative
107+
}
108+
109+
div.code-toolbar > .toolbar {
110+
position: absolute;
111+
z-index: 10;
112+
top: .3em;
113+
right: .2em;
114+
transition: opacity .3s ease-in-out;
115+
opacity: 0
116+
}
117+
118+
div.code-toolbar:hover > .toolbar {
119+
opacity: 1
120+
}
121+
122+
div.code-toolbar:focus-within > .toolbar {
123+
opacity: 1
124+
}
125+
126+
div.code-toolbar > .toolbar > .toolbar-item {
127+
display: inline-block
128+
}
129+
130+
div.code-toolbar > .toolbar > .toolbar-item > a {
131+
cursor: pointer
132+
}
133+
134+
div.code-toolbar > .toolbar > .toolbar-item > button {
135+
background: 0 0;
136+
border: 0;
137+
color: inherit;
138+
font: inherit;
139+
line-height: normal;
140+
overflow: visible;
141+
padding: 0;
142+
-webkit-user-select: none;
143+
-moz-user-select: none;
144+
-ms-user-select: none
145+
}
146+
147+
div.code-toolbar > .toolbar > .toolbar-item > a, div.code-toolbar > .toolbar > .toolbar-item > button, div.code-toolbar > .toolbar > .toolbar-item > span {
148+
color: #bbb;
149+
font-size: .8em;
150+
padding: 0 .5em;
151+
background: #f5f2f0;
152+
background: rgba(224, 224, 224, .2);
153+
box-shadow: 0 2px 0 0 rgba(0, 0, 0, .2);
154+
border-radius: .5em
155+
}
156+
157+
div.code-toolbar > .toolbar > .toolbar-item > a:focus, div.code-toolbar > .toolbar > .toolbar-item > a:hover, div.code-toolbar > .toolbar > .toolbar-item > button:focus, div.code-toolbar > .toolbar > .toolbar-item > button:hover, div.code-toolbar > .toolbar > .toolbar-item > span:focus, div.code-toolbar > .toolbar > .toolbar-item > span:hover {
158+
color: inherit;
159+
text-decoration: none
160+
}

0 commit comments

Comments
 (0)