Skip to content

A static site generator in Rust that converts Markdown files to HTML.

License

Notifications You must be signed in to change notification settings

8sun/static-site-gen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

static-site-gen — Static Site Generator

A lightweight Rust-based static site generator powering the site/ output directory. It converts Markdown posts into HTML pages with SEO-friendly metadata, pagination, category pages, and an auto-generated sitemap.

Features

  • Markdown to HTML conversion (Comrak, smart punctuation, fenced code class injection)
  • Category pages with automatic pagination (window & ellipsis logic)
  • Synthetic Home navigation item with configurable display name (home_page_name)
  • Home page population via home: true flag (independent of synthetic category entry)
  • Per-post categories with inline link rendering (footer of post pages)
  • Unified pagination component with ARIA & rel attributes (prev, next, first, last)
  • SEO metadata injection (Open Graph, Twitter, canonical, robots)
  • Structured data (JSON-LD) injected (WebSite on home, sitePosting on posts) & stripped if empty
  • Dynamic or overridden language attribute (<html lang> fallback from og_locale)
  • Automatic sitemap generation (sitemap.xml)
  • Accessibility: list item images get descriptive alt attributes
  • Canonical URL validation (empty canonical panics early)
  • Robots meta emitted only when deviating from defaults (noindex,follow on paginated category pages)
  • Newest-first sorting by ISO date for home & category lists

Getting Started

1. Prepare the resources directory

This project provides a sample directory containing example configuration, templates, and content. To try the sample site or use the generator, you must:

  • Rename the sample directory to resources in your project root:
    mv sample resources
  • Or, create your own resources directory with the following structure:
    resources/
      params.yaml         # global site params (required)
      posts.yaml          # list of posts (required)
      content/            # markdown source (each post: <id>.md)
        assets/img/       # images used by posts + logo/no-image fallback
      templates/          # HTML templates (required set below)
    

2. Minimum templates required

All template filenames are currently fixed:

resources/templates/category_item.html
resources/templates/category.html
resources/templates/home.html
resources/templates/meny.html              # menu navigation item list wrapper
resources/templates/category_link.html     # category anchor inside a post footer
resources/templates/pagination_item.html   # one pagination list item (number / prev / next / ellipsis)
resources/templates/pagination.html        # container wrapping pagination items
resources/templates/post.html              # referenced per post via posts.yaml "template" field

Copy these from the sample/templates directory or create your own with the documented placeholders (see below).

3. Create params.yaml

Example starter:

items_per_page: 5
pagination_window: 2
language: en
og_locale: en_US
site_base_url: https://example.com
site_name: Example Site
site_description: A demonstration static site.
home_page_name: Home

4. Create posts.yaml & markdown files

Example (one post):

- id: hello-world
  title: "Hello World"
  description: "First post"
  template: post.html
  icon: ""                  # empty => uses no-image.png fallback
  categories:
    general: "General"
  home: true
  date: "2025-01-01"

Create matching markdown: resources/content/hello-world.md:

# Hello World

This is my first post.

Add optional images into resources/content/assets/img/ (include logo.png and no-image.png if you rely on fallbacks).

5. Run the generator

From the project root:

./static-site-gen

Optional logging verbosity (uses env_logger):

RUST_LOG=info ./static-site-gen      # default informational messages
RUST_LOG=debug ./static-site-gen     # more granular debug output if added later

On success you'll see log lines ending with Done. and a populated site/ directory:

site/
  index.html
  <post-slug>.html
  <category>.html
  <category>/<n>/ (paginated directories, trailing slash form)
  sitemap.xml
  assets/ (copied from src/md/assets)

6. Serving the output

You can host site/ with any static server (Nginx, Netlify, GitHub Pages). Ensure site_base_url in params.yaml matches the deployed origin; it is baked into canonical, OG image URLs, and sitemap entries.

7. Placeholders recap (for authoring / customizing templates)

Common tokens (replace automatically):

{{page_title}} {{page_description}} {{title}} {{description}}
{{canonical}} {{robots}} {{og_type}} {{og_locale}} {{og_image}} {{site_name}} {{structured_data}} {{lang}}
{{menu}} {{content}} {{path}}
{{categories}} {{date}} {{page}} {{icon}}

Avoid conflicting substring names (e.g. do not put title inside page_title unintentionally).

8. Re-running / incremental changes

Repeat ./static-site-gen after editing markdown or YAML. The entire site/ folder is deleted first; keep anything persistent (like deployment scripts) outside site/.

9. Non-fatal vs fatal issues

  • Missing markdown (src/md/<id>.md) or missing referenced post template: post skipped (warning logged, build continues).
  • Empty canonical (derived from site_base_url + path) would trigger a panic (guard against empty site_base_url).
  • Serious I/O or YAML parse errors: process exits non-zero and prints a message.

10. Exit codes

  • 0: Successful generation
  • Non-zero: Fatal error (inspect printed message)

11. Customization tips

  • Change pagination size via items_per_page.
  • Adjust visible pagination range with pagination_window.
  • Override <html lang> with language; omit to derive from first part of og_locale.
  • Provide post-specific icons (images) in src/md/assets/img/ and reference via icon field; empty string uses no-image.png.
  • Replace logo.png for site-level OG images.

12. Quick start script (macOS/Linux)

mkdir -p my-site/src/{md/assets/img,html}
cd my-site
# (Download binary here)
chmod +x static-site-gen
cat > src/params.yaml <<'EOF'
items_per_page: 5
pagination_window: 2
og_locale: en_US
site_base_url: https://example.com
site_name: Example Site
site_description: Example description
home_page_name: Home
EOF
cat > src/posts.yaml <<'EOF'
- id: hello-world
  title: "Hello World"
  description: "First post"
  template: post.html
  icon: ""
  categories:
    general: "General"
  home: true
  date: "2025-01-01"
EOF
cat > src/md/hello-world.md <<'EOF'
# Hello World

This is my first post.
EOF
# Copy or author required templates into src/html/ ...
./static-site-gen

13. Troubleshooting

Symptom Cause Fix
Post missing in output Missing markdown or template file Ensure src/md/<id>.md and src/html/<template> exist
Images not showing Wrong filename or missing in assets/img Place correct file under src/md/assets/img
Wrong canonical base Incorrect site_base_url in params Update params.yaml and rerun
Sitemap absent Fatal earlier error stopped build Re-run with RUST_LOG=info and inspect output
Language incorrect language omitted & og_locale prefix unexpected Set explicit language: in params

14. Future enhancements (planned)

CLI flags for custom source/output paths are not yet available (paths are fixed). For now, keep the described structure. Roadmap items (RSS, drafts, incremental builds) will extend this section as they land.


Generation Flow (Destructive to Output Directory)

Markdown (.md) + posts.yaml + params.yaml
          | parse & load
          v
Aggregate categories (insert synthetic Home, sort by title)
          v
Render pages:
  - Posts (markdown -> HTML, template fill, SEO cleanup)
  - Category pages (paginate, template fill, canonical/robots logic)
  - Home page (filter posts with home: true)
          v
Generate sitemap (scan output .html -> canonical URLs)
          v
Copy assets (CLI step AFTER generation to avoid deletion)

⚠ The site_path directory is fully cleared before writing new output. Do not place persistent artifacts there.

Canonical URL Style

Current pattern (mixed by design):

  • Home: https://example.com/ (trailing slash)
  • Posts: https://example.com/<slug>.html
  • First category page: https://example.com/<category>.html
  • Paginated category pages: https://example.com/<category>/<n>/ (trailing slash directory form) This is documented for transparency; you may standardize later (e.g. all trailing slash or all .html).

Settings Structure (Library API)

The Settings struct includes:

  • md_path: Source Markdown directory (<id>.md per post)
  • site_path: Output directory (site/, cleared before generation)
  • html_path: Directory containing HTML templates
  • site_params: Runtime & SEO params (see below)
  • category_item_template, category_template, home_template, menu_item_template, category_link_template, pagination_item_template, pagination_template: Template file paths (string paths to HTML files)

Runtime Params (params.yaml)

Example:

items_per_page: 2             # category pagination size
pagination_window: 2          # number of page numbers shown on EACH side of current page
language: en                  # optional; if omitted derived from og_locale (e.g. en_US -> en)
og_locale: en_US
site_base_url: https://example.com
site_name: 8sun site
site_description: An example site for demonstration purposes
home_page_name: Home          # label for synthetic home menu item (slug fixed: index)

Field notes:

  • items_per_page: category pagination size
  • pagination_window: symmetric window of adjacent page indices (total visible pages grows with current location)
  • language: direct <html lang> override, else derived from og_locale
  • og_locale: Open Graph locale & fallback source for language
  • home_page_name: display name for navigation link to home page (index.html)

Post Metadata (posts.yaml)

Required fields per post id (also expect a <id>.md file in md_path):

- id: example-post
  title: "Example Post"
  description: "Short description"
  template: post.html          # must exist under html_path
  icon: "optional_icon.png"    # leave empty string for fallback image (no-image.png)
  categories:
    philosophy: "Philosophy"   # slug: display name (multiple allowed)
  home: true                   # includes on home page list
  date: "2025-01-01"           # ISO format (YYYY-MM-DD) used for lexicographic newest-first sorting

Missing markdown or template => post skipped with a logged warning (build continues).

Sorting & Ordering

  • Home posts: filter home: true, then sort descending by date (ISO 8601 string compare).
  • Category pages: posts in that category sorted descending by date.
  • Categories: synthetic index first, remaining categories sorted lexicographically by title.

SEO Behavior

Aspect Behavior
Canonical Always emitted; generation panics if empty
Robots Omitted for indexable pages (home, posts, first category page); noindex,follow on paginated category pages
Open Graph og:title, og:description, og:type, og:site_name, og:locale, og:image
Twitter summary_large_image card (via OG fields)
Structured Data WebSite (home), sitePosting (posts); empty script node removed
Language <html lang> from language or derived from og_locale prefix
Sitemap site/sitemap.xml enumerates canonical URLs

Templates & Placeholders

Placeholders are simple {{key}} tokens. Avoid overlapping names (e.g. title inside page_title). Provided pairs include aliases for convenience.

Core placeholders:

  • Page meta: {{page_title}}, {{page_description}}, {{title}} (alias), {{description}} (alias)
  • SEO: {{canonical}}, {{robots}}, {{og_type}}, {{og_locale}}, {{og_image}}, {{site_name}}, {{structured_data}}, {{lang}}
  • Layout: {{menu}}, {{content}}, {{path}}
  • Listing / post specific: {{categories}}, {{date}}, {{page}} (link stem for items), {{icon}}

Template roles:

  • category_item_template: per-post summary item (uses page, icon, title, description, date)
  • category_template: wraps combined list + pagination block
  • home_template: wraps home list
  • menu_item_template: per-category navigation link (including synthetic Home)
  • category_link_template: category anchor inside a post page footer
  • pagination_item_template + pagination_template: pagination UI
  • Post page template (e.g. post.html): full post layout with categories/date injection

Structured Data Types

  • Home: JSON-LD WebSite object
  • Post: JSON-LD sitePosting object Empty payload => script removed during cleanup.

Language Override

YAML usage:

language: fr
og_locale: fr_FR

Produces <html lang="fr"> regardless of other inference rules.

Assets Handling

  • Source images & static files expected under src/md/assets/.
  • After page generation the CLI copies to site/assets/ (order intentional: generation wipes output first).
  • Fallback filenames: logo.png (site logo for home/category OG image), no-image.png (used when post icon empty).
  • Image URLs constructed as: <site_base_url>/assets/img/<file>.

Missing Resources Behavior

  • Missing markdown file for a post id: post skipped (warning logged).
  • Missing template file referenced by a post: post skipped (warning logged).
  • These are non-fatal; overall build succeeds unless a critical I/O error occurs.

Pagination Details

  • pagination_window = number of adjacent page numbers displayed on each side of current page.
  • Always includes first & last page numbers.
  • Ellipsis item inserted where numeric gaps occur.
  • Rel attributes (prev, next, first, last) applied when links are active for crawlers & accessibility.

Extension Guide

Adding a new metadata field:

  1. Add to MetaPage and propagate in create_basic_page placeholder pairs.
  2. Add {{field}} to relevant templates.
  3. Populate when constructing MetaPage in generation code.

Adding a new page type:

  1. Create template under html_path.
  2. Implement helper akin to existing create_*_page functions.
  3. Construct MetaPage and invoke helper during generation.

Customizing pagination appearance:

  • Edit pagination_item_template / pagination_template (markup & classes).

Custom category ordering:

  • Modify Category::from_posts or add a custom ordering layer (future roadmap includes config-based ordering).

Adding a Post

  1. Place Markdown at src/md/<slug>.md.
  2. Add entry to src/posts.yaml.
  3. (Optional) Add image: src/md/assets/img/<icon>.
  4. Run cargo run.

Testing Guarantees

Tests assert:

  • Pagination (window, ellipsis, prev/next/first/last states)
  • Robots meta omission & paginated noindex,follow
  • Language override & fallback derivation
  • Canonical presence (panic if empty)
  • Structured data retention vs cleanup
  • Sitemap URL discovery logic
  • Category link isolation vs menu
  • Markdown rendering (headings, emphasis, fenced code language)
  • Path prefix correctness for nested pages

Roadmap (See features.md for full details)

Near Term examples: RSS generation, draft support, selective rebuild flags. Medium Term: Incremental builds, tags taxonomy, pluggable templating. Stretch: Multilingual, image optimization, search index, live preview server.

License

MIT License. See LICENSE file.

About

A static site generator in Rust that converts Markdown files to HTML.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages