summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Ilvokhin <d@ilvokhin.com>2025-04-19 21:43:53 +0100
committerDmitry Ilvokhin <d@ilvokhin.com>2025-04-19 21:43:53 +0100
commit96d437baab4a622eb540719feb90ea810fb01756 (patch)
tree8757d41e9e2c673965483b2466ae306c9a7b8368
parentea523d411689338876abd7b87d85164009e7ec73 (diff)
downloadblog-96d437baab4a622eb540719feb90ea810fb01756.tar.gz
blog-96d437baab4a622eb540719feb90ea810fb01756.tar.bz2
blog-96d437baab4a622eb540719feb90ea810fb01756.zip
Add support for atom / rss feedHEADmaster
Date format was changed to support `updated` in ISO format. Now every post will require date and time, which might be a usability regression, but we'll see. Also, `updated` field was introduced in metadata. If there is no such field, them date field will be used instead.
-rw-r--r--README.md2
-rw-r--r--blog/atom.py34
-rw-r--r--blog/blog.py4
-rw-r--r--blog/feed.py9
-rw-r--r--blog/post.py50
-rw-r--r--posts/hello-world/metadata.txt2
-rw-r--r--posts/jemalloc-hpa-reference/metadata.txt2
-rw-r--r--posts/libstdc++-std-unordered-map/metadata.txt2
-rw-r--r--share/style.css4
-rw-r--r--templates/atom.xml24
-rw-r--r--templates/feed.html7
-rw-r--r--templates/post.html11
12 files changed, 111 insertions, 40 deletions
diff --git a/README.md b/README.md
index 7ac9d4a..94afe61 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ $ pip3 install -r requirements.txt
```
$ mkdir posts/hello-world
$ echo 'Title: Hello, World!' > posts/hello-world/metadata.txt
-$ echo 'Date: 2022-12-23' >> posts/hello-world/metadata.txt
+$ echo "Date: `date +"%Y-%m-%dT%H:%M:%S%z"`" >> posts/hello-world/metadata.txt
$ echo 'Hello, world!' > posts/hello-world/hello-world.md
$ make deploy
```
diff --git a/blog/atom.py b/blog/atom.py
new file mode 100644
index 0000000..e6d836c
--- /dev/null
+++ b/blog/atom.py
@@ -0,0 +1,34 @@
+import os
+import datetime
+import functools
+from typing import List
+
+from jinja2 import Template
+
+import render
+from post import Post, remove_drafts
+
+
+class Atom(object):
+ template: Template
+ posts: List[Post]
+
+ def __init__(self, template: Template, posts: List[Post]) -> None:
+ self.template = template
+ self.posts = remove_drafts(posts)
+
+ @staticmethod
+ def _now() -> datetime.datetime:
+ now = datetime.datetime.now(datetime.timezone.utc)
+ return now.replace(microsecond=0)
+
+ @functools.cached_property
+ def updated(self) -> datetime.datetime:
+ if not self.posts:
+ return self._now()
+ return self.posts[0].metadata.updated
+
+ def generate(self, basedir: str) -> None:
+ atom = os.path.join(basedir, "atom.xml")
+ rendered = self.template.render(updated=self.updated, posts=self.posts)
+ render.write_file_content(atom, rendered)
diff --git a/blog/blog.py b/blog/blog.py
index 27845bc..4b9a345 100644
--- a/blog/blog.py
+++ b/blog/blog.py
@@ -10,6 +10,7 @@ from jinja2 import Template, Environment, FileSystemLoader, select_autoescape
from post import Post
from feed import Feed
+from atom import Atom
def recreate_workdir(basedir: str) -> None:
@@ -49,6 +50,9 @@ def generate_blog() -> None:
feed = Feed(env.get_template("feed.html"), posts)
feed.generate(workdir)
+ atom = Atom(env.get_template("atom.xml"), posts)
+ atom.generate(workdir)
+
copy_share(workdir)
diff --git a/blog/feed.py b/blog/feed.py
index 3e5b2e0..381aab6 100644
--- a/blog/feed.py
+++ b/blog/feed.py
@@ -1,10 +1,11 @@
import os
+import datetime
from typing import List
from jinja2 import Template
import render
-from post import Post
+from post import Post, remove_drafts
class Feed(object):
@@ -13,11 +14,7 @@ class Feed(object):
def __init__(self, template: Template, posts: List[Post]) -> None:
self.template = template
- self.posts = Feed._remove_drafts(posts)
-
- @staticmethod
- def _remove_drafts(posts: List[Post]) -> List[Post]:
- return list(filter(lambda x: x.metadata.status != "draft", posts))
+ self.posts = remove_drafts(posts)
def generate(self, basedir: str) -> None:
index = os.path.join(basedir, "index.html")
diff --git a/blog/post.py b/blog/post.py
index 8a7c880..955724a 100644
--- a/blog/post.py
+++ b/blog/post.py
@@ -2,27 +2,23 @@ import os
import shutil
import datetime
import functools
-from typing import Dict
+from typing import Dict, List
from jinja2 import Template
+from dataclasses import dataclass
+
import render
+@dataclass(slots=True)
class Metadata(object):
- __slots__ = ("title", "author", "date", "status")
title: str
author: str
- date: str
+ date: datetime.datetime
+ updated: datetime.datetime
status: str
- def __init__(self, title: str, author: str,
- date: str, status: str) -> None:
- self.title = title
- self.author = author
- self.date = date
- self.status = status
-
class Post(object):
template: Template
@@ -52,33 +48,45 @@ class Post(object):
title = raw["Title"]
author = raw["Author"]
- date = raw.get("Date", datetime.date.today().strftime("%Y-%m-%d"))
+ date = raw["Date"]
+ updated = raw.get("Updated", date)
status = raw.get("Status", "draft")
- return Metadata(title, author, date, status)
+ return Metadata(
+ title,
+ author,
+ datetime.datetime.fromisoformat(date),
+ datetime.datetime.fromisoformat(updated),
+ status
+ )
+
+ @functools.cached_property
+ def content(self) -> str:
+ md = None
+ for filename in os.listdir(self.directory):
+ if filename.endswith(".md"):
+ return render.to_html(os.path.join(self.directory, filename))
+ assert False, f"There is no markdown file in `{self.directory}`"
def generate(self, basedir: str) -> None:
postdir = os.path.basename(self.directory)
workdir = os.path.join(basedir, postdir)
os.makedirs(workdir)
- md = None
for filename in os.listdir(self.directory):
source = os.path.join(self.directory, filename)
destination = os.path.join(workdir, filename)
-
shutil.copy(source, destination)
- if filename.endswith(".md"):
- md = source
-
- assert md, f"There is no markdown file in `{self.directory}`"
-
- content = render.to_html(md)
rendered = self.template.render(title=self.metadata.title,
author=self.metadata.author,
date=self.metadata.date,
+ updated=self.metadata.updated,
status=self.metadata.status,
- content=content)
+ content=self.content)
render.write_file_content(os.path.join(workdir, "index.html"),
rendered)
+
+
+def remove_drafts(posts: List[Post]) -> List[Post]:
+ return list(filter(lambda x: x.metadata.status != "draft", posts))
diff --git a/posts/hello-world/metadata.txt b/posts/hello-world/metadata.txt
index 4c57cf0..6a41b6b 100644
--- a/posts/hello-world/metadata.txt
+++ b/posts/hello-world/metadata.txt
@@ -1,4 +1,4 @@
Title: Hello, World!
Author: Dmitry Ilvokhin
-Date: 2022-12-23
+Date: 2022-12-23T17:34:00+00:00
Status: published
diff --git a/posts/jemalloc-hpa-reference/metadata.txt b/posts/jemalloc-hpa-reference/metadata.txt
index 0a84976..44feafe 100644
--- a/posts/jemalloc-hpa-reference/metadata.txt
+++ b/posts/jemalloc-hpa-reference/metadata.txt
@@ -1,4 +1,4 @@
Title: Jemalloc HPA Reference
Author: Dmitry Ilvokhin
-Date: 2025-03-23
+Date: 2025-03-23T12:58:00+00:00
Status: published
diff --git a/posts/libstdc++-std-unordered-map/metadata.txt b/posts/libstdc++-std-unordered-map/metadata.txt
index 24b3750..603d518 100644
--- a/posts/libstdc++-std-unordered-map/metadata.txt
+++ b/posts/libstdc++-std-unordered-map/metadata.txt
@@ -1,4 +1,4 @@
Title: How libstdc++ `std::unordered_map` implemented?
Author: Dmitry Ilvokhin
-Date: 2023-07-02
+Date: 2023-07-02T13:08:00+01:00
Status: published
diff --git a/share/style.css b/share/style.css
index 592e612..a6b2576 100644
--- a/share/style.css
+++ b/share/style.css
@@ -112,10 +112,6 @@ ul.posts li span {
ul.posts time {
}
-ul.posts li a:visited {
- color: #8b6fcb;
-}
-
.toclink {
color: #222;
}
diff --git a/templates/atom.xml b/templates/atom.xml
new file mode 100644
index 0000000..ba7fbda
--- /dev/null
+++ b/templates/atom.xml
@@ -0,0 +1,24 @@
+<feed xmlns="http://www.w3.org/2005/Atom">
+ <title>blog.ilvokhin.com</title>
+ <id>tag:blog.ilvokhin.com,2022:blog.ilvokhin.com</id>
+ <link rel="alternate" href="https://blog.ilvokhin.com"></link>
+ <link rel="self" href="https://blog.ilvokhin.com/atom.xml"></link>
+ <updated>{{ updated.isoformat() }}</updated>
+ <author>
+ <name>Dmitry Ilvokhin</name>
+ <uri>https://ilvokhin.com/</uri>
+ <email>d@ilvokhin.com</email>
+ </author>
+{% if posts %}
+ {% for post in posts %}
+ <entry>
+ <title>{{ post.metadata.title }}</title>
+ <id>tag:blog.ilvokhin.com,2022:blog.ilvokhin.com/{{ post.name }}</id>
+ <link rel="alternate" href="https://blog.ilvokhin.com/{{ post.name }}"></link>
+ <published>{{ post.metadata.date.isoformat() }}</published>
+ <updated>{{ post.metadata.updated.isoformat() }}</updated>
+ <content type="html">{{ post.content }}</content>
+ </entry>
+ {% endfor %}
+{% endif %}
+</feed>
diff --git a/templates/feed.html b/templates/feed.html
index deb89c5..e17d3d0 100644
--- a/templates/feed.html
+++ b/templates/feed.html
@@ -28,8 +28,8 @@
<li>
<span>
<i>
- <time datetime="{{ post.metadata.date }}" pubdate>
- {{ post.metadata.date }}
+ <time datetime="{{ post.metadata.date.strftime('%Y-%m-%d') }}" pubdate>
+ {{ post.metadata.date.strftime('%Y-%m-%d') }}
</time>
</i>
</span>
@@ -41,6 +41,9 @@
</content>
</main>
+<footer>
+ <small><p><a class="rss" href="/atom.xml">Atom / RSS</a></p><small>
+</footer>
</body>
</html>
diff --git a/templates/post.html b/templates/post.html
index 20c3ac2..f61a2bd 100644
--- a/templates/post.html
+++ b/templates/post.html
@@ -22,9 +22,14 @@
<main>
<h1>{{ title }}</h1>
<p>
- <i><time datetime="{{ date }}" pubdate>
- {{ date }}
- </time></i>
+ <i>
+ <time datetime="{{ date.strftime('%Y-%m-%d') }}" pubdate>
+ {{ date.strftime('%Y-%m-%d') }}
+ </time>
+ {% if date != updated %}
+ (updated <time datetime="{{ updated.strftime('%Y-%m-%d') }}">{{ updated.strftime('%Y-%m-%d') }}</time>)
+ {% endif %}
+ </i>
{% if status == "draft" %}
• {{ status }}
{% endif %}