编程打卡?来写个静态博客生成器吧。

编程打卡?来写个静态博客生成器吧。

一、问题描述

纯粹是觉得写教科书上的习题太过于无聊了,所以我想尝试写个静态博客生成器。我的博客(另一个)是完全用手写的静态网页,只使用了现学的一点点 HTML,CSS和一点点 JavaScript ,还有各种各样插入到内容中的图片文件,效果还不错?但当我要尝试编辑整个博客的一些样式的时候,痛苦的事情发生了。我不得不批量的进行替换,每一个页面文件,重复的HTML代码替换工作。当然,有自动化的工具帮助我们完成这些,比如常见于 Unix 中的 sed 命令,来批量替换文本,但还是好麻烦,我不得不去想如何才能准确的替换而不把 HTML 文件弄得一团糟。然后博客中有个目录页面,如果能随着文章的增加而自动更新目录界面,该有多好,此外用稍微有点复杂的 HTML 写文章实在是痛苦,于是我想到了,不如写个博客生成器吧?

听起来好困难,但是稍微想想的话,它的工作还是很容易的。首先从我们的一个目录中读取 .md 格式的文章,把它变成 HTML ,然后再插入到模板中。自然是选择 Python 作为工具,因为前者可以用 Python 中的 markdown 库来实现,后者可以用 jinja2 这个库来实现,问题一下子就变得简单许多了?虽然说有点偷懒,但它能解决问题,重复造轮子之类的事情我想意义不大。

大概只是写一个简单能跑的,更多还是要等待后续完善。

二、设计思路

  1. 读取配置文件,查找相关目录和模板,如果不存在则自动创建。
  2. 读取页面,读取文章
  3. 转换成 HTML,插入模板,输出到静态博客目录

三、程序流程图

启动
启动
Yes
Yes
No
No
存在配置文件
存在配置文件
创建空文件
创建空文件
No
No
Yes
Yes
存在相关目录
存在相关目录
创建相关目录
创建相关目录
提示先编辑,退出
提示先编辑,退出
检测模板
检测模板
No
No
退出,提示添加默认模板
退出,提示添加默认模板
渲染
渲染
遍历文件夹,查找要渲染的页面
遍历文件夹,查找要渲染的页面
存在index.md
存在index.md
创建index.md
创建index.md
No
No
将每个页面转换成HTML,连同页面标题,文件创建时间,存进Data
将每个页面转换成HTML,连同页面标题,文件创建时间,存进Data
Yes
Yes
将文件套用模板
将文件套用模板
输出到静态页面文件夹的相应位置
输出到静态页面文件夹的相应位置
开始渲染
开始渲染
套用模板
套用模板
存在该页面专用模板
存在该页面专用模板
这个页面是否为文章
这个页面是否为文章
No
No
是否存在文章专用模板
是否存在文章专用模板
Yes
Yes
专用模板输出
专用模板输出
Yes
Yes
默认模板输出
默认模板输出
文章专用模板输出
文章专用模板输出
No
No
Yes
Yes
Text is not SVG - cannot display

四、编写

Python语言是一种简洁的语言,基本上可以看作可以直接执行的伪代码了。

创建目录

我们需要一个函数来创建目录,如果目录不存在则创建,存在则忽略

def mkdir(path):
    folder = os.path.exists(path)
    if not folder:
        os.makedirs(path)
        print("create " + path + " folder")
    else:
        print("found " + path + " folder")

启动

为程序做一些初始化的准备,包括创建相关目录配置,查找模板。

def init():
    mkdir(os.getcwd() + "/article")
    mkdir(os.getcwd() + "/article/posts")
    mkdir(os.getcwd() + "/template")
    mkdir(os.getcwd() + "/styles")
    mkdir(os.getcwd() + "/static")

    if not os.path.exists(os.getcwd() + "/config.json"):
        with open(os.getcwd() + "/config.json", "w") as f:
            json.dump(
                {
                    "title": "My Blog",
                    "author": "My Name",
                    "url": "https://example.com",
                    "discription": "My Blog",
                    "email":"",
                },
                f,
            )
            print("create config.json, please edit it first.")
            exit()

    with open(os.getcwd() + "/config.json", "r") as f:
        global config
        config = json.load(f)
        print("found config.json")

    mkdir(os.getcwd() + "/static/styles")
    mkdir(os.getcwd() + "/static/scripts")
    mkdir(os.getcwd() + "/static/res")
    mkdir(os.getcwd() + "/static/posts")
    
    if not os.path.exists(os.getcwd() + "/template/default.html"):
        print("no default.html template found, please create one.")
        exit()

    if not os.path.exists(os.getcwd() + "/styles/style.css"):
        print("no style.css found")
    else:
        copyfile(os.getcwd() + "/styles/style.css", os.getcwd() + "/static/styles/style.css")
        print("copy style.css to static/styles/style.css")
    global env
    env = Environment(loader=FileSystemLoader(os.getcwd() + "/template"))

全局变量env用来初始化jinja2的环境,告诉它,你要在这个目录里面查找模板嗷这样。

转换成HTML

接下来这个函数用来将文件批量转换成HTML,和其他一些信息组成一个dict类型的数据,返回,来为下一步的渲染网页做准备。

def parseMD(page):
    with open(page, "r", encoding="utf-8") as f:
        content = f.read()
        html = markdown.markdown(content, extensions=extension)
        title = content.splitlines()[0].replace("#", "")
        data = {
            "title": title + " - " + config["title"],
            "content": html,
            "date": datetime.datetime.fromtimestamp(os.path.getmtime(page)).strftime("%Y-%m-%d"),
        }
        return data

渲染页面

这个函数包含三个参数,要渲染的页面路径,文件名和类型

def renderPages(path,filename,type):
    if os.path.exists(os.getcwd() + "/template/" + filename.replace(".md",".html")):
        print("found " + filename.replace(".md",".html") +  " in template folder, using it.")
        template = env.get_template(filename.replace(".md",".html"))
    elif os.path.exists(os.getcwd() + "/template/" + type + ".html"):
        print("found " + type + ".html in template folder, using it.")
        template = env.get_template(type + ".html")
    else:
        print("no " + filename.replace(".md",".html") + " or " + type + ".html found, using default.html")
        template = env.get_template("default.html")
    with open(path.replace("article/","static/") + filename.replace(".md",".html"), "w",encoding='utf-8') as f:
        f.write(template.render(parseMD(path + filename)))
        print("created " + filename.replace(".md",".html"))

渲染目录根部的是页面

从article目录遍历,如果未找到index.md,就新创建一个

def render():
    if not os.path.exists(os.getcwd() + "/article/index.md"):
        with open(os.getcwd() + "/article/index.md", "w") as f:
            f.write("# Index")
            print("create index.md")
    for page in os.listdir(os.getcwd() + "/article"):
        if page.endswith(".md"):
            renderPages(os.getcwd() + "/article/",page,"page")
    for post in os.listdir(os.getcwd() + "/article/posts"):
        if post.endswith(".md"):
            renderPages(os.getcwd() + "/article/posts/",post,"post")
    print("done.")

五、完整代码

import os
import markdown
import json
import datetime
from jinja2 import Environment, FileSystemLoader
from shutil import copyfile

extension = {
    "markdown.extensions.extra",
    "markdown.extensions.codehilite",
    "markdown.extensions.toc",
    "markdown.extensions.tables",
    "markdown.extensions.fenced_code",
}
# 使用插件来确保 markdown 库可以正确解析文档中的表格和代码

def mkdir(path):
    folder = os.path.exists(path)
    if not folder:
        os.makedirs(path)
        print("create " + path + " folder")
    else:
        print("found " + path + " folder")


def init():
    mkdir(os.getcwd() + "/article")
    mkdir(os.getcwd() + "/article/posts")
    mkdir(os.getcwd() + "/template")
    mkdir(os.getcwd() + "/styles")
    mkdir(os.getcwd() + "/static")

    if not os.path.exists(os.getcwd() + "/config.json"):
        with open(os.getcwd() + "/config.json", "w") as f:
            json.dump(
                {
                    "title": "My Blog",
                    "author": "My Name",
                    "url": "https://example.com",
                    "discription": "My Blog",
                    "email": "",
                },
                f,
            )
            print("create config.json, please edit it first.")
            exit()

    with open(os.getcwd() + "/config.json", "r") as f:
        global config
        config = json.load(f)
        print("found config.json")

    mkdir(os.getcwd() + "/static/styles")
    mkdir(os.getcwd() + "/static/scripts")
    mkdir(os.getcwd() + "/static/res")
    mkdir(os.getcwd() + "/static/posts")

    if not os.path.exists(os.getcwd() + "/template/default.html"):
        print("no default.html template found, please create one.")
        exit()

    if not os.path.exists(os.getcwd() + "/styles/style.css"):
        print("no style.css found")
    else:
        copyfile(
            os.getcwd() + "/styles/style.css", os.getcwd() + "/static/styles/style.css"
        )
        print("copy style.css to static/styles/style.css")
    global env
    env = Environment(loader=FileSystemLoader(os.getcwd() + "/template"))


def parseMD(page):
    with open(page, "r", encoding="utf-8") as f:
        content = f.read()
        html = markdown.markdown(content, extensions=extension)
        title = content.splitlines()[0].replace("#", "")
        data = {
            "title": title + " - " + config["title"],
            "content": html,
            "date": datetime.datetime.fromtimestamp(os.path.getmtime(page)).strftime(
                "%Y-%m-%d"
            ),
        }
        return data


def renderPages(path, filename, type):
    if os.path.exists(os.getcwd() + "/template/" + filename.replace(".md", ".html")):
        print(
            "found "
            + filename.replace(".md", ".html")
            + " in template folder, using it."
        )
        template = env.get_template(filename.replace(".md", ".html"))
    elif os.path.exists(os.getcwd() + "/template/" + type + ".html"):
        print("found " + type + ".html in template folder, using it.")
        template = env.get_template(type + ".html")
    else:
        print(
            "no "
            + filename.replace(".md", ".html")
            + " or "
            + type
            + ".html found, using default.html"
        )
        template = env.get_template("default.html")
    with open(
        path.replace("article/", "static/") + filename.replace(".md", ".html"),
        "w",
        encoding="utf-8",
    ) as f:
        f.write(template.render(parseMD(path + filename)))
        print("created " + filename.replace(".md", ".html"))


def render():
    if not os.path.exists(os.getcwd() + "/article/index.md"):
        with open(os.getcwd() + "/article/index.md", "w") as f:
            f.write("# Index")
            print("create index.md")
    for page in os.listdir(os.getcwd() + "/article"):
        if page.endswith(".md"):
            renderPages(os.getcwd() + "/article/", page, "page")
    for post in os.listdir(os.getcwd() + "/article/posts"):
        if post.endswith(".md"):
            renderPages(os.getcwd() + "/article/posts/", post, "post")
    print("done.")


def main():
    init()
    render()


main()

六、其他

这里实现了大概做到了将写好的markdown变成静态网站功能,不过工作还远没有结束,目前想到的等待解决的问题如下

  • 博客中的图片资源还没有处理,一般来说还应该应该将图片分类存储在静态网页的对应文件夹中,除非你只使用图床。
  • 每次运行都会重新渲染所有的页面,过于耗时,如果只是添加了文章而没有修改模板/其他文章,可以仅渲染添加的几篇。或许应该添加一个启动参数来让用户决定。
  • 暂时没有去管CSS,因为我发现CSS真的很难,我自己也不懂。
  • 好看好用的模板还在编写中,我还在学习一些jinja2的语法,简单写了个测试的页面,确认正常替换,也就只是这样。

以上

posted @ 2023-04-12 15:56  satou_matsuzaka  阅读(30)  评论(0编辑  收藏  举报

This is a Test

メイドノココロハ アヤツリドール