Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/psc/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
57 changes: 51 additions & 6 deletions src/psc/app.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -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.
Expand All @@ -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",
Expand All @@ -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),
Expand Down
6 changes: 6 additions & 0 deletions src/psc/gallery/authors/meg-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Margaret
---

An I.T. student.
Currently focusing on programming with Python and learning Django development.
6 changes: 6 additions & 0 deletions src/psc/gallery/authors/pauleveritt.md
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 2 additions & 1 deletion src/psc/gallery/examples/interest_calculator/index.md
Original file line number Diff line number Diff line change
@@ -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.
71 changes: 47 additions & 24 deletions src/psc/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand All @@ -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", "")
Expand All @@ -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
8 changes: 8 additions & 0 deletions src/psc/templates/author.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% extends "layout.jinja2" %}
{% block extra_head %}
{% block main %}
<main id="main_container" class="container">
<h1 class="title">{{ title }}</h1>
<div class="content">{{ body | safe }}</div>
</main>
{% endblock %}
29 changes: 29 additions & 0 deletions src/psc/templates/authors.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{% extends "layout.jinja2" %}
{% block main %}
<section class="hero is-small is-dark">
<div class="hero-body">
<p class="title">
PyScript Authors
</p>
<p class="subtitle">
All the contributors to authors and more.
</p>
</div>
</section>
<main id="main_container" class="container">
<div class="tile is-ancestor">
{% for author in authors %}
<div class="tile is-parent is-4">
<article class="tile is-child box">
<p class="title"><a
href="{{ root_path }}/authors/{{ author.name }}.html/">{{ author.title }}</a>
</p>
<div class="content">
{{ author.body | safe }}
</div>
</article>
</div>
{% endfor %}
</div>
</main>
{% endblock %}
3 changes: 3 additions & 0 deletions src/psc/templates/example.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
{% block main %}
<main id="main_container" class="container">
<h1 class="title">{{ title }}</h1>
{% if author %}
<p>By <a href="../authors/{{ author.name }}.html">{{ author.title }}</a></p>
{% endif %}
<h1 class="subtitle">{{ subtitle }}</h1>
<div class="content">{{ body | safe }}</div>
</main>
Expand Down
32 changes: 17 additions & 15 deletions src/psc/templates/gallery.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,22 @@
</div>
</section>
<main id="main_container" class="container">
<div class="tile is-ancestor">
{% for example in examples %}
<div class="tile is-parent is-4">
<article class="tile is-child box">
<p class="title"><a
href="{{ root_path }}/gallery/examples/{{ example.path.name }}/">{{ example.title }}</a>
</p>
<p class="subtitle">{{ example.subtitle }}</p>
<div class="content">
{{ example.description | safe }}
</div>
</article>
</div>
{% endfor %}
</div>
{% for row in examples | batch(3) %}
<div class="tile is-ancestor">
{% for example in row %}
<div class="tile is-parent is-4">
<article class="tile is-child box">
<p class="title"><a
href="{{ root_path }}/gallery/examples/{{ example.name }}/">{{ example.title }}</a>
</p>
<p class="subtitle">{{ example.subtitle }}</p>
{% if example.author %}
<p style="margin-top: 1em">By <a href="../authors/{{ example.author }}.html">{{ authors[example.author].title }}</a></p>
{% endif %}
</article>
</div>
{% endfor %}
</div>
{% endfor %}
</main>
{% endblock %}
3 changes: 3 additions & 0 deletions src/psc/templates/layout.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
<a id="navbarGallery" class="navbar-item" href="{{ root_path }}/gallery">
Gallery
</a>
<a id="navbarAuthors" class="navbar-item" href="{{ root_path }}/authors">
Authors
</a>
<a id="navbarJoin" class="navbar-item" href="{{ root_path }}/pages/contributing.html">
Join
</a>
Expand Down
Loading