Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

org-grimoire — An Emacs-native Static Site Generator

https://github.com/spiperac/org-grimoire/actions/workflows/melpazoid.yml/badge.svg https://stable.melpa.org/packages/org-grimoire-badge.svg https://melpa.org/packages/org-grimoire-badge.svg

docs/logo.png

An Emacs static site generator. Write your content in org-mode, configure everything in your Emacs config, build with a single command.

Just Emacs, org-mode, and HTML templates!

Installation

MELPA

M-x package-install RET org-grimoire RET

Or with use package:

(use-package org-grimoire
  :ensure t)

Manual

Clone the repository and add it to your load path:

(add-to-list 'load-path "~/path/to/org-grimoire/")
(require 'org-grimoire)

Optional dependencies

simple-httpd is not required to build a site, but if it is installed M-x org-grimoire-serve will serve your built site at http://localhost:8080 for a quick local preview.

Configuration

Add to your Emacs config:

(org-grimoire-setup "my-blog"
  :base-dir    "~/blog"
  :base-url    "https://yoursite.com"
  :site-title  "My Blog"
  :description "A blog about things"
  :theme       "mytheme"
  :per-page    10)

org-grimoire derives all paths from :base-dir automatically:

KeyDerived path
:sourcebase-dir/content/
:outputbase-dir/public_html/
:staticbase-dir/static/
:themebase-dir/themes/mytheme/

You can override any of these individually by passing :source, :output, :static, or a full path to :theme explicitly.

All available setup keys:

KeyRequiredDescription
:base-diryesRoot directory of your site
:base-urlyesFull URL including scheme (e.g. https://example.com)
:site-titleyesGlobal site name, used in feeds and navigation
:descriptionnoSite description, used in feeds and meta tags
:authornoAuthor name, used in Atom feed
:themenoTheme name under base-dir/themes/
:per-pagenoPosts per index page, defaults to 10
:reading-timenoSet to t to enable reading time estimates
:index-exclude-tagsnoTag name(s) to keep off the index; string or list

Excluding tags from the index

Posts carrying any tag listed in :index-exclude-tags are left off the index and its pagination. They are still rendered, and still appear on tag pages, in the feeds and in the sitemap. This is useful when one category dominates your output and you would rather browse it at /tags/name.html:

(org-grimoire-setup "my-blog"
  :base-dir           "~/blog"
  :base-url           "https://yoursite.com"
  :site-title         "My Blog"
  :index-exclude-tags '("ctf"))

Matching is case-insensitive, and a bare string is accepted for a single tag: :index-exclude-tags "ctf".

For per-post control, use #+LISTED: nil instead.

Multiple sites

You can configure and build multiple sites independently:

(org-grimoire-setup "blog"
  :base-dir   "~/blog"
  :base-url   "https://blog.com"
  :site-title "My Blog"
  :theme      "minimal")

(org-grimoire-setup "company"
  :base-dir   "~/company-site"
  :base-url   "https://company.com"
  :site-title "My Company"
  :theme      "corporate")

(org-grimoire-build "blog")
(org-grimoire-build "company")

Directory structure

blog/
├── content/             # your org files
│   ├── post/            # content type inferred from directory name
│   │   ├── my-post.org
│   │   └── images/
│   │       └── screenshot.png
│   └── page/
│       └── about.org
├── themes/
│   └── mytheme/         # HTML templates
│       ├── base.html
│       ├── post.html
│       ├── page.html
│       ├── index.html
│       ├── tags.html
│       ├── static/      # theme static files (css, fonts, etc.)
│       └── partials/
│           ├── navbar.html
│           ├── footer.html
│           ├── post-item.html
│           ├── tag-item.html
│           ├── tag-index.html
│           └── pagination.html
├── static/              # your static files, copied as-is to output/static/
│   └── css/
│       └── style.css
└── public_html/         # generated output

Writing content

The content type is inferred from the directory the file lives in. A file in content/post/ is of type post and uses post.html template. A file in content/page/ is of type page and uses page.html, and so on. You can create any number of content types by adding subdirectories.

#+TITLE: My Post
#+DATE: 2024-01-15
#+TAGS: emacs lisp

Post content goes here. Full org-mode syntax supported.

Frontmatter keywords

KeywordDescriptionExample
#+TITLEPage title#+TITLE: My Post
#+DATEPublication date (yyyy-mm-dd)#+DATE: 2024-01-15
#+TAGSSpace or comma separated list of tags#+TAGS: emacs lisp
#+DRAFTSet to t to exclude from build#+DRAFT: t
#+LISTEDSet to nil to hide from index#+LISTED: nil

Draft vs Listed

  • #+DRAFT: t — post is not built at all, completely excluded
  • #+LISTED: nil — post is built and accessible via its URL and tag pages, but does not appear on the main index listing. Defaults to t if omitted.

Images and file links

org-grimoire automatically detects file: links in your org files and copies referenced files to the output directory, preserving relative paths. This means org-download works out of the box:

[[file:images/screenshot.png]]

Gets copied from content/post/images/screenshot.png to public_html/post/images/screenshot.png and the relative link works in the browser with no extra configuration.

Templates

Theme resolution

:theme is expanded against your :base-dir, so :theme "mytheme" means base-dir/themes/mytheme/. Pass a full path to point somewhere else.

Template lookup falls back per file: each template is looked for in your theme first, and anything missing is taken from the built-in default theme that ships with the package. A theme can therefore override just post.html and inherit everything else.

Static files do not merge. Your theme’s static/ directory is copied as-is, so a theme that ships one is responsible for all of it. The default theme’s static files are used only when the theme directory itself is missing, which is what makes a site with no theme of its own come out styled.

M-x org-grimoire-init writes an editable copy of the default theme to base-dir/themes/default/ to start from.

Base template

base.html is the site-wide layout. All pages are wrapped in it.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{{title}}</title>
  <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
  {{include partials/navbar.html}}
  {{content}}
  {{include partials/footer.html}}
</body>
</html>

Available placeholders in base.html:

PlaceholderDescription
{{title}}Current page title
{{site-title}}Global site title from :site-title
{{description}}=| Site description from =:description
{{author}}Author from :author
{{base-url}}Base URL from :base-url
{{url}}Root-relative URL of the current page
{{content}}Rendered page content

Type templates

Each content type maps to its own template file. A file in content/post/ uses post.html, a file in content/page/ uses page.html, and so on.

post.html:

<article>
  <h1>{{title}}</h1>
  <time>{{date}}</time>
  {{tags}}
  <span>{{reading-time}}</span>
  <div class="content">{{content}}</div>
</article>

Available placeholders in type templates:

PlaceholderDescription
{{title}}Post title
{{content}}Rendered HTML content
{{date}}Publication date
{{tags}}Rendered tag links (grimoire-tags div)
{{slug}}URL slug derived from filename
{{reading-time}}Estimated reading time (e.g. “3 min read”), requires :reading-time t in setup

Index template

index.html is used for the paginated post listing at the site root.

<h1>{{site-title}}</h1>
<p>{{description}}</p>
<ul>{{posts}}</ul>
{{pagination}}
PlaceholderDescription
{{site-title}}Global site title
={{description}}=Site description
{{posts}}Generated list of post items
{{pagination}}Previous/next page navigation

Tags templates

tags.html is the tags index at /tags/index.html, listing all tags:

<h1>{{title}}</h1>
<ul>{{tags}}</ul>

partials/tag-index.html is used for individual tag listing pages at /tags/tag-name.html:

<h1>{{title}}</h1>
<ul>{{posts}}</ul>

partials/tag-item.html renders a single tag in the tags index:

PlaceholderDescription
{{name}}Tag name
{{slug}}URL-safe tag slug
{{count}}Number of posts with tag

Includes

Any template can include another file using {{include}}:

{{include partials/navbar.html}}
{{include partials/footer.html}}

Files are resolved relative to your theme directory, with fallback to the built-in default theme.

Commands

CommandDescription
org-grimoire-initInitialize a new site with boilerplate structure
org-grimoire-setupRegister a site configuration
org-grimoire-buildBuild the site
org-grimoire-newCreate a new post interactively
org-grimoire-serveServe the site locally (requires simple-httpd)

Building

Run M-x org-grimoire-build and enter the site name when prompted, or call it directly:

(org-grimoire-build "my-blog")

Output goes to base-dir/public_html/. Build log is written to the *Messages* buffer. Errors in individual files are logged as warnings and the build continues, with a summary printed at the end.

Starting a new site

Run M-x org-grimoire-init to create a new site with a boilerplate directory structure, sample post and page, and a config snippet to add to your Emacs config.

Creating new content

Run M-x org-grimoire-new and enter the site name. You will be prompted to pick a content type from existing directories, then enter a title and tags. The file is created and opened automatically as a draft.

Local preview

If you have simple-httpd installed, run M-x org-grimoire-serve to serve your built site at http://localhost:8080. simple-httpd is not a required dependency.

Feeds

RSS and Atom feeds are automatically generated at /rss.xml and /atom.xml from all published posts.

A sitemap is generated at /sitemap.xml.

License

org-grimoire is free software, released under the GNU General Public License version 3 or later. See the LICENSE file for details.

About

Static Site Generator writen in Elisp, running from GNU/Emacs.

Topics

Resources

Stars

30 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages