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:
-
Content First: Everything starts in the
content/directory. Here, posts like this one, pages like 'About', and the specialhome.mdare written using simple Markdown syntax. This makes writing feel natural and focused, without getting bogged down in HTML. -
Metadata with Frontmatter: To add details like titles, dates, authors, or special instructions (like marking a post as a
draftorhidden), 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. -
Templating with Jinja2: The overall look and structure of the site reside in the
templates/directory. We use the powerful Jinja2 templating engine. Abase.htmlfile defines the skeleton (the<html>,<head>,<body>, common CSS/JS includes, header, footer), while specific templates likepage_template.html(for posts/pages) andindex_template.html(for the homepage layout) extend this base and fill in the unique content blocks. Reusable snippets like the header and footer live intemplates/partials/. -
The Orchestrator (
generate.py): The heart of the operation is thegenerate.pyscript. It reads theconfig.yamlfile 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
.mdfile found (that isn't a draft or the specialhome.md), it extracts the frontmatter, converts the Markdown body to HTML (using themarkdownlibrary), and cleverly rewrites relative image paths (usingBeautifulSoup4) 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 fromcontent/. - Handling the Homepage: The
content/home.mdfile 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 processedhome.mdcontent 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 intooutput/static/.
- Cleaning: It first cleans the
-
The Result: A complete, static website sits in the
output/directory, ready for previewing locally (python -m http.serverinsideoutput/) or deploying.
Key Ingredients (Libraries)
This process relies on a handful of excellent Python libraries:
os,shutil: Essential tools for interacting with the file system.PyYAML: Makes reading the configuration and frontmatter effortless.markdown: The core engine for transforming Markdown text into HTML.Jinja2: Provides the flexible and powerful templating system.BeautifulSoup4: Used for the specific task of parsing and correcting image paths in the generated HTML.uuid: Used (if configured) to generate deterministic unique IDs for pages marked withstatus: guid.datetime: For easily adding the current year to the footer.
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:
- Simplicity & Understanding: The entire logic is relatively concise and understandable, residing mostly in
generate.py. - Direct Control: Every step of the process is explicitly defined and customizable.
- Learning: It provided a practical opportunity to work with file handling, templating, and process orchestration in Python.
- Minimalism: It includes only the features needed for this site, avoiding bloat.
- Leveraging Standards: It relies on widely used formats like Markdown, YAML, HTML, CSS, and common Python libraries.
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:
- The
deploy.shScript: A simple bash script (deploy.sh) was created to streamline the process. It first runspython generate.pyto ensure the site is freshly built. - Synchronization with
rsync: The crucial part is thersynccommand withindeploy.sh. This powerful utility efficiently synchronizes the localoutput/directory with the target directory on the remote server (/somepath/workshop.esoup.net/) over SSH.- It only transfers changed files.
- The
--deleteflag 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.
- Git Hook Integration (Optional): To automate further, this
deploy.shscript can be triggered by a local Git hook, such aspre-push. This means simply attempting togit 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.