Skip to main content

A blog-oriented static site generation engine

Project description

Blogmore

A blog-oriented static site generation engine built in Python.

[!IMPORTANT] This project is built almost 100% using GitHub Copilot. Every other Python project you will find in my repository is good old human-built code. This project is the complete opposite: as much as possible I'm trying to write no code at all as an experiment in getting to know how this process works, how to recognise such code, and to understand those who use this process every day to, in future, better guide them.

If "AI written" is a huge red flag for you I suggest you avoid this project; you'll find plenty of other pure-davep-built projects via my profile.

Features

  • Write everything in Markdown
  • All metadata comes from frontmatter
  • Uses Jinja2 for templating
  • Simple and clean design
  • Automatic tag pages and archive generation
  • Built-in search - Optional client-side full-text search across post titles and content, enabled with --with-search; no external services required
  • Automatic icon generation - Generate favicons and platform-specific icons from a single source image
    • iOS (Apple Touch Icons)
    • Android/Chrome (with PWA manifest)
    • Windows/Edge (with tile configuration)

Installation

Using uv (recommended)

uv tool install blogmore

Using pipx

pipx install blogmore

From source

git clone https://github.com/davep/blogmore.git
cd blogmore
uv sync

Usage

Basic Usage

Create a directory with your markdown posts:

mkdir posts

Create a markdown file with frontmatter:

---
title: My First Post
date: 2024-01-15
tags: [python, blog]
---

This is my first blog post!

Generate your site:

blogmore build posts/

This will generate your site in the output/ directory.

Serve the Site Locally

To serve an existing site:

blogmore serve -o output/

Or generate and serve with auto-reload on changes:

blogmore serve posts/ -o output/

This starts a local HTTP server on port 8000 and watches for changes. Open http://localhost:8000/ in your browser.

Options:

  • -o, --output - Output directory to serve (default: output/)
  • -p, --port - Port to serve on (default: 8000)
  • --no-watch - Disable watching for changes

Example:

blogmore serve posts/ --port 3000 --output my-site/

Custom Options

blogmore build posts/ \
  --templates my-templates/ \
  --output my-site/ \
  --site-title "My Awesome Blog" \
  --site-subtitle "Thoughts on code and technology" \
  --site-url "https://example.com"

Configuration File

Blogmore supports configuration files to avoid repetitive command-line arguments. Create a blogmore.yaml or blogmore.yml file in your project directory:

# blogmore.yaml
content_dir: posts
output: my-site
templates: my-templates
site_title: "My Awesome Blog"
site_subtitle: "Thoughts on code and technology"
site_url: "https://example.com"
include_drafts: false
clean_first: false
posts_per_feed: 30
default_author: "Your Name"
extra_stylesheets:
  - https://example.com/custom.css
  - /assets/extra.css

# Serve-specific options
port: 3000
no_watch: false

# Publish-specific options
branch: gh-pages
remote: origin

Using Configuration Files

Automatic Discovery: Blogmore automatically searches for blogmore.yaml or blogmore.yml in the current directory (.yaml takes precedence):

blogmore build  # Uses blogmore.yaml if found

Specify a Config File: Use the -c or --config flag to specify a custom config file:

blogmore build --config my-config.yaml

Override Config with CLI: Command-line arguments always take precedence over configuration file values:

# Uses blogmore.yaml but overrides site_title
blogmore build --site-title "Different Title"

Configuration Options

All command-line options can be configured in the YAML file:

  • content_dir - Directory containing markdown posts
  • templates - Custom templates directory
  • output - Output directory (default: output/)
  • site_title - Site title (default: "My Blog")
  • site_subtitle - Site subtitle (optional)
  • site_url - Base URL of the site
  • include_drafts - Include posts marked as drafts (default: false)
  • clean_first - Remove output directory before generating (default: false)
  • posts_per_feed - Maximum posts in feeds (default: 20)
  • default_author - Default author name for posts without author in frontmatter
  • extra_stylesheets - List of additional stylesheet URLs
  • port - Port for serve command (default: 8000)
  • no_watch - Disable file watching in serve mode (default: false)
  • branch - Git branch for publish command (default: gh-pages)
  • remote - Git remote for publish command (default: origin)
  • socials_title - Title for the social media icons section in the sidebar (default: "Social")

Note: The --config option itself cannot be set in a configuration file.

Commands

Build (build, generate, gen) Generate the static site from markdown posts:

blogmore build posts/ [options]

Serve (serve, test) Serve the site locally with optional generation and auto-reload:

blogmore serve [posts/] [options]

Publish (publish) Build and publish the site to a git branch (e.g., for GitHub Pages):

blogmore publish posts/ [options]

This command:

  1. Builds your site to the output directory
  2. Checks that you're in a git repository
  3. Creates or updates a git branch (default: gh-pages)
  4. Copies the built site to that branch
  5. Commits and pushes the changes

Example for GitHub Pages:

blogmore publish posts/ --branch gh-pages --remote origin

Note: The publish command requires git to be installed and available in your PATH.

Common Options

Available for both build and serve commands:

  • content_dir - Directory containing markdown posts (required for build, optional for serve)
  • -c, --config - Path to configuration file (default: searches for blogmore.yaml or blogmore.yml)
  • -t, --templates - Custom templates directory (default: uses bundled templates)
  • -o, --output - Output directory (default: output/)
  • --site-title - Site title (default: "My Blog")
  • --site-subtitle - Site subtitle (optional)
  • --site-url - Base URL of the site
  • --include-drafts - Include posts marked as drafts
  • --clean-first - Remove output directory before generating
  • --posts-per-feed - Maximum posts in feeds (default: 20)
  • --default-author - Default author name for posts without author in frontmatter
  • --extra-stylesheet - Additional stylesheet URL (can be used multiple times)

Serve-Specific Options

  • -p, --port - Port to serve on (default: 8000)
  • --no-watch - Disable watching for changes

Publish-Specific Options

  • --branch - Git branch to publish to (default: gh-pages)
  • --remote - Git remote to push to (default: origin)

Frontmatter Fields

Required:

  • title - Post title

Optional:

  • date - Publication date (YYYY-MM-DD format)
  • category - Post category (e.g., "python", "webdev")
  • tags - List of tags or comma-separated string
  • draft - Set to true to mark as draft
  • author - Author name (uses default_author if not specified)

Example:

---
title: My Blog Post
date: 2024-01-15
category: python
tags: [python, webdev, tutorial]
author: Jane Smith
draft: false
---

Categories vs Tags

Categories allow you to organize posts into distinct sections or "sub-blogs" within your site. Each post can have one category, and visitors can view all posts in a category at /category/{category-name}.html.

Tags are for cross-categorization and can be applied multiple times per post. They're useful for topics that span multiple categories.

For example, a blog might use categories like "python", "javascript", "devops" to separate major topics, while using tags like "tutorial", "advanced", "beginner" to indicate post type.

Icon Generation

Blogmore can automatically generate favicons and platform-specific icons from a single source image. Place a high-resolution square image (ideally 1024×1024 or larger) in your extras/ directory, and Blogmore will generate all necessary icon formats.

Generated Icons

From a single source image, Blogmore generates 18 icon files for all major platforms:

  • Favicon files: Multi-resolution .ico and PNG sizes (16×16, 32×32, 96×96)
  • Apple Touch Icons: Optimized for iOS devices (120×120, 152×152, 167×167, 180×180)
  • Android/Chrome icons: PWA-ready with web manifest (192×192, 512×512)
  • Windows tiles: Microsoft Edge and Windows 10+ tiles (70×70, 144×144, 150×150, 310×310, 310×150)

All icons are generated to the /icons subdirectory to avoid conflicts with other files.

Configuration

Auto-detection (no configuration needed): Place one of these files in your extras/ directory:

  • icon.png (recommended)
  • icon.jpg or icon.jpeg
  • source-icon.png
  • app-icon.png

Custom filename via CLI:

blogmore build content/ --icon-source my-logo.png

Custom filename via config file:

# Icon generation
icon_source: "my-logo.png"

Requirements

  • Source image should be square
  • Recommended size: 1024×1024 or larger
  • Supported formats: PNG, JPEG
  • Transparent backgrounds (PNG) work best

Templates

Blogmore uses Jinja2 templates. The default templates are included, but you can customize them:

  • base.html - Base template
  • index.html - Homepage listing (shows full post content)
  • post.html - Individual post page
  • archive.html - Archive page
  • tag.html - Tag page
  • category.html - Category page
  • search.html - Search page
  • static/style.css - Stylesheet

Search

Search is disabled by default. To enable it, pass --with-search on the command line or set with_search: true in the configuration file.

blogmore build posts/ --with-search
# blogmore.yaml
with_search: true

How it works

When the site is built with search enabled, two files are added to the output directory:

  • search_index.json — A JSON array containing the title, URL, date, and plain-text body of every published post.
  • search.html — A search page with a text input that loads search_index.json and performs an in-browser search as the user types.

A Search link is added to the top navigation bar on every page.

No external services or server-side processing are required — everything runs entirely in the reader's browser.

Performance

The search index is only fetched when the reader opens the search page; it does not affect the load time of any other page. The search itself uses built-in JavaScript string operations — no extra libraries are downloaded.

Linking to a pre-filled search

Append a ?q= query string to the search URL to pre-fill the search input and immediately show results. For example:

https://example.com/search.html?q=python

Reading Time

Estimated reading time is disabled by default. To enable it, pass --with-read-time on the command line or set with_read_time: true in the configuration file.

blogmore build posts/ --with-read-time
# blogmore.yaml
with_read_time: true

When enabled, BlogMore calculates the approximate time to read each post based on 200 words per minute and displays it next to the post date on the index, archive, tag, category, and individual post pages.

Markdown Features

Blogmore supports all standard Markdown features plus:

  • Fenced code blocks with syntax highlighting
  • Tables
  • Table of contents generation
  • Footnotes - Use [^1] in text and [^1]: Footnote text at the bottom
  • GitHub-style admonitions - Alert boxes for notes, tips, warnings, etc.

Admonitions (Alerts)

Blogmore supports GitHub-style admonitions (also known as alerts) to highlight important information. These use the same syntax as GitHub Markdown:

> [!NOTE]
> Useful information that users should know, even when skimming content.

> [!TIP]
> Helpful advice for doing things better or more easily.

> [!IMPORTANT]
> Key information users need to know to achieve their goal.

> [!WARNING]
> Urgent info that needs immediate user attention to avoid problems.

> [!CAUTION]
> Advises about risks or negative outcomes of certain actions.

Each admonition type has its own color scheme and icon:

  • Note - Blue with ℹ️ icon
  • Tip - Green with 💡 icon
  • Important - Purple with ❗ icon
  • Warning - Orange with ⚠️ icon
  • Caution - Red with 🚨 icon

Admonitions support all standard Markdown formatting within them, including bold, italic, code, links, and multiple paragraphs.

Example with formatting:

> [!TIP]
> You can use **bold**, *italic*, and `code` formatting.
>
> Multiple paragraphs work too!

Footnotes Example

Example with footnote:

---
title: My Post
---

This is a post with a footnote[^1].

[^1]: This is the footnote content.

Development

Setup

make setup

Run Checks

make checkall  # Run all checks (lint, format, typecheck, spell, tests)
make lint      # Check linting
make typecheck # Type checking with mypy
make test      # Run test suite

Format Code

make tidy  # Fix formatting and linting issues

Testing

Blogmore has a comprehensive test suite with 143 tests achieving 84% code coverage.

make test              # Run all tests
make test-verbose      # Run with verbose output
make test-coverage     # Run with detailed coverage report

For more information, see the tests README.

License

GPL-3.0-or-later

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

blogmore-1.11.0.tar.gz (69.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

blogmore-1.11.0-py3-none-any.whl (83.6 kB view details)

Uploaded Python 3

File details

Details for the file blogmore-1.11.0.tar.gz.

File metadata

  • Download URL: blogmore-1.11.0.tar.gz
  • Upload date:
  • Size: 69.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for blogmore-1.11.0.tar.gz
Algorithm Hash digest
SHA256 154fd58a2d98e88410deeff220fe23da650bbd8db7d3c645c2867f410b97ae33
MD5 267a45e24f355f5f2d07bb93ce5c8d41
BLAKE2b-256 ee9d0e6117ed7047ed61634bf4650b349996639ce764a854ceecdc84ee7c05c0

See more details on using hashes here.

File details

Details for the file blogmore-1.11.0-py3-none-any.whl.

File metadata

  • Download URL: blogmore-1.11.0-py3-none-any.whl
  • Upload date:
  • Size: 83.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for blogmore-1.11.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bcd29533149747add9ca234cb666059303fabee091f1483aa528085c65804aad
MD5 7d5f6f08c1ee97a990867cf2f8aed656
BLAKE2b-256 c8359b996f3f468765060cea233e7c009ef562bbfb6a7f24d720c86560342fa5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page