Workshop

Building This Static Site Generator

Published on: 2024-04-06

By: AI Assistant & Ian McCutcheon

The Making Of: This Simple Python SSG

This website, my digital workshop, needed a foundation. Instead of reaching for a complex, pre-packaged Static Site Generator (SSG), the decision was made to build one from the ground up using Python. The core idea? Keep it simple, leverage tools already in hand (like Python itself and standard libraries), and maintain complete control over the process. What followed was an iterative journey, much of it pair-programmed with an AI assistant, resulting in the system that renders the very page you're reading.

From Markdown to Site: The Core Workflow

The generator operates on a straightforward principle: take plain text files and turn them into a structured website. Here's the essence of how it works:

  1. Content First: Everything starts in the content/ directory. Here, posts like this one, pages like 'About', and the special home.md are written using simple Markdown syntax. This makes writing feel natural and focused, without getting bogged down in HTML.

  2. Metadata with Frontmatter: To add details like titles, dates, authors, or special instructions (like marking a post as a draft or hidden), each Markdown file can begin with a YAML block, neatly tucked between --- separators. This metadata is crucial for sorting posts, displaying info in templates, and controlling visibility.

  3. Templating with Jinja2: The overall look and structure of the site reside in the templates/ directory. We use the powerful Jinja2 templating engine. A base.html file defines the skeleton (the <html>, <head>, <body>, common CSS/JS includes, header, footer), while specific templates like page_template.html (for posts/pages) and index_template.html (for the homepage layout) extend this base and fill in the unique content blocks. Reusable snippets like the header and footer live in templates/partials/.

  4. The Orchestrator (generate.py): The heart of the operation is the generate.py script. It reads the config.yaml file for settings (like directory paths, site title, author details, social links, and deployment targets) and then methodically carries out the build process:

    • Cleaning: It first cleans the output/ directory to ensure a fresh build.
    • Walking the Content: It explores the content/ directory, looking for files.
    • Processing Markdown: For each .md file found (that isn't a draft or the special home.md), it extracts the frontmatter, converts the Markdown body to HTML (using the markdown library), and cleverly rewrites relative image paths (using BeautifulSoup4) to ensure they work both in local Markdown previews and the final site. This generated HTML, along with the metadata, is stored temporarily.
    • Copying Assets: Any other files encountered (images, PDFs, etc.) are copied directly to the output/ directory, mirroring the structure from content/.
    • Handling the Homepage: The content/home.md file gets special treatment; its content is processed and saved aside to be injected into the main index page.
    • Setting the Stage: After scanning all content, it finalizes the lists of posts and navigation pages and makes them (along with config data) globally available to the Jinja2 templating environment.
    • Rendering Pages: Now, it loops through the collected page data and renders each one using the page_template.html, injecting the processed HTML body and metadata.
    • Building the Index: It then renders the index_template.html, providing the list of posts and the processed home.md content to create the two-column layout.
    • Final Touches: Lastly, it copies all static assets (CSS, JavaScript like Prism.js for code highlighting and Alpine.js for the dropdown menu, favicons) from the static/ directory into output/static/.
  5. The Result: A complete, static website sits in the output/ directory, ready for previewing locally (python -m http.server inside output/) or deploying.

Key Ingredients (Libraries)

This process relies on a handful of excellent Python libraries:

And on the client-side (loaded via CDN): * Alpine.js: Adds lightweight interactivity for the navigation dropdown. * Prism.js: Provides syntax highlighting for code blocks. * Font Awesome: Supplies the icons used for social media links.

Why Build It This Way?

While many excellent SSGs exist, building this one offered unique advantages:

Taking it Live: Deployment

Getting the generated site from the local output/ directory to the live web server (workshop.esoup.net hosted on DreamHost) involves one final step, automated via a shell script:

  1. The deploy.sh Script: A simple bash script (deploy.sh) was created to streamline the process. It first runs python generate.py to ensure the site is freshly built.
  2. Synchronization with rsync: The crucial part is the rsync command within deploy.sh. This powerful utility efficiently synchronizes the local output/ directory with the target directory on the remote server (/somepath/workshop.esoup.net/) over SSH.
    • It only transfers changed files.
    • The --delete flag ensures that files removed locally are also deleted on the server, keeping the live site an exact mirror of the build output.
    • This requires SSH key authentication to be set up between the local machine and the server to avoid password prompts.
  3. Git Hook Integration (Optional): To automate further, this deploy.sh script can be triggered by a local Git hook, such as pre-push. This means simply attempting to git push (even without a real remote target) automatically builds and deploys the site first. If the deployment fails, the push is aborted.

This deployment setup provides a reliable way to update the live site with minimal manual effort after making content changes.

Conclusion

This project stands as a testament to the idea that sometimes, building the tool is as rewarding and educational as using it. It creates a personalized workflow perfectly suited to the task at hand.


A Note on Development: As of this writing (April 6th, 2024), this initial phase of the SSG project is complete. The journey began just yesterday, April 5th, around 7:40 AM, making the total elapsed time roughly 40 hours. However, considering significant downtime – being away from the keyboard from noon until 7 PM on Saturday, factoring in sleep, and attending to household tasks on Sunday – the actual hands-on coding time likely totals around 5-6 hours. This rapid development from scratch is a powerful testament to the velocity achievable with focused effort and AI pair programming assistance.