kiln(1)
π NAME π
okay so bestie, meet kiln(1) from the General Commands Manual β and what is it? it's literally "a simple static site generator". no cap, that's the whole pitch. π
β¨ SYNOPSIS β¨
kiln build [-c config]
kiln new path
πΈ DESCRIPTION πΈ
kiln buildβ builds a kiln site. iconic behavior.kiln newβ creates a new kiln site at the given path. cute.
π OPTIONS π
π οΈ kiln build
-c configβ specifies the configuration file to use. defaults to"config.toml". that's it, that's the one flag, she's lowkey efficient π
π¦ OVERVIEW π¦
so a kiln site gets built in one or more steps, and those steps are called tasks. tasks read content from the content directory, process the content, and write the content to the output directory. tasks can also be configured to copy static content to the output directory. multitasking queen π
the following directories are common to all tasks:
content/β content directorytemplates/β templates directory
the basic unit of a kiln site is the page. each page either represents a content file or a subdirectory containing other pages. pages may be preprocessed, run through templates, and postprocessed β in that order, bestie, the order matters fr fr. each operation takes the output of the last operation as input. it's giving pipeline. π₯
π CONTENT DIRECTORY π
the content directory contains site content files, which can be nested in subdirectories. any file or directory in the content directory whose name begins with _ will be ignored β with the exception of files with the name _index (e.g. _index.gmi). so like, _ is the "don't look at me" prefix, except for _index, who is the main character. π
content files can specify dates in their filenames. for example, the file content/2020-11-20-Hello-world.gmi will result in a page with a path of /Hello-world/ and a date of November 20, 2020. date in the filename is such a flex honestly.
files with the name _index are treated specially. they can be used to provide frontmatter and content for the parent directory, which will otherwise have none. if an _index file is present in a directory, an index page (e.g. "index.gmi") for that directory will be generated and written to the output directory, periodt.
π FRONTMATTER
pages can specify additional metadata in frontmatter. frontmatter is delimited by --- and is specified in YAML. newlines after the closing delimiter are removed from the content.
example:
---
title: Page title
date: 2021-04-24
params:
key: value
---
Page content
the following keys are supported π
titleβ the title of the page.
example:
---
title: My first post
---
dateβ the date of the page. pages are sorted by date in reverse order, so newer pages will be placed above older pages. newest gets the aux cord π§
example:
---
date: 2021-05-21
---
weightβ the weight of the page. pages are sorted by weight in increasing order, so pages with a smaller weight will be placed above pages with a larger weight. smaller number = higher up, don't ask, it's just the vibe.
example:
---
weight: 1
---
outputsβ optionally specifies a list of tasks that can build this page. defaults to all tasks.
example:
---
outputs: ["Gemini", "HTTPS"]
---
---
outputs: [] # Excluded from all tasks
---
templateβ optionally specifies the name of the template to use when building this page. if unspecified, defaults to"page"for regular pages and"index"for index pages. the template is then found according to TEMPLATE RESOLUTION.
example:
---
title: About me
template: about
---
paramsβ specifies extra parameters to be provided to templates. lil care package for your templates π
example:
---
params:
key: value
---
π SORTING
pages are sorted automatically, bestie. pages are first ordered by weight in increasing order, then by date from newest to oldest, and then by filename in alphabetical order. three-tier sorting, absolutely ate. β¨
π TEMPLATES DIRECTORY π
the templates directory contains templates for use when building the site. templates use the Go templating language. the following templates are supported:
page.extβ page templateindex.extβ directory index templatebase.extβ base template from which the page and index templates inheritatom.xmlβ Atom feed template
the extension of page and index templates is configurable and will replace ".ext" above. see CONFIGURATION. (yes, it's a choose-your-own-extension situation π
)
for more information on the Go templating language, see https://golang.org/pkg/text/template/.
π«Ά PAGE AND INDEX TEMPLATES
the content for page and index templates can be accessed using the .Content page variable. for example:
page header
{{ .Content }}
page footer
other page variables are documented in PAGE VARIABLES.
in HTML templates, page content is escaped by default. if the content is known to be safe, it must be marked as safe to avoid escaping. for example:
<body>
{{ .Content | safeHTML }}
</body>
see TEMPLATE FUNCTIONS for more information.
π BASE TEMPLATES
base templates are inherited only by page and index templates. base templates generally define at least one block which can optionally be customized by page and index templates, according to the Go templating language.
for example, the base template could contain:
{{ block "body" . }}
Blocks can have default content
{{ end }}
{{ block "extra_content" . }}{{ end }}
the page and index templates can then optionally customize these blocks, for example:
{{ define "body" }}
Body goes here
{{ end }}
π§ TEMPLATE RESOLUTION
the scope of a template is limited by the directory it's placed in. for example, a page template in the templates/blog/ directory will only apply to files in content/blog/. a page template placed in templates/ will only apply to files in content/ and not its subdirectories. it's giving "know your lane" energy.
fallback templates can be specified in the templates/_default/ directory. these templates will apply only when the required kind of template is not found in the template directory. backup bestie behavior π«Ά
the template for a specific page can be overridden by setting the template key in the page's frontmatter. see FRONTMATTER for more details.
for example, the page file content/blog/my_first_post.gmi will be rendered with the template templates/blog/page.ext. if that template is not found, it falls back to templates/_default/page.ext. if that template is also not found, then no template will be used. π
base templates also follow the same rules. for example, the index template templates/blog/index.ext inherits firstly from templates/blog/base.ext, and then falls back to templates/_default/base.ext if present.
there is no override mechanism for base templates. that's the rule, no exceptions, sorry bestie π
π PARTIAL TEMPLATES
partial templates can be placed in the templates/_partials directory. partial templates can be executed from any other template using the partial function. for example, a template could contain:
{{ partial "navbar.ext" . }}
then templates/_partials/navbar.ext is executed. since argument . is provided, all data from the current context is provided. see TEMPLATE FUNCTIONS for more information. passing the dot is so important, don't forget the dot π₯
in HTML templates, the partial template content is escaped by default. if the content is known to be safe, it must be marked as safe to avoid escaping. for example:
<body>
{{ partial "navbar.ext" . | safeHTML }}
</body>
see TEMPLATE FUNCTIONS for more information.
βοΈ CONFIGURATION βοΈ
okay so by default kiln goes looking for a config file named config.toml. if you want a different one, just say so with the -c flag π
(check OPTIONS, bestie).
the config file is in TOML format, no cap.
here are the keys it supports:
titleβ the site title πparamsβ extra parameters made available to templates β¨
π§ TASKS π§
tasks get specified in the [[tasks]] array of tables. yes, plural energy, you can have as many as you want.
these config options are supported per task:
nameβ an optional name for the task. like:
[[tasks]]
name = "Gemini"
[[tasks]]
name = "HTML export"
inputβ a list of input file extensions. any file in the content directory with a matching extension gets processed. like:
[[tasks]]
input = [".gmi", ".md"]
outputβ the output file extension. files written to the output directory use this extension. like:
[[tasks]]
output = ".html"
templateβ the template file extension. templates with this file extension get used to format the content. if you leave it unset, no templates will be used. like:
[[tasks]]
template = ".gmi"
preprocessβ maps file extensions to preprocess commands. preprocess commands run before templating. the commands get the content of the page as standard input and should write the processed content to standard output. like:
[[tasks]]
input = [".gmi", ".md"]
output = ".html"
preprocess.gmi = "gmnitohtml"
preprocess.md = "mdtohtml"
postprocessβ specifies a command that runs after templating and before content gets written to the output directory. it gets the content of the page as standard input and should write the processed content to standard output. like:
[[tasks]]
input = [".gmi"]
output = ".html"
template = ".gmi"
postprocess = "gmnitohtml"
static_dirβ specifies a directory to read static content from. every file in this directory gets copied to the output directory without any modification. static assets like images belong here β¨. if you leave it unset, no static content directory will be used. like:
[[tasks]]
static_dir = "static"
output_dirβ specifies the directory where output files get written. like:
[[tasks]]
output_dir = "public"
urlβ the base URL to use for page URLs. important: the base URL should not have trailing forward slashes π.ugly_urlsβ specifies whether page paths contain file extensions. by default, clean paths with no extension at all are used π. like:
[[tasks]]
ugly_urls = true
okay now the fun part, example configs πΈ
this one builds a simple Gemini site:
[[tasks]]
input = [".gmi"]
output = ".gmi"
template = ".gmi"
output_dir = "public"
this one generates a Gemini text site and exports an HTML version of the site. it makes use of the gmnitohtml(1) command to convert Gemini text to HTML π
# Build the site
[[tasks]]
input = [".gmi"]
output = ".gmi"
template = ".gmi"
static_dir = "static"
output_dir = "public"
# Export an HTML version of the site
[[tasks]]
input = [".gmi"]
output = ".html"
template = ".gmi"
postprocess = "gmnitohtml"
static_dir = "static"
output_dir = "public_html"
and this one generates an HTML site from Markdown and Gemini text files in the content directory, plus HTML templates in the templates directory. it uses the mdtohtml(1) command to convert Markdown to HTML, and the gmnitohtml(1) command to convert Gemini text to HTML π¦
[[tasks]]
input = [".md", ".gmi"]
output = ".html"
template = ".html"
preprocess.md = "mdtohtml"
preprocess.gmi = "gmnitohtml"
static_dir = "static"
output_dir = "public"
π PERMALINKS π
permalinks let you rewrite page paths, which is lowkey iconic. you specify them in the [tasks.permalinks] table of the config file. the keys denote a path to a directory, and the values use the Go templating language to rewrite the final path of pages in that directory. those templates have the same data that page templates have available to them (see PAGE VARIABLES).
this config rewrites the paths of pages in the content/blog directory to /YYYY/MM/DD/slug. so for example, the file content/blog/2021-05-12-hello-world.gmi will have a path of /2021/05/12/hello-world/ β¨
[[tasks]]
# ...
[tasks.permalinks]
"/blog/" = "/{{ .Date.Format `2006/01/02` }}/{{ path.Base .Path }}"
for more info on templates, see TEMPLATES π«Ά
π‘ FEEDS π‘
feeds get specified in the [[tasks.feeds]] array of tables. you can have multiple feeds per task, go off π
example feed configuration:
[[tasks]]
# ...
# This generates a feed for the files in content/blog
# and writes it to blog/atom.xml (relative to the output directory)
[[tasks.feeds]]
input_dir = "blog"
title = "My Blog"
template = "atom.xml"
output = "blog/atom.xml"
# You can generate multiple feeds per task
# The generated feed can be written anywhere
# Here it is written to feed.xml (relative to the output directory)
[[tasks.feeds]]
input_dir = "blog"
title = "My Blog"
template = "custom_feed.xml"
output = "feed.xml"
input_dirβ the content folder with which to populate the feed πtitleβ the title of the feed, accessible via{{ .Title }}in the feed templatetemplateβ the template to use for the feedoutputβ the output path for the rendered feed
π TEMPLATES π
templates have certain data and functions available to them. this is the good stuff, bestie π
β SITE VARIABLES β
these site-wide variables are available:
.Titleβ the title of the site.Paramsβ extra parameters specified in configuration.Generatedβ site generation time.Rootβ the root page of the site. see PAGE VARIABLES
some of these variables are defined in your site's configuration. see CONFIGURATION.
site variables can be accessed from templates with the site function. see TEMPLATE FUNCTIONS β¨
π PAGE VARIABLES π
these page variables are available:
.Titleβ the title of the page.Dateβ the date of the page.Weightβ the weight of the page.Pathβ the path to the page.URLβ the URL of the page. if no base URL is configured, it's equivalent to.Path.FilePathβ the path of the page file or directory relative to the content directory.Contentβ the contents of the page.Paramsβ extra parameters specified in frontmatter.Prevβ the previous page in sorted order.Nextβ the next page in sorted order.Pagesβ list of pages in this directory.Dirsβ list of subdirectories in this directory
some of these variables are defined in page frontmatter. see FRONTMATTER.
page variables can be accessed from page and index templates π
π PAGE FUNCTIONS π
these page functions are available:
.GetPage pathβ retrieves the page in this directory with the given path, which may be relative or absolute.
example:
{{/* Retrieve a directory relative to the root directory
and iterate over its pages */}}
{{ with site.Root.GetPage "/blog" }}
{{ range .Pages }}
{{ .Title }}
{{ end }}
{{ end }}
{{/* Retrieve a directory relative to the current directory
and iterate over its pages */}}
{{ with .GetPage "posts" }}
{{ range .Pages }}
{{ .Title }}
{{ end }}
{{ end }}
{{/* Retrieve a page relative to the current directory */}}
{{ with .GetPage "posts/hello-world.gmi" }}
{{ .Title }}
{{ end }}
page functions can be accessed from page and index templates π¦
π» FEED VARIABLES π»
these feed variables are available:
.Titleβ the title of the feed.Pathβ the path to the feed directory.URLβ the URL of the feed directory.Pagesβ list of pages in this feed
some of these variables are defined in feed configuration. see FEEDS.
feed variables can be accessed from feed templates πΈ
π§© PARTIAL TEMPLATES π§©
partial templates can be placed in the templates/_partials directory. you can execute partial templates from any other template with the partial function. see TEMPLATE FUNCTIONS π
π οΈ TEMPLATE FUNCTIONS π οΈ
every template has these functions available to it:
and args...β returns the boolean AND of its arguments by returning the first empty argument or the last argument. that is,"and x y"behaves as"if x then y else x". all the arguments are evaluated πcall function, args...β returns the result of calling the first argument, which must be a function, with the remaining arguments as parameters. so"call .X.Y 1 2"is, in Go notation,dot.X.Y(1, 2)whereYis a func-valued field, map entry, or the like. the first argument must be the result of an evaluation that yields a value of function type (as distinct from a predefined function such asprint). the function must return either one or two result values, the second of which is of typeerror. if the arguments don't match the function or the returned error value is non-nil, execution stops πeq arg1, arg2β gives you the boolean truth ofarg1 == arg2, no cap πexec command, inputβ runs the given external command withinputprovided as standard input, then returns its standard output. Very demure, very subprocess β¨ge arg1, arg2β returns the boolean truth ofarg1 >= arg2.gt arg1, arg2β returns the boolean truth ofarg1 > arg2.html args...β returns the escaped HTML equivalent of the textual representation of its arguments. Big caveat bestie: this function is unavailable in HTML templates, with a few exceptions πindex collection, args...β returns the result of indexing its first argument by the following arguments. Soindex x 1 2 3is, in Go syntax,x[1][2][3]. Each indexed item must be a map, slice, or array β themβs the rules π«Άjs args...β returns the escaped JavaScript equivalent of the textual representation of its arguments.le arg1, arg2β returns the boolean truth ofarg1 <= arg2.len listβ returns the integer length of its argument. Short king behavior π₯lt arg1, arg2β returns the boolean truth ofarg1 < arg2.math.Add arg1, arg2β returnsarg1 + arg2as an integer if both arguments are integers, otherwise as a float.math.Sub arg1, arg2β returnsarg1 - arg2as an integer if both arguments are integers, otherwise as a float.math.Mul arg1, arg2β returnsarg1 * arg2as an integer if both arguments are integers, otherwise as a float.math.Div arg1, arg2β returnsarg1 / arg2as a float.math.Mod arg1, arg2β returnsarg1 % arg2as an integer.math.Ceil argβ returns the least integer value greater than or equal toarg.math.Floor argβ returns the greatest integer value less than or equal toarg.math.Log argβ returns the natural logarithm ofargas a float.math.Max arg1, arg2β returns the greater ofarg1andarg2as an integer if both arguments are integers, otherwise as a float.math.Min arg1, arg2β returns the lesser ofarg1andarg2as an integer if both arguments are integers, otherwise as a float.math.Pow arg1, arg2β returnsarg1 ^ arg2as a float.math.Round argβ returns the nearest integer toarg, rounding half away from zero.math.Sqrt argβ returns square root ofargas a float.ne arg1, arg2β returns the boolean truth ofarg1 != arg2.not argβ returns the boolean negation of its single argument. Flip it and reverse it πor args...β returns the boolean OR of its arguments by returning the first non-empty argument or the last argument, that is,or x ybehaves asif x then x else y. All the arguments are evaluated. Everyone gets a vibe check, no shortcuts β¨partial name, dataβ executes the named partial template with the provided data. See PARTIAL TEMPLATES. Example:
{{ partial "header.gmi" . }}
path.Base pathβ returns the last element ofpath.path.Clean pathβ returns the shortest path name equivalent topath.path.Dir pathβ returns all but the last element ofpath, typically the pathβs directory.path.Ext pathβ returns the filename extension used bypath.path.Join elem...β joins any number of path elements into a single path.print args...β formats using the default formats for its operands and returns the resulting string. Spaces are added between operands when neither is a string. Tiny formatting gremlin behavior πprintf format, args...β formats according to a format specifier and returns the resulting string.println args...β formats using the default formats for its operands and returns the resulting string. Spaces are always added between operands and a newline is appended. She adds the newline so you donβt have to πreverse listβ returns a reversed copy of the provided slice or array.safeCSS cssβ encapsulates known safe CSS content.safeHTML htmlβ encapsulates a known safe HTML document fragment.safeHTMLAttr attrβ encapsulates an HTML attribute from a trusted source.safeJS jsβ encapsulates a known safe JavaScript expression.safeURL urlβ encapsulates a known safe URL or URL substring.siteβ returns site information (see SITE VARIABLES). Main character data source energy πslice list, args...βslicereturns the result of slicing its first argument by the remaining arguments. Thusslice x 1 2is, in Go syntax,x[1:2], whileslice xisx[:],slice x 1isx[1:], andslice x 1 2 3isx[1:2:3]. The first argument must be a string, slice, or array. Slicing, but make it fashion π¦strings.Count string, substrβ counts the number of non-overlapping instances ofsubstrinstring. Ifsubstris an empty string, Count returns 1 + the number of Unicode code points instring.strings.HasPrefix string, prefixβ reports whetherstringbegins withprefix.strings.HasSuffix string, suffixβ reports whetherstringends withsuffix.strings.Join elems, sepβ concatenates the elements of its first argument to create a single string. The separator stringsepis placed between elements in the resulting string.strings.Repeat string, countβ returns a new string consisting ofcountcopies ofstring. It panics ifcountis negative or if the result of(len(string) * count)overflows. So do not come for her with negative counts, bestie πstrings.Replace string, old, new, nβ returns a copy ofstringwith the firstnnon-overlapping instances ofoldreplaced bynew. Ifoldis empty, it matches at the beginning of the string and after each UTF-8 sequence, yielding up tok+1replacements for ak-rune string. Ifn < 0, there is no limit on the number of replacements. Chaotic but iconic πstrings.ReplaceAll string, old, newβ returns a copy ofstringwith all non-overlapping instances ofoldreplaced bynew. Ifoldis empty, it matches at the beginning of the string and after each UTF-8 sequence, yielding up tok+1replacements for ak-rune string.strings.Split string, sepβ slicesstringinto all substrings separated bysepand returns a slice of the substrings between those separators. Ifstringdoes not containsepandsepis not empty, Split returns a slice of length 1 whose only element isstring. Ifsepis empty, Split splits after each UTF-8 sequence. If bothstringandsepare empty, Split returns an empty slice. Break it down now πΆstrings.Title stringβ returns a copy of the string with all Unicode letters that begin words mapped to their Unicode title case. BUG: The rule Title uses for word boundaries does not handle Unicode punctuation properly. Not me finding a bug note in the wild πstrings.ToLower stringβ returnsstringwith all Unicode letters mapped to their lower case.strings.ToUpper stringβ returnsstringwith all Unicode letters mapped to their upper case. Loud and proud π£strings.Trim string, cutsetβ returns a slice ofstringwith all leading and trailing Unicode code points contained incutsetremoved.strings.TrimLeft string, cutsetβ returns a slice ofstringwith all leading Unicode code points contained incutsetremoved. To remove a prefix, usestrings.TrimPrefixinstead.strings.TrimPrefix string, prefixβ returnsstringwithout the provided leadingprefixstring. Ifstringdoesnβt start withprefix, it is returned unchanged.strings.TrimRight string, cutsetβ returns a slice ofstringwith all trailing Unicode code points contained incutsetremoved. To remove a suffix, usestrings.TrimSuffixinstead.strings.TrimSpace stringβ returns a slice ofstringwith all leading and trailing white space removed, as defined by Unicode. Self-care for strings β¨strings.TrimSuffix string, suffixβ returnsstringwithout the provided trailingsuffixstring. Ifstringdoesnβt end withsuffix, it is returned unchanged.urlquery args...β returns the escaped value of the textual representation of its arguments in a form suitable for embedding in a URL query. This function is unavailable in HTML templates, with a few exceptions.
Thatβs the whole function fam β later bestie, this was kiln(1), dated 2026-10-14 π
π¦ where this page comes from
- alpine 3.24: kiln-doc 0.4.1-r17 Β· GPL-3.0-only
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms