This generator was built for blogs or newsletter websites consisting of a series of dated posts that follow a common style.
It will render posts written in GitHub-Flavored Markdown and Go template files, and it will copy any other file type.
Posts may be translated, and the translations for each post will be interlinked.
It will use the information from the posts' headers to build time-based and tag-based tables of contents. The tables of contents will appear in every language that is present in the website and can be configured to display only articles written (or translated) in that language or articles written in any available language.
RSS feeds will also be generated.
This generator can also schedule emails to be sent. You will need an external system that will take care of managing subscriptions and sending the emails. So far, only Mailerlite is supported.
There is an experimental, optional commenting system. It requires you to run a server and stores its data in a SQL database. It will be documented at a later date, when its design is more stable.
Note: I built this for my personal site, jacobo.tarrio.org and my newsletters, A Folla and Coding Sheet. Therefore, it fits my needs even though it is quite limited in many areas. If you are looking for a website generator for yourself, please consider Hugo and Lume.
The site generator is configured through a file, though you can override certain values from the command line.
You must specify the name of the configuration file with the --config_file
command-line option.
The configuration file is YAML with the following format:
# Source file configuration.
files:
# Location of the site content files.
content: "src/content"
# Location of the templates for rendering Markdown files.
templates: "src/templates"
# Site configuration.
site:
# Base for all internal links.
# May be overridden through the --webroot flag.
webroot: "http://example.com/"
# Name of the site.
name: "My example site"
# URI of the site. Optional. If omitted, defaults to the value of `webroot`
uri: "https://example.com"
# Language-dependent variants of the website. Optional. One per language.
by_language:
# Spanish.
es:
webroot: "http://example.com/es/"
name: "Sitio de ejemplo"
uri: "http://example.com/es"
# Galician
gl:
name: "Sitio de exemplo"
# Unspecified values are copied from the default (even `uri`, so beware).
# Author configuration.
author:
# The site's author's name.
name: "John Doe"
# The site's author's webpage. Optional. If omitted, defaults to `site.uri`.
uri: "https://example.com/johndoe"
# Website generator configuration. Only required to run the generator.
generator:
# Path where the website will be written to.
# May be overridden through the --output_path flag.
output: "rendered"
# If true, tables of contents in each language will only show content in
# that language. If false, tables of contents will show content in other
# languages if it is not available in the same language. Default: false.
hide_untranslated: false
# If true, the generator does not run by default, but it can be enabled
# through the --operations flag. Default: false.
skip_operation: false
# Mail configurations
mailers:
# A name for this configuration.
- name: "galego"
# If true, this mail configuration will not run by default, but it can
# be enabled through the --operations flag. You can use this for your
# test configurations. Default: false.
skip_operation: false
# Send posts available in this language.
language: "gl"
# The subject line will contain this prefix followed by
# the episode number and the page's title. Optional.
subject_prefix: "O meu boletín"
# Mailerlite configuration.
mailerlite:
# The name of the secret file containing the API key.
apikey_secret: "mailerlite-key"
# The group to send email to.
group: 12345
# Date filters
date_filters:
# Sets a maximum date for the posts that will be generated.
# By default, the maximum date is the current date and time.
# You would normally not specify it in your configuration,
# and only override it for testing.
generate:
# A hard cutoff date. Defaults to the current date.
# May be overridden with the --generate_not_after flag.
not_after: "2023-08-01T00:00:00Z"
# A rolling cutoff date, in days after today. May be negative.
# Only used if not_before is unspecified.
not_after_days: 14
# Sets a minimum and maximum date for the posts that will be emailed.
# By default, the minimum date is the current date and time, and there
# is no maximum date.
mail:
# Do not send posts before this date. Defaults to the current date.
# May be overridden with the --mail_not_before flag.
not_before: "2023-08-01T00:00:00Z"
# Do not send posts after this date. Defaults to the far future.
# May be overridden with the --mail_not_after flag.
not_after: "2023-08-01T00:00:00Z"
# Do not send posts this many days after today. Optional.
# Useful to only schedule posts for a few days at a time even if you
# write content many months in advance.
not_after_days: 14
If you use secrets in your configuration (such as in the apikey_secret
field),
you must specify a secrets directory using the --secrets_dir
command-line
flag.
This directory must contain one file for each secret, with the same name that is used in the configuration file, and the content of this file is the secret itself.
Be careful with line endings when you create the secret files! For example,
this command creates a file named mailerlite-key
with an API key:
$ echo -n 'THE_MAILERLITE_API_KEY' > secrets/mailerlite-key
The -n
flag to echo
prevents a newline at the end of the API key.
The following command-line flags are available.
-
--config_file
-- The name of the file that contains the configuration. -
--secrets_dir
-- The name of the directory containing secrets files. -
--operations
-- The names of the operations that must be performed, or the word "list".This is a list of items to be added, separated using commas. If an item is prepended with a minus sign (
-
), it is removed instead of added. You may use wildcards to match several operations at once for adding or for removing.Some operations appear as "skipped" in the list, and by default they are not added. You can add them by specifying them with their exact name (no wildcards). You can remove them by exact name or by wildcard, though.
The default value is
*
, which adds all (non-skipped) operations.Examples:
--operations=*,-mail=
-- adds all operations and then removes all the "mail" operations.--operations=*,mail=test
-- adds all operations and also the (possibly skipped) operationmail=test
.--operations=
-- no operations (empty list).--operations=mail=*,-mail=foo
-- adds all "mail" operations exceptmail=foo
.
-
--webroot
-- Override thesite.webroot
configuration for every language. -
--generate_not_after
-- Override thedate_filters.generate.not_after
configuration. -
--mail_not_before
-- Override thedate_filters.mail.not_before
configuration. -
--mail_not_after
-- Override thedate_filters.mail.not_after
configuration. -
--dry_run
-- Simulate the file generation and email scheduling operations, printing out what would happen.
In general, the site generator will copy any files found in files.content
under the corresponding path in the generator.output
. The exceptions are
Markdown files, Go template files, and .htaccess
files.
Markdown files have an .md
extension. Their syntax is GitHub-Flavored
Markdown, with a few extensions.
The name of a Markdown file is its path relative to files.content
, minus
its .md
extension. The output file name its formed by adding the .html
extension to the Markdown file's name.
Markdown files are rendered through the template files found in
files.templates
. There must be a set of page-LANG.tmpl
and toc-LANG.tmpl
files for each language that you have pages in.
There must be a comment block including the header data. This comment block
starts with the <!--HEADER
pseudo-tag and contains a YAML block.
<!--HEADER
# The page's title
title: "A nice article"
# A short summary of the page, shown in the table of contents.
summary: "A brief discussion of beautiful and calming things."
# (Optional) An episode number, for serial publications.
episode: "43"
# The language the page is written in. The default is `en` (English).
language: "es"
# The publication date for the page. Used to sort the table of contents.
publish_date: "2020-04-01 01:23"
# (Optional) If true, do not show the publication date. Default: false..
no_publish_date: true
# (Optional) The name of the page's author, to override the site-wide setting.
author_name: "John Doe"
# (Optional) The URI of the page's author's website, to override the site-wide setting.
author_uri: "http://example.com/johndoe"
# (Optional) If true, do not show the author's name in the page. Default: false.
hide_author: true
# (Optional) If true, do not add this page in tables of contents. Default: false.
no_index: true
# (Optional) The image to display in social media cards for links to this post.
# The first image in the article will be added if unspecified. Leave empty for "none".
cover_image: image.jpg
# (Optional) The name of the page this is a translation of.
translation_of: "textos/bonito-articulo"
# (Optional) A list of tags for this page. This page will appear in those tag's tables of contents.
tags:
- "Tag 1"
- "Another tag"
- "And yet another one"
# (Optional) A list of old URIs, relative to the site's webroot, where this article used to be.
# If provided, the old URIs will redirect to this page.
old_uris:
- "texts/a-nice-article.html"
- "a-beautiful-post.html"
- "posts/id/1234"
# (Optional) If true, this page will not be indexed and a "draft" marker will appear. Default: false.
draft: true
-->
When several images are specified one right after the other in the same
paragraph, they are grouped in a <span class="multipleImgs">
element.
When there is content in the same paragraph after an image or after a group
of images, this content is wrapped in a <span class="imageCaption">
element.
You can add YouTube videos using the !youtube(URI)
syntax.
Markdown files are rendered to HTML and written using the template files for
the page's language. The template files are found in the template_path
and
have the names page-LANG.tmpl
and toc-LANG.tmpl
, where LANG
is the ISO
code specified in the page header's language
field.
This template is used to render the page itself. It is rendered from a
templates.PageData
structure that contains the following fields:
Title
--- the page's title.Permalink
--- the page's permalink.Author
--- atemplates.LinkData
structure containing the author's name and website URI.Summary
--- the page's one-line summary.Episode
--- the episode number or name.PublishDate
--- the page's publish date, or zero if it's been unspecified or hidden.Tags
--- an array of tag names.Content
--- the page's rendered content in HTML.NewerPage
--- atemplates.LinkData
structure that points to the next newer page by publish date.OlderPage
--- atemplates.LinkData
structure that points to the next older page by publish date.Translations
--- an array oftemplates.TranslationData
structures pointing to other translations of this page.Draft
--- a boolean indicating whether this page is a draft.
templates.LinkData
structures contain a Name
field and a URI
field.
templates.TranslationData
structures contain a Language
field,
a Name
field and a URI
field.
This template is used to render a table of contents for a particular year.
This table of contents could be general or for a particular tag. It is
rendered from a templates.TocData
structure that contains the following
fields:
Tag
--- the current tag, if any.TotalCount
--- the total number of indexed stories.Stories
--- an array oftemplates.PageData
structures with the story data for the current page.
Several functions are available in these three templates:
formatDate
--- takes atime.Time
structure and formats it according to the current language.getTagURI
--- takes a tag name and returns the URI of its table of contents file.getTocURI
--- returns the URI of the general table of contents file.getURI
--- takes a relative URI and makes it absolute to the webroot.language
--- returns the current language's ISO code.plural
--- takes a number, a singular form and a plural form, and returns either the singular or plural form depending on whether the number is 1 or not.site
--- returns atemplate.LinkData
structure containing the current site's name and URI.webRoot
--- returns the webroot URI.
Template files have a .tmpl
extension that will be stripped off from
the output file's name. They are treated as Go HTML template files, as
defined in the Go html/template
package.
Template files receive a site.Contents
structure. The exact contents
of the structure are subject to change, so you should use the source
code to know what's in it and how to use it.
The templates can also use the following functions:
latestPage
--- receives a language's ISO code and returns apage.Page
structure for the latest Markdown page in that language.webRoot
--- returns the webroot URI.
These files are mostly copied as is, but you can insert RewriteRule
directives by inserting the following line:
### REDIRECTS ###
Where this line appears, it will be replaced with a series of
RewriteRule
statements that redirect each page's old_uri
URIs to
its current location.
Don't forget to enable RewriteEngine on
in your .htaccess
file or
your virtual host configuration!
Copyright 2020 Jacobo Tarrío. Distributed under the terms of the Apache License version 2.0.