Skip to content

Commit 6622b99

Browse files
committed
Link the author in each example. Switch from PurePath keys to just strings.
1 parent 87770df commit 6622b99

File tree

10 files changed

+96
-82
lines changed

10 files changed

+96
-82
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: 17 additions & 8 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
@@ -20,6 +19,7 @@
2019
from psc.resources import Resources
2120
from psc.resources import get_resources
2221

22+
2323
templates = Jinja2Templates(directory=HERE / "templates")
2424

2525

@@ -46,8 +46,10 @@ async def homepage(request: Request) -> _TemplateResponse:
4646

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

5254
return templates.TemplateResponse(
5355
"gallery.jinja2",
@@ -56,6 +58,7 @@ async def gallery(request: Request) -> _TemplateResponse:
5658
examples=these_examples,
5759
root_path=root_path,
5860
request=request,
61+
authors=these_authors,
5962
),
6063
)
6164

@@ -78,9 +81,9 @@ async def authors(request: Request) -> _TemplateResponse:
7881

7982
async def author(request: Request) -> _TemplateResponse:
8083
"""Handle an author page."""
81-
author_path = PurePath(request.path_params["author_name"])
84+
author_name = request.path_params["author_name"]
8285
resources: Resources = request.app.state.resources
83-
this_author = resources.authors[author_path]
86+
this_author = resources.authors[author_name]
8487
root_path = "../../.."
8588

8689
return templates.TemplateResponse(
@@ -96,10 +99,15 @@ async def author(request: Request) -> _TemplateResponse:
9699

97100
async def example(request: Request) -> _TemplateResponse:
98101
"""Handle an example page."""
99-
example_path = PurePath(request.path_params["example_name"])
102+
example_name = request.path_params["example_name"]
100103
resources: Resources = request.app.state.resources
101-
this_example = resources.examples[example_path]
104+
this_example = resources.examples[example_name]
102105
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
103111

104112
# Set the pyscript URL to the CDN if we are being built from
105113
# the ``psc build`` command.
@@ -119,15 +127,16 @@ async def example(request: Request) -> _TemplateResponse:
119127
request=request,
120128
root_path=root_path,
121129
pyscript_url=pyscript_url,
130+
author=this_author,
122131
),
123132
)
124133

125134

126135
async def content_page(request: Request) -> _TemplateResponse:
127136
"""Handle a content page."""
128-
page_path = PurePath(request.path_params["page_name"])
137+
page_name = request.path_params["page_name"]
129138
resources: Resources = request.app.state.resources
130-
this_page = resources.pages[page_path]
139+
this_page = resources.pages[page_name]
131140

132141
return templates.TemplateResponse(
133142
"page.jinja2",

src/psc/gallery/authors/pauleveritt.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ title: Paul Everitt
33
---
44

55
Python and Web Developer Advocate at @JetBrains for @PyCharm and @WebStormIDE.
6-
Python oldster, Zope/Plone/Pyramid mafia. Girls lacrosse, formerly running.
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: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
from psc.here import HERE
1818
from psc.here import PYODIDE
1919

20+
2021
EXCLUSIONS = ("pyscript.css", "pyscript.js", "favicon.png")
2122

2223

2324
def tag_filter(
24-
tag: Tag,
25-
exclusions: tuple[str, ...] = EXCLUSIONS,
25+
tag: Tag,
26+
exclusions: tuple[str, ...] = EXCLUSIONS,
2627
) -> bool:
2728
"""Filter nodes from example that should not get included."""
2829
attr = "href" if tag.name == "link" else "src"
@@ -74,7 +75,7 @@ def get_body_content(s: BeautifulSoup, test_path: Path = PYODIDE) -> str:
7475
class Resource:
7576
"""Base dataclass used for all resources."""
7677

77-
path: PurePath
78+
name: str
7879
title: str = ""
7980
body: str = ""
8081
extra_head: str = ""
@@ -91,21 +92,24 @@ class Example(Resource):
9192
"""
9293

9394
subtitle: str = ""
95+
description: str = ""
96+
author: str | None = None
9497

9598
def __post_init__(self) -> None:
9699
"""Extract most of the data from the HTML file."""
97100
# Title, subtitle, body come from the example's MD file.
98-
index_md_file = HERE / "gallery/examples" / self.path / "index.md"
101+
index_md_file = HERE / "gallery/examples" / self.name / "index.md"
99102
md_fm = frontmatter.load(index_md_file)
100103
self.title = md_fm.get("title", "")
104+
self.author = md_fm.get("author", "")
101105
self.subtitle = md_fm.get("subtitle", "")
102106
md = MarkdownIt()
103-
self.body = str(md.render(md_fm.content))
107+
self.description = str(md.render(md_fm.content))
104108

105109
# Main, extra head example's HTML file.
106-
index_html_file = HERE / "gallery/examples" / self.path / "index.html"
110+
index_html_file = HERE / "gallery/examples" / self.name / "index.html"
107111
if not index_html_file.exists(): # pragma: nocover
108-
raise ValueError(f"No example at {self.path}")
112+
raise ValueError(f"No example at {self.name}")
109113
soup = BeautifulSoup(index_html_file.read_text(), "html5lib")
110114
self.extra_head = get_head_nodes(soup)
111115
self.body = get_body_content(soup)
@@ -117,7 +121,7 @@ class Author(Resource):
117121

118122
def __post_init__(self) -> None:
119123
"""Initialize the rest of the fields from the Markdown."""
120-
md_file = HERE / "gallery/authors" / f"{self.path}.md"
124+
md_file = HERE / "gallery/authors" / f"{self.name}.md"
121125
md_fm = frontmatter.load(md_file)
122126
self.title = md_fm.get("title", "")
123127
md = MarkdownIt()
@@ -129,14 +133,13 @@ class Page(Resource):
129133
"""A Markdown+frontmatter driven content page."""
130134

131135
subtitle: str = ""
132-
body: str = ""
133136

134137
def __post_init__(self) -> None:
135138
"""Extract content from either Markdown or HTML file."""
136-
md_file = HERE / "pages" / f"{self.path}.md"
137-
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"
138141

139-
# 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
140143
if md_file.exists():
141144
md_fm = frontmatter.load(md_file)
142145
self.title = md_fm.get("title", "")
@@ -157,16 +160,16 @@ def __post_init__(self) -> None:
157160
if body_node and isinstance(body_node, Tag):
158161
self.body = body_node.prettify()
159162
else: # pragma: no cover
160-
raise ValueError(f"No page at {self.path}")
163+
raise ValueError(f"No page at {self.name}")
161164

162165

163166
@dataclass
164167
class Resources:
165168
"""Container for all resources in site."""
166169

167-
authors: dict[PurePath, Author] = field(default_factory=dict)
168-
examples: dict[PurePath, Example] = field(default_factory=dict)
169-
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)
170173

171174

172175
def get_sorted_paths(target_dir: Path, only_dirs: bool = True) -> list[PurePath]:
@@ -183,26 +186,23 @@ def get_resources() -> Resources:
183186
"""Factory to construct all the resources in the site."""
184187
resources = Resources()
185188

186-
# Load the examples
187-
examples = HERE / "gallery/examples"
188-
for example in get_sorted_paths(examples):
189-
this_path = PurePath(example.name)
190-
this_example = Example(path=this_path)
191-
resources.examples[this_path] = this_example
192-
193189
# Load the authors
194190
authors = HERE / "gallery/authors"
195191
for author in get_sorted_paths(authors, only_dirs=False):
196-
this_path = PurePath(author.stem)
197-
this_author = Author(path=this_path)
198-
resources.authors[this_path] = this_author
192+
this_author = Author(name=author.stem)
193+
resources.authors[author.stem] = this_author
194+
195+
# Load the examples
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
199200

200201
# Load the Pages
201202
pages_dir = HERE / "pages"
202203
pages = [e for e in pages_dir.iterdir()]
203204
for page in pages:
204-
this_path = PurePath(page.stem)
205-
this_page = Page(path=this_path)
206-
resources.pages[this_path] = this_page
205+
this_page = Page(name=page.stem)
206+
resources.pages[page.stem] = this_page
207207

208208
return resources

src/psc/templates/authors.jinja2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<div class="tile is-parent is-4">
1717
<article class="tile is-child box">
1818
<p class="title"><a
19-
href="{{ root_path }}/authors/{{ author.path.name }}.html/">{{ author.title }}</a>
19+
href="{{ root_path }}/authors/{{ author.name }}.html/">{{ author.title }}</a>
2020
</p>
2121
<div class="content">
2222
{{ author.body | safe }}

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.body | 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 %}

tests/test_author_pages.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ def test_authors_page(client_page: PageT) -> None:
99
assert page_title and "Authors | PyScript Collective" == page_title.text
1010

1111
authors = soup.select_one("article.tile p.title")
12-
assert "Margaret" == authors.text.strip()
12+
if authors:
13+
assert "Margaret" == authors.text.strip()
1314

1415

1516
def test_author_page(client_page: PageT) -> None:
@@ -19,4 +20,5 @@ def test_author_page(client_page: PageT) -> None:
1920
assert page_title and "Margaret | PyScript Collective" == page_title.text
2021

2122
author = soup.select_one("main h1")
22-
assert "Margaret" == author.text.strip()
23+
if author:
24+
assert "Margaret" == author.text.strip()

0 commit comments

Comments
 (0)