#!/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//.""" 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'
\n' f'

{post["title"]}

\n' f' \n' f'

{post["description"]}

\n' f' Read More →\n' f"
" ) 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'
  • {p["title"]} ' f'
  • ' for p in posts ) return ( f"

    {title}

    \n" f" " ) 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()