Skip to main content

Rendering

Rendering turns templates plus context into files. Three functions cover the spectrum — whole directories, single files, and inline strings.

directory.render — the workhorse

directory.render("contents", context)

Renders everything under the named directory (relative to the archetype root) into the destination — file contents and file/directory names both pass through ATL.

Options:

directory.render("docker", context, {
destination = "deploy", -- subdirectory of the render destination
if_exists = Existing.Overwrite, -- existing-file policy (see below)
})

Multiple content roots, rendered conditionally, are the backbone of feature-gated archetypes.

file.render — single files

-- Write a single template to the destination
file.render("templates/gitignore", context, { destination = ".gitignore" })

-- Or return the rendered text (no destination = no file written)
local banner = file.render("templates/banner.txt", context)

The source path always resolves against the archetype root. When destination is present the file is written and the call returns nil; without it, the rendered string is returned for further use.

template.render — inline strings

local artifact = template.render("{{ org }}-{{ project-name }}.jar", context)

-- Or write an inline template straight to a file
template.render("# {{ Project-Name }}\n", context, { destination = "NOTES.md" })

Handy for computing derived values with template syntax instead of Lua string plumbing.

Existing-file policies

What happens when a target file already exists? The if_exists option (on all three functions) takes an Existing policy:

PolicyBehavior
Existing.PreserveKeep the existing file (default)
Existing.OverwriteReplace it
Existing.PromptAsk the user, per file
Existing.ErrorFail the render — for CI and idempotent pipelines

The Preserve default makes re-rendering safe: users can re-run an archetype over an existing project without losing local edits. Reach for Overwrite only for files the archetype truly owns.

Reading and probing files

file.exists and file.read complete the toolkit:

-- Read a data file shipped inside the archetype
local defaults = format.from_yaml(file.read("data/defaults.yaml"))
context:merge(defaults)

-- Probe the destination to adapt to an existing project
if file.exists("Cargo.toml", { within = Location.Destination }) then
log.info("Existing Cargo project detected")
end

The within option picks the resolution root:

LocationResolves against
Location.ArchetypeThe archetype's own directory (default)
Location.DestinationThe render destination (honors --destination)
Location.CwdThe process working directory

:::warning Sandboxed paths All file paths are sandboxed: absolute paths, .. traversal, and ~ expansion are rejected, in every location. Archetypes read and write only where they're supposed to. :::

Where files land

All output paths are relative to the render destination (the positional/--destination argument, default .). Composed child archetypes can be aimed at subdirectories via catalog.render's destination option — see Composition.

Full signatures: Rendering Modules reference.