From bea80928f694671010bc99493d31879df7d42836 Mon Sep 17 00:00:00 2001 From: Botond Hende Date: Wed, 4 Sep 2024 20:05:51 +0200 Subject: initial commit --- .gitignore | 3 + .gitmodules | 3 + __init__.py | 0 __main__.py | 126 +++++++++++++ assets/css/blog.css | 278 +++++++++++++++++++++++++++++ assets/font/JetBrainsMono-Regular.woff2 | Bin 0 -> 92164 bytes assets/image/arch.svg | 1 + assets/image/email.svg | 1 + assets/image/git.svg | 1 + assets/image/rss.svg | 1 + assets/image/website.svg | 1 + config.py | 29 +++ modules/__init__.py | 0 modules/blogpost_processor.py | 228 +++++++++++++++++++++++ modules/warko | 1 + posts/let-there-be-light-theme/blog.webp | Bin 0 -> 67994 bytes posts/let-there-be-light-theme/content.md | 22 +++ posts/let-there-be-light-theme/deeper.html | 46 +++++ posts/let-there-be-light-theme/meta.json | 7 + posts/making-this-blog/book.md | 13 ++ posts/making-this-blog/content.md | 74 ++++++++ posts/making-this-blog/meta.json | 7 + posts/making-this-blog/yggdrasil.webp | Bin 0 -> 24140 bytes requirements.txt | 2 + templates/acknowledgement.html.j2 | 18 ++ templates/base.html.j2 | 48 +++++ templates/blogpost.html.j2 | 30 ++++ templates/blogpost_sub.html.j2 | 10 ++ templates/feed.xml.j2 | 20 +++ templates/index.html.j2 | 18 ++ templates/sidebar.html.j2 | 35 ++++ templates/tag.html.j2 | 18 ++ templates/tags.html.j2 | 8 + 33 files changed, 1049 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 __init__.py create mode 100644 __main__.py create mode 100644 assets/css/blog.css create mode 100644 assets/font/JetBrainsMono-Regular.woff2 create mode 100644 assets/image/arch.svg create mode 100644 assets/image/email.svg create mode 100644 assets/image/git.svg create mode 100644 assets/image/rss.svg create mode 100644 assets/image/website.svg create mode 100644 config.py create mode 100644 modules/__init__.py create mode 100644 modules/blogpost_processor.py create mode 160000 modules/warko create mode 100644 posts/let-there-be-light-theme/blog.webp create mode 100644 posts/let-there-be-light-theme/content.md create mode 100644 posts/let-there-be-light-theme/deeper.html create mode 100644 posts/let-there-be-light-theme/meta.json create mode 100644 posts/making-this-blog/book.md create mode 100644 posts/making-this-blog/content.md create mode 100644 posts/making-this-blog/meta.json create mode 100644 posts/making-this-blog/yggdrasil.webp create mode 100644 requirements.txt create mode 100644 templates/acknowledgement.html.j2 create mode 100644 templates/base.html.j2 create mode 100644 templates/blogpost.html.j2 create mode 100644 templates/blogpost_sub.html.j2 create mode 100644 templates/feed.xml.j2 create mode 100644 templates/index.html.j2 create mode 100644 templates/sidebar.html.j2 create mode 100644 templates/tag.html.j2 create mode 100644 templates/tags.html.j2 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 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'{tag}/' 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 Binary files /dev/null and b/assets/font/JetBrainsMono-Regular.woff2 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 @@ + \ 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 @@ + 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 @@ + \ 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 @@ + 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 @@ + 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 "{}@{} {} $ {}".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 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(["{}".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 "
".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 index 0000000..ed0aa13 --- /dev/null +++ b/modules/warko @@ -0,0 +1 @@ +Subproject commit ed0aa1323a7acae363c670cc1dd6611c09e0c3fb 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 Binary files /dev/null and b/posts/let-there-be-light-theme/blog.webp 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. \ I know, shocking. \ + +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 @@ + + + + + Let there be light theme + + +

…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 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 + +
+

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. + <sarcasm> I know, shocking. </sarcasm>

+

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!

+ + 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**: + +

36j9mt1q6uz4yhglzs9gcclw6x0mhe08g9uo3sfsb2yikskdjq0hllrje5xrod8g51ohi1z9074tkb19zqq3o06x5rml5w6z5lv2w7vc8obmp1m9sczo8vwkhgnk3b7v0f15vvhlappmoveaaq8scehsafitgp2msy7b1tk853ykfyfk3f5i1vizh30r993n2asssj2lyi46ee0xh91fltajjkediytmuenmi7zbtqeqtwyqpw7jjrcehf1kazkws5oujuxabdbb4dovcz1l9zild0lzjzpk9qpye1ms7uzxxvs2w08fl1fwi3brr6izrjo9rvxll0kpee3dv9mux5gjjaqz3vxmvjmz1187nl8rf0lsnkitj7mhcpxox8djbhzs6qq6b3dkdgsleplwtcixjhckjrbcmfa7ynik9hp906lg60zh2rbd6ar8q7pjg9cso1agthxjapbb4gi3mqqxu5jvhbnwdrwop4nj6btnius2j2biy7e75cd8hwo1qdmz9fexysbbxbiwfefj1mas88z4rdu1lfcfuqr6xhi21gliehnr9qi1l37k9cmkyztge3u8ke66e1lz4ftrwgu6radi1nvf5qkpp144tlmdlcl4kj91h7q54rqsi5ewrkxhgasjfjzwyfdqqu6enw2hl5knlthd0pgttlr2095raj8877g35u4v95z26bp9kz61tang689fb9p2e1udcotb9hh5ik76g5ilroazrfr3d2tpoblwr2017zcx2tszmh629toog6ptfk2hbbw7gonfkbx4u2vtvvspktx06td77k901wyby0jlnzrmb9t47rse3xbjpmwipb1oxxo1zjrml4mu4y0fmhfvbc9nrqvn56w6tmzrx10pjcvat5lyd4nqnmx3hl2zvnityu5z76swmalkjcjr1uuowx2redeuwpfuhl76xmgiloh8j0xyxp3amhp41kduxf8h9d6htui3wmqgtrndec35ucnx0jyyntcuyriw26utz3m3lsjtub53taugyly96yf9on3ffop1vxqnpy3s5ko7yp99zdnwgcgsafto64ll5g0c1ovouhc8e44it62yb0u01o4jrg7z1ur712mcrzb3pwiydmi1d7k9aakruak3rxbp420g47s7j7gl4wnsatauiuetl9i3t6mk7ig0zcierq961rqhie21hfh50jv28gqpdr12sd50nkjhmxyj70n12vw9dc0gwhb38q0ym6euba6zl0a3jq7ngnesyg9alvit34f9eloykd8q3hjj1na223uq04gkpjlhfbq7x6muhb7mhpiqctazsewsgg1j8hjr41yeyluxdfychbpgv6jwp79kbymcm6ekoqwi8nvq1fa3rfyy373hph4cacylo4fy0sgsrtpridvxdl80l9mdqe69y88tjfx1fi0bjknlgxsz1bjilg8ojfesrigof73kzr6lszrks89zhwb0azxe2vj1654hk1dkox9zcrfubakfcgzw83kuxmjbj666lvzjabd7hoyakrk1kewdfqctdlxonq7t4qvst5n1h0kgn4dqqtepwfkfwl1elu63x0qentorg04g775caehdoqervlb258tr2r7thrq6d34600j8zp41tfmkdsif7x5g3sv8i2oqtnmb581xblxrzbx8i4bolee489yuh2yqb9aw9hdcxjvgwytgwqqfgcu1x1uf4wetp2w5dkk5hh7ve4m4a03dequypgoe7mff9i7s6ul40qq966fyva37hl5jgzwnck4lej13d2ja95eqdyx3sh6c7ks9u7rp007e44ytqp1x0r9iqlh8ekmsm2rdjj6c80j3zy5f0sr1d7nm6yc3ptazentl88jxbo045lc3rvwb4ybqyq0v9ofsdu12fr9ync29u05ovk3vrj14jmsfhiy6mnnwxnsxbez8i003e3sju4xgaaho36qjm4ijeufrf1c1y92j0qe0zzxsut5p7sv1a13c7ezfstr7g9lfd9qrdk9dkddfh9d0edmy01ei6997ap6mhqt2tlmm6tuehqokruwt4ng314z6xcqtfug9w4744dvwyzst2agssk2th5jqir3waalu3eihtibyjkoy2in184nyxd3yp8pnwxztdpfmoy615z8iero719yz3ft4sx6pv3fgxhz8i8uot1taebqicat2xc809wgr27wgonrdxfrkhzmkg22hwjca2nymfti284dyn6a90tvsy7itzwz8mmfr9zvnp0io0wkfpq7ihkh6fnuq7ieg84pjaeba3srff2gs5f2i14sg014ghmjfd0itafnm3d6ly8wg3c8mxe95x11c6gl668ydjlfusffssaee4qt0yshlypw8e0z2nb6c95102pcq3dvhl3rd6ma59eju2zo1of3ovxsdcogzvbjsau23rmwlrlzyvfdxsojk822gm3c5jdf30hux28vguz2w3oz3j8wxh03vq0gewb12tcbg5zfm3zspap7bhzb85b35e3v5oxigfepbqv0i43jh4nyvmelgv7g679f96sbya4p8aemyuf2wgvaakhazk6zrt3s1tc6m8cgi5mqzxmri21jz24r9jyhymnj3vfns51qyx59p2ipf8jf4j07w18rlbdj5vh57nm5kmfh7aigeww92k8ypdilnl1ekld2nrx9w7g1nkx0im2zq4zxhkobeyth02y0g93f0eq5wi7r28wfaruzdm24ipeo562zjm1jerpyqrwym2ygspbmed1jjec6x1jssjreckdwosvxdobfaqr80j3c0xuhwfyie2ivhwlxay3dwkismpqthy32tnu13dajh563acuvtbpivl7deo7f3s0rc17xtra6d0ewz6jcbrl95x6126yuq7geme9tr3jxvryezrgthxhh8evif5yub8rlyo9fmuglwiolz9qj7lc7dfsu28qxonjqg2gt652zdynfm9o9gzwg301vh97e0s5suz1ke2n69ulbq3dcc06eppuqj9ujm681l9auh5z20ban4oab1k20rvs6vfxr4oj759v2vkvqk80lb8kzhvm6bsjnfdwwelfjjp46c3ppg1i5qcaksf53ash92c0zzvsyzjz44j9o6makhp6asljgivsfrw3b2czeh79zuntnftv9xegi9vs4jjsnu653zdinai2x21kgf656z8k4t6v6e3izdz8fl6ec1ufyx3047fr2cnla6jq6rug

+ +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: + +
+

fg: #b5aba8

+

bg: #2b2b2b

+

black: #515151

+

red: #cc6666

+

green: #b5bd68

+

yellow: #f0c674

+

blue: #81a2be

+

magenta: #b294bb

+

cyan: #8abeb7

+

white: #b5aba8

+
+ +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, + +- but unfortunately only the ligatures version, which I don't like that much, so in the end I had to use **.ttf**. + ++ 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: + +
+

html { + font-family: "jetbrains-mono", serif; + font-variant-ligatures: none; +}

+
+ ++ 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. + +## 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?). + +- 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. + ++ 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. + +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. \ Piece of cake! \ + +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. + +
+ +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 Binary files /dev/null and b/posts/making-this-blog/yggdrasil.webp 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 %} +

Acknowledgement

+

Thank you for everyone, who made this blog possible, like...
+
+Marcsello who was kind enough to share his blog source with the world, so I could steal get inspired by it
+David Moore for the ascii art tree
+Bootstrap for the awesome CSS library and dmhendricks for the grid only extraction
+Jinja for the intuitive templating language
+Marko for the extensible MD converter
+Jetbrains for their amazing font which I use literally everywhere
+terminal.sexy for the cool terminal color template
+iconify for all the icons
+
+... and last but not least, You dear Reader!

+{% 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 @@ + + + + + {% block page_title %}{{ site.blog_name }}{% endblock %} + + + + + + + + + +
+ + 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 %} +

{{ post.title() }}

+

{{ post.get_prompt("ls tags/") }} +
+{{ post.get_tags() }}

+
+

{{ post.get_prompt("stat -c %.10w content") }} +
+{{ post.get_publish_time() }}

+
+

{{ post.get_cat_prompt("content") }}

+
+{{ post.html|safe }} +
+

{{ post.get_prompt("find other_posts/ -type f -exec head {} +") }}

+{% for similar in post.get_similar_posts() %} + +
+

{{ similar.title() }}

+

{{ similar.intro }}

+
+
+ +
+
+{% 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 %} +

{{ post.get_cat_prompt(subpage_name) }}

+
+{{ subpage_html|safe }} +
+

{{ post.get_cat_prompt("") }}content

+{% 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 @@ + + + + @yggdrasil + https://blog.wazul.moe/feed.xml + The chronicle of my works and learnings + en + {{ build_date }} + {% for post in posts %} + + {{ post.title() }} + {{ post.get_link() }} + {{ post.intro }} + {{ post.get_link() }} + contact@wazul.moe + {{ post.get_publish_time_rfc2822() }} + + {% endfor %} + + \ 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 %} +

{{ post.get_index_prompt() }}

+{% else %} +

{{ post.get_index_prompt() }}

+{% endif %} + + +

{{ post.title() }}

+

{{ post.intro }}

+
+{% if not loop.last %} +
+{% 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 @@ + +

About me

+

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.

+
+

btw I use arch

+ +
+

Stuff I do

+ + +My website + + + +Git repositories + +

Top tags

+

{% for tag in site.top_tags %}{{ tag }}/{% if not loop.last %} {% endif %}{% endfor %}

+

more: cd ~/tags

+

Never miss a post

+ + +RSS feed + +

Contact me at

+ + +contact@wazul.moe + +

especially if you find some typos or bugs on the page

+

Thank you

+

for everyone who made this blog possible

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 %} +

tags/{{ tag }}/

+

{{ config.get_tag_prompt(tag, "find other_posts/ -type f -exec head {} +") }}

+{% for post in posts %} + +
+

{{ post.title() }}

+

{{ post.intro }}

+
+
+ +
+
+{% 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 %} +

tags/

+

{{ config.get_tag_prompt("", "ls .") }}

+

{{ tags_ls }}

+{% endblock %} -- cgit v1.2.3-70-g09d2