Update blog layout
This commit is contained in:
204
build.py
Normal file
204
build.py
Normal file
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "pyyaml",
|
||||
# "markdown",
|
||||
# ]
|
||||
# ///
|
||||
"""Build script for Quiet's Blog.
|
||||
|
||||
Reads Markdown files with YAML frontmatter from content/ and generates
|
||||
static HTML in dist/ using templates from templates/.
|
||||
"""
|
||||
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import markdown
|
||||
import yaml
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
CONTENT_DIR = os.path.join(BASE_DIR, "content")
|
||||
TEMPLATE_DIR = os.path.join(BASE_DIR, "templates")
|
||||
STATIC_DIR = os.path.join(BASE_DIR, "static")
|
||||
DIST_DIR = os.path.join(BASE_DIR, "dist")
|
||||
|
||||
|
||||
def parse_post(filepath):
|
||||
"""Parse a Markdown file with YAML frontmatter. Returns dict with
|
||||
title, date, description, content (HTML), and source path info."""
|
||||
with open(filepath, "r") as f:
|
||||
text = f.read()
|
||||
|
||||
if not text.startswith("---"):
|
||||
raise ValueError(f"Missing frontmatter in {filepath}")
|
||||
|
||||
_, fm_raw, body = text.split("---", 2)
|
||||
meta = yaml.safe_load(fm_raw)
|
||||
|
||||
md = markdown.Markdown(extensions=["fenced_code", "tables"])
|
||||
html_content = md.convert(body.strip())
|
||||
|
||||
slug = os.path.splitext(os.path.basename(filepath))[0]
|
||||
|
||||
return {
|
||||
"title": meta["title"],
|
||||
"date": str(meta["date"]),
|
||||
"description": meta.get("description", ""),
|
||||
"content": html_content,
|
||||
"slug": slug,
|
||||
"source": filepath,
|
||||
}
|
||||
|
||||
|
||||
def load_template(name):
|
||||
path = os.path.join(TEMPLATE_DIR, name)
|
||||
with open(path, "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def render(template_str, **kwargs):
|
||||
result = template_str
|
||||
for key, value in kwargs.items():
|
||||
result = result.replace("{{" + key + "}}", str(value))
|
||||
return result
|
||||
|
||||
|
||||
def collect_posts(subdir):
|
||||
"""Collect and parse all .md files in content/<subdir>/."""
|
||||
pattern = os.path.join(CONTENT_DIR, subdir, "*.md")
|
||||
posts = []
|
||||
for filepath in sorted(glob.glob(pattern)):
|
||||
post = parse_post(filepath)
|
||||
post["category"] = subdir
|
||||
posts.append(post)
|
||||
posts.sort(key=lambda p: p["date"], reverse=True)
|
||||
return posts
|
||||
|
||||
|
||||
def build_post_page(post, template, output_dir, root_prefix):
|
||||
"""Render a single post page and write it to output_dir."""
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
html = render(
|
||||
template,
|
||||
title=post["title"],
|
||||
date=post["date"],
|
||||
content=post["content"],
|
||||
css_path=root_prefix + "styles.css",
|
||||
root=root_prefix,
|
||||
)
|
||||
out_path = os.path.join(output_dir, post["slug"] + ".html")
|
||||
with open(out_path, "w") as f:
|
||||
f.write(html)
|
||||
return out_path
|
||||
|
||||
|
||||
def build_post_card(post, href):
|
||||
"""Generate HTML for a post card on the index page."""
|
||||
return (
|
||||
f' <article class="post-card">\n'
|
||||
f' <h3>{post["title"]}</h3>\n'
|
||||
f' <p class="post-date">{post["date"]}</p>\n'
|
||||
f' <p>{post["description"]}</p>\n'
|
||||
f' <a href="{href}" class="read-more">Read More →</a>\n'
|
||||
f" </article>"
|
||||
)
|
||||
|
||||
|
||||
def build_list_section(title, posts, url_prefix):
|
||||
"""Generate HTML for a section in the posts listing page."""
|
||||
if not posts:
|
||||
return ""
|
||||
items = "\n".join(
|
||||
f' <li><a href="{url_prefix}/{p["slug"]}.html">{p["title"]}</a> '
|
||||
f'<span class="post-date">{p["date"]}</span></li>'
|
||||
for p in posts
|
||||
)
|
||||
return (
|
||||
f" <h3>{title}</h3>\n"
|
||||
f" <ul>\n{items}\n"
|
||||
f" </ul>"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
# Clean dist
|
||||
if os.path.exists(DIST_DIR):
|
||||
shutil.rmtree(DIST_DIR)
|
||||
os.makedirs(DIST_DIR)
|
||||
|
||||
# Load templates
|
||||
post_template = load_template("post.html")
|
||||
index_template = load_template("index.html")
|
||||
posts_template = load_template("posts.html")
|
||||
about_template = load_template("about.html")
|
||||
|
||||
# Collect posts by category
|
||||
regular_posts = collect_posts("posts")
|
||||
daily_posts = collect_posts("daily")
|
||||
weekly_posts = collect_posts("weekly")
|
||||
|
||||
# Build individual post pages
|
||||
for post in regular_posts:
|
||||
build_post_page(post, post_template, os.path.join(DIST_DIR, "posts"), "../")
|
||||
for post in daily_posts:
|
||||
build_post_page(post, post_template, os.path.join(DIST_DIR, "daily"), "../")
|
||||
for post in weekly_posts:
|
||||
build_post_page(post, post_template, os.path.join(DIST_DIR, "weekly"), "../")
|
||||
|
||||
# Build index page with latest posts across all categories
|
||||
all_posts = regular_posts + daily_posts + weekly_posts
|
||||
all_posts.sort(key=lambda p: p["date"], reverse=True)
|
||||
|
||||
def post_href(post):
|
||||
return f'{post["category"]}/{post["slug"]}.html'
|
||||
|
||||
post_cards = "\n\n".join(
|
||||
build_post_card(p, post_href(p)) for p in all_posts
|
||||
)
|
||||
index_html = render(index_template, post_cards=post_cards)
|
||||
with open(os.path.join(DIST_DIR, "index.html"), "w") as f:
|
||||
f.write(index_html)
|
||||
|
||||
# Build posts listing page
|
||||
daily_section = build_list_section("Daily Posts", daily_posts, "daily")
|
||||
weekly_section = build_list_section("Weekly Posts", weekly_posts, "weekly")
|
||||
posts_section = build_list_section("Posts", regular_posts, "posts")
|
||||
|
||||
posts_html = render(
|
||||
posts_template,
|
||||
daily_section=daily_section,
|
||||
weekly_section=weekly_section,
|
||||
posts_section=posts_section,
|
||||
)
|
||||
with open(os.path.join(DIST_DIR, "posts.html"), "w") as f:
|
||||
f.write(posts_html)
|
||||
|
||||
# Build about page
|
||||
about_post = parse_post(os.path.join(CONTENT_DIR, "about.md"))
|
||||
about_html = render(
|
||||
about_template,
|
||||
title=about_post["title"],
|
||||
date=about_post["date"],
|
||||
content=about_post["content"],
|
||||
)
|
||||
with open(os.path.join(DIST_DIR, "about.html"), "w") as f:
|
||||
f.write(about_html)
|
||||
|
||||
# Copy static files
|
||||
shutil.copy2(
|
||||
os.path.join(STATIC_DIR, "styles.css"),
|
||||
os.path.join(DIST_DIR, "styles.css"),
|
||||
)
|
||||
|
||||
# Summary
|
||||
total = len(regular_posts) + len(daily_posts) + len(weekly_posts)
|
||||
print(f"Built {total} posts ({len(regular_posts)} regular, "
|
||||
f"{len(daily_posts)} daily, {len(weekly_posts)} weekly)")
|
||||
print(f"Output: {DIST_DIR}/")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user