Releases: PataphysicalSociety/soupault
5.1.0 release
5.1.0 (2025-06-17)
Release blog post: https://soupault.app/blog/soupault-5.1.0-release/
New features
- Target directories for page files are now guaranteed to exist when soupault starts processing widgets,
so that widgets can create new asset files in page directories. - Global data for Lua plugins is now reintroduced: the startup hook can add values to the
global_data
table
and all Lua plugins and hooks can retrieve them using the newPlugin.get_global_data(name)
function.
Plugins and hooks cannot modify the global data, so it doesn't create a consistency problem. site_index
andindex_entry
variables are now available in the template environment of theelement_template
widget.- New index field option
strip_tags
for people who want to strip HTML tags from particular index fields.
New Lua API functions
There are now functions for case conversion. They only affect the case of ASCII characters,
Unicode characters are ignored because handling their case requires knowing their language.
String.lowercase_ascii(string)
String.uppercase_ascii(string)
String.capitalize_ascii(string)
String.uncapitalize_ascii(string)
Behavior changes
- Index extraction is now always enabled in generator mode.
index.index
option no longer has any effect.
5.0.0 release
Release post: https://soupault.app/blog/soupault-5.0.0-release/
Removed features
index.index_first
is no longer a valid configuration option.
If you use it, simply remove it from the config — index data is now always available to all pages
(more on that later).settings.process_pages_first
is no longer a valid option — there is no sequential page processing anymore
so the concept of "processing specific pages first" no longer applies.
It should have no effect on any websites.- There is no separate
post-save
hook anymore. If you used it, move the code to thesave
hook instead. - There is no way to make the
post-index
hook tell soupault to ignore a page
(previously that was possble to do by setting the undocumentedingore_page
variable). persistent_data
andglobal_data
variables are no longer available in the plugin environment.
If you want to share data, place it in the page or in the index entry.
New features
Built-in Markdown support
Soupault now includes a built-in Markdown processor (compatible with CommonMark and popular extensions,
based on CMarkit).
Support for built-in Markdown needs to be explicitly enabled in the config:
[settings]
markdown_extensions = ["md"]
If enabled, it takes priority over page preprocessors — if "md"
(or another extension)
is in settings.markdown_extensions
, soupault does not try to look up a preprocessor
and processed it using the built-in implementation right away.
element_template
widget
The new element_template
widget replaces an element with an HTML snippet produced by rendering
a template using attributed and content of the source element.
Its goal is to allow users to easily create "shortcodes" without writing Lua code.
os_family
option for exec
and preprocess_element
widgets
Those widgets now support a new os_family
option to limit them to only one OS family
and use different commands for those OSes. At the moment, it can be "unix"
(any UNIX-like OS)
or "windows"
(Microsoft Windows or compatibles, such as ReactOS).
New plugin functions
Table.sort(func, table)
— sorts a table with numeric indices usingfunc
for value comparison.String.to_integer
— converts a string to an integer (returnsnil
if conversion fails).String.to_float
— a clearer-named alias forString.to_number
.
Deprecated options
settings.strict
and--strict <true|false>
options are not deprecated
and will be removed in future releases.
All built-in errors are now treated as fatal and cannot be ignored.
Behavior changes
- Site index is now available to all pages by default.
- If a website requires more RAM to process than the machine has available,
soupault now can run out or memory, like most other static site generators
(that's the compromise required to make index data available to all pages). - Metadata extraction now occurs as early as possible — just after all widgets listed in
index.extract_after_widgets
. - Caching is now enabled by default.
Bug fixes
- Clean URL in rendered index views now include trailing slashes,
which reduces the number of unncessessary redirects (GitHub issue #81).
The old behavior can be restored withsettings.clean_url_trailing_slash = false
. - Lists of selectors are now consistently supported in all built-in widgets (GitHub issue #77).
- If the
pre-process
hook modifies the path of a leaf bundle,
its child asset paths are adjusted accordingly (GitHub issue #63). soupault --show-effective-config
now correctly updates values
that are overridden by commmand line options or internal processes.
4.11.0 release
Release post: https://soupault.app/blog/soupault-4.11.0-release/
New features
- It's now possible to use
:has()
selector in options that accept CSS selectors (implemented in lambdasoup 1.1.0)
New plugin API functions
HTML.is_text(e)
— checks if an HTML element tree node is a text node. Thanks to @jbhoot!
Bug fixes
HTML.is_document(e)
now correctly returns true for values created withHTML.parse()
andHTML.create_document()
.- Namespaces are now correctly preserved in HTML element attribute names (implemented in lambdasoup 1.1.1).
4.10.0 release
Release blog post: https://soupault.app/blog/soupault-4.10.0-release/
New features
Deleting only elements that do not have certain children
The delete_element
widget has a new option: when_no_child
.
For example, suppose you have footnotes container in your template that looks like this:
<div id="footnotes"> <hr class="footnotes-separator"> </div>
. If a page has footnotes,
it would contain something like <p class="footnote">...
. If not, it would only have the <hr>
element in it.
Deleting it from pages that don't have any footnotes cannot be done with only_if_empty
because the container has that auxilliary element in it.
However, with the new option you can make the widget delete the container
only if nothing inside it matches the selector of actual footnotes.
[widgets.clean-up-footnote-containers]
after = "footnotes"
widget = "delete_element"
selector = "div#footnotes"
when_no_child = "p.footnote"
Bug fixes
- Complete HTML pages work correctly in generator mode again (report by Auguste Baum)
- Config files with multiline strings and Windows newlines (CRLF) no longer cause parse errors
(report by Bohdan Kolesnikov) - Configs that consist of a single comment line followed by EOF no longer cause parse errors
(found thanks to the TOML test suite v1.4.0)
4.9.0
Release blog post: https://soupault.app/blog/soupault-4.9.0-release
New features and improvements
- New
startup
hook that runs before soupault processes any pages and can modify theglobal_data
variable.
New plugin API functions
New Digest
module offers functions for calculating cryptographic hash sums of strings.
All those functions return hex digests.
Digest.md5(str)
Digest.sha1(str)
Digest.sha256(str)
Digest.sha512(str)
Digest.blake2s(str)
Digest.blake2b(str)
Other new functions:
Sys.basename_url(str)
andSys.dirname_url(str)
— aliases forSys.basename_unix
andSys.dirname_unix
, respectively.
4.8.0 release
Full announcement: https://soupault.app/blog/soupault-4.8.0-release (includes important information about future plans)
4.8.0 (2024-01-12)
New features and improvements
site_index
variable is now available to the post-build hook.index_entry
variable (the complete site index entry for the current page) is now available to post-index, save and post-save hooks and to Lua index processors.- New options for ignoring certain paths in the sire dir:
settings.ignore_path_regexes
andsettings.ignore_directories
.
New plugin API functions
HTML.inner_text()
— returns the text nodes from inside a node, stripped of all HTML tags.
Bug fixes
- In generator mode, page files are parsed as HTML fragments so
<style>
tags and similar no longer call issues
with duplicate<body>
tag inserted in the page (#58, report by Delan Azabani).
4.7.0 release
Release blog post: https://soupault.app/blog/soupault-4.7.0-release
New features and improvements
- New
max_items
option in index views allows limiting the number of displayed items. - New
settings.page_character_encoding
option for correctly loading pages in encodings other than ASCII and UTF-8. - New
post-build
hook that runs when all pages are processed and soupault is about to terminate. - Info logs to indicate the first and second passes in the
index_first = true
mode. - Debug logs now tell why a page is included or excluded from an index view:
"page_included checks for %s: regex=%b, page=%b, section=%b"
New plugin API functions
CSV.from_string(str)
— parses CSV data and returns it as a list (i.e., an int-indexed table) of lists.CSV.unsafe_from_string(str)
— likeCSV.from_string
but returnsnil
on errors instead or raising an exception.CSV.to_list_of_tables(csv_data)
— converts CSV data with a header returned byCSV.from_string
into a list of string-indexed tables for easy rendering.HTML.swap(l, r)
— swaps two elements in an element tree.HTML.wrap(node, elem)
— wrapsnode
inelem
.- New
global_data
hash table for sharing data between plugins. - New
soupault_pass
plugin environment variable (0 whenindex_first = false
, 1 and 2 for the first and the second pass respectively when it's true).
Bug fixes
- Fixed an unhandled exception on index entry sorting failures when
sort_strict = true
andsort_by
is unspecified. - Fixed a typo in the comments of the config generated by
soupault --init
(s/ULRs/URLs/).
Misc
New state
record now holds both the settings record and the TOML config datastructure,
plus the new global_data
and soupault_pass
variables, and can be easily extended to support global state new variables.
Official binaries are now available for Linux on ARM64.
4.6.0 release
Release post: https://soupault.app/blog/soupault-4.6.0-release/
New features and improvements
New plugin API functions
Sys.getenv(name, default_value)
function (default_value
is optional).String.ends_with(string, suffix)
.String.is_valid_utf8(string)
andString.is_valid_ascii(string)
functions.Table.length(table)
— returns the number of items in a table.Table.for_all(func, table)
— checks if boolean functionfunc
is true for all items in a table.Table.for_any(func, table)
— checks if boolean functionfunc
is true for at least one item in a table.Table.is_empty(t)
— returns true ift
has no items in it.Table.copy(t)
— returns a copy of the tablet
.HTML.is_empty(e)
— returns true ife
has zero child nodes.HTML.is_root(e)
— returns true ife
has no parent node.HTML.is_document(e)
— returns true ife
is a soup (document) node rather than an element or a text.Value.is_html(v)
— returns true isv
is an HTML document or node.
Bug fixes
- Fixed an unhandled OTOML exception when loading configs with duplicate key names (such issues generate proper parse errors now).
4.5.0 release
Full announcement: https://soupault.app/blog/soupault-4.5.0-release
New features and improvements
--no-caching
option allows the user to disable caching even ifsettings.caching
is true in the config.- [Plugin API] New
HTML.prepend_root(node, child)
function for inserting new nodes in HTML documents before all existing nodes. - The name of the Lua index processor file and the index view that calls it are displayed in the logs now.
- Clearer breadcrumb template parse error message (mentions Jingoo now).
Bug fixes
soupault --version
correctly prints a trailing newline again.
4.4.0 release
New features
Support for caching the output of page preprocessors and commands used by preprocess_element
widgets.
[settings]
# Caching is off by default so you need to enable it
caching = true
# Change the cache directory name if you wish
cache_dir = ".soupault-cache"
You can force soupault to clear the cache and rebuild everything by running soupault --force
.