summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Ilvokhin <d@ilvokhin.com>2022-12-24 18:10:17 +0000
committerDmitry Ilvokhin <d@ilvokhin.com>2022-12-24 18:10:17 +0000
commit54ac714da7175a8a08239e500a4e75a48b97d97e (patch)
tree8fa4bb4e7d37f2b6eff7068d47a58ca2ad93f18d
downloadblog-54ac714da7175a8a08239e500a4e75a48b97d97e.tar.gz
blog-54ac714da7175a8a08239e500a4e75a48b97d97e.tar.bz2
blog-54ac714da7175a8a08239e500a4e75a48b97d97e.zip
Initial version of generator
Generation of feed page and post pages are supported. Markdown to html conversion seems working. At least there is nothing strange with hello-world example.
-rw-r--r--.gitignore4
-rw-r--r--blog/__init__.py0
-rw-r--r--blog/blog.py53
-rw-r--r--blog/feed.py14
-rw-r--r--blog/post.py71
-rw-r--r--blog/render.py16
-rw-r--r--posts/drafts/hello-world/hello-world.md59
-rw-r--r--posts/drafts/hello-world/metadata.txt3
-rw-r--r--templates/feed.html23
-rw-r--r--templates/post.html17
10 files changed, 260 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0dd11be
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.env
+*.swp
+.DS_Store
+remote
diff --git a/blog/__init__.py b/blog/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/blog/__init__.py
diff --git a/blog/blog.py b/blog/blog.py
new file mode 100644
index 0000000..cb6ab1e
--- /dev/null
+++ b/blog/blog.py
@@ -0,0 +1,53 @@
+#! /usr/bin/env python3
+# -*- coding: utf-8 -*
+
+import os
+import shutil
+
+from jinja2 import Environment, FileSystemLoader, select_autoescape
+
+from post import Post
+from feed import Feed
+
+
+def recreate_workdir(basedir):
+ if os.path.exists(basedir):
+ shutil.rmtree(basedir)
+ os.makedirs(basedir)
+
+
+def find_posts(template, basedir):
+ posts = []
+ for subdir in os.listdir(basedir):
+ posts.append(Post(template, os.path.join(basedir, subdir)))
+ return posts
+
+
+def generate_blog(include_drafts=False):
+ env = Environment(loader=FileSystemLoader(searchpath="templates"),
+ autoescape=select_autoescape())
+
+ posts = find_posts(env.get_template("post.html"),
+ os.path.join("posts", "public"))
+
+ if include_drafts:
+ drafts = find_posts(env.get_template("post.html"),
+ os.path.join("posts", "drafts"))
+ posts.extend(drafts)
+
+ workdir = "remote"
+ recreate_workdir(workdir)
+
+ for post in posts:
+ post.generate(workdir)
+
+ feed = Feed(env.get_template("feed.html"), posts)
+ feed.generate(workdir)
+
+
+def main():
+ generate_blog(include_drafts=True)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/blog/feed.py b/blog/feed.py
new file mode 100644
index 0000000..d8ffc8c
--- /dev/null
+++ b/blog/feed.py
@@ -0,0 +1,14 @@
+import os
+
+import render
+
+
+class Feed(object):
+ def __init__(self, template, posts):
+ self.template = template
+ self.posts = posts
+
+ def generate(self, basedir):
+ index = os.path.join(basedir, "index.html")
+ rendered = self.template.render(posts=self.posts)
+ render.write_file_content(index, rendered)
diff --git a/blog/post.py b/blog/post.py
new file mode 100644
index 0000000..1d35b6c
--- /dev/null
+++ b/blog/post.py
@@ -0,0 +1,71 @@
+import os
+import shutil
+import datetime
+import functools
+
+import render
+
+
+class Metadata(object):
+ __slots__ = ("title", "date", "status")
+
+ def __init__(self, title, date, status):
+ self.title = title
+ self.date = date
+ self.status = status
+
+
+class Post(object):
+ def __init__(self, template, directory):
+ self.template = template
+ self.directory = directory
+ self.name = os.path.basename(directory)
+
+ @staticmethod
+ def _load_raw_metadata(filename):
+ data = {}
+
+ with open(filename) as f:
+ for line in f:
+ k, v = line.strip().split(": ")
+ data[k] = v
+
+ return data
+
+ @functools.cached_property
+ def metadata(self):
+ raw = Post._load_raw_metadata(os.path.join(self.directory,
+ "metadata.txt"))
+
+ title = raw["Title"]
+ date = raw.get("Date", datetime.date.today().strftime("%Y-%m-%d"))
+ status = raw.get("Status", "draft")
+
+ return Metadata(title, date, status)
+
+ def is_draft(self):
+ return self.metadata.status == "draft"
+
+ def generate(self, basedir):
+ postdir = os.path.basename(self.directory)
+ workdir = os.path.join(basedir, postdir)
+ os.makedirs(workdir)
+
+ md = None
+ for filename in os.listdir(self.directory):
+ source = os.path.join(self.directory, filename)
+ destination = os.path.join(workdir, filename)
+
+ shutil.copy(source, destination)
+
+ if filename.endswith(".md"):
+ md = source
+
+ assert md, f"There is no markdown file in `{self.directory}`"
+
+ body = render.to_html(md)
+ rendered = self.template.render(title=self.metadata.title,
+ date=self.metadata.date,
+ body=body)
+ render.write_file_content(os.path.join(workdir, "index.html"),
+ rendered)
diff --git a/blog/render.py b/blog/render.py
new file mode 100644
index 0000000..4e26318
--- /dev/null
+++ b/blog/render.py
@@ -0,0 +1,16 @@
+import markdown
+
+
+def read_file_content(filename):
+ with open(filename) as f:
+ return f.read()
+
+
+def write_file_content(filename, data):
+ with open(filename, mode='w') as f:
+ f.write(data)
+
+
+def to_html(filename):
+ text = read_file_content(filename)
+ return markdown.markdown(text, extensions=["fenced_code", "footnotes"])
diff --git a/posts/drafts/hello-world/hello-world.md b/posts/drafts/hello-world/hello-world.md
new file mode 100644
index 0000000..cecfa2d
--- /dev/null
+++ b/posts/drafts/hello-world/hello-world.md
@@ -0,0 +1,59 @@
+This is a post to demonstrate and test content generation code. I am not a
+writing master, so I am going just enumerate features I am going to use to see
+how they are rendered.
+
+And I want to know how multiple paragraphs are rendered.
+
+
+## Code
+
+Standalone code block like this one.
+
+```
+#include <iostream>
+
+int main() {
+ std::cout << "Hello, World" << std::endl;
+ return 0;
+}
+```
+
+And embedded code like `this` and `this`.
+
+
+## Lists
+* I
+* like
+* itemized
+* lists
+
+Also, sometimes I use enumerated lists like this one.
+1. One.
+2. Two.
+3. Three.
+
+
+## Formatting
+
+This is a **bold** text. And this is _italic_.
+
+
+## Quotes
+> I don't know what's the matter with people: they don't learn by
+> understanding; they learn by some other way — by rote or something.
+> Their knowledge is so fragile!
+
+
+## Footnotes
+
+Oh, some it would nice to have footnotes like this one[^1].
+
+[^1]: This should work.
+
+
+## URLs
+
+Here is the url to [website][1]. And the same [url](https://ilvokhin.com), but
+embedded into the text.
+
+[1]: https://ilvokhin.com
diff --git a/posts/drafts/hello-world/metadata.txt b/posts/drafts/hello-world/metadata.txt
new file mode 100644
index 0000000..800612c
--- /dev/null
+++ b/posts/drafts/hello-world/metadata.txt
@@ -0,0 +1,3 @@
+Title: Hello, World!
+Date: 2022-12-23
+Status: draft
diff --git a/templates/feed.html b/templates/feed.html
new file mode 100644
index 0000000..ee1411e
--- /dev/null
+++ b/templates/feed.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+
+<html>
+
+<head>
+ <meta charset="UTF-8">
+ <title>Blog</title>
+</head>
+
+<body>
+ {% if posts %}
+ <ul>
+ {% for post in posts %}
+ <li>
+ <a href="{{ post.name }}">{{ post.metadata.title }}</a>
+ ({{ post.metadata.date }})
+ </li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+</body>
+
+</html>
diff --git a/templates/post.html b/templates/post.html
new file mode 100644
index 0000000..088b2f6
--- /dev/null
+++ b/templates/post.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+
+<html>
+
+<head>
+ <meta charset="UTF-8">
+ <title>{{ title }}</title>
+</head>
+
+<body>
+ <h1>{{ title }}</h1>
+ <h3>{{ date }}</h3>
+
+ {{ body|safe }}
+</body>
+
+</html>