Skip to content

Commit 22768b8

Browse files
authored
Merge pull request #67 from pyscript/pe-authors
List authors and link from examples
2 parents 1193d33 + 6622b99 commit 22768b8

File tree

13 files changed

+237
-72
lines changed

13 files changed

+237
-72
lines changed

src/psc/__main__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,15 @@ def build() -> None: # pragma: no cover
102102
# Now for each page
103103
resources = get_resources()
104104
for page in resources.pages.values():
105-
response = test_client.get(f"/pages/{page.path.stem}.html")
106-
output = public / f"pages/{page.path.stem}.html"
105+
response = test_client.get(f"/pages/{page.name}.html")
106+
output = public / f"pages/{page.name}.html"
107107
output.write_text(response.text)
108108

109109
# And for each example
110110
for example in resources.examples.values():
111-
url = f"/gallery/examples/{example.path.stem}/index.html"
111+
url = f"/gallery/examples/{example.name}/index.html"
112112
response = test_client.get(url)
113-
output = public / f"gallery/examples/{example.path.stem}/index.html"
113+
output = public / f"gallery/examples/{example.name}/index.html"
114114
output.write_text(response.text)
115115

116116

src/psc/app.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Provide a web server to browse the examples."""
22
import contextlib
33
from collections.abc import Iterator
4-
from pathlib import PurePath
54
from typing import AsyncContextManager
65

76
from starlette.applications import Starlette
@@ -47,8 +46,10 @@ async def homepage(request: Request) -> _TemplateResponse:
4746

4847
async def gallery(request: Request) -> _TemplateResponse:
4948
"""Handle the gallery listing page."""
50-
these_examples: Iterator[Example] = request.app.state.resources.examples.values()
49+
resources = request.app.state.resources
50+
these_examples: Iterator[Example] = resources.examples.values()
5151
root_path = ".."
52+
these_authors = resources.authors
5253

5354
return templates.TemplateResponse(
5455
"gallery.jinja2",
@@ -57,16 +58,56 @@ async def gallery(request: Request) -> _TemplateResponse:
5758
examples=these_examples,
5859
root_path=root_path,
5960
request=request,
61+
authors=these_authors,
62+
),
63+
)
64+
65+
66+
async def authors(request: Request) -> _TemplateResponse:
67+
"""Handle the author listing page."""
68+
these_authors: Iterator[Example] = request.app.state.resources.authors.values()
69+
root_path = ".."
70+
71+
return templates.TemplateResponse(
72+
"authors.jinja2",
73+
dict(
74+
title="Authors",
75+
authors=these_authors,
76+
root_path=root_path,
77+
request=request,
78+
),
79+
)
80+
81+
82+
async def author(request: Request) -> _TemplateResponse:
83+
"""Handle an author page."""
84+
author_name = request.path_params["author_name"]
85+
resources: Resources = request.app.state.resources
86+
this_author = resources.authors[author_name]
87+
root_path = "../../.."
88+
89+
return templates.TemplateResponse(
90+
"example.jinja2",
91+
dict(
92+
title=this_author.title,
93+
body=this_author.body,
94+
request=request,
95+
root_path=root_path,
6096
),
6197
)
6298

6399

64100
async def example(request: Request) -> _TemplateResponse:
65101
"""Handle an example page."""
66-
example_path = PurePath(request.path_params["example_name"])
102+
example_name = request.path_params["example_name"]
67103
resources: Resources = request.app.state.resources
68-
this_example = resources.examples[example_path]
104+
this_example = resources.examples[example_name]
69105
root_path = "../../.."
106+
author_name = this_example.author
107+
if author_name:
108+
this_author = resources.authors.get(author_name, None)
109+
else:
110+
this_author = None
70111

71112
# Set the pyscript URL to the CDN if we are being built from
72113
# the ``psc build`` command.
@@ -86,15 +127,16 @@ async def example(request: Request) -> _TemplateResponse:
86127
request=request,
87128
root_path=root_path,
88129
pyscript_url=pyscript_url,
130+
author=this_author,
89131
),
90132
)
91133

92134

93135
async def content_page(request: Request) -> _TemplateResponse:
94136
"""Handle a content page."""
95-
page_path = PurePath(request.path_params["page_name"])
137+
page_name = request.path_params["page_name"]
96138
resources: Resources = request.app.state.resources
97-
this_page = resources.pages[page_path]
139+
this_page = resources.pages[page_name]
98140

99141
return templates.TemplateResponse(
100142
"page.jinja2",
@@ -113,6 +155,9 @@ async def content_page(request: Request) -> _TemplateResponse:
113155
Route("/favicon.png", favicon),
114156
Route("/gallery/index.html", gallery),
115157
Route("/gallery", gallery),
158+
Route("/authors/index.html", authors),
159+
Route("/authors", authors),
160+
Route("/authors/{author_name}.html", author),
116161
Route("/gallery/examples/{example_name}/index.html", example),
117162
Route("/gallery/examples/{example_name}/", example),
118163
Route("/pages/{page_name}.html", content_page),

src/psc/gallery/authors/meg-1.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: Margaret
3+
---
4+
5+
An I.T. student.
6+
Currently focusing on programming with Python and learning Django development.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: Paul Everitt
3+
---
4+
5+
Python and Web Developer Advocate at @JetBrains for @PyCharm and @WebStormIDE.
6+
Python oldster, Zope/Plone/Pyramid mafia. Girls lacrosse, formerly running.
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
title: Compound Interest Calculator
3-
subtitle: The classic hello world, but in Python -- in a browser!
3+
subtitle: Enter some numbers, get some numbers.
4+
author: meg-1
45
---
56
The *body* description.

src/psc/resources.py

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def get_body_content(s: BeautifulSoup, test_path: Path = PYODIDE) -> str:
7575
class Resource:
7676
"""Base dataclass used for all resources."""
7777

78-
path: PurePath
78+
name: str
7979
title: str = ""
8080
body: str = ""
8181
extra_head: str = ""
@@ -91,41 +91,55 @@ class Example(Resource):
9191
Meaning, HERE / "examples" / name / "index.html".
9292
"""
9393

94-
description: str = ""
9594
subtitle: str = ""
95+
description: str = ""
96+
author: str | None = None
9697

9798
def __post_init__(self) -> None:
9899
"""Extract most of the data from the HTML file."""
99-
# Title, subtitle, description come from the example's MD file.
100-
index_md_file = HERE / "gallery/examples" / self.path / "index.md"
100+
# Title, subtitle, body come from the example's MD file.
101+
index_md_file = HERE / "gallery/examples" / self.name / "index.md"
101102
md_fm = frontmatter.load(index_md_file)
102103
self.title = md_fm.get("title", "")
104+
self.author = md_fm.get("author", "")
103105
self.subtitle = md_fm.get("subtitle", "")
104106
md = MarkdownIt()
105107
self.description = str(md.render(md_fm.content))
106108

107109
# Main, extra head example's HTML file.
108-
index_html_file = HERE / "gallery/examples" / self.path / "index.html"
110+
index_html_file = HERE / "gallery/examples" / self.name / "index.html"
109111
if not index_html_file.exists(): # pragma: nocover
110-
raise ValueError(f"No example at {self.path}")
112+
raise ValueError(f"No example at {self.name}")
111113
soup = BeautifulSoup(index_html_file.read_text(), "html5lib")
112114
self.extra_head = get_head_nodes(soup)
113115
self.body = get_body_content(soup)
114116

115117

118+
@dataclass
119+
class Author(Resource):
120+
"""Information about an author, from Markdown."""
121+
122+
def __post_init__(self) -> None:
123+
"""Initialize the rest of the fields from the Markdown."""
124+
md_file = HERE / "gallery/authors" / f"{self.name}.md"
125+
md_fm = frontmatter.load(md_file)
126+
self.title = md_fm.get("title", "")
127+
md = MarkdownIt()
128+
self.body = str(md.render(md_fm.content))
129+
130+
116131
@dataclass
117132
class Page(Resource):
118133
"""A Markdown+frontmatter driven content page."""
119134

120135
subtitle: str = ""
121-
body: str = ""
122136

123137
def __post_init__(self) -> None:
124138
"""Extract content from either Markdown or HTML file."""
125-
md_file = HERE / "pages" / f"{self.path}.md"
126-
html_file = HERE / "pages" / f"{self.path}.html"
139+
md_file = HERE / "pages" / f"{self.name}.md"
140+
html_file = HERE / "pages" / f"{self.name}.html"
127141

128-
# If this self.path resolves to a Markdown file, use it first
142+
# If this self.name resolves to a Markdown file, use it first
129143
if md_file.exists():
130144
md_fm = frontmatter.load(md_file)
131145
self.title = md_fm.get("title", "")
@@ -146,40 +160,49 @@ def __post_init__(self) -> None:
146160
if body_node and isinstance(body_node, Tag):
147161
self.body = body_node.prettify()
148162
else: # pragma: no cover
149-
raise ValueError(f"No page at {self.path}")
163+
raise ValueError(f"No page at {self.name}")
150164

151165

152166
@dataclass
153167
class Resources:
154168
"""Container for all resources in site."""
155169

156-
examples: dict[PurePath, Example] = field(default_factory=dict)
157-
pages: dict[PurePath, Page] = field(default_factory=dict)
170+
authors: dict[str, Author] = field(default_factory=dict)
171+
examples: dict[str, Example] = field(default_factory=dict)
172+
pages: dict[str, Page] = field(default_factory=dict)
158173

159174

160-
def get_sorted_examples() -> list[PurePath]:
175+
def get_sorted_paths(target_dir: Path, only_dirs: bool = True) -> list[PurePath]:
161176
"""Return an alphabetized listing of the examples."""
162-
examples_dir = HERE / "gallery/examples"
163-
examples = [e for e in examples_dir.iterdir() if e.is_dir()]
164-
return sorted(examples, key=attrgetter("name"))
177+
if only_dirs:
178+
paths = [e for e in target_dir.iterdir() if e.is_dir()]
179+
else:
180+
paths = [e for e in target_dir.iterdir()]
181+
182+
return sorted(paths, key=attrgetter("name"))
165183

166184

167185
def get_resources() -> Resources:
168186
"""Factory to construct all the resources in the site."""
169187
resources = Resources()
170188

189+
# Load the authors
190+
authors = HERE / "gallery/authors"
191+
for author in get_sorted_paths(authors, only_dirs=False):
192+
this_author = Author(name=author.stem)
193+
resources.authors[author.stem] = this_author
194+
171195
# Load the examples
172-
for example in get_sorted_examples():
173-
this_path = PurePath(example.name)
174-
this_example = Example(path=this_path)
175-
resources.examples[this_path] = this_example
196+
examples = HERE / "gallery/examples"
197+
for example in get_sorted_paths(examples):
198+
this_example = Example(example.stem)
199+
resources.examples[example.stem] = this_example
176200

177201
# Load the Pages
178202
pages_dir = HERE / "pages"
179203
pages = [e for e in pages_dir.iterdir()]
180204
for page in pages:
181-
this_path = PurePath(page.stem)
182-
this_page = Page(path=this_path)
183-
resources.pages[this_path] = this_page
205+
this_page = Page(name=page.stem)
206+
resources.pages[page.stem] = this_page
184207

185208
return resources

src/psc/templates/author.jinja2

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{% extends "layout.jinja2" %}
2+
{% block extra_head %}
3+
{% block main %}
4+
<main id="main_container" class="container">
5+
<h1 class="title">{{ title }}</h1>
6+
<div class="content">{{ body | safe }}</div>
7+
</main>
8+
{% endblock %}

src/psc/templates/authors.jinja2

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{% extends "layout.jinja2" %}
2+
{% block main %}
3+
<section class="hero is-small is-dark">
4+
<div class="hero-body">
5+
<p class="title">
6+
PyScript Authors
7+
</p>
8+
<p class="subtitle">
9+
All the contributors to authors and more.
10+
</p>
11+
</div>
12+
</section>
13+
<main id="main_container" class="container">
14+
<div class="tile is-ancestor">
15+
{% for author in authors %}
16+
<div class="tile is-parent is-4">
17+
<article class="tile is-child box">
18+
<p class="title"><a
19+
href="{{ root_path }}/authors/{{ author.name }}.html/">{{ author.title }}</a>
20+
</p>
21+
<div class="content">
22+
{{ author.body | safe }}
23+
</div>
24+
</article>
25+
</div>
26+
{% endfor %}
27+
</div>
28+
</main>
29+
{% endblock %}

src/psc/templates/example.jinja2

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
{% block main %}
77
<main id="main_container" class="container">
88
<h1 class="title">{{ title }}</h1>
9+
{% if author %}
10+
<p>By <a href="../authors/{{ author.name }}.html">{{ author.title }}</a></p>
11+
{% endif %}
912
<h1 class="subtitle">{{ subtitle }}</h1>
1013
<div class="content">{{ body | safe }}</div>
1114
</main>

src/psc/templates/gallery.jinja2

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,22 @@
1111
</div>
1212
</section>
1313
<main id="main_container" class="container">
14-
<div class="tile is-ancestor">
15-
{% for example in examples %}
16-
<div class="tile is-parent is-4">
17-
<article class="tile is-child box">
18-
<p class="title"><a
19-
href="{{ root_path }}/gallery/examples/{{ example.path.name }}/">{{ example.title }}</a>
20-
</p>
21-
<p class="subtitle">{{ example.subtitle }}</p>
22-
<div class="content">
23-
{{ example.description | safe }}
24-
</div>
25-
</article>
26-
</div>
27-
{% endfor %}
28-
</div>
14+
{% for row in examples | batch(3) %}
15+
<div class="tile is-ancestor">
16+
{% for example in row %}
17+
<div class="tile is-parent is-4">
18+
<article class="tile is-child box">
19+
<p class="title"><a
20+
href="{{ root_path }}/gallery/examples/{{ example.name }}/">{{ example.title }}</a>
21+
</p>
22+
<p class="subtitle">{{ example.subtitle }}</p>
23+
{% if example.author %}
24+
<p style="margin-top: 1em">By <a href="../authors/{{ example.author }}.html">{{ authors[example.author].title }}</a></p>
25+
{% endif %}
26+
</article>
27+
</div>
28+
{% endfor %}
29+
</div>
30+
{% endfor %}
2931
</main>
3032
{% endblock %}

src/psc/templates/layout.jinja2

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
<a id="navbarGallery" class="navbar-item" href="{{ root_path }}/gallery">
2727
Gallery
2828
</a>
29+
<a id="navbarAuthors" class="navbar-item" href="{{ root_path }}/authors">
30+
Authors
31+
</a>
2932
<a id="navbarJoin" class="navbar-item" href="{{ root_path }}/pages/contributing.html">
3033
Join
3134
</a>

0 commit comments

Comments
 (0)