编程打卡?来写个静态博客生成器吧。
编程打卡?来写个静态博客生成器吧。
一、问题描述
纯粹是觉得写教科书上的习题太过于无聊了,所以我想尝试写个静态博客生成器。我的博客(另一个)是完全用手写的静态网页,只使用了现学的一点点 HTML,CSS和一点点 JavaScript ,还有各种各样插入到内容中的图片文件,效果还不错?但当我要尝试编辑整个博客的一些样式的时候,痛苦的事情发生了。我不得不批量的进行替换,每一个页面文件,重复的HTML代码替换工作。当然,有自动化的工具帮助我们完成这些,比如常见于 Unix 中的 sed
命令,来批量替换文本,但还是好麻烦,我不得不去想如何才能准确的替换而不把 HTML 文件弄得一团糟。然后博客中有个目录页面,如果能随着文章的增加而自动更新目录界面,该有多好,此外用稍微有点复杂的 HTML 写文章实在是痛苦,于是我想到了,不如写个博客生成器吧?
听起来好困难,但是稍微想想的话,它的工作还是很容易的。首先从我们的一个目录中读取 .md 格式的文章,把它变成 HTML ,然后再插入到模板中。自然是选择 Python 作为工具,因为前者可以用 Python 中的 markdown 库来实现,后者可以用 jinja2 这个库来实现,问题一下子就变得简单许多了?虽然说有点偷懒,但它能解决问题,重复造轮子之类的事情我想意义不大。
大概只是写一个简单能跑的,更多还是要等待后续完善。
二、设计思路
- 读取配置文件,查找相关目录和模板,如果不存在则自动创建。
- 读取页面,读取文章
- 转换成 HTML,插入模板,输出到静态博客目录
三、程序流程图
四、编写
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的语法,简单写了个测试的页面,确认正常替换,也就只是这样。
以上