Asset licensing
Heine supports a small, REUSE-compatible convention for declaring the rights information of copied assets. It keeps licensing beside the asset it describes, or records one explicit shared declaration for a coherent asset set, publishes the matching license texts, and exposes the declared information to ordinary Tera templates. It does not determine whether a declaration satisfies the law applicable to a particular work or use.
Use this guide after the tutorial's asset-licensing step when a copied asset needs published licensing information or an attribution page.
Layout
Put each license text directly in the optional project-root LICENSES/
directory. Its filename is an SPDX license or exception identifier, or a local
LicenseRef-…, followed by .txt. Heine copies it unchanged to
<build.output_dir>/LICENSES/. It never downloads, supplies, or rewrites
license texts:
the site author chooses and provides the text that backs each declaration.
Look up standard identifiers in the SPDX License List;
use its exact identifier as the filename stem.
Put a sidecar named <asset filename>.license next to every copied asset that
needs an attribution. A sidecar is never copied as an asset. It must contain at
least one copyright notice and one SPDX expression:
SPDX-FileCopyrightText: 2001-2021 The STIX Fonts Project Authors
SPDX-License-Identifier: OFL-1.1
SPDX-FileComment: https://github.com/stipub/stixfonts
The .license suffix is reserved for this purpose: a file with that suffix
cannot itself be a copied asset.
Every SPDX license identifier and WITH exception in the expression requires
the corresponding LICENSES/<identifier>.txt file. A local declaration such
as LicenseRef-Public-Domain similarly requires
LICENSES/LicenseRef-Public-Domain.txt. Heine checks expressions with the
SPDX license list, diagnoses malformed sidecars at their authored value, and
rejects an orphaned sidecar rather than silently ignoring it.
This rule applies to every identifier named by a compound expression. For
example, OFL-1.1 AND MIT requires both LICENSES/OFL-1.1.txt and
LICENSES/MIT.txt; OFL-1.1 OR MIT requires those same two texts. Heine does
not choose one branch of an OR expression on the site's behalf.
The license-text directory is deliberately local to the site. Heine accepts
local LicenseRef-… values, but rejects DocumentRef-… and AdditionRef-…
forms: those refer to an external SPDX document or a non-standard exception
that this small local-file model cannot identify or publish unambiguously.
Shared declarations and required coverage
An adjacent sidecar is the ordinary choice for one copied asset. When several
files form one coherent set with exactly the same licensing facts, such as a
family of font files or a first-party site asset set, a site may record one
licensing group in heine.toml instead:
[licensing]
required = true
[[licensing.groups]]
assets = [
"assets/fonts/example-roman.woff2",
"assets/fonts/example-bold.woff2",
]
SPDX-FileCopyrightText = "2000 Example Foundry"
SPDX-License-Identifier = "OFL-1.1"
SPDX-FileComment = "https://example.test/fonts"
required = true requires a declaration for every copied asset Heine
publishes. Omit required when declarations remain optional. Writing
required = false is a checked error because omission already selects that
policy.
Each group lists literal published output_path values. They have no leading
slash and do not include site.base_path. Use the output identity shown by
attributions(), not an asset() ID, an asset: URL, or a public URL. For
example, a shared asset may be assets/fonts/example.woff2, while a German
locale overlay is de/assets/fonts/example.woff2.
Groups are exact lists, not directory rules or glob patterns. Adding a package member means adding its output path to the group. If one member has different facts, remove it from the group and give that asset its own sidecar. A group and sidecar may not both declare one asset, even when their current values match. Heine reports both locations rather than choosing a precedence rule.
All group fields use the same rules as sidecars. Copyright notices and SPDX
expressions may be one string or an array of strings. An array contains
separate authored SPDX tag values: it does not imply AND or OR; express
that relationship inside an SPDX expression. Unknown group fields, empty
groups, duplicate members, unavailable paths, invalid expressions, and missing
license texts are checked errors.
Required coverage applies to every copied asset, including an unused file, a
binary file, a locale overlay, and an asset used only by a draft. It does not
apply to declared content, templates, data, deployment files, generated
resources, sidecars, or license texts. A full build reports every uncovered
asset in deterministic output-path order. A quick build also checks the current
asset catalog, but may leave stale output from an earlier catalog after a
deletion or rename. Use a full build after changing heine.toml, deleting or
renaming an asset or sidecar, or changing a license text.
Heine uses REUSE-compatible fields and sidecars, but groups are not
REUSE.toml annotations and successful coverage is not a claim of REUSE or
legal compliance.
Template views
asset() returns licensing as null when no declaration exists. Otherwise it
contains copyrights, expressions, comments, and license_texts. Each
license text has its id, base-path-aware url, and current-page-relative
relative_url.
{% set image = asset(id="blog/images/portrait.jpg") %}
<img src="{{ image.url }}" alt="Portrait">
{% if image.licensing %}
<p>{{ image.licensing.copyrights | join(sep=", ") }};
<a href="{{ image.licensing.license_texts[0].url }}">
{{ image.licensing.expressions | join(sep="; ") }}
</a></p>
{% endif %}
For a complete deterministic inventory, use attributions(). Each item has
asset (output_path, url, relative_url) and the same licensing view.
output_path is the locale-qualified published path. For example,
de/images/logo.svg, rather than the locale-relative ID passed to asset().
It is therefore unambiguous across the whole site and suitable as a key for
localized presentation data. An attribution page is an ordinary authored page,
so a site decides its route, language, and presentation:
<ul>
{% for item in attributions() %}
<li><a href="{{ item.asset.url }}">{{ item.asset.output_path }}</a> -
{{ item.licensing.copyrights | join(sep=", ") }}
({{ item.licensing.expressions | join(sep="; ") }})</li>
{% endfor %}
</ul>
A central inventory is useful for a site-wide attribution page. Some licenses
or a work’s provenance may require attribution near a particular use; render
that asset’s licensing view there as well. Heine deliberately records the
declared facts and links, rather than guessing legal presentation requirements.
The stable published output_path also lets a localized template select its
own locale-specific presentation text while retaining the canonical declaration.
Build behavior
License texts participate in normal output ownership and collision checks.
Full builds publish exactly the current LICENSES/ tree and, when coverage is
required, establish that every copied asset in that tree's current publication
model has one declaration. Quick builds validate the current copied-asset
catalog as well. They update present license texts and re-render pages when a
sidecar changes, but, like other quick-build inputs, do not remove stale
output after deletion. Run a full build after changing licensing configuration
or deleting or renaming an asset, sidecar, or license text.