This documentation is split into a few sections:
- Lua standard library information
- General helper functions
- Item schema for representing items in luasmith
- Built-in processing nodes for constructing pipelines
- Infrastructure for creating new processing nodes (TODO)
luasmith exposes Lua 5.2's standard library. See the Lua 5.2 manual for information about basic Lua functions such as pairs, string.find, etc.
In particular, note that luasmith builds on top of Lua's string matching patterns (which are similar to Regular Expressions).
The built-in themes each support site-level metadata, as returned by a script in the input directory named site.lua.
An optional index.md file in the root of the input directory will have its rendered contents added to the top of (only) the site's landing page. This is useful for adding an introduction, contact info, links to other sites, etc.
Required/recommended:
title: Title for the siteurl: Root URL for the site (e.g.https://example.com/) -- this is used to provide absolute links in the Atom feed
Optional:
subtitle: Subtitle for the site (default:nil/none)footer: Footer (raw HTML) to append to the end of every page (default:nil/none)keywordDirectoryPattern: Lua pattern for deriving keywords from the first capture group of item paths (default:"^posts/(.-)/.+%.html$", meaning the (first) subdirectory ofposts/is the name of a keyword)syntaxAliases: Aliases for syntax highlighting, e.g. to have a code block tagged asshuse the Scintillua highlighter ("lexer") for Bash, you could setsyntaxAliases = { sh = "bash" }
title: Title for the sitesubtitle: Subtitle for the siteurl: Root URL for the site (e.g.https://example.com/) -- this is used to provide absolute links in the Atom feedlinks: List of links to include at the top of each page, example:links = { { "About", "about.html" }, { "Contact", "mailto:contact@example.com" } }
Both blog and md2blog items make use of the following frontmatter properties:
title: Name of the articledate: Creation date of the article, in the formYYYY-MM-DDorYYYY-MM-DD HH:mmdescription: Description or summary of the article (optional in theblogtheme)keywords: Optional array of keywords, e.g.keywords: [foo,bar]
Optional:
draft: Optionally set totrueto exclude an item
Additional properties can be added, for use in custom templates.
Items in luasmith are represented as Lua tables, with a few known keys that can be used in templates (in addition to any frontmatter properties):
paththe path (relative to the input/output root) of the itempathToRootthe path from the item to the root (useful for relative references)contentthe content of the item/file, represented as a Lua string (note that Lua strings can contain binary data)
Additional properties can be added by simply setting additional key-value pairs on the item's table.
There are a few different kinds of processing nodes in luasmith (number of inputs and outputs shown in parentheses):
- Source nodes: find and produce new items (0:M)
- Sink nodes: process items, but don't produce anything (N:0)
- Transform nodes: process items in isolation (1:M)
- Aggregate nodes: process items as a group (N:M)
readFromSource(dir)reads files from a directoryinjectFiles(files)inserts new files; the table format is{ [path] = content, ... }
writeToDestination(dir)writes items into files indiromitWhen(test, pattern)calls functionteston each item matching pathpatternand removes items that return non-false
processMarkdown()converts*.mdfiles from Markdown to HTML (.html), extracting either Lua, (limited) YAML, or (limited) TOML frontmatter metadata in the processhighlightSyntax(options?)adds HTML spans with.hl-*CSS classes to fenced code blocks (see notes below for more detail)processEtlua(pattern?)evaluates any etlua blocks in item content, similar to Hugo shortcodes, but using Lua (note: due to Markdown processing escaping angle brackets, be sure to use this node prior toprocessMarkdown());patterndefaults to%.md$("*.md")injectMetadata(properties, pattern)mergespropertiesinto items that match pathpatternderiveMetadata(derivations, pattern)similar toinjectMetadatabut instead of adding fixed metadata, it runs functions on the item; the format ofderivationsis{ [property] = f, ... }whereftakes in the item and returns the new valueapplyTemplates(templates)applies a single template to each matched item; note thattemplatesis an array of the format{ [pattern] = template }and the last match wins (e.g. so you can match "all HTML files" but then override that logic for specific items using more specific patterns)
Syntax highlighting uses Scintillua internally. highlightSyntax() accepts an optional table, with the following keys:
aliases: Table of syntax aliases, where keys are code block tags and values are the corresponding highlighters ("lexers"), for examplehighlightSyntax({ sh = "bash" })would highlight code blocks taggedshwith the embedded Bash lexer (bash.lua)
luasmith's embedded copy of Scintillua may be slightly out of date, but, for reference, here is a list of Scintillua lexers.
Note: if you need to tweak or add a lexer, you can simply add a lexer on Lua's search path (usually the current working directory) and it will take precedence over any embedded lexer.
aggregate(path, pattern)creates a new item atpathwith emptycontent, but with anitemsproperty that is an array of all items matched bypatterncreateIndexes(createPath, property, pattern)creates multiple new "index" items, one for each unique value of the propertypropertyon items matchingpattern, at the path computed bycreatePath(value); the "index" item format includes{ key = <the unique value>, items = <array of items with that value>, groups = <map of unique values to items for ALL unique values> }checkLinks()verifies that relative link targets in HTML files exist, including hash/fragments/anchors (i.e. "checks for broken links")
Note: aggregate() can be easily used to create a blog index/home page with a list of posts. createIndexes() can be used to create e.g. "keyword index" pages.
There is also an "escape hatch" for arbitrarily modifying the entire site at once, for example if you want to add previous/next links between posts:
processItems(process): Callsprocess(a function) with a representation of all items, in the form of a key-value table with paths as keys and items as values; you can add and remove entries to/from the map (luasmith will internally addedpathandpathToRootproperties to new items)
Here's an example for adding previousItem/nextItem links to all items that have a date property:
processItems(function (items)
local itemsWithDates = table.include(table.values(items), function (item) return item.date end)
local sorted = table.sortBy(itemsWithDates, "date")
local previousItem = nil
for _, item in ipairs(sorted) do
if previousItem then
previousItem.nextItem = item
item.previousItem = previousItem
end
previousItem = item
end
end),The built-in themes rely on some shared and reusable (though not necessarily guaranteed to be stable across release!) functionality.
To add an Atom feed to a from-scratch theme, you'll need to
- Load the shared helpers:
shared = require("themes.shared") - Define site metadata:
local site = { title = ..., url = ... } - Aggregate articles into a new item named e.g.
feed.xml, in the build pipeline:aggregate("feed.xml", "%.html$"), - Inject site metadata into items, as part of the build pipeline:
injectMetadata({ site = site }), - Ensure that
applyTemplates()to generate the feed using the built-in feed template is run, in a separate step, before generic/outer HTML templates (to avoid having those included in the generated feed):applyTemplates({ { "^feed.xml$", fs.readFile("themes/shared/feed.etlua") } })
Note: the last bit of code uses readFile instead of the usual readThemeFile because it's using the built-in feed.etlua file instead of a file from this particular theme's directory.
For convenience, luasmith exposes some generic Lua helper functions, as documented here. See main.lua for the source code.
log.warn(message)logs a warninglog.info(message)logs a message
table.merge(source, dest)copies (shallowly) keys and values from tablesourcetodesttable.copy(table)creates a shallow copy oftabletable.map(table, func)creates a new (array) table that is the result of applyingfuncto each value intabletable.sortBy(table, prop, desc)creates a new (array) table with items ordered by the value of keyprop, optionally in descending order (ifdescis not false)table.sorted(table, compare)creates a new (array) table with items ordered by the result of callingcompare(a, b)on two elementstable.groupBy(table, key)groups items in arraytableunder keys in a new table -- this is used for creating e.g. index pages based on keywordstable.concatenate(table1, table2)creates a new array table that contains values fromtable1followed by items fromtable2table.include(table, incl)creates a new array table that contains values from array tabletablefor which the functioninclreturns a truthy valuetable.values(table)creates a new array table that contains the values fromtable(in unspecified order); think of this as converting a map to an array (discarding the keys in the process)
iterator.count(iterator)counts the number of items in a Lua iteratoriterator.collect(iterator)collects items from a Lua iterator into an array table
string.toNumber(str)converts a decimal integer string into a numberstring.charAt(str, index)returns a single-byte string that represents the byte at positionindexinstrstring.split(str, separator)returns a Lua iterator that returns substrings ofstrthat are separated by the single character stringseparatorstring.lines(str)returns a Lua iterator that returns lines ("\n"-separated) ofstrstring.trim(str)returnsstr, with spaces from the beginning and end removed
fs.join(part1, part2)joinspart1andpart2with a/(note: if one value is an empty string, this just returns the other value)fs.dir(path)returns the directory part ofpath(without any trailing slash)fs.normalize(path)normalizespathby converting\to/and resolving..and.fs.resolveRelative(base, relative)resolves pathrelative, relative tobasefs.createDirectory(dir)creates directory at pathdir, including any necessary parent directoriesfs.enumerateFiles(dir)returns a list of files underdirfs.tryReadFile(path)tries to read the file atpathand return its contents as a string, returningnilif the file doesn't exist or can't be openedfs.tryLoadFile(path)tries to load a Lua script atpath, returningnilon errorfs.readFile(path)reads the file atpathand returns its contents as a stringfs.readThemeFile(path)reads the file atpath, relative to the currenttheme.luafilefs.loadThemeFile(path)loads the Lua script atpath(relative to the currenttheme.luafile) into a Lua chunk (but does not execute it), returning a function to execute the codefs.doThemeFile(path)loads and executes the Lua script atpath(relative to the currenttheme.luafile), returning any value that is returned from the script (this is useful for splitting theme code into multiple files and loading them relative to the main script)fs.writeFile(path, content)writes file withcontenttopath(relative to the working directory, i.e. whereluasmithwas invoked)
url.isRelative(url)returns true ifurlis a relative URL (instead of absolute)
lib.item.repathRelativeLinks(item, prefix)rewrites relative HTML links so that they start with the given prefix (this can be used to rewrite relative links as absolute links, e.g. as used when built-in themes produce Atom feeds)lib.item.createTableOfContents(item)generates a table of contents for the given HTML item (see example below)
Example of inserting a table of contents into an etlua template (note the call to lib.item.createTableOfContents:
<header>
<h1><%= title %></h1>
<details><summary>Table of contents</summary>
<%- lib.item.createTableOfContents(self) %>
</details>
</header>