From 54ac714da7175a8a08239e500a4e75a48b97d97e Mon Sep 17 00:00:00 2001 From: Dmitry Ilvokhin Date: Sat, 24 Dec 2022 18:10:17 +0000 Subject: 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. --- .gitignore | 4 ++ blog/__init__.py | 0 blog/blog.py | 53 ++++++++++++++++++++++++ blog/feed.py | 14 +++++++ blog/post.py | 71 +++++++++++++++++++++++++++++++++ blog/render.py | 16 ++++++++ posts/drafts/hello-world/hello-world.md | 59 +++++++++++++++++++++++++++ posts/drafts/hello-world/metadata.txt | 3 ++ templates/feed.html | 23 +++++++++++ templates/post.html | 17 ++++++++ 10 files changed, 260 insertions(+) create mode 100644 .gitignore create mode 100644 blog/__init__.py create mode 100644 blog/blog.py create mode 100644 blog/feed.py create mode 100644 blog/post.py create mode 100644 blog/render.py create mode 100644 posts/drafts/hello-world/hello-world.md create mode 100644 posts/drafts/hello-world/metadata.txt create mode 100644 templates/feed.html create mode 100644 templates/post.html 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 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 + +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 @@ + + + + + + + Blog + + + + {% if posts %} + + {% endif %} + + + 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 @@ + + + + + + + {{ title }} + + + +

{{ title }}

+

{{ date }}

+ + {{ body|safe }} + + + -- cgit v1.2.3-70-g09d2