summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBotond Hende <nettingman@gmail.com>2024-09-04 20:05:51 +0200
committerBotond Hende <nettingman@gmail.com>2024-09-04 20:05:51 +0200
commitbea80928f694671010bc99493d31879df7d42836 (patch)
treeafb71282df8f01ce4b2dd87a292febe7ad8e3537
initial commit
-rw-r--r--.gitignore3
-rw-r--r--.gitmodules3
-rw-r--r--__init__.py0
-rw-r--r--__main__.py126
-rw-r--r--assets/css/blog.css278
-rw-r--r--assets/font/JetBrainsMono-Regular.woff2bin0 -> 92164 bytes
-rw-r--r--assets/image/arch.svg1
-rw-r--r--assets/image/email.svg1
-rw-r--r--assets/image/git.svg1
-rw-r--r--assets/image/rss.svg1
-rw-r--r--assets/image/website.svg1
-rw-r--r--config.py29
-rw-r--r--modules/__init__.py0
-rw-r--r--modules/blogpost_processor.py228
m---------modules/warko0
-rw-r--r--posts/let-there-be-light-theme/blog.webpbin0 -> 67994 bytes
-rw-r--r--posts/let-there-be-light-theme/content.md22
-rw-r--r--posts/let-there-be-light-theme/deeper.html46
-rw-r--r--posts/let-there-be-light-theme/meta.json7
-rw-r--r--posts/making-this-blog/book.md13
-rw-r--r--posts/making-this-blog/content.md74
-rw-r--r--posts/making-this-blog/meta.json7
-rw-r--r--posts/making-this-blog/yggdrasil.webpbin0 -> 24140 bytes
-rw-r--r--requirements.txt2
-rw-r--r--templates/acknowledgement.html.j218
-rw-r--r--templates/base.html.j248
-rw-r--r--templates/blogpost.html.j230
-rw-r--r--templates/blogpost_sub.html.j210
-rw-r--r--templates/feed.xml.j220
-rw-r--r--templates/index.html.j218
-rw-r--r--templates/sidebar.html.j235
-rw-r--r--templates/tag.html.j218
-rw-r--r--templates/tags.html.j28
33 files changed, 1048 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..07da734
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.idea/
+__pycache__/
+venv/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..8a16fa0
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "modules/warko"]
+ path = modules/warko
+ url = https://git.wazul.moe/warko
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/__init__.py
diff --git a/__main__.py b/__main__.py
new file mode 100644
index 0000000..14819f6
--- /dev/null
+++ b/__main__.py
@@ -0,0 +1,126 @@
+import datetime
+import email.utils
+import os.path
+import shutil
+import sys
+
+import jinja2
+from .config import Config
+from .modules import blogpost_processor
+
+
+def init_jinja_env() -> jinja2.Environment:
+ jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader(Config.TEMPLATES_SOURCE_DIR))
+ return jinja_env
+
+
+def main(output_root_path: str, local: bool):
+ if local:
+ Config.BLOG_ROOT_URL = "http://localhost"
+
+ if os.path.exists(output_root_path):
+ shutil.rmtree(output_root_path)
+
+ os.mkdir(output_root_path)
+
+ jinja_env = init_jinja_env()
+ posts = blogpost_processor.get_posts(Config.POST_SOURCE_DIR)
+ tag_occurrence_count = {}
+ for post in posts:
+ for tag in post.meta_data[blogpost_processor.TAGS_KEY]:
+ if tag in tag_occurrence_count:
+ tag_occurrence_count[tag] += 1
+ else:
+ tag_occurrence_count[tag] = 1
+
+ tags = [str(key) for key in tag_occurrence_count.keys()]
+ tags.sort(key=lambda key: tag_occurrence_count[key], reverse=True)
+ top_tags = tags[:10]
+
+ jinja_env.globals.update(site={
+ "assets_path": Config.ASSETS_IMPORT_PATH,
+ "assets_path_static": Config.ASSETS_IMPORT_PATH_STATIC,
+ "host_name": Config.BLOG_HOSTNAME,
+ "blog_name": Config.BLOG_NAME,
+ "subtitle": Config.BLOG_SUBTITLE,
+ "top_tags": top_tags
+ })
+
+ blogpost_template = jinja_env.get_template("blogpost.html.j2")
+ subpage_template = jinja_env.get_template("blogpost_sub.html.j2")
+ for post in posts:
+ output_dir = os.path.join(output_root_path, "posts", post.get_publish_year(), post.name)
+ os.makedirs(output_dir, exist_ok=True)
+ for extra_file in post.extra_files:
+ from_path = os.path.join(post.path, extra_file)
+ if os.path.isdir(from_path):
+ shutil.copytree(from_path, os.path.join(output_dir, extra_file))
+ else:
+ shutil.copyfile(from_path, os.path.join(output_dir, extra_file))
+
+ ctx = {
+ "post": post,
+ "url": f"{Config.BLOG_ROOT_URL}{post.href}"
+ }
+ with open(os.path.join(output_dir, "index.html"), "w") as f:
+ f.write(blogpost_template.render(ctx))
+
+ for subpage in post.subpages:
+ ctx = {
+ "post": post,
+ "subpage_name": subpage[0],
+ "subpage_html": subpage[1],
+ "url": f"{Config.BLOG_ROOT_URL}{post.href}/{subpage[0]}.html"
+ }
+
+ with open(os.path.join(output_dir, f"{subpage[0]}.html"), "w") as f:
+ f.write(subpage_template.render(ctx))
+
+ tags_template = jinja_env.get_template("tags.html.j2")
+ output_dir = os.path.join(output_root_path, "tags")
+ os.makedirs(output_dir, exist_ok=True)
+
+ ctx = {
+ "config": Config,
+ "tags_ls": " ".join([f'<a href="{tag}.html">{tag}/</a>' for tag in tags]),
+ "url": f"{Config.BLOG_ROOT_URL}/tags"
+ }
+ with open(os.path.join(output_dir, "index.html"), "w") as f:
+ f.write(tags_template.render(ctx))
+
+ tag_template = jinja_env.get_template("tag.html.j2")
+ for tag in tags:
+ ctx = {
+ "config": Config,
+ "tag": tag,
+ "posts": [post for post in posts if tag in post.meta_data["tags"]],
+ "url": f"{Config.BLOG_ROOT_URL}/tags/{tag}.html"
+ }
+ with open(os.path.join(output_dir, "{}.html".format(tag)), "w") as f:
+ f.write(tag_template.render(ctx))
+
+ index_template = jinja_env.get_template("index.html.j2")
+ ctx = {
+ "posts": posts,
+ "url": Config.BLOG_ROOT_URL
+ }
+ with open(os.path.join(output_root_path, "index.html"), "w") as f:
+ f.write(index_template.render(ctx))
+
+ acknowledgement_template = jinja_env.get_template("acknowledgement.html.j2")
+ with open(os.path.join(output_root_path, "acknowledgement.html"), "w") as f:
+ f.write(acknowledgement_template.render({}))
+
+ rss_template = jinja_env.get_template("feed.xml.j2")
+ ctx = {
+ "build_date": email.utils.format_datetime(datetime.datetime.now(Config.TIMEZONE)),
+ "posts": posts[:5]
+ }
+ with open(os.path.join(output_root_path, "feed.xml"), "w") as f:
+ f.write(rss_template.render(ctx))
+
+ shutil.copytree(Config.ASSETS_SOURCE_DIR, output_root_path + Config.ASSETS_IMPORT_PATH)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1], bool(int(sys.argv[2])) if len(sys.argv) > 2 else False)
diff --git a/assets/css/blog.css b/assets/css/blog.css
new file mode 100644
index 0000000..de80799
--- /dev/null
+++ b/assets/css/blog.css
@@ -0,0 +1,278 @@
+:root
+{
+ --term-fg: #b5aba8;
+ --term-bg: #2b2b2b;
+ --term-black: #515151;
+ --term-red: #cc6666;
+ --term-green: #b5bd68;
+ --term-yellow: #f0c674;
+ --term-blue: #81a2be;
+ --term-magenta: #b294bb;
+ --term-cyan: #8abeb7;
+ --term-white: #b5aba8;
+}
+
+@font-face {
+ font-family: "jetbrains-mono";
+ src: url("/assets/font/JetBrainsMono-Regular.woff2");
+}
+
+html {
+ font-family: "jetbrains-mono", serif;
+ font-variant-ligatures: none;
+}
+
+header, .icon-text {
+ display: flex;
+ flex-flow: row;
+ align-items: center;
+}
+
+.icon-text {
+ margin-top: 0.2rem;
+ margin-bottom: 0.2rem;
+}
+
+.icon-text > :first-child {
+ margin-right: 0.3rem;
+}
+
+header {
+ margin-top: 0rem;
+ margin-bottom 1rem;
+}
+
+header div {
+ flex: 1;
+}
+
+header span {
+ flex: 0.2;
+}
+
+.blog-title {
+ margin-top: 0rem;
+}
+
+.ascii-art {
+ font-size: 0.6rem;
+ white-space: pre;
+}
+
+hr {
+ width: 90%;
+ color: var(--term-black);
+}
+
+.nobr,
+.icon-text p,
+.icon-text span {
+ white-space: nowrap;
+}
+
+.force-wrap p, .force-wrap {
+ word-wrap: break-word;
+}
+
+.code-block,
+.code-block-wrap {
+ border-style: solid;
+ border-color: var(--term-white);
+ border-width: 0.1rem;
+ padding: 0.7rem;
+}
+
+.code-block {
+ overflow-x: auto;
+}
+
+figure {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+figcaption {
+ text-align: center;
+ color: var(--term-fg);
+}
+
+h1 {
+ font-size: 2rem;
+ margin-top: 2rem;
+}
+
+h2 {
+ font-size: 1.5rem;
+ margin-top: 1.5rem;
+}
+
+h3 {
+ font-size: 1.2rem;
+ margin-top: 1.2rem;
+}
+
+p {
+ font-size: 1rem;
+ margin-top: 0.8rem;
+}
+
+.code-block p,
+.code-block-wrap p {
+ margin: 0;
+}
+
+.code-block p {
+ white-space: pre;
+}
+
+body {
+ background-color: var(--term-bg);
+}
+
+p, h1, h2, h3 {
+ color: var(--term-fg);
+ margin-bottom: 0.8rem;
+}
+
+h2.index-title {
+ margin-top: 0.8rem;
+}
+
+img
+{
+ max-width: 100%;
+ max-height: 100%;
+ border-style: solid;
+ border-color: var(--term-black);
+ border-width: 0.1rem;
+ width: 100%;
+}
+
+.svg-icon {
+ max-width: 1.5rem;
+ max-height: 1.5rem;
+ border: none;
+ width: 1.5rem;
+ height: 1.5rem;
+}
+
+a.dont-bother {
+ color: var(--term-fg);
+ font-weight: normal;
+}
+
+a.dont-bother:hover {
+ text-decoration: none;
+}
+
+strong {
+ color: var(--term-yellow);
+}
+
+a {
+ font-weight: bold;
+ color: var(--term-blue);
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+a.posts-listing-link:hover :is(h2, h3)
+{
+ text-decoration: underline;
+}
+
+@media (min-width: 768px) {
+ main {
+ border-width: 0.15rem;
+ border-color: var(--term-black);
+ border-style: none solid none solid;
+ }
+}
+
+main :is(h1, h2, h3) {
+ color: var(--term-yellow);
+ margin-top: 2rem;
+}
+
+.other_post {
+ display: flex;
+ flex-flow: row;
+ align-items: center;
+ margin: 2rem 0rem;
+ gap: 1rem;
+}
+
+.other_post div {
+ flex: 1;
+}
+
+.other_post img {
+ max-width: 100%;
+ max-height: 100%;
+}
+
+.first-element {
+ margin-top: 0;
+}
+
+.last-element {
+ margin-bottom: 0;
+}
+
+div.huge-vertical-spacer-at-the-bottom {
+ height: 10rem;
+}
+
+@media (max-width: 767px) {
+ div.sidebar-spacing {
+ height: 10rem;
+ }
+}
+
+div.blog-content-spacer {
+ height: 2rem;
+}
+
+/* Extra "terminal" colors */
+.fg {
+ color: var(--term-fg);
+}
+
+.bg {
+ color: var(--term-bg);
+}
+
+.black {
+ color: var(--term-black);
+}
+
+.red {
+ color: var(--term-red);
+}
+
+.green {
+ color: var(--term-green);
+}
+
+.yellow {
+ color: var(--term-yellow);
+}
+
+.blue {
+ color: var(--term-blue);
+}
+
+.magenta {
+ color: var(--term-magenta);
+}
+
+.cyan {
+ color: var(--term-cyan);
+}
+
+.white {
+ color: var(--term-white);
+}
diff --git a/assets/font/JetBrainsMono-Regular.woff2 b/assets/font/JetBrainsMono-Regular.woff2
new file mode 100644
index 0000000..40da427
--- /dev/null
+++ b/assets/font/JetBrainsMono-Regular.woff2
Binary files differ
diff --git a/assets/image/arch.svg b/assets/image/arch.svg
new file mode 100644
index 0000000..2d7e87a
--- /dev/null
+++ b/assets/image/arch.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="#1793d1" d="M12 2c-.89 2.18-1.43 3.61-2.42 5.73c.61.64 1.35 1.39 2.56 2.24c-1.3-.54-2.19-1.07-2.85-1.63C8 11 6.03 14.75 2 22c3.17-1.83 5.63-2.96 7.92-3.39c-.1-.42-.16-.88-.15-1.36v-.1c.05-2.03 1.11-3.59 2.36-3.48c1.25.1 2.22 1.83 2.17 3.87c-.01.38-.05.75-.12 1.09c2.26.44 4.69 1.56 7.82 3.37c-.62-1.14-1.17-2.16-1.69-3.13c-.81-.64-1.7-1.48-3.46-2.37c1.21.3 2.08.66 2.76 1.07C14.26 7.62 13.83 6.3 12 2"/></svg> \ No newline at end of file
diff --git a/assets/image/email.svg b/assets/image/email.svg
new file mode 100644
index 0000000..ed4c824
--- /dev/null
+++ b/assets/image/email.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="#f0c674" d="M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2zm-2 0l-8 5l-8-5zm0 12H4V8l8 5l8-5z"/></svg>
diff --git a/assets/image/git.svg b/assets/image/git.svg
new file mode 100644
index 0000000..304ecff
--- /dev/null
+++ b/assets/image/git.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 128 128"><path fill="#f34f29" d="M124.737 58.378L69.621 3.264c-3.172-3.174-8.32-3.174-11.497 0L46.68 14.71l14.518 14.518c3.375-1.139 7.243-.375 9.932 2.314c2.703 2.706 3.461 6.607 2.294 9.993l13.992 13.993c3.385-1.167 7.292-.413 9.994 2.295c3.78 3.777 3.78 9.9 0 13.679a9.673 9.673 0 0 1-13.683 0a9.68 9.68 0 0 1-2.105-10.521L68.574 47.933l-.002 34.341a9.7 9.7 0 0 1 2.559 1.828c3.778 3.777 3.778 9.898 0 13.683c-3.779 3.777-9.904 3.777-13.679 0c-3.778-3.784-3.778-9.905 0-13.683a9.7 9.7 0 0 1 3.167-2.11V47.333a9.6 9.6 0 0 1-3.167-2.111c-2.862-2.86-3.551-7.06-2.083-10.576L41.056 20.333L3.264 58.123a8.133 8.133 0 0 0 0 11.5l55.117 55.114c3.174 3.174 8.32 3.174 11.499 0l54.858-54.858a8.135 8.135 0 0 0-.001-11.501"/></svg> \ No newline at end of file
diff --git a/assets/image/rss.svg b/assets/image/rss.svg
new file mode 100644
index 0000000..3554b8a
--- /dev/null
+++ b/assets/image/rss.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="#f34f29" d="M6.18 15.64a2.18 2.18 0 0 1 2.18 2.18C8.36 19 7.38 20 6.18 20C5 20 4 19 4 17.82a2.18 2.18 0 0 1 2.18-2.18M4 4.44A15.56 15.56 0 0 1 19.56 20h-2.83A12.73 12.73 0 0 0 4 7.27zm0 5.66a9.9 9.9 0 0 1 9.9 9.9h-2.83A7.07 7.07 0 0 0 4 12.93z"/></svg>
diff --git a/assets/image/website.svg b/assets/image/website.svg
new file mode 100644
index 0000000..809a2ac
--- /dev/null
+++ b/assets/image/website.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="#f0c674" d="M16.36 14c.08-.66.14-1.32.14-2s-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2s.06-1.35.16-2h4.68c.09.65.16 1.32.16 2s-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.92 7.92 0 0 1 9.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8 8 0 0 1 5.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2s.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.7 15.7 0 0 0-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2"/></svg>
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..2e57f6f
--- /dev/null
+++ b/config.py
@@ -0,0 +1,29 @@
+import os.path
+import datetime
+
+
+class Config:
+ ASSETS_IMPORT_PATH = "/assets"
+ ASSETS_IMPORT_PATH_STATIC = "https://static.wazul.moe"
+ RSS_FILE_NAME = "feed.xml"
+ BLOG_ROOT_URL = "https://blog.wazul.moe"
+ TIMEZONE = datetime.timezone(datetime.timedelta(hours=2))
+
+ __DIR = os.path.dirname(__file__)
+ ASSETS_SOURCE_DIR = os.path.join(__DIR, "assets")
+ TEMPLATES_SOURCE_DIR = os.path.join(__DIR, "templates")
+ POST_SOURCE_DIR = os.path.join(__DIR, "posts")
+
+ BLOG_HOSTNAME = "yggdrasil"
+ BLOG_NAME = "@{}".format(BLOG_HOSTNAME)
+ BLOG_SUBTITLE = "The chronicle of my works and learnings"
+ BLOG_USER = "reader"
+ BLOG_OWNER = "http"
+
+ @staticmethod
+ def get_prompt(path: str, cmd: str) -> str:
+ return "<span class=\"green\">{}@{}</span> <span class=\"red\">{} $</span> {}".format(Config.BLOG_USER, Config.BLOG_HOSTNAME, path, cmd)
+
+ @staticmethod
+ def get_tag_prompt(tag: str, cmd: str) -> str:
+ return Config.get_prompt("~/tags/{}".format("" if tag == "" else tag + "/"), cmd)
diff --git a/modules/__init__.py b/modules/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/modules/__init__.py
diff --git a/modules/blogpost_processor.py b/modules/blogpost_processor.py
new file mode 100644
index 0000000..b29d91c
--- /dev/null
+++ b/modules/blogpost_processor.py
@@ -0,0 +1,228 @@
+import datetime
+import email.utils
+import json
+import os.path
+import random
+import urllib
+from typing import Optional
+
+import marko
+import marko.inline
+
+from .warko.strikethrough import StrikeThrough
+from .warko.headinginjector import get_heading_injector
+from .warko.extendedimage import get_image_renderer, ExtendedImageElement
+from .warko.newtablink import get_new_tab_links
+from ..config import Config
+
+CONTENT_FILE_NAME = "content.md"
+META_FILE_NAME = "meta.json"
+
+TITLE_KEY = "title"
+PUBLISH_DATE_KEY = "publish_date"
+TAGS_KEY = "tags"
+THUMBNAIL_KEY = "thumbnail"
+INTRO_KEY = "intro"
+
+INTRO_DESIRED_LENGTH = 320
+INTRO_MAX_EXTRA_LENGTH = 100
+
+posts_cache = []
+
+
+def get_posts_from_cache() -> list('Post'):
+ if len(posts_cache) == 0:
+ raise Exception("Getting posts list from cache while it is still empty.")
+
+ return posts_cache.copy()
+
+
+class Post:
+ PARSER = marko.Markdown(renderer=marko.HTMLRenderer
+ , extensions=[
+ StrikeThrough
+ , get_heading_injector("> ")
+ , get_image_renderer(["blog.wazul.moe"])
+ , get_new_tab_links(["blog.wazul.moe"])
+ ]
+ )
+
+ def __init__(self, path: str):
+ self.path = path
+ self.name = os.path.basename(path)
+
+ with open(os.path.join(path, CONTENT_FILE_NAME)) as f:
+ self.content = Post.PARSER.parse(f.read())
+
+ self.html = Post.PARSER.render(self.content)
+
+ with open(os.path.join(path, META_FILE_NAME)) as f:
+ self.meta_data = json.load(f)
+
+ self.subpages = []
+ self.extra_files = []
+ for elem in os.listdir(path):
+ if elem != CONTENT_FILE_NAME and elem != META_FILE_NAME:
+ if elem.endswith(".md"):
+ with open(os.path.join(path, elem)) as f:
+ subpage_content = Post.PARSER.parse(f.read())
+
+ self.subpages.append((os.path.basename(elem)[:-3], Post.PARSER.render(subpage_content)))
+ else:
+ self.extra_files.append(elem)
+
+ self.href = f"/posts/{self.get_publish_year()}/{self.name}"
+
+ if THUMBNAIL_KEY in self.meta_data:
+ self.thumbnail = self.meta_data[THUMBNAIL_KEY]
+ else:
+ self.thumbnail = Post._get_first_image_path(self.content)
+
+ self.thumbnail = f"{self.href}/{self.thumbnail}"
+
+ if INTRO_KEY in self.meta_data:
+ self.intro = self.meta_data[INTRO_KEY]
+ else:
+ intro_str = Post._extract_first_paragraph_text(self.content)
+ if len(intro_str) > 320:
+ intro_str = intro_str[:320] # cut to length
+ # first try to cut at the last period, if it's not too far...
+ last_dot_pos = intro_str.rfind(".")
+ if 320 - last_dot_pos > 100:
+ intro_str = intro_str[:last_dot_pos + 1]
+ else:
+ intro_str += "..." # If too far, just add more dots
+
+ self.intro = intro_str
+
+ def title(self) -> str:
+ return self.meta_data[TITLE_KEY]
+
+ def get_tags(self) -> str:
+ return " ".join(["<a href=\"{}/tags/{}.html\">{}</a>".format(Config.BLOG_ROOT_URL, tag, tag) for tag in
+ self.meta_data[TAGS_KEY]])
+
+ def get_link(self):
+ return urllib.parse.urljoin(Config.BLOG_ROOT_URL, self.href)
+
+ def get_publish_time(self) -> str:
+ return self.meta_data[PUBLISH_DATE_KEY]
+
+ def get_publish_time_rfc2822(self) -> str:
+ return email.utils.format_datetime(datetime.datetime.fromisoformat(self.get_publish_time() + " 12:00+02:00"))
+
+ def get_publish_year(self) -> str:
+ return self.meta_data[PUBLISH_DATE_KEY][0:4]
+
+ def get_fake_path(self) -> str:
+ return "~/posts/{}/{}".format(self.get_publish_year(), self.name)
+
+ def get_prompt(self, cmd: str) -> str:
+ return Config.get_prompt(self.get_fake_path(), cmd)
+
+ def get_cat_prompt(self, file: str) -> str:
+ return self.get_prompt(f"cat {file}")
+
+ def get_index_prompt(self) -> str:
+ return Config.get_prompt("~", f"head {self.get_fake_path()}/content")
+
+ def get_similar_posts(self) -> list['Post']:
+ ret_list = []
+
+ posts = get_posts_from_cache()
+ idx_self = posts.index(self)
+
+ # TODO move to config
+ MAX_SIMILAR_POST_COUNT = 5
+ POSTS_AROUND_DISTANCE = 5
+
+ # add the previous post
+ if idx_self < len(posts) - 1:
+ ret_list.append(posts.pop(idx_self + 1))
+
+ # TODO add some tagbased search when I have more content
+
+ # fallback: add random posts from around the current post
+ posts_around = posts[idx_self - POSTS_AROUND_DISTANCE:idx_self + POSTS_AROUND_DISTANCE]
+ posts_around.remove(self)
+
+ while len(posts_around) > 0 and len(ret_list) < MAX_SIMILAR_POST_COUNT:
+ rand_index = random.randint(0, len(posts_around) - 1)
+ ret_list.append(posts_around.pop(rand_index))
+
+ return ret_list
+
+ def get_similar_posts_ls(self) -> str:
+ # TODO fix
+ return self.generate_similar_posts_ls([self, self, self])
+
+ @staticmethod
+ def generate_similar_posts_ls(other_posts: list['Post']) -> str:
+ lines = ["total 0"]
+
+ for post in other_posts:
+ lines.append("lrwxrwxrwx 1 {} {} {} {} 11:11 '{}' -> {}".format( # TODO random time and fix filename escape
+ Config.BLOG_OWNER, Config.BLOG_OWNER, len(post.get_fake_path()), post.get_publish_time(), post.title(),
+ post.get_fake_path())
+ )
+
+ return "<br>".join(lines)
+
+ @staticmethod
+ def _extract_first_paragraph_text(root) -> str:
+ for child in root.children:
+ if isinstance(child, marko.block.Paragraph):
+ paragraph_str = ""
+ for part in child.children:
+ if isinstance(part, marko.inline.RawText) and isinstance(part.children, str):
+ paragraph_str += part.children
+ if isinstance(part, marko.inline.LineBreak):
+ if paragraph_str[-1:] != " ":
+ paragraph_str += " "
+ if isinstance(part, marko.inline.Link):
+ for part_child in part.children:
+ if isinstance(part_child, marko.inline.RawText) and isinstance(part_child.children, str):
+ paragraph_str += part_child.children
+
+ if len(paragraph_str) > INTRO_DESIRED_LENGTH:
+ paragraph_str = paragraph_str[:INTRO_DESIRED_LENGTH] # cut to length
+ # first try to cut at the last period, if it's not too far...
+ last_dot_pos = paragraph_str.rfind(".")
+ if INTRO_DESIRED_LENGTH - last_dot_pos > INTRO_MAX_EXTRA_LENGTH:
+ intro_str = paragraph_str[:last_dot_pos + 1]
+ else:
+ paragraph_str += "..." # If too far, just add more dots
+
+ return paragraph_str # return after the first paragraph
+
+ return ""
+
+ @staticmethod
+ def _get_first_image_path(root) -> Optional[str]:
+ if isinstance(root, marko.inline.Image):
+ return root.dest
+
+ if isinstance(root, ExtendedImageElement):
+ return root.src
+
+ if hasattr(root, 'children'):
+ for elm in root.children:
+ img = Post._get_first_image_path(elm)
+ if img:
+ return img
+
+ return None
+
+
+def get_posts(path: str) -> list[Post]:
+ return_list = []
+
+ for directory in os.listdir(path):
+ return_list.append(Post(os.path.join(path, directory)))
+
+ return_list.sort(key=lambda post: post.meta_data[PUBLISH_DATE_KEY], reverse=True)
+
+ posts_cache.clear()
+ posts_cache.extend(return_list)
+
+ return return_list
diff --git a/modules/warko b/modules/warko
new file mode 160000
+Subproject ed0aa1323a7acae363c670cc1dd6611c09e0c3f
diff --git a/posts/let-there-be-light-theme/blog.webp b/posts/let-there-be-light-theme/blog.webp
new file mode 100644
index 0000000..625a835
--- /dev/null
+++ b/posts/let-there-be-light-theme/blog.webp
Binary files differ
diff --git a/posts/let-there-be-light-theme/content.md b/posts/let-there-be-light-theme/content.md
new file mode 100644
index 0000000..0439ffb
--- /dev/null
+++ b/posts/let-there-be-light-theme/content.md
@@ -0,0 +1,22 @@
+...and there was light theme.
+
+## print("Hello world.")
+
+Whenever I read techy blogs, whether one of my friends' or some random dude's who haven't updated the format of his website since the '90s, I get the urge to write one myself. Ever since I first picked up coding as a hobby (and later as a job) I was always on the lookout for opportunities to use my newfound skillset for something fun or useful. About 3 years ago I switched from Windows to Arch Linux as my main OS. I never really used Linux before, so it was quite a challenge at first, but thanks to my good friend [oliva](https://teml.in) I was able to learn more about computers in a year than ever before. Eventually this website was born, hosting my silly little projects and my first ever serious attempt to create something on the internet.
+
+## What's with the blog?
+
+I plan to use this blog as a ~~wall of shame for all my abandoned projects~~ collection/documentation/chronicle for all my running projects. Most of them will probably be software related, hopefully some of them hardware related (because I want to get into it more), but I also plan to work on some other geeky stuff (like building a lightsaber!) or learning 3D modeling and animation.
+
+The skybox is the limit!
+
+## What's next?
+
+If you are one of the few unfortunate people to lay your gaze upon this blog too early, you are probably looking at something like this:
+
+![very ugly blog post with no css at all](blog.webp "WE NEED TO GO DEEPER" deeper.html INCEPTION)
+
+This is the current state of the blog as I'm writing these lines. I knew that if I _ever_ want this blog to happen, I have to start writing before setting up the blog itself, otherwise I would never be able to convince myself to work on it. I'm not a huge fan of frontend. <span class=nobr>\<sarcasm\></span> I know, shocking. <span class=nobr>\</sarcasm\></span>
+
+So the first official project: making a somewhat more aesthetically pleasing layout and theme. Probably with dark mode.
+I may keep the ugly original theme as an easter egg. Try and find it!
diff --git a/posts/let-there-be-light-theme/deeper.html b/posts/let-there-be-light-theme/deeper.html
new file mode 100644
index 0000000..90966cc
--- /dev/null
+++ b/posts/let-there-be-light-theme/deeper.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Let there be light theme</title>
+</head>
+<body>
+ <p>…and there was light theme.</p>
+ <h2 id="printhello-world.">print(“Hello world.”)</h2>
+ <p>Whenever I read techy blogs, whether one of my friends’ or some
+ random dude’s who haven’t updated the format of his website since the
+ ’90s, I get the urge to write one myself. Ever since I first picked up
+ coding as a hobby (and later as a job) I was always on the lookout for
+ opportunities to use my newfound skillset for something fun or useful.
+ About 3 years ago I switched from Windows to Arch Linux as my main OS. I
+ never really used Linux before, so it was quite a challenge at first,
+ but thanks to my good friend <a href="https://teml.in">oliva</a> I was
+ able to learn more about computers in a year than ever before.
+ Eventually this website was born, hosting my silly little projects and
+ my first ever serious attempt to create something on the internet.</p>
+ <h2 id="whats-with-the-blog">What’s with the blog?</h2>
+ <p>I plan to use this blog as a <del>wall of shame for all my abandoned
+ projects</del> collection/documentation/chronicle for all my running
+ projects. Most of them will probably be software related, hopefully some
+ of them hardware related (because I want to get into it more), but I
+ also plan to work on some other geeky stuff (like building a
+ lightsaber!) or learning 3D modeling and animation.</p>
+ <p>The skybox is the limit!</p>
+ <h2 id="whats-next">What’s next?</h2>
+ <p>If you are one of the few unfortunate people to lay your gaze upon
+ this blog too early, you are probably looking at something like this:</p>
+ <figure>
+ <img src="blog.webp" title="INCEPTION"
+ alt="very ugly blog post with no css at all" style="border: 2px solid #555;width:80%;height:80%" />
+ <figcaption aria-hidden="true">INCEPTION</figcaption>
+ </figure>
+ <p>This is the current state of the blog as I’m writing these lines. I
+ knew that if I <em>ever</em> want this blog to happen, I have to start
+ writing before setting up the blog itself, otherwise I would never be
+ able to convince myself to work on it. I’m not a huge fan of frontend.
+ &lt;sarcasm&gt; I know, shocking. &lt;/sarcasm&gt;</p>
+ <p>So the first official project: making a somewhat more asteticaly
+ pleasing layout and theme. Probably with dark mode. I may keep the ugly
+ original theme as an easter egg. Try and find it!</p>
+</body>
+</html>
diff --git a/posts/let-there-be-light-theme/meta.json b/posts/let-there-be-light-theme/meta.json
new file mode 100644
index 0000000..4b0daaa
--- /dev/null
+++ b/posts/let-there-be-light-theme/meta.json
@@ -0,0 +1,7 @@
+{
+ "title" : "Let there be light theme...",
+ "publish_date" : "2024-06-23",
+ "tags" : [
+ "website"
+ ]
+}
diff --git a/posts/making-this-blog/book.md b/posts/making-this-blog/book.md
new file mode 100644
index 0000000..4f38185
--- /dev/null
+++ b/posts/making-this-blog/book.md
@@ -0,0 +1,13 @@
+[Enter the Library of Babel...](https://libraryofbabel.info/browse.cgi)
+
+In the Library of Babel, every little room (a.k.a. hex) has a name. Most of them are pretty long, because they are unique and there is a lot of them. You should start by visiting **this hex**:
+
+<div class="code-block-wrap"><p class="force-wrap">36j9mt1q6uz4yhglzs9gcclw6x0mhe08g9uo3sfsb2yikskdjq0hllrje5xrod8g51ohi1z9074tkb19zqq3o06x5rml5w6z5lv2w7vc8obmp1m9sczo8vwkhgnk3b7v0f15vvhlappmoveaaq8scehsafitgp2msy7b1tk853ykfyfk3f5i1vizh30r993n2asssj2lyi46ee0xh91fltajjkediytmuenmi7zbtqeqtwyqpw7jjrcehf1kazkws5oujuxabdbb4dovcz1l9zild0lzjzpk9qpye1ms7uzxxvs2w08fl1fwi3brr6izrjo9rvxll0kpee3dv9mux5gjjaqz3vxmvjmz1187nl8rf0lsnkitj7mhcpxox8djbhzs6qq6b3dkdgsleplwtcixjhckjrbcmfa7ynik9hp906lg60zh2rbd6ar8q7pjg9cso1agthxjapbb4gi3mqqxu5jvhbnwdrwop4nj6btnius2j2biy7e75cd8hwo1qdmz9fexysbbxbiwfefj1mas88z4rdu1lfcfuqr6xhi21gliehnr9qi1l37k9cmkyztge3u8ke66e1lz4ftrwgu6radi1nvf5qkpp144tlmdlcl4kj91h7q54rqsi5ewrkxhgasjfjzwyfdqqu6enw2hl5knlthd0pgttlr2095raj8877g35u4v95z26bp9kz61tang689fb9p2e1udcotb9hh5ik76g5ilroazrfr3d2tpoblwr2017zcx2tszmh629toog6ptfk2hbbw7gonfkbx4u2vtvvspktx06td77k901wyby0jlnzrmb9t47rse3xbjpmwipb1oxxo1zjrml4mu4y0fmhfvbc9nrqvn56w6tmzrx10pjcvat5lyd4nqnmx3hl2zvnityu5z76swmalkjcjr1uuowx2redeuwpfuhl76xmgiloh8j0xyxp3amhp41kduxf8h9d6htui3wmqgtrndec35ucnx0jyyntcuyriw26utz3m3lsjtub53taugyly96yf9on3ffop1vxqnpy3s5ko7yp99zdnwgcgsafto64ll5g0c1ovouhc8e44it62yb0u01o4jrg7z1ur712mcrzb3pwiydmi1d7k9aakruak3rxbp420g47s7j7gl4wnsatauiuetl9i3t6mk7ig0zcierq961rqhie21hfh50jv28gqpdr12sd50nkjhmxyj70n12vw9dc0gwhb38q0ym6euba6zl0a3jq7ngnesyg9alvit34f9eloykd8q3hjj1na223uq04gkpjlhfbq7x6muhb7mhpiqctazsewsgg1j8hjr41yeyluxdfychbpgv6jwp79kbymcm6ekoqwi8nvq1fa3rfyy373hph4cacylo4fy0sgsrtpridvxdl80l9mdqe69y88tjfx1fi0bjknlgxsz1bjilg8ojfesrigof73kzr6lszrks89zhwb0azxe2vj1654hk1dkox9zcrfubakfcgzw83kuxmjbj666lvzjabd7hoyakrk1kewdfqctdlxonq7t4qvst5n1h0kgn4dqqtepwfkfwl1elu63x0qentorg04g775caehdoqervlb258tr2r7thrq6d34600j8zp41tfmkdsif7x5g3sv8i2oqtnmb581xblxrzbx8i4bolee489yuh2yqb9aw9hdcxjvgwytgwqqfgcu1x1uf4wetp2w5dkk5hh7ve4m4a03dequypgoe7mff9i7s6ul40qq966fyva37hl5jgzwnck4lej13d2ja95eqdyx3sh6c7ks9u7rp007e44ytqp1x0r9iqlh8ekmsm2rdjj6c80j3zy5f0sr1d7nm6yc3ptazentl88jxbo045lc3rvwb4ybqyq0v9ofsdu12fr9ync29u05ovk3vrj14jmsfhiy6mnnwxnsxbez8i003e3sju4xgaaho36qjm4ijeufrf1c1y92j0qe0zzxsut5p7sv1a13c7ezfstr7g9lfd9qrdk9dkddfh9d0edmy01ei6997ap6mhqt2tlmm6tuehqokruwt4ng314z6xcqtfug9w4744dvwyzst2agssk2th5jqir3waalu3eihtibyjkoy2in184nyxd3yp8pnwxztdpfmoy615z8iero719yz3ft4sx6pv3fgxhz8i8uot1taebqicat2xc809wgr27wgonrdxfrkhzmkg22hwjca2nymfti284dyn6a90tvsy7itzwz8mmfr9zvnp0io0wkfpq7ihkh6fnuq7ieg84pjaeba3srff2gs5f2i14sg014ghmjfd0itafnm3d6ly8wg3c8mxe95x11c6gl668ydjlfusffssaee4qt0yshlypw8e0z2nb6c95102pcq3dvhl3rd6ma59eju2zo1of3ovxsdcogzvbjsau23rmwlrlzyvfdxsojk822gm3c5jdf30hux28vguz2w3oz3j8wxh03vq0gewb12tcbg5zfm3zspap7bhzb85b35e3v5oxigfepbqv0i43jh4nyvmelgv7g679f96sbya4p8aemyuf2wgvaakhazk6zrt3s1tc6m8cgi5mqzxmri21jz24r9jyhymnj3vfns51qyx59p2ipf8jf4j07w18rlbdj5vh57nm5kmfh7aigeww92k8ypdilnl1ekld2nrx9w7g1nkx0im2zq4zxhkobeyth02y0g93f0eq5wi7r28wfaruzdm24ipeo562zjm1jerpyqrwym2ygspbmed1jjec6x1jssjreckdwosvxdobfaqr80j3c0xuhwfyie2ivhwlxay3dwkismpqthy32tnu13dajh563acuvtbpivl7deo7f3s0rc17xtra6d0ewz6jcbrl95x6126yuq7geme9tr3jxvryezrgthxhh8evif5yub8rlyo9fmuglwiolz9qj7lc7dfsu28qxonjqg2gt652zdynfm9o9gzwg301vh97e0s5suz1ke2n69ulbq3dcc06eppuqj9ujm681l9auh5z20ban4oab1k20rvs6vfxr4oj759v2vkvqk80lb8kzhvm6bsjnfdwwelfjjp46c3ppg1i5qcaksf53ash92c0zzvsyzjz44j9o6makhp6asljgivsfrw3b2czeh79zuntnftv9xegi9vs4jjsnu653zdinai2x21kgf656z8k4t6v6e3izdz8fl6ec1ufyx3047fr2cnla6jq6rug</p></div>
+
+When you have arrived to the room, walk up to the **2nd wall**.
+
+Look for a book on the **3rd shelf**.
+
+It should be the **22nd volume** on this shelf. You can't miss it. It's called **qbhejxylqtqfuo**.
+
+Now turn to **page 201** and be amazed...
diff --git a/posts/making-this-blog/content.md b/posts/making-this-blog/content.md
new file mode 100644
index 0000000..a8f28ef
--- /dev/null
+++ b/posts/making-this-blog/content.md
@@ -0,0 +1,74 @@
+This is the story of how I was finally able to create my first normal looking webpage, this blog.
+
+## The most crucial part of every project
+If you have a fondness for your projects like me, you know that finding a good name for your stuff is the most important thing. You want to choose a clever, fitting name, because it makes it _yours_. Almost like a pet dog, cat, raptor or whatever. Sometimes... okay, almost always I use some sort of **pun or acronym**. I had projects where I've decided on the acronym way before trying to figure out a name which it could be an abbreviation for. This is the reason I was able to have multiple serious discussions with colleagues about what my "Maid" (Manageable Asset Information Database) could do, and what additional features should be considered (but this is a story for another time).
+
+The other approach I have for selecting a name, is finding something with **sentimental value or a grandiose/cool sounding vibe**. For example in a relatively difficult period in my life, I had a PC with the hostname of "Hyouka". It's a Japanese word that translates to "ice cream". If you want to understand the deeper meaning behind the hostname, I recommend checking out the brilliant Kyoto Animation [anime](https://myanimelist.net/anime/12189/Hyouka) with the same name.
+
+When trying to find a name for my blog I considered things like the [Library of Babel](https://en.wikipedia.org/wiki/The_Library_of_Babel). I love the idea that the place contains all the knowledge that had been or _will_ be known, perfect for a blog striving to be the chronicle of my learnings. In the end I dropped the idea because it was too long and there is a [website](https://libraryofbabel.info) by that name already. If you are interested, I've found a pretty interesting [book](book.html) there.
+
+In the end, my choice of name fell on... well I don't know, actually. I'm still looking at the placeholder text "@myblogname" as I'm writing these lines. Scroll up to the title at the top of the page, and you will know what I ended up with.
+
+![the image of the webpage title, @yggdrasil, with an ascii art of a tree](yggdrasil.webp "Yggdrasil is the giant World Tree of the Norse mythology. More about this in the future!" "https://en.wikipedia.org/wiki/Yggdrasil" "A note from my future self: Never mind, here it is.")
+
+## The core concept
+From the very beginning I wanted to create something nerdy and relatively original. Then I remembered, that a computer science teacher (unfortunately I forgot who) back in my university days had a webpage where all the menu items and chapters looked like **shell commands**. I decided I wanted to do something similar.
+
+I started (with the help of a few friends) brainstorming on ideas I could use for the blog, like a fake "head" command for post summaries, or "ls" for listing the related tags for a post. For a while it was really bugging me, that I couldn't make the whole system **"realistic"**. For example "head" obviously can't display images, even though I wanted the previews to contain one. In the end I decided I will just let it go, and have the commands mean **"something similar"** to their real shell counterparts.
+
+Unfortunately I don't really have any graphics designer experience, so I had to look up some terminal colors on the internet. Luckily I've found a website called [terminal.sexy](http://terminal.sexy/) where I really liked the default color template, so I could use it almost without any modification.
+The current color scheme is this:
+
+<div class="code-block">
+ <p class="fg">fg: #b5aba8</p>
+ <p class="bg" style="background-color:var(--term-fg)">bg: #2b2b2b</p>
+ <p class="black">black: #515151</p>
+ <p class="red">red: #cc6666</p>
+ <p class="green">green: #b5bd68</p>
+ <p class="yellow">yellow: #f0c674</p>
+ <p class="blue">blue: #81a2be</p>
+ <p class="magenta">magenta: #b294bb</p>
+ <p class="cyan">cyan: #8abeb7</p>
+ <p class="white">white: #b5aba8</p>
+</div>
+
+For the font I used my all-time favorite font, [JetBrains Mono](https://www.jetbrains.com/lp/mono), regular size, no ligatures. They had **.woff** format on their GitHub page,
+
+<span class="red">- but unfortunately only the ligatures version, which I don't like that much, so in the end I had to use **.ttf**.</span>
+
+<span class="green">+ Update (2024-08-05): as a kind reader, [György](https://kuruczgy.com/) has pointed it out, I can use the **.woff** font, if I specify that I want no ligatures in the css:</span>
+
+<div class="code-block">
+<p><span class="yellow">html</span> <span class="cyan">{</span>
+ <span class="green">font-family</span>: <span class="red">"jetbrains-mono"</span>, <span class="red">serif</span>;
+ <span class="green">font-variant-ligatures</span>: <span class="red">none</span>;
+<span class="cyan">}</span></p>
+</div>
+
+<span class="green">+ Also, as a result, I had to implement the css for this **codeblock**, and I got the idea to add updates to a post in a **git diff** +/- style, so it was a good exercise.</span>
+
+## Putting together the whole thing
+For most of the other subdomains under **wazul.moe** I already had a few thrown together Python scripts to generate the content. Luckily my friend [Marcsello](https://blog.marcsello.com) opensourced his whole blog, so it was easy for me to _get inspired_ by it, and the templating engine he used. You could say he "led me into the Jinja". (If you are Hungarian this is your cue to laugh. Otherwise, disregard this pun.)
+
+So I started learning [Jinja](https://jinja.palletsprojects.com/), the **templating engine**. This was the first time I used it, but probably not the last one. I can see it being used for other than HTML as well.
+
+Since I'm not really a fan of **frontend**, this was really my first "professional" looking webpage. I was always afraid of creating display size responsive layouts. Fortunately [Bootstrap](https://github.com/twbs/bootstrap) ended up being far easier to use than I anticipated. Truth to be told, I used a [stripped down version](https://github.com/dmhendricks/bootstrap-grid-css) with only the grid system included, nothing else.
+
+I've written the actual content of the posts in **Markdown**, so I also used [Marko](https://marko-py.readthedocs.io/), which is a neat little Markdown converter with great extensibility. I ended up creating a few extensions for it, they can be found [here](https://git.wazul.moe/warko) on my git (and it's called warko, because W is an upside down M, and also W like Wazul, get it?).
+
+<span class="red">- When I got to implementing the **RSS feed** I was a bit tired already so I almost straight up ripped it from Marcsello's code (not like I didn't steal all the other libraries from his project). It's using [feedgen](https://feedgen.kiesow.be) to do the job.</span>
+
+<span class="green">+ Update (2024-08-07): I was not satisfied with the original **RSS feed** generator, as it was leaving out some of the tags, so in the end I made the whole thing in a Jinja template. I guess _that's_ a use other than HTML technically.</span>
+
+I'm planning to **opensource my blog** as soon as I've got it in order, it will be available on my [git](https://git.wazul.moe) soon™.
+
+## TODO
+There is still a lot of small things to do on the blog. I ended up implementing mostly those features I needed for writing this second blogpost. I also want to use my Jinja knowledge and **rebuild the rest of my webpage**. Maybe add a normal menu to the main domain instead of the current content, which is just the game over message from the Legend of Zelda: Mayora's Mask.
+
+I also want to do other projects, like **designing an icon for myself**. I have some ideas already, now I only need to learn Inkscape and do it. <span class=nobr>\<sarcasm\></span> Piece of cake! <span class=nobr>\</sarcasm\></span>
+
+And of course, I have a **ton to write about**, both my past and future projects, so I think I'm going to be busy. I ended up working a bit more on this post than I first anticipated but I enjoyed it regardless. Hell, I even took my laptop to a holiday drinking party and coded some of the last features for the blog! No sarcasm this time.
+
+<br>
+
+Alright then... see you in the next one I guess! \ No newline at end of file
diff --git a/posts/making-this-blog/meta.json b/posts/making-this-blog/meta.json
new file mode 100644
index 0000000..e034f3d
--- /dev/null
+++ b/posts/making-this-blog/meta.json
@@ -0,0 +1,7 @@
+{
+ "title" : "Making this blog",
+ "publish_date" : "2024-07-03",
+ "tags" : [
+ "website", "coding", "python"
+ ]
+}
diff --git a/posts/making-this-blog/yggdrasil.webp b/posts/making-this-blog/yggdrasil.webp
new file mode 100644
index 0000000..ebd7a4d
--- /dev/null
+++ b/posts/making-this-blog/yggdrasil.webp
Binary files differ
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..5e64b09
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+jinja2
+marko
diff --git a/templates/acknowledgement.html.j2 b/templates/acknowledgement.html.j2
new file mode 100644
index 0000000..6cb5938
--- /dev/null
+++ b/templates/acknowledgement.html.j2
@@ -0,0 +1,18 @@
+{% extends "base.html.j2" %}
+{% block page_title %}Acknowledgement {{ super() }}{% endblock %}
+{% block page_title_short %}Acknowledgement{% endblock %}
+{% block content %}
+<h1 class="first-element">Acknowledgement<h1>
+<p>Thank you for everyone, who made this blog possible, like...<br>
+<br>
+<a href="https://blog.marcsello.com">Marcsello</a> who was kind enough to share his blog source with the world, so I could <del>steal</del> get inspired by it<br>
+<a href="https://ascii.co.uk/art/tree">David Moore</a> for the ascii art tree<br>
+<a href="https://github.com/twbs/bootstrap">Bootstrap</a> for the awesome CSS library and <a href="https://github.com/dmhendricks/bootstrap-grid-css">dmhendricks</a> for the grid only extraction<br>
+<a href="https://jinja.palletsprojects.com">Jinja</a> for the intuitive templating language<br>
+<a href="https://marko-py.readthedocs.io">Marko</a> for the extensible MD converter<br>
+<a href="https://www.jetbrains.com">Jetbrains</a> for their amazing <a href="https://www.jetbrains.com/lp/mono">font</a> which I use literally everywhere<br>
+<a href="http://terminal.sexy">terminal.sexy</a> for the cool terminal color template<br>
+<a href="https://icon-sets.iconify.design">iconify</a> for all the icons<br>
+<br>
+... and last but not least, You dear Reader!</p>
+{% endblock %} \ No newline at end of file
diff --git a/templates/base.html.j2 b/templates/base.html.j2
new file mode 100644
index 0000000..1925ed2
--- /dev/null
+++ b/templates/base.html.j2
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html dir="ltr" lang="en">
+<head>
+ <meta charset="utf-8"/>
+ <title>{% block page_title %}{{ site.blog_name }}{% endblock %}</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
+ <meta property="og:title" content="{% block page_title_short %}{{ site.blog_name }}{% endblock %}" />
+ <meta property="og:type" content="article" />
+ <meta property="og:url" content="{{ url }}" />
+ <link rel="stylesheet" href="{{ site.assets_path }}/css/blog.css" />
+ <link rel="stylesheet" href="{{ site.assets_path_static }}/css/bootstrap-grid.min.css" />
+</head>
+<body>
+<div class="bootstrap-wrapper">
+ <div class="container">
+ <a href="/" class="row dont-bother">
+ <header class="col-lg-9" role="banner">
+ <div>
+ <h1 class="green blog-title">{{ site.blog_name }}</h1>
+ <p>{{ site.subtitle }}</p>
+ </div>
+ <span class="ascii-art"><b><span class="green"> ccee88oo
+ C8O8O8Q8PoOb o8oo
+ dOB68QO8PdUOpugoO9bD
+CgggbU8OU qOp qOdoUOdcb
+ 6OuU</span><span class="yellow"> /</span><span class="green">p u gcoUodpP</span>
+<span class="yellow"> \\\// /<span><span class="green">douUP</span>
+<span class="yellow"> \\\////
+ |||/\
+ |||\/
+ |||||
+ |||||
+ //||||\</span></b></span>
+ </header>
+ </a>
+ <div class="row">
+ <main class="col-md-9">
+ {% block content required %}{% endblock %}
+ </main>
+ <aside class="col-md-3">
+ {% include 'sidebar.html.j2' %}
+ </aside>
+ </div>
+ </div>
+</div>
+<div class="huge-vertical-spacer-at-the-bottom"></div>
+</body>
+</html>
diff --git a/templates/blogpost.html.j2 b/templates/blogpost.html.j2
new file mode 100644
index 0000000..7375ee4
--- /dev/null
+++ b/templates/blogpost.html.j2
@@ -0,0 +1,30 @@
+{% extends "base.html.j2" %}
+{% block page_title %}{{ post.title() }} {{ super() }}{% endblock %}
+{% block page_title_short %}{{ post.title() }}{% endblock %}
+{% block content %}
+<h1 class="first-element">{{ post.title() }}</h1>
+<p>{{ post.get_prompt("ls tags/") }}
+<br>
+{{ post.get_tags() }}</p>
+<hr>
+<p>{{ post.get_prompt("stat -c %.10w content") }}
+<br>
+{{ post.get_publish_time() }}</p>
+<hr>
+<p>{{ post.get_cat_prompt("content") }}</p>
+<div class="blog-content-spacer"></div>
+{{ post.html|safe }}
+<hr>
+<p>{{ post.get_prompt("find other_posts/ -type f -exec head {} +") }}</p>
+{% for similar in post.get_similar_posts() %}
+<a href="{{ similar.href }}" class="other_post dont-bother posts-listing-link {% if loop.last %}last-element{% endif%}">
+<div>
+<h3>{{ similar.title() }}</h3>
+<p>{{ similar.intro }}</p>
+</div>
+<div>
+<img src="{{ similar.thumbnail }}" />
+</div>
+</a>
+{% endfor %}
+{% endblock %}
diff --git a/templates/blogpost_sub.html.j2 b/templates/blogpost_sub.html.j2
new file mode 100644
index 0000000..5bf4d94
--- /dev/null
+++ b/templates/blogpost_sub.html.j2
@@ -0,0 +1,10 @@
+{% extends "base.html.j2" %}
+{% block page_title %}{{ post.title() }} {{ super() }}{% endblock %}
+{% block page_title_short %}{{ post.title() }}{% endblock %}
+{% block content %}
+<p class="first-element">{{ post.get_cat_prompt(subpage_name) }}</p>
+<div class="blog-content-spacer"></div>
+{{ subpage_html|safe }}
+<hr>
+<p class="last-element">{{ post.get_cat_prompt("") }}<a href=".">content</a></p>
+{% endblock %}
diff --git a/templates/feed.xml.j2 b/templates/feed.xml.j2
new file mode 100644
index 0000000..7e51092
--- /dev/null
+++ b/templates/feed.xml.j2
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0">
+<channel>
+ <title>@yggdrasil</title>
+ <link>https://blog.wazul.moe/feed.xml</link>
+ <description>The chronicle of my works and learnings</description>
+ <language>en</language>
+ <lastBuildDate>{{ build_date }}</lastBuildDate>
+ {% for post in posts %}
+ <item>
+ <title>{{ post.title() }}</title>
+ <link>{{ post.get_link() }}</link>
+ <description>{{ post.intro }}</description>
+ <guid isPermaLink="false">{{ post.get_link() }}</guid>
+ <author>contact@wazul.moe</author>
+ <pubDate>{{ post.get_publish_time_rfc2822() }}</pubDate>
+ </item>
+ {% endfor %}
+ </channel>
+</rss> \ No newline at end of file
diff --git a/templates/index.html.j2 b/templates/index.html.j2
new file mode 100644
index 0000000..09ce8ad
--- /dev/null
+++ b/templates/index.html.j2
@@ -0,0 +1,18 @@
+{% extends "base.html.j2" %}
+{% block content %}
+{% for post in posts %}
+{% if loop.first %}
+<p class="first-element">{{ post.get_index_prompt() }}</p>
+{% else %}
+<p>{{ post.get_index_prompt() }}</p>
+{% endif %}
+<a class="posts-listing-link dont-bother" href="{{ post.href }}">
+ <img src="{{ post.thumbnail }}" />
+ <h2 class="index-title">{{ post.title() }}</h2>
+ <p>{{ post.intro }}</p>
+</a>
+{% if not loop.last %}
+<hr>
+{% endif %}
+{% endfor %}
+{% endblock %}
diff --git a/templates/sidebar.html.j2 b/templates/sidebar.html.j2
new file mode 100644
index 0000000..2c75a8c
--- /dev/null
+++ b/templates/sidebar.html.j2
@@ -0,0 +1,35 @@
+<div class="sidebar-spacing"></div>
+<h3 class="magenta first-element">About me</h3>
+<p>I'm a game developer by occupation and a computer enthusiast by hobby.
+I love learning about and working on anything related to computers or geek stuff,
+although I consider myself only a passionate greenhorn.
+Sometimes I touch upon some more artistic stuff, but mostly just to amuse myself.</p>
+<div class="icon-text">
+<p>btw I use arch</p>
+<img src="/assets/image/arch.svg" class="svg-icon" />
+</div>
+<h3 class="magenta">Stuff I do</h3>
+<a href="https://wazul.moe" class="icon-text">
+<img src="/assets/image/website.svg" class="svg-icon" />
+<span>My website</span>
+</a>
+<a href="https://git.wazul.moe" class="icon-text">
+<img src="/assets/image/git.svg" class="svg-icon" />
+<span>Git repositories</span>
+</a>
+<h3 class="magenta">Top tags</h3>
+<p>{% for tag in site.top_tags %}<a href="/tags/{{ tag }}.html">{{ tag }}/</a>{% if not loop.last %} {% endif %}{% endfor %}</p>
+<p>more: cd <a href="/tags">~/tags</a></p>
+<h3 class="magenta">Never miss a post</h3>
+<a href="/feed.xml" class="icon-text">
+<img src="/assets/image/rss.svg" class="svg-icon" />
+<span>RSS feed</span>
+</a>
+<h3 class="magenta">Contact me at</h3>
+<a href="mailto:contact@wazul.moe" class="icon-text">
+<img src="/assets/image/email.svg" class="svg-icon" />
+<span>contact@wazul.moe</span>
+</a>
+<p>especially if you find some typos or bugs on the page</p>
+<h3 class="magenta">Thank you</h3>
+<p class="last-element">for <a href="/acknowledgement.html">everyone</a> who made this blog possible</p>
diff --git a/templates/tag.html.j2 b/templates/tag.html.j2
new file mode 100644
index 0000000..093f41b
--- /dev/null
+++ b/templates/tag.html.j2
@@ -0,0 +1,18 @@
+{% extends "base.html.j2" %}
+{% block page_title %}tags/{{ tag }} {{ super() }}{% endblock %}
+{% block page_title_short %}tags/{{ tag }}{% endblock %}
+{% block content %}
+<h1 class="first-element">tags/{{ tag }}/</h1>
+<p>{{ config.get_tag_prompt(tag, "find other_posts/ -type f -exec head {} +") }}</p>
+{% for post in posts %}
+<a href="{{ post.href }}" class="other_post dont-bother posts-listing-link {% if loop.last %}last-element{% endif%}">
+<div>
+<h3>{{ post.title() }}</h3>
+<p>{{ post.intro }}</p>
+</div>
+<div>
+<img src="{{ post.thumbnail }}" />
+</div>
+</a>
+{% endfor %}
+{% endblock %}
diff --git a/templates/tags.html.j2 b/templates/tags.html.j2
new file mode 100644
index 0000000..8812559
--- /dev/null
+++ b/templates/tags.html.j2
@@ -0,0 +1,8 @@
+{% extends "base.html.j2" %}
+{% block page_title %}tags/ {{ super() }}{% endblock %}
+{% block page_title_short %}tags/{% endblock %}
+{% block content %}
+<h1 class="first-element">tags/</h1>
+<p>{{ config.get_tag_prompt("", "ls .") }}</p>
+<p class="last-element">{{ tags_ls }}</p>
+{% endblock %}