Alter 21 files

Update `README.md`
Update `__main__.py`
Add `feeds.py`
Update `process.py`
Add `typedef.py`
Update `util.py`
Add `docs.md`
Update `poetry.lock`
Update `pyproject.toml`
Add `content.md`
Add `paralol.jpg`
Update `the-modern-web-sucks.md`
Update `config.yml`
Update `head.html`
Update `header.html`
Update `base.html`
Add `blog_index.html`
Update `blog_post.html`
Update `custom.css`
Delete `house-palette.css`
Update `typography.css`
This commit is contained in:
akp 2023-12-29 00:13:27 +00:00
parent e9601d0476
commit 53d678f5f2
No known key found for this signature in database
GPG key ID: CF8D58F3DEB20755
21 changed files with 395 additions and 47 deletions

View file

@ -1,4 +1,8 @@
# website
* Blog code
* Aside contents
* Aside contents
* Blog tags
* Blog favourites
* New button
* Homepage
* Callouts?

View file

@ -4,14 +4,11 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
import fire
from collections.abc import Callable
import process
import functools
def make_absurl_filter(site_conf: any) -> Callable[[str], str]:
def inner(value):
bu = site_conf["baseURL"]
return bu.rstrip("/") + "/" + value.lstrip("/")
return inner
return functools.partial(get_absolute_url, site_conf)
def run(base_dir: str, output_dir: str = "_dist"):
@ -33,19 +30,20 @@ def run(base_dir: str, output_dir: str = "_dist"):
)
jinja_env.filters["absurl"] = make_absurl_filter(site_config)
jinja_env.filters["fmtdate"] = lambda x: x.strftime("%Y-%m-%d")
process.content(base_dir, output_dir, jinja_env, site_config)
print()
process.blog(base_dir, output_dir, jinja_env, site_config)
os.makedirs((d := output_dir / "blog" / "the-modern-web-sucks"))
with open(d/"index.htm", "w") as f:
f.write(jinja_env.get_template("_layouts/blog_post.html").render({"site": site_config, "post": (x := {"title": "The Modern Web Sucks", "description": "And what can be done about it"}), "page": x}))
thing_overrides = {"rendered": "template"}
thing_overrides = {"rendered": "page"}
res_parts = []
for (key, count) in get_counts().items():
s = "" if count == 1 else "s"
res_parts.append(f"{key} {count} {thing_overrides.get(key, 'file')}{s}")
print()
rprint(INFO_LEADER + "Finished working, " + ", ".join(res_parts))

82
builder/feeds.py Normal file
View file

@ -0,0 +1,82 @@
import xml.etree.ElementTree as ElementTree
from typedef import *
from util import *
import datetime
import json as libjson
def _format_atom_date(date: datetime.date) -> str:
return date_to_datetime(date).astimezone(datetime.timezone.utc).isoformat()
def _get_feed_title(site_config: any) -> str:
return f"{site_config['person']['nickname']}'s blog"
def atom(site_config: any, posts: list[AbbreviatedPost]) -> str:
root = ElementTree.Element("feed", xmlns="http://www.w3.org/2005/Atom")
blog_url = get_absolute_url(site_config, "/blog/")
ElementTree.SubElement(root, "title").text = _get_feed_title(site_config)
ElementTree.SubElement(root, "id").text = blog_url
ElementTree.SubElement(root, "updated").text = (
_format_atom_date(
max(
[post.publishedDate for post in posts]
+ [post.updatedDate for post in posts if post.updatedDate is not None]
)
)
if len(posts) != 0
else ""
)
ElementTree.SubElement(root, "link").text = blog_url
ElementTree.SubElement(
ElementTree.SubElement(root, "author"),
"name",
).text = f"{site_config['person']['firstName']} {site_config['person']['surname']}"
for post in posts:
entry = ElementTree.SubElement(root, "entry")
ElementTree.SubElement(entry, "title").text = post.title
ElementTree.SubElement(entry, "updated").text = _format_atom_date(
post.updatedDate if post.updatedDate is not None else post.publishedDate
)
ElementTree.SubElement(entry, "id").text = post.slug
ElementTree.SubElement(
entry, "link", href=get_absolute_url(site_config, f"/blog/{post.slug}/")
)
ElementTree.SubElement(entry, "summary", type="text").text = post.description
return ElementTree.tostring(root, xml_declaration=True, encoding="unicode")
def json(site_config: any, posts: list[AbbreviatedPost]) -> str:
root = {
"version": "https://jsonfeed.org/version/1.1",
"title": _get_feed_title(site_config),
"home_page_url": get_absolute_url(site_config, "/blog/"),
"feed_url": get_absolute_url(site_config, "/blog/feed.json"),
"authors": [
{
"name": f"{site_config['person']['firstName']} {site_config['person']['surname']}"
}
],
"items": [],
}
for post in posts:
x = {
"id": post.slug,
"url": get_absolute_url(site_config, f"/blog/{post.slug}/"),
"title": post.title,
"content_text": "",
"date_published": _format_atom_date(post.publishedDate),
}
if post.description != "":
x["summary"] = post.description
if post.updatedDate is not None:
x["date_modified"] = _format_atom_date(post.updatedDate)
root["items"].append(x)
return libjson.dumps(root, indent="\t")

View file

@ -3,6 +3,9 @@ from pathlib import Path
from jinja2 import Environment
import shutil
from rich import print as rprint
import mistune
import feeds
from typedef import *
def _template_frontmatter(data: any, jinja_env: Environment, context: any):
@ -20,7 +23,9 @@ class _FileType(enum.Enum):
POST = enum.auto()
def _walk_content(start_dir: str | Path) -> Generator[tuple[str, _FileType], None, None]:
def _walk_content(
start_dir: str | Path,
) -> Generator[tuple[str, _FileType], None, None]:
if type(start_dir) is not Path:
start_dir = Path(start_dir)
@ -46,7 +51,8 @@ def _walk_content(start_dir: str | Path) -> Generator[tuple[str, _FileType], Non
def content(base_dir: Path, output_dir: Path, jinja_env: Environment, site_config: any):
for (fpath, filetype) in _walk_content((walk_dir := base_dir / "content")):
walk_dir = base_dir / "content"
for (fpath, filetype) in _walk_content(walk_dir):
site_inner_path = fpath.relative_to(
walk_dir
) # the path of the file *inside* a site directory structure (eg. inside of `_dist` or inside of `content`)
@ -58,7 +64,7 @@ def content(base_dir: Path, output_dir: Path, jinja_env: Environment, site_confi
render_as_directory = (
site_inner_path.name.lower() != "index.html"
and tpl_frontmatter.get("asDirectory", True)
and bool(tpl_frontmatter.get("asDirectory", True))
)
target_path = output_dir / (
site_inner_path
@ -87,8 +93,149 @@ def content(base_dir: Path, output_dir: Path, jinja_env: Environment, site_confi
update_counts("rendered", 1)
case _:
if filetype != _FileType.STATIC:
rprint(WARN_LEADER + f"Treating [bold]{fpath}[/bold] (type {filetype.name}) as a static file")
rprint(
WARN_LEADER
+ f"Treating [bold]{fpath}[/bold] (type {filetype.name}) as a static file"
)
target_path = output_dir / site_inner_path
os.makedirs(target_path.parent, exist_ok=True)
shutil.copy(fpath, target_path)
update_counts("copied", 1)
BLOG_DATE_FORMAT = "%Y-%m-%d"
def blog(base_dir: Path, output_dir: Path, jinja_env: Environment, site_config: any):
walk_dir = base_dir / "blog"
posts = {}
for (fpath, filetype) in _walk_content(walk_dir):
inner_path = fpath.relative_to(walk_dir)
match filetype:
case _FileType.POST:
with open(fpath) as f:
post_frontmatter, raw_post_md = extract_frontmatter(f.read())
post_slug = (
fpath.name[:-3] if fpath.name != "content.md" else fpath.parent.name
)
# check required keys
missing_keys = [
key
for key in ["title", "publishedDate"]
if key not in post_frontmatter
]
if len(missing_keys) > 0:
rprint(
ERROR_LEADER
+ f"Post [bold]{post_slug}[/bold] missing the following frontmatter keys: "
+ ",".join(missing_keys)
)
raise SystemExit(1)
if post_slug in posts:
rprint(
ERROR_LEADER + f"Duplicate post slug [bold]{post_slug}[/bold]"
)
raise SystemExit(1)
target_path = output_dir / "blog" / post_slug / "index.html"
os.makedirs(target_path.parent, exist_ok=True)
rprint(
INFO_LEADER
+ f"Rendering [bold]{fpath.relative_to(base_dir)}[/bold] => [bold]{target_path}[/bold]"
)
if "updatedDate" in post_frontmatter:
post_frontmatter["updatedDate"] = list(
sorted(post_frontmatter["updatedDate"], reverse=True)
)
posts[post_slug] = post_frontmatter
# render markdown to html
markdown = mistune.create_markdown(escape=False)
rendered_html = markdown(raw_post_md)
# build jinja context
ctx = {
"site": site_config,
"post": post_frontmatter,
"content": rendered_html,
"page": {
k: post_frontmatter[k]
for k in ["title", "description"]
if k in post_frontmatter
},
}
ctx["page"]["canonicalURL"] = f"/blog/{post_slug}/"
# execute jinja template
tpl = jinja_env.get_template("_layouts/blog_post.html")
res = tpl.render(ctx)
# dump to file
with open(target_path, "w") as f:
f.write(res)
update_counts("rendered", 1)
case _:
if filetype != _FileType.STATIC:
rprint(
WARN_LEADER
+ f"Treating [bold]{fpath}[/bold] (type {filetype.name}) as a static file"
)
target_path = output_dir / "blog" / inner_path
os.makedirs(target_path.parent, exist_ok=True)
shutil.copy(fpath, target_path)
update_counts("copied", 1)
# generate listing
post_list = []
for slug in posts:
post = posts[slug]
post_list.append(
AbbreviatedPost(
slug,
post["title"],
post.get("description", ""),
post["publishedDate"],
None
if "updatedDate" not in post or len(post["updatedDate"]) == 0
else post["updatedDate"][0],
)
)
post_list = list(sorted(post_list, key=lambda x: x.publishedDate))
with open(output_dir / "blog" / "index.html", "w") as f:
tpl = jinja_env.get_template("_layouts/blog_index.html")
r = tpl.render(
{
"site": site_config,
"page": {
"title": "Blog",
"canonicalURL": "/blog/",
},
"posts": post_list,
}
)
f.write(r)
update_counts("generated", 1)
# generate feeds
with open(output_dir / "blog" / "feed.atom", "w") as f:
f.write(
feeds.atom(site_config, post_list)
)
with open(output_dir / "blog" / "feed.json", "w") as f:
f.write(
feeds.json(site_config, post_list)
)
update_counts("generated", 2)

5
builder/typedef.py Normal file
View file

@ -0,0 +1,5 @@
from collections import namedtuple
AbbreviatedPost = namedtuple(
"AbbreviatedPost", ["slug", "title", "description", "publishedDate", "updatedDate"]
)

View file

@ -1,10 +1,11 @@
import datetime
import os
from pathlib import Path
import yaml
import enum
from collections.abc import Generator
ERROR_LEADER = "[[red bold]ERR [/bold red] "
ERROR_LEADER = "[[red bold]FAIL[/bold red]] "
WARN_LEADER = "[[yellow bold]WARN[/bold yellow]] "
INFO_LEADER = "[[turquoise2 bold]info[/turquoise2 bold]] "
@ -45,3 +46,12 @@ def extract_frontmatter(instr: str) -> tuple[any, str]:
frontmatter = load_yaml(raw_frontmatter.strip("-\n"))
return frontmatter, trailer
def get_absolute_url(site_config: any, path: str):
bu = site_config["baseURL"]
return bu.rstrip("/") + "/" + path.lstrip("/")
def date_to_datetime(d: datetime.date) -> datetime.datetime:
return datetime.datetime(d.year, d.month, d.day)

56
docs.md Normal file
View file

@ -0,0 +1,56 @@
# akpain.net documentation
## Content templating
The `site/content` directory will be walked with any files and directories starting with `_` being ignored.
`*.html` files will be templated; other files will be copied directly to the output.
When being templated, files are executed with the following context:
```python
{
"site": "<parsed YAML content of the site/config.yml file>: any",
"page": "<parsed YAML frontmatter of the template>: any",
}
```
Frontmatter is templated with the context of `{"site": "<as above>"}`.
Some frontmatter keys that are treated specially are:
* `title`: the page title, used in the \<title> tag and metadata
* `description`: the page description, used in the page's metadata
* `canonicalURL`: the canonical URL of the page, used in the page's metadata
* `imageURL`: the URL of an image to be used in the page's metadata
* `asDirectory`: if the boolean value of this is `True`, this file will not be rendered as a directory.
Once rendered, a template called `/credits.html` will be rendered to `/credits/index.html` unless `asDirectory == True`.
## Blog content
The `site/blog` directory will be walked with any files and directories starting with `_` being ignored.
`*.md` files will be templated and rendered using the `site/content/_layouts/blog_post.html` template using the frontmatter below; other files will be copied directly to the output.
The filename of the Markdown file will be used as the slug of the post. If the filename is `content.md`, the name of the parent directory of the file will be used as the post slug.
```python
{
"site": "<parsed YAML content of the site/config.yml file>: any",
"post": "<parsed YAML frontmatter of the post>: any",
"content": "<rendered HTML content of the post>: str"
}
```
The frontmatter is not templated.
Some frontmatter keys that are treated specially are:
* `title`: **required**, the post title
* `description`: the post description
* `imageURL`: the URL of an image to be used in the page's metadata
* `publishedDate`: **required**, date in the form `YYYY-MM-DD`
* `updatedDate`: a list of dates in the form `YYYY-MM-DD`
* `hidden`: a boolean value, omits the post from the listing and feeds if true
* `tags`: a list of tags that can be applied to a post
Any post with the special tag `favourite` is

14
poetry.lock generated
View file

@ -130,6 +130,18 @@ files = [
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]]
name = "mistune"
version = "3.0.2"
description = "A sane and fast Markdown parser with useful plugins and renderers"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"},
{file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"},
]
[[package]]
name = "pygments"
version = "2.17.2"
@ -255,4 +267,4 @@ tests = ["pytest", "pytest-cov"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "8fe1345ad8aabb6289287c6ec7ad9fcc5c420086c9c437a204b5982b446aac1d"
content-hash = "a89183bd8594d0aa4c78441a7c0307ac71a0195e657ddfb94a033a5fe41cb300"

View file

@ -11,6 +11,7 @@ Jinja2 = "^3.1.2"
PyYAML = "^6.0.1"
fire = "^0.5.0"
rich = "^13.7.0"
mistune = "^3.0.2"
[build-system]

View file

@ -0,0 +1,13 @@
---
title: Things I Learnt During AoC 2023
publishedDate: 2022-05-18
updatedDate:
- 2023-07-09
- 2024-05-26
- 2022-05-26
image: null
tags:
- manifesto
favourite: true
---
![para lol](paralol.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -1,7 +1,7 @@
---
title: The Modern Web Sucks
description: And what can be done about it
date: 2022-05-18
publishedDate: 2022-05-18
updatedAt:
- 2022-05-26
image: null

View file

@ -1,4 +1,4 @@
title: "abi" # used in the header bar and the page titles
title: "Abi K. Pain" # used in the header bar and the page titles
person:
firstName: "Abigail"

View file

@ -1,5 +1,7 @@
<meta charset="UTF-8"/>
<title>{% if page.title %}{{ page.title }} :: {% endif %}{{ site.title }}</title>
{% set __title %}{% if page.title %}{{ page.title }} :: {% endif %}{{ site.title }}{% endset %}
<title>{{ __title }}</title>
<meta property="og:title" content="{{ __title }}">
{% if page.description %}<meta name="description" content="{{ page.description }}">{% endif %}
{% if page.canonicalURL %}<link rel="canonical" href="{{ page.canonicalURL }}">{% endif %}
{% if page.imageURL %}<meta property="og:image" content="{{ page.imageURL }}">{% endif %}
@ -10,7 +12,6 @@
<link rel="stylesheet" href="/assets/fontawesome/css/all.min.css">
<link rel="stylesheet" href="/assets/css/house-palette.css">
<link rel="stylesheet" href="/assets/css/risotto.css">
<link rel="stylesheet" href="/assets/css/custom.css">

View file

@ -1,6 +1,6 @@
<nav class="page__nav main-nav">
<ul>
<h1 class="page__logo"><a href="{{ site.baseURL }}" class="page__logo-inner">{{ site.title }}</a></h1>
<h1 class="page__logo"><a href="{{ site.baseURL }}" class="page__logo-inner">{{ site.title | lower }}</a></h1>
<!-- TODO: Add `active` class to these anchors -->
{% for item in site.menu %}
<li class="main-nav__item"><a class="nav-main-item" href="{{ item.url }}" {% if item.title %}title="{{ item.title }}"{% endif %}>{{ item.name }}</a></li>

View file

@ -3,6 +3,7 @@
<head>
{% include "_includes/head.html" %}
{% block head %}{% endblock %}
</head>
<body>

View file

@ -0,0 +1,24 @@
{% extends "_layouts/base.html" %}
{% block head %}
<link rel="alternate" type="application/atom+xml" href="/blog/feed.atom">
<link rel="alternate" type="application/json" href="/blog/feed.json">
{% endblock %}
{% block main %}
<h1>Blog</h1>
<p>This blog has both an <a href="/blog/feed.atom">Atom</a> and a <a href="/blog/feed.json">JSON</a> feed.</p>
<ul>
{% for (slug, title, _, date, _) in posts %}
<li>
{{ date | fmtdate }} :: <a href="/blog/{{ slug }}">{{ title }}</a>
</li>
{% endfor %}
</ul>
{% endblock %}
{% block aside %}
// TODO(akp): this
{% endblock %}

View file

@ -1,11 +1,18 @@
{% extends "_layouts/base.html" %}
{% block head %}
<link rel="alternate" type="application/atom+xml" href="/blog/feed.atom">
<link rel="alternate" type="application/json" href="/blog/feed.json">
{% endblock %}
{% block main %}
<h1>{{ post.title }}</h1>
{% if post.description %}
<p class="secondary">{{ post.description }}</p>
<p class="small-margin">{{ post.description }}</p>
{% endif %}
<p class="small-margin secondary"><i>{{ post.publishedDate | fmtdate }}{% if post.updatedDate and post.updatedDate | length >= 1 %} (updated {{ post.updatedDate.0 }}){% endif %}</i></p>
<hr>
{{ content | safe }}
{% endblock %}
{% block aside %}

View file

@ -8,4 +8,9 @@ div.padding {
a:hover > img {
filter: brightness(0.8);
}
}
.secondary {
color: var(--muted);
}

View file

@ -1,22 +0,0 @@
/* base16 default dark
* https://github.com/chriskempson/base16-default-schemes
*/
:root {
--base00: #181818;
--base01: #282828;
--base02: #383838;
--base03: #585858;
--base04: #b8b8b8;
--base05: #d8d8d8;
--base06: #e8e8e8;
--base07: #f8f8f8;
--base08: #ab4642;
--base09: #dc9656;
--base0A: #f7ca88;
--base0B: #a1b56c;
--base0C: #86c1b9;
--base0D: #7cafc2;
--base0E: #ba8baf;
--base0F: #a16946;
}

View file

@ -65,6 +65,10 @@ p {
margin: 0 0 1.5rem 0;
}
p.small-margin {
margin: 0 0 0.5rem 0;
}
/* Links */
a:link, a:visited {