Skip to content

Show the code for examples #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 20, 2022
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
20 changes: 20 additions & 0 deletions src/psc/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,25 @@ async def example(request: Request) -> _TemplateResponse:
)


async def example_code(request: Request) -> _TemplateResponse:
"""Handle the linked files for the code example."""
example_name = request.path_params["example_name"]
resources: Resources = request.app.state.resources
this_example = resources.examples[example_name]
root_path = "../../.."

return templates.TemplateResponse(
"example_code.jinja2",
dict(
title=f"{this_example.title} Code",
extra_head=this_example.extra_head,
request=request,
root_path=root_path,
linked_files=this_example.linked_files,
),
)


async def content_page(request: Request) -> _TemplateResponse:
"""Handle a content page."""
page_name = request.path_params["page_name"]
Expand Down Expand Up @@ -159,6 +178,7 @@ async def content_page(request: Request) -> _TemplateResponse:
Route("/authors", authors),
Route("/authors/{author_name}.html", author),
Route("/gallery/examples/{example_name}/index.html", example),
Route("/gallery/examples/{example_name}/code.html", example_code),
Route("/gallery/examples/{example_name}/", example),
Route("/pages/{page_name}.html", content_page),
Mount("/gallery", StaticFiles(directory=HERE / "gallery")),
Expand Down
2 changes: 2 additions & 0 deletions src/psc/gallery/examples/antigravity/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
---
title: xkcd Antigravity
subtitle: We can fly!
linked_files:
- antigravity.py
---
Based on the [xkcd antigravity](https://xkcd.com/353/)
1 change: 1 addition & 0 deletions src/psc/gallery/examples/hello_world/hello_world.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
h1 {
font-weight: normal;
}
3 changes: 3 additions & 0 deletions src/psc/gallery/examples/hello_world/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
---
title: Hello World
subtitle: The classic hello world, but in Python -- in a browser!
linked_files:
- hello_world.css
- hello_world.js
---
The *body* description.
4 changes: 4 additions & 0 deletions src/psc/gallery/examples/hello_world_py/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
---
title: Hello World Python
subtitle: The hello world example, but in a .py file.
linked_files:
- hello_world.py
- hello_world.css
- hello_world.js
---
The *body* description.
3 changes: 3 additions & 0 deletions src/psc/gallery/examples/interest_calculator/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
title: Compound Interest Calculator
subtitle: Enter some numbers, get some numbers.
author: meg-1
linked_files:
- calculator.py
- styles.css
---
The *body* description.
42 changes: 37 additions & 5 deletions src/psc/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,28 @@ class Resource:
extra_head: str = ""


linked_file_mapping = dict(
py="python",
css="css",
html="html",
js="javascript",
)


@dataclass
class LinkedFile:
"""A source file on disk that gets attached to an example."""

path: Path
language: str = field(init=False)
body: str = field(init=False)

def __post_init__(self) -> None:
"""Read the file contents into the body."""
self.language = linked_file_mapping[self.path.suffix[1:]]
self.body = self.path.read_text()


@dataclass
class Example(Resource):
"""Create an example from an HTML location on disk.
Expand All @@ -91,9 +113,10 @@ class Example(Resource):
Meaning, HERE / "examples" / name / "index.html".
"""

subtitle: str = ""
description: str = ""
author: str | None = None
subtitle: str = field(init=False)
description: str = field(init=False)
author: str = field(init=False)
linked_files: list[LinkedFile] = field(default_factory=list)

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

# Main, extra head example's HTML file.
index_html_file = HERE / "gallery/examples" / self.name / "index.html"
this_example_path = HERE / "gallery/examples" / self.name
index_html_file = this_example_path / "index.html"
if not index_html_file.exists(): # pragma: nocover
raise ValueError(f"No example at {self.name}")
soup = BeautifulSoup(index_html_file.read_text(), "html5lib")
index_html_text = index_html_file.read_text()
soup = BeautifulSoup(index_html_text, "html5lib")
self.extra_head = get_head_nodes(soup)
self.body = get_body_content(soup)

# Process any linked files
linked_paths = [*["index.html"], *md_fm.get("linked_files", [])]
for linked_name in linked_paths:
linked_path = this_example_path / linked_name
linked_file = LinkedFile(path=linked_path)
self.linked_files.append(linked_file)


@dataclass
class Author(Resource):
Expand Down
160 changes: 160 additions & 0 deletions src/psc/static/prism.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+python&plugins=custom-class+toolbar+copy-to-clipboard+download-button */
code[class*=language-], pre[class*=language-] {
color: #000;
background: 0 0;
text-shadow: 0 1px #fff;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none
}

code[class*=language-] ::-moz-selection, code[class*=language-]::-moz-selection, pre[class*=language-] ::-moz-selection, pre[class*=language-]::-moz-selection {
text-shadow: none;
background: #b3d4fc
}

code[class*=language-] ::selection, code[class*=language-]::selection, pre[class*=language-] ::selection, pre[class*=language-]::selection {
text-shadow: none;
background: #b3d4fc
}

@media print {
code[class*=language-], pre[class*=language-] {
text-shadow: none
}
}

pre[class*=language-] {
padding: 1em;
margin: .5em 0;
overflow: auto
}

:not(pre) > code[class*=language-], pre[class*=language-] {
background: #f5f2f0
}

:not(pre) > code[class*=language-] {
padding: .1em;
border-radius: .3em;
white-space: normal
}

.prism--token.prism--cdata, .prism--token.prism--comment, .prism--token.prism--doctype, .prism--token.prism--prolog {
color: #708090
}

.prism--token.prism--punctuation {
color: #999
}

.prism--token.prism--namespace {
opacity: .7
}

.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 {
color: #905
}

.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 {
color: #690
}

.language-css .prism--token.prism--string, .style .prism--token.prism--string, .prism--token.prism--entity, .prism--token.prism--operator, .prism--token.prism--url {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5)
}

.prism--token.prism--atrule, .prism--token.prism--attr-value, .prism--token.prism--keyword {
color: #07a
}

.prism--token.prism--class-name, .prism--token.prism--function {
color: #dd4a68
}

.prism--token.prism--important, .prism--token.prism--regex, .prism--token.prism--variable {
color: #e90
}

.prism--token.prism--bold, .prism--token.prism--important {
font-weight: 700
}

.prism--token.prism--italic {
font-style: italic
}

.prism--token.prism--entity {
cursor: help
}

div.code-toolbar {
position: relative
}

div.code-toolbar > .toolbar {
position: absolute;
z-index: 10;
top: .3em;
right: .2em;
transition: opacity .3s ease-in-out;
opacity: 0
}

div.code-toolbar:hover > .toolbar {
opacity: 1
}

div.code-toolbar:focus-within > .toolbar {
opacity: 1
}

div.code-toolbar > .toolbar > .toolbar-item {
display: inline-block
}

div.code-toolbar > .toolbar > .toolbar-item > a {
cursor: pointer
}

div.code-toolbar > .toolbar > .toolbar-item > button {
background: 0 0;
border: 0;
color: inherit;
font: inherit;
line-height: normal;
overflow: visible;
padding: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none
}

div.code-toolbar > .toolbar > .toolbar-item > a, div.code-toolbar > .toolbar > .toolbar-item > button, div.code-toolbar > .toolbar > .toolbar-item > span {
color: #bbb;
font-size: .8em;
padding: 0 .5em;
background: #f5f2f0;
background: rgba(224, 224, 224, .2);
box-shadow: 0 2px 0 0 rgba(0, 0, 0, .2);
border-radius: .5em
}

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 {
color: inherit;
text-decoration: none
}
Loading