diff options
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | blog/__init__.py | 0 | ||||
| -rw-r--r-- | blog/blog.py | 53 | ||||
| -rw-r--r-- | blog/feed.py | 14 | ||||
| -rw-r--r-- | blog/post.py | 71 | ||||
| -rw-r--r-- | blog/render.py | 16 | ||||
| -rw-r--r-- | posts/drafts/hello-world/hello-world.md | 59 | ||||
| -rw-r--r-- | posts/drafts/hello-world/metadata.txt | 3 | ||||
| -rw-r--r-- | templates/feed.html | 23 | ||||
| -rw-r--r-- | templates/post.html | 17 | 
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> |