diff --git a/src/psc/__main__.py b/src/psc/__main__.py
index cb3a100..162c6f5 100644
--- a/src/psc/__main__.py
+++ b/src/psc/__main__.py
@@ -102,15 +102,15 @@ def build() -> None: # pragma: no cover
# Now for each page
resources = get_resources()
for page in resources.pages.values():
- response = test_client.get(f"/pages/{page.path.stem}.html")
- output = public / f"pages/{page.path.stem}.html"
+ response = test_client.get(f"/pages/{page.name}.html")
+ output = public / f"pages/{page.name}.html"
output.write_text(response.text)
# And for each example
for example in resources.examples.values():
- url = f"/gallery/examples/{example.path.stem}/index.html"
+ url = f"/gallery/examples/{example.name}/index.html"
response = test_client.get(url)
- output = public / f"gallery/examples/{example.path.stem}/index.html"
+ output = public / f"gallery/examples/{example.name}/index.html"
output.write_text(response.text)
diff --git a/src/psc/app.py b/src/psc/app.py
index d86e90d..7b5da85 100644
--- a/src/psc/app.py
+++ b/src/psc/app.py
@@ -1,7 +1,6 @@
"""Provide a web server to browse the examples."""
import contextlib
from collections.abc import Iterator
-from pathlib import PurePath
from typing import AsyncContextManager
from starlette.applications import Starlette
@@ -47,8 +46,10 @@ async def homepage(request: Request) -> _TemplateResponse:
async def gallery(request: Request) -> _TemplateResponse:
"""Handle the gallery listing page."""
- these_examples: Iterator[Example] = request.app.state.resources.examples.values()
+ resources = request.app.state.resources
+ these_examples: Iterator[Example] = resources.examples.values()
root_path = ".."
+ these_authors = resources.authors
return templates.TemplateResponse(
"gallery.jinja2",
@@ -57,16 +58,56 @@ async def gallery(request: Request) -> _TemplateResponse:
examples=these_examples,
root_path=root_path,
request=request,
+ authors=these_authors,
+ ),
+ )
+
+
+async def authors(request: Request) -> _TemplateResponse:
+ """Handle the author listing page."""
+ these_authors: Iterator[Example] = request.app.state.resources.authors.values()
+ root_path = ".."
+
+ return templates.TemplateResponse(
+ "authors.jinja2",
+ dict(
+ title="Authors",
+ authors=these_authors,
+ root_path=root_path,
+ request=request,
+ ),
+ )
+
+
+async def author(request: Request) -> _TemplateResponse:
+ """Handle an author page."""
+ author_name = request.path_params["author_name"]
+ resources: Resources = request.app.state.resources
+ this_author = resources.authors[author_name]
+ root_path = "../../.."
+
+ return templates.TemplateResponse(
+ "example.jinja2",
+ dict(
+ title=this_author.title,
+ body=this_author.body,
+ request=request,
+ root_path=root_path,
),
)
async def example(request: Request) -> _TemplateResponse:
"""Handle an example page."""
- example_path = PurePath(request.path_params["example_name"])
+ example_name = request.path_params["example_name"]
resources: Resources = request.app.state.resources
- this_example = resources.examples[example_path]
+ this_example = resources.examples[example_name]
root_path = "../../.."
+ author_name = this_example.author
+ if author_name:
+ this_author = resources.authors.get(author_name, None)
+ else:
+ this_author = None
# Set the pyscript URL to the CDN if we are being built from
# the ``psc build`` command.
@@ -86,15 +127,16 @@ async def example(request: Request) -> _TemplateResponse:
request=request,
root_path=root_path,
pyscript_url=pyscript_url,
+ author=this_author,
),
)
async def content_page(request: Request) -> _TemplateResponse:
"""Handle a content page."""
- page_path = PurePath(request.path_params["page_name"])
+ page_name = request.path_params["page_name"]
resources: Resources = request.app.state.resources
- this_page = resources.pages[page_path]
+ this_page = resources.pages[page_name]
return templates.TemplateResponse(
"page.jinja2",
@@ -113,6 +155,9 @@ async def content_page(request: Request) -> _TemplateResponse:
Route("/favicon.png", favicon),
Route("/gallery/index.html", gallery),
Route("/gallery", gallery),
+ Route("/authors/index.html", authors),
+ Route("/authors", authors),
+ Route("/authors/{author_name}.html", author),
Route("/gallery/examples/{example_name}/index.html", example),
Route("/gallery/examples/{example_name}/", example),
Route("/pages/{page_name}.html", content_page),
diff --git a/src/psc/gallery/authors/meg-1.md b/src/psc/gallery/authors/meg-1.md
new file mode 100644
index 0000000..004f312
--- /dev/null
+++ b/src/psc/gallery/authors/meg-1.md
@@ -0,0 +1,6 @@
+---
+title: Margaret
+---
+
+An I.T. student.
+Currently focusing on programming with Python and learning Django development.
diff --git a/src/psc/gallery/authors/pauleveritt.md b/src/psc/gallery/authors/pauleveritt.md
new file mode 100644
index 0000000..63d665c
--- /dev/null
+++ b/src/psc/gallery/authors/pauleveritt.md
@@ -0,0 +1,6 @@
+---
+title: Paul Everitt
+---
+
+Python and Web Developer Advocate at @JetBrains for @PyCharm and @WebStormIDE.
+Python oldster, Zope/Plone/Pyramid mafia. Girls lacrosse, formerly running.
diff --git a/src/psc/gallery/examples/interest_calculator/index.md b/src/psc/gallery/examples/interest_calculator/index.md
index 35b0a54..bffb179 100644
--- a/src/psc/gallery/examples/interest_calculator/index.md
+++ b/src/psc/gallery/examples/interest_calculator/index.md
@@ -1,5 +1,6 @@
---
title: Compound Interest Calculator
-subtitle: The classic hello world, but in Python -- in a browser!
+subtitle: Enter some numbers, get some numbers.
+author: meg-1
---
The *body* description.
diff --git a/src/psc/resources.py b/src/psc/resources.py
index 808b4bf..5717f8a 100644
--- a/src/psc/resources.py
+++ b/src/psc/resources.py
@@ -75,7 +75,7 @@ def get_body_content(s: BeautifulSoup, test_path: Path = PYODIDE) -> str:
class Resource:
"""Base dataclass used for all resources."""
- path: PurePath
+ name: str
title: str = ""
body: str = ""
extra_head: str = ""
@@ -91,41 +91,55 @@ class Example(Resource):
Meaning, HERE / "examples" / name / "index.html".
"""
- description: str = ""
subtitle: str = ""
+ description: str = ""
+ author: str | None = None
def __post_init__(self) -> None:
"""Extract most of the data from the HTML file."""
- # Title, subtitle, description come from the example's MD file.
- index_md_file = HERE / "gallery/examples" / self.path / "index.md"
+ # Title, subtitle, body come from the example's MD file.
+ index_md_file = HERE / "gallery/examples" / self.name / "index.md"
md_fm = frontmatter.load(index_md_file)
self.title = md_fm.get("title", "")
+ self.author = md_fm.get("author", "")
self.subtitle = md_fm.get("subtitle", "")
md = MarkdownIt()
self.description = str(md.render(md_fm.content))
# Main, extra head example's HTML file.
- index_html_file = HERE / "gallery/examples" / self.path / "index.html"
+ index_html_file = HERE / "gallery/examples" / self.name / "index.html"
if not index_html_file.exists(): # pragma: nocover
- raise ValueError(f"No example at {self.path}")
+ raise ValueError(f"No example at {self.name}")
soup = BeautifulSoup(index_html_file.read_text(), "html5lib")
self.extra_head = get_head_nodes(soup)
self.body = get_body_content(soup)
+@dataclass
+class Author(Resource):
+ """Information about an author, from Markdown."""
+
+ def __post_init__(self) -> None:
+ """Initialize the rest of the fields from the Markdown."""
+ md_file = HERE / "gallery/authors" / f"{self.name}.md"
+ md_fm = frontmatter.load(md_file)
+ self.title = md_fm.get("title", "")
+ md = MarkdownIt()
+ self.body = str(md.render(md_fm.content))
+
+
@dataclass
class Page(Resource):
"""A Markdown+frontmatter driven content page."""
subtitle: str = ""
- body: str = ""
def __post_init__(self) -> None:
"""Extract content from either Markdown or HTML file."""
- md_file = HERE / "pages" / f"{self.path}.md"
- html_file = HERE / "pages" / f"{self.path}.html"
+ md_file = HERE / "pages" / f"{self.name}.md"
+ html_file = HERE / "pages" / f"{self.name}.html"
- # If this self.path resolves to a Markdown file, use it first
+ # If this self.name resolves to a Markdown file, use it first
if md_file.exists():
md_fm = frontmatter.load(md_file)
self.title = md_fm.get("title", "")
@@ -146,40 +160,49 @@ def __post_init__(self) -> None:
if body_node and isinstance(body_node, Tag):
self.body = body_node.prettify()
else: # pragma: no cover
- raise ValueError(f"No page at {self.path}")
+ raise ValueError(f"No page at {self.name}")
@dataclass
class Resources:
"""Container for all resources in site."""
- examples: dict[PurePath, Example] = field(default_factory=dict)
- pages: dict[PurePath, Page] = field(default_factory=dict)
+ authors: dict[str, Author] = field(default_factory=dict)
+ examples: dict[str, Example] = field(default_factory=dict)
+ pages: dict[str, Page] = field(default_factory=dict)
-def get_sorted_examples() -> list[PurePath]:
+def get_sorted_paths(target_dir: Path, only_dirs: bool = True) -> list[PurePath]:
"""Return an alphabetized listing of the examples."""
- examples_dir = HERE / "gallery/examples"
- examples = [e for e in examples_dir.iterdir() if e.is_dir()]
- return sorted(examples, key=attrgetter("name"))
+ if only_dirs:
+ paths = [e for e in target_dir.iterdir() if e.is_dir()]
+ else:
+ paths = [e for e in target_dir.iterdir()]
+
+ return sorted(paths, key=attrgetter("name"))
def get_resources() -> Resources:
"""Factory to construct all the resources in the site."""
resources = Resources()
+ # Load the authors
+ authors = HERE / "gallery/authors"
+ for author in get_sorted_paths(authors, only_dirs=False):
+ this_author = Author(name=author.stem)
+ resources.authors[author.stem] = this_author
+
# Load the examples
- for example in get_sorted_examples():
- this_path = PurePath(example.name)
- this_example = Example(path=this_path)
- resources.examples[this_path] = this_example
+ examples = HERE / "gallery/examples"
+ for example in get_sorted_paths(examples):
+ this_example = Example(example.stem)
+ resources.examples[example.stem] = this_example
# Load the Pages
pages_dir = HERE / "pages"
pages = [e for e in pages_dir.iterdir()]
for page in pages:
- this_path = PurePath(page.stem)
- this_page = Page(path=this_path)
- resources.pages[this_path] = this_page
+ this_page = Page(name=page.stem)
+ resources.pages[page.stem] = this_page
return resources
diff --git a/src/psc/templates/author.jinja2 b/src/psc/templates/author.jinja2
new file mode 100644
index 0000000..8547fa8
--- /dev/null
+++ b/src/psc/templates/author.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+{% block extra_head %}
+{% block main %}
+
+ PyScript Authors
+
+ All the contributors to authors and more.
+ {{ example.subtitle }} {{ example.subtitle }}{{ title }}
+ {{ title }}
+ {% if author %}
+
+ {% endif %}
{{ subtitle }}