ssg

package module
v0.0.0-...-25a4204 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 4, 2023 License: Apache-2.0 Imports: 23 Imported by: 0

README

Static Site Generator

This project is a Static Site Generator. It is written in Go, and makes heavy use of Go's templates; you will need to be familiar with these to be able to use this project. They are a little weird in places.

Install the ssg command by running:

$ go install wellquite.org/ssg/cmd/ssg@latest

The command ssg has the following flags:

  • -in The directory to use as input. Always required.
  • -out The directory to use for output. Mutually exclusive with -serve.
  • -serve Takes an optional port number. 1313 is the default. If this flag is given, then ssg runs as a webserver and rebuilds the site everytime it detects a change within the input. Mutually exclusive with -out.
  • -log Specify the log level. Default is info. debug and trace provide more verbosity. warn and error provide less verbosity.

The go doc shows all the fields that are available to the templates.

Semantics

  • Every file found (recursively) within the input directory will have a corresponding file in the output directory, unless it has parseable meta-data section and that meta-data section sets output = false.
  • For a file to have parseable meta-data, it must be the case that valid TOML exists at the start of the file, demarkated by a line with --- and nothing more, before and after the TOML.
  • If the input file does not have a parseable meta-data section then the file is copied verbatim to the output directory.
  • If the input file has a .md extension then it is required the content of the file is markdown (after the meta-data section).
  • If the input file is a markdown file, and has path foo/bar/baz.md then its output path will be foo/bar/baz/index.html. All other files have their output path equal to their input path, relative to the input directory.
  • If an input file has parseable meta-data and that meta-data specifies a non-zero date field, then the input file is considered a Post and a Page. Otherwise, it is considered a Page only. This affects which fields the file appears in within the .Global template field.
  • The inner template is the content of the input file after the meta-data section. If the page has no parseable meta-data then it is not considered to have an inner template (or outer template).
  • The outer template is indicated by the field template in the meta-data.
  • All files with parseable meta-data are loaded as templates, and the template name is the file's path, relative to the input directory.
  • If an input file has parseable meta-data and that meta-data does not specify a summary field and the input file is a markdown file, then the summary is automatically determined from the page content (after the inner template has been run but before the conversion to HTML), by taking the plain text from the start of the content to the end of a sentence that finishes after at least 70 words from the start.

Order of operations

  1. All files in the input directory are loaded and parsed before any templates are run. Thus all templates have access to the meta-data of all input files.
  2. Once all files have been loaded, the inner templates for all markdown files which have output = true are run. The result of running these inner templates must be valid markdown. The markdown is then converted to HTML, and is stored in the ContentInner field.
  3. Next, all files have their inner templates run if they've not been already run, and then their outer template if it has been specified. The result of running the outer templates is stored in the ContentOuter field. If no outer template is specified then the ContentOuter field will have the same value as the ContentInner field.
  4. After that, all tag-templates are run.
  5. Finally, write everything to the output directory.

Examples

Example 1

This example, if it exists as a file within the input directory, would have a corresponding file in the output directory, which would contain the result of transforming this input file from Markdown to HTML.

example.md

Example 2

This example shows how to generate an RSS feed for all the Posts. You might put this example in a file rss.xml:

---
output = true
---
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>{{ html .Site.Title }}</title>
    <link>{{ .Site.BaseURL }}</link>
    <description>Recent content on {{ html .Site.Title }}</description>
    <generator>{{ html .Generator }}</generator>
    <language>en-us</language>
    <copyright>Copyright © {{ .Now.Year }}, {{ .Site.Author }}</copyright>
    <lastBuildDate>{{ .Now.Format .RFC1123Z }}</lastBuildDate>
    <image>
      <url>{{- .Site.BaseURL -}}logo.png</url>
      <title>{{ html .Site.Title }}</title>
      <link>{{ .Site.BaseURL }}</link>
    </image>

    <atom:link href="{{ .Page.AbsoluteURL }}" rel="self" type="application/rss+xml" />

    {{- $dot := . -}}
    {{- range $post := .Global.Posts }}
    <item>
      <title>{{ html $post.Meta.Title }}</title>
      <link>{{ $post.AbsoluteURL }}</link>
      <pubDate>{{ $post.Meta.Date.Format $dot.RFC1123Z }}</pubDate>
      <guid>{{ $post.AbsoluteURL }}</guid>
      <description>
        {{ $post.ContentInner | printf "%s" | html }}
      </description>
    </item>
    {{- end }}
  </channel>
</rss>
Example 3

The inner template can call other templates. For example, this file could be at posts/series/onions/post3.md:

---
title = "Onions: the revenge"
date = 2021-12-09T11:01:09Z
tags = ['onions']
output = true
template = "templates/post.html"
'''
---

In this series:

{{ template "templates/list-pages.md" (index .Global.PostsByTag "onions").OldestFirst }}

For many people, onions make them cry...

It specifies a outer template that should be found at templates/post.html (which presumably is responsible for turning the HTML-from-markdown into a fully valid HTML page); and in the inner template, it calls templates/list-pages.md. That file could look like this:

---
output = false
---

{{ range $page := . }}
* [{{html $page.Meta.Title}}]({{$page.AbsoluteURL}})
{{- end }}

It sets output = false because this file is only useful as a template to be used by others, so it should not have a corresponding output file for itself. This template generates markdown (which is hinted at by the fact it has a .md extension). So, the inner template of post3.md is run (which calls templates/list-pages.md), and the result of that is assumed to be valid markdown and then converted to HTML. The result of all of that is passed to the outer template (templates/post.html), and the result of that will be stored in the output directory at posts/series/onions/post3/index.html.

Documentation

Overview

SSG is a Static Site Generator.

It uses Go's templates, so you need to be familiar and comfortable with text/templates from Go's stdlib. It can cope with input files of any type, parsing and running them as templates. If the input file happens to be markdown (i.e. has a .md extension) then ssg will run the input file as a template, and then attempt to convert the result from markdown to HTML. It uses https://github.com/gomarkdown/markdown to do this (and https://github.com/alecthomas/chroma for syntax highlighting of code blocks).

Pages can, in their meta data, specify an "outer" template name. If given, the result of running the page as a template (and converting from markdown to HTML if necessary) is then passed to the named outer template. This means you can use a common outer template to add headers, footers, structure etc to pages.

It is trivial to create your own templates and template snippets: just make sure you set `output = false` in the meta data of such files, and there will be no attempt made to convert such files to output; instead they will solely be made available for use by other templates.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Page

type Page struct {

	// The absolute URL (i.e. prefixed by the Site config's BaseURL)
	// where this page will be available.
	AbsoluteURL string

	// If this page is a Post (i.e. it has a non-zero Date field in its
	// meta data), then PreviousByDate and NextByDate will link this
	// Post to the previous and next Posts, if they exist.
	PreviousByDate *Page
	NextByDate     *Page

	// The Time at which the input file which defines this page was
	// last modified. For a Post, this is distinct from the Date field
	// in a Post's meta data. This field is used to ensure the
	// corresponding output file (if there is one), has the same
	// LastModified time (as a file attribute).
	LastModified time.Time

	// The meta data for this page.
	Meta PageMeta

	// If this page's .Meta.Output is true, then the result of running
	// the "inner template" will be available here, otherwise this
	// field will be nil. For input files which are markdown
	// (i.e. extension is .md), the result of the inner template is
	// converted to HTML which is then stored in this field.
	ContentInner []byte
	// If this page has non-empty .ContentInner, and this page's
	// .Meta.Template field is non empty (which defines the "outer
	// template", then the result of running the outer template is
	// stored in this field. The outer template is provided with this
	// page as its .Page variable.
	//
	// If this page's .Meta.Template is empty, then .ContentOuter will
	// be the same as its .ContentInner field.
	//
	// This content is written to the output file.
	ContentOuter []byte
	// contains filtered or unexported fields
}

When a page is run as a template, the .Page variable will be set to a value of type Page.

func NewPage

func NewPage(log zerolog.Logger, ssg *SSG, inputPath string) *Page

func (*Page) HasContent

func (self *Page) HasContent() bool

Test to see if this Page's .Meta.Output field is true, and if there was some content found within the page.

func (*Page) HasDate

func (self *Page) HasDate() bool

Test to see if the Page's .Meta.Date field is zero. If it is not zero, then this Page is considered a Post.

func (*Page) IsMarkdown

func (self *Page) IsMarkdown() bool

func (*Page) SetLastModified

func (self *Page) SetLastModified(then time.Time) string

For use by templates, especially index and tag indices, to make the modified timestamp of their output file match the most recent post they're including.

type PageMeta

type PageMeta struct {
	Title string
	Date  time.Time
	Tags  []string
	// If you provide this field, then this page will first be run as
	// its own template, and then the result of that will be passed to
	// the named outer template. So you can use the outer template to
	// create HTML, by adding headers and footers etc.
	Template string
	// If this page is just a template to be used by other pages
	// (either as an outer template, or as some template snipped that
	// generates a bit of content as part of running the other page's
	// inner template), then make sure this field is false. Pages which
	// have a false Output will never generate their own output file.
	Output bool
	// A plain-text summary, or blurb, of the Page's content. This is
	// very handy for building lists of blurbs, index pages etc.
	Summary string
	// If true, and if the input Page is markdown, then the
	// mardown-to-HTML conversion will create a Table Of Contents and
	// add that at the start of the Page.
	TableOfContents bool
}

These fields are all loaded from the Page's input file's meta data, which is parsed as TOML. For example:

mypost.md:

---
title = "my first post"
date = 2021-12-16T11:01:09Z
summary = '''a brief plain-text summary of the content of this post'''
---

## Introduction

The actual post content goes in here...

type Pages

type Pages []*Page

In several fields available in templates, you'll get values of type Pages. There have a couple of useful methods for ordering their content: YoungestFirst and OldestFirst, which help when creating indices of pages, or linked to related pages. Note the sorting is done by the Page.Meta.Date field, and not the Page.LastModified field.

func (Pages) MostRecentModified

func (self Pages) MostRecentModified() time.Time

func (Pages) OldestFirst

func (self Pages) OldestFirst() Pages

Creates and returns a new slice. Original slice is unaltered.

func (Pages) YoungestFirst

func (self Pages) YoungestFirst() Pages

Creates and returns a new slice. Original slice is unaltered.

type SSG

type SSG struct {

	// Internal: you won't want to access this from templates. Use the
	// TemplateDataObject.Site field instead.
	SiteConfigFile SiteConfigFile
	// All the pages that have been found in the input directory. This
	// will include pages for which Page.Meta.Output is false, and
	// pages for which it was not possible to parse (e.g. image
	// files). It will not include any pages that match
	// SiteMeta.IgnoreRegExp.
	Pages Pages
	// Pages that have Page.Meta.Output = true, and have a non-zero Page.Meta.Date
	Posts Pages
	// The above Pages field, but grouped by tag.
	PostsByTag map[string]Pages
	// Pages, rather than posts. So this allows access to images, for
	// example, and thus to their expected AbsoluteURL. E.g.:
	//
	//     In [last week's]({{ (index .Global.PagesByInputPath "posts/foo/bar.md").AbsoluteURL }}) post...
	PagesByInputPath map[string]*Page
	// Internal: not for use by templates: at the point at which
	// templates are run, this field will be empty.
	PagesByOutputPath map[string]*Page
	// contains filtered or unexported fields
}

func NewSSG

func NewSSG(log zerolog.Logger, inputDir string, outputDir string) (*SSG, error)

func (*SSG) FindPagesAndRunTemplates

func (self *SSG) FindPagesAndRunTemplates() error

func (*SSG) NewTagPage

func (self *SSG) NewTagPage(tag string, template string) *tagPage

func (*SSG) WriteOutput

func (self *SSG) WriteOutput() error

type SiteConfigFile

type SiteConfigFile struct {
	Meta SiteMeta
	// contains filtered or unexported fields
}

type SiteMeta

type SiteMeta struct {
	Title       string
	Author      string
	Description string
	BaseURL     string
	// This is very handy to get ssg to ignore backup files, dot files
	// etc. A useful value for this field might be:
	//
	//     ignoreRegExp = ”'(^#.+#$)|(~$)|(\.bak$)|((^|/)\.[^\./]+)|(\.nix$)”'
	IgnoreRegExp string
	// Used for formatting code blocks in markdown. ssg uses Chroma
	// (https://github.com/alecthomas/chroma) so you want to have a
	// look at its style gallery at
	// https://xyproto.github.io/splash/docs/
	CodeStyle string
	// ssg will run these named templates for each tag that is
	// encountered in Posts. So these templates can be used to generate
	// indices of pages grouped by tag.
	TemplatesForTags []string
}

These fields are extracted from config.toml, which must exist at the root of your site.

type TagTemplateDataObject

type TagTemplateDataObject struct {
	// The Posts that have this tag set.
	Posts Pages
	// The normal set of data available to the template. However, there
	// is no specific input page, so the meta data is faked, to provide
	// the tag name in .Page.Meta.Title. The .Page.AbsoluteURL field is
	// correctly set, and the .Page.LastModified field will be the most
	// recent last modified field of all the Posts in this tag.
	TemplateDataObject
}

For tag templates (i.e. templates named in the site's meta data TemplatesForTags field), a value of this type will be provided to the template as its . variable.

type TemplateDataObject

type TemplateDataObject struct {
	// The page that is being processed. For a page with an outer
	// template specified, the outer template will still find the inner
	// page here. If you accidentally set output=true on the outer
	// template, then the outer template will *also* be run, only this
	// time with .Page set to itself.
	Page *Page
	// Access to the site's meta data.
	Site *SiteMeta
	// Access to all the other pages, and posts and tags and stuff.
	Global *SSG
	// Useful constants.
	*TimeConstants
}

For non-tag templates, a value of this type will be provided to the template as its . variable.

type TimeConstants

type TimeConstants struct {
	RFC850   string
	RFC3339  string
	RFC1123Z string
	// Monday, 2 January 2006
	HumanDateOnly string
	Generator     string
	Now           time.Time
}

These fields are available to all templates, directly accessible off the . variable.

Directories

Path Synopsis
cmd
ssg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL