themecore

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 29 Imported by: 0

README

themecore

themecore is the shared Go package of the Umono theme system. The Umono runtime and the Umono CLI embed it, so a theme is checked by the same rules when it is built, installed and activated.

Status: under development for Umono v0.9. The API is not stable yet.

Install

go get github.com/umono-cms/themecore

File Format

All configuration files of the theme system (theme.toml, built.toml, contract.toml and attributes.toml) are TOML v1.0.0 and UTF-8. Syntax added by newer TOML versions is rejected, and so are keys that a schema does not define.

The syntax that TOML v1.1.0 adds is rejected by a scan before decoding, whatever version of the TOML decoder the binary is built with: a newline or a trailing comma in an inline table, the escapes \e and \xHH, and a time or date-time without seconds. A program that builds themecore in calls TOMLConformance in its tests:

func TestTOMLConformance(t *testing.T) {
	if err := themecore.TOMLConformance(); err != nil {
		t.Fatal(err)
	}
}

It returns an error if the decoder accepts a document that is not TOML v1.0.0 or rejects one that is. Go's version selection cannot cap the version of a dependency, so the decoder in a binary may be newer than the one themecore is tested with.

Each file has a schema key. It is required and must be a schema URL that themecore reads for that file, for example https://umono.dev/schemas/theme-manifest/v1 for theme.toml. An unknown URL is rejected, not ignored.

Text fields are single line, without control characters and without leading or trailing whitespace.

Packages

A theme package is read either from a built file (.umono-theme, a zip) or from its installed directory:

p, err := themecore.ReadZip(f, size) // size from the file system's metadata
p, err := themecore.ReadDir("themes/acme/minimal/0.1.0")

Both check the package limits before any file is read, counting the real bytes while reading, and then the package content allowlist.

Hard links are detected by the link count of the file system, which only Unix gives. ReadDir therefore fails on other platforms instead of accepting a package it cannot check; ReadZip works everywhere.

Measure Limit
Built file (zip) 64 MB
All files unpacked 64 MB
A file under assets/ 16 MB
assets/theme.css 2 MB
built.toml, checksums.json, contract.toml, attributes.toml 1 MB each
LICENSE 256 KB
Files 1,000
Directories in a path 8, assets included
Path length 256 bytes

The package root holds only built.toml, contract.toml, checksums.json, attributes.toml and LICENSE, and assets/ is the only directory. Every path segment matches [a-z0-9][a-z0-9._-]* and is at most 64 bytes; LICENSE is the only exception. Empty directories, symlinks, hard links, duplicate or encrypted zip entries and compression methods other than store and deflate are rejected.

assets/theme.css is required and is the only CSS file. Every other file under assets/ has one of these extensions, and its content must start with the format's magic bytes:

Extension Magic bytes
.woff2 wOF2
.woff wOFF
.png PNG signature
.jpeg, .jpg FF D8 FF
.webp RIFF, then WEBP at byte 8
.avif An ISO BMFF ftyp box with the brand avif or avis
.gif GIF87a or GIF89a

Checks

The checks of a package run from one place, in the order of the static check table; CLI and Umono do not call them one by one. CheckPackage is for install and activation:

result, err := themecore.CheckPackage(p) // after VerifyChecksums and the Theme API range

CheckSource is for build and the dev proxy. dir is the development directory and contract the contract file that theme.toml refers to; themecore does not know where the CLI caches it:

p, report, err := themecore.CheckSource(dir, contract)

Only assets/, attributes.toml and LICENSE are read from dir into the package, with contract as contract.toml; theme.toml is checked but does not go in, and other files such as .dev/ and dist/ are not read. An empty directory is skipped, any other file that breaks the package rules stops the build. assets/theme.css is replaced by its canonical CSS. The returned package is what goes into the built file; the CLI adds built.toml with report.Version and then checksums.json. report also holds the evidence of the version and the Lint warnings.

CheckPackage checks that built.toml, contract.toml, checksums.json and assets/theme.css exist, parses built.toml and contract.toml and checks that the contract of built.toml is the package's contract.toml. Then it checks the assets, Verify, the files the CSS URLs point to, attributes.toml against the contract and LICENSE. Last, it rejects a theme-api-version lower than the version of the features the package uses. The Result holds the contract, the compiled attribute table (empty without attributes.toml) and the feature floor with its evidence.

CheckDev is for Umono's dev mode. dir is themes/dev/dev/0.0.0/ and root the theme project (UMONO_THEME_DEV):

result, err := themecore.CheckDev(dir, root) // on every request; nothing is cached

It runs the checks of CheckPackage with the privileges of dev/dev: there is no checksums.json; assets/, attributes.toml and contract.toml may be symlinks that resolve inside root; assets/theme.css goes through Transform instead of Verify; built.toml has the id dev/dev and the version 0.0.0, which ParseBuilt and CheckPackage reject. The package limits and content, built.toml, contract.toml, LICENSE and the feature floor are fatal. Errors of theme.css, attributes.toml and single assets are returned in the result instead, so dev mode goes on: CSSErr, AttributesErr and AssetErrs.

The result also holds Built, the validated built.toml, and ContractSource, the unchanged bytes of contract.toml. Umono checks the Theme API range from Built and adds the contract record from ContractSource instead of reading the files itself: only CheckDev accepts dev/dev and resolves the symlinks inside root. Both are empty on a fatal error.

checksums.json

Checksums returns the content of checksums.json during build; VerifyChecksums checks a package against it during install and activation:

b, err := themecore.Checksums(p) // p without checksums.json
err = themecore.VerifyChecksums(p)

The algorithm is sha256, and files lists every package file except checksums.json itself, with no other entries. Checksums is deterministic: keys in byte order and a fixed format. VerifyChecksums looks at values, not format.

theme.toml

ParseManifest parses and validates theme.toml, the manifest the theme author writes:

m, err := themecore.ParseManifest(b)
Key Required Rule
schema Yes https://umono.dev/schemas/theme-manifest/v1
id Yes org/id, both parts kebab-case. Not dev/dev
contract Yes org/id@vMAJOR.MINOR, e.g. umono/blog@v1.0
name Yes Not empty, at most 64 characters
version Yes MAJOR.MINOR.PATCH, without v, pre-release or build metadata
description No At most 280 characters
license No An SPDX license expression. Only the syntax is checked
homepage, repository No Absolute https:// URL, at most 2048 bytes
authors No At least one [[authors]] with name and an optional https:// url

Optional keys are nil in Manifest when they are absent.

built.toml

ParseBuilt parses and validates built.toml, the manifest of a package. It has the keys of theme.toml, with the same rules, under the schema https://umono.dev/schemas/theme-built/v1, and two more:

Key Required Rule
theme-api-version Yes An integer, 1 or greater
built-at Yes An offset date-time in UTC, e.g. 2026-06-02T00:00:00Z

CheckInstallDir checks that the install path themes/{org}/{id}/{version} matches id and version:

b, err := themecore.ParseBuilt(data)
err = b.CheckInstallDir("themes/acme/minimal/0.1.0")

contract.toml

ParseContract parses and validates contract.toml under the schema https://umono.dev/schemas/theme-contract/v1: first the structural layer below, then the sources, which are parsed with Compono (compono.Analyze).

Key Required Rule
id Yes org/id, both parts kebab-case
version Yes vMAJOR.MINOR, e.g. v1.0
description No At most 280 characters
layouts Yes At least one [[layouts]] with a unique kebab-case id, an optional description and a non-empty source
components No [[components]] with a unique SCREAMING_SNAKE_CASE name, a non-empty source and an optional boolean expose

Sources are the only multi-line text fields.

A source with a grammar error that does not depend on values (invalid-expression, unclosed-expression or invalid-parameter-definition), also in a local that is never called, cannot be parsed and is rejected. So is a call or a value that resolves to nothing: not to a local of its source, a component of the contract or a name of Builtins. A default (content = NO_MATTER) only names the type of a parameter and is not checked. A published contract has neither, so the error means that the program is older than the contract. It wraps ErrOutdated, and CLI and Umono report that they are out of date:

if errors.Is(err, themecore.ErrOutdated) {
	// "CLI is out of date" or "Umono is out of date"
}

attributes.toml

CompileAttributes validates attributes.toml under the schema https://umono.dev/schemas/theme-attributes/v1 against the package's contract and compiles it into a table that rendering reads without touching files:

table, err := themecore.CompileAttributes(b, contract)

table.Root("post") // attributes of <umono-root> for the layout post

compono.WithAttributeHook(func(builtin string, chain []compono.Frame) map[string]string {
	return table.Lookup(layout, builtin, chain)
})
  • An address is layout, layout BUILTIN or layout COMPONENT BUILTIN, with parts separated by a single space. The layout is a layout of the contract, the component is an exposed one and the built-in is a name of Builtins. A name the contract defines as a component is never a built-in. layout COMPONENT is rejected: an exposed component has no element of its own to write to.
  • Only class and sizes are allowed. class is a list of unique printable ASCII tokens separated by single spaces, without the umono- and compono- prefixes (case-insensitive), of at most 1024 bytes. sizes is allowed only on an address that ends with IMAGE and is an HTML source size list without auto. Its media conditions use the media feature vocabulary of the theme CSS, its sizes are lengths in its units or calc(), min(), max() and clamp() of them, and percentages are rejected.
  • Lookup finds the scope from the frame chain: the innermost global frame whose scope path is [UMONO_LAYOUT] and whose name is an exposed component, otherwise the layout. A local frame, or a site global with the name of an exposed component, is not a scope. Root and Lookup return a new map on every call, or nil when no address matches.

Theme CSS

Transform turns the theme author's assets/theme.css into the canonical CSS that goes into the package; Verify checks the canonical CSS of a package:

out, err := themecore.Transform(src) // build
err = themecore.Verify(css)          // install and activation

Every style rule of the canonical CSS is inside an @scope (umono-root) block, so the theme cannot style anything outside <umono-root>. Transform parses strictly and never relies on the browser's error recovery; its output always passes Verify. Verify checks the grammar, not the bytes.

The author grammar is closed:

  • At-rules: style rules, @media, @supports and @layer blocks; @layer statements, @font-face and @keyframes at the top level only. Every other at-rule, @import and @scope included, is rejected. No nested CSS.
  • Selectors: a closed list of pseudo-classes and pseudo-elements. html, body, umono-root, :root, &, id (#id, [id], :target) and namespace prefixes are rejected. <umono-root> itself is styled with :scope in the first compound selector, e.g. :scope.layout-post h1.
  • @media: the media types all, screen, print and a closed list of media features.
  • Values: a closed list of value functions (env(), if(), sibling-index() and others are rejected); attr() cannot read id; behavior and -moz-binding are rejected.
  • Reserved names: umono- and compono- (and --umono-, --compono-) are reserved for Umono.
  • URLs are relative paths to allowed files under assets/, e.g. url(fonts/inter.woff2).
  • Generated text: content, quotes, list-style-type and list-style cannot generate words. content with a string or an image needs alternative text: content: "→" / "";.

FallbackSource returns the fallback theme, the default look of pages without an active theme, in the author grammar; FallbackCSS returns its canonical CSS. FallbackSource is also the starting assets/theme.css of a new theme.

Lint returns warnings for CSS that is valid but never matches, for development tools only. It reports root classes used without :scope: a class given only to layout addresses in attributes.toml is only on <umono-root>, so .layout-post h1 never matches; write :scope.layout-post h1. It also reports addresses whose component the layout never renders: post-list MAIN_MENU LINK never matches when MAIN_MENU cannot be reached from the source of post-list through calls, values and defaults of the contract sources.

Theme API

The capability ledger (Features and Breaks) is the single source of the Theme API versions. Rows are only appended.

themecore.ThemeAPICurrent() // newest Theme API version
themecore.ThemeAPIMin()     // oldest Theme API version still accepted

DetectThemeAPIVersion returns the version a package declares in built.toml during build, with the evidence for it. DetectFeatureFloor returns the largest version of the features a package uses; install and activation reject a package whose declared version is lower.

version, evidence, err := themecore.DetectThemeAPIVersion(p)
floor, evidence, err := themecore.DetectFeatureFloor(p)

Each Signal names a feature, the version that introduced it and the first place in the package that shows it.

The built-ins and Markdown elements open to themes are rows too (compono.builtin.*, compono.markdown.*), and the sets derive from them:

themecore.Builtins()         // "LINK", "IMAGE", "NAV", …
themecore.MarkdownElements() // "heading", "p", "br", …

Address validation, the detectors and the undefined call rule look only at these sets, not at Compono's lists, so a built-in Compono adds stays closed to themes until its row is added. A built-in detector looks for a reference in contract.toml that resolves to the built-in (a call, a value or a default) and for an address of attributes.toml whose type filter is it. A name the contract defines as a component, in [[components]] or as a local, is never a built-in. A Markdown detector looks for the element in contract.toml. The place of a signal in contract.toml is the key of the source and the line in it, such as contract.toml:layouts[0].source:4.

The detectors that read contract.toml depend on how Compono parses it. themecore embeds a detector corpus (detectorcorpus/: contract inputs and the signals each must give), and a program that builds themecore in runs it in its tests, with the Compono version it is built with:

func TestDetectorCorpus(t *testing.T) {
	if err := themecore.DetectorCorpus(); err != nil {
		t.Fatal(err)
	}
}

License

MIT

Documentation

Overview

Package themecore implements the Umono theme engine. This file defines the v0.9 media-feature dictionary, the media types, accepted length and resolution units, and the helpers that validate a media feature's parenthesised content.

Package themecore is the shared static analysis package of the Umono theme system. The Umono runtime and the Umono CLI use it to check theme packages.

Index

Constants

This section is empty.

Variables

View Source
var Breaks = []Break{
	{At: 1, Reason: "First Theme API version"},
}

Breaks are the break rows of the ledger.

View Source
var ErrOutdated = errors.New("themecore is older than the contract")

ErrOutdated is wrapped by the error for a contract that uses Compono syntax or a component that this themecore does not know. A published contract has neither, so the program is older than the contract: CLI reports that CLI is out of date, Umono that Umono is out of date.

View Source
var Features = []Feature{
	{ID: "pkg.attributes", IntroducedIn: 1, Detect: detectFile("attributes.toml")},
	{ID: "attr.class", IntroducedIn: 1, Detect: detectAddress(hasClass)},
	{ID: "attr.sizes", IntroducedIn: 1, Detect: detectAddress(hasSizes)},
	{ID: "attr.address.layout", IntroducedIn: 1, Detect: detectAddress(layoutAddress)},
	{ID: "attr.address.layout-builtin", IntroducedIn: 1, Detect: detectAddress(layoutBuiltinAddress)},
	{ID: "attr.address.layout-component-builtin", IntroducedIn: 1, Detect: detectAddress(layoutComponentBuiltinAddress)},
	{ID: "pkg.license", IntroducedIn: 1, Detect: detectFile("LICENSE")},
	{ID: "assets.woff2", IntroducedIn: 1, Detect: detectAsset(".woff2")},
	{ID: "assets.woff", IntroducedIn: 1, Detect: detectAsset(".woff")},
	{ID: "assets.png", IntroducedIn: 1, Detect: detectAsset(".png")},
	{ID: "assets.jpeg", IntroducedIn: 1, Detect: detectAsset(".jpeg")},
	{ID: "assets.jpg", IntroducedIn: 1, Detect: detectAsset(".jpg")},
	{ID: "assets.webp", IntroducedIn: 1, Detect: detectAsset(".webp")},
	{ID: "assets.avif", IntroducedIn: 1, Detect: detectAsset(".avif")},
	{ID: "assets.gif", IntroducedIn: 1, Detect: detectAsset(".gif")},
	{ID: "css.at.media", IntroducedIn: 1, Detect: detectAtRule("media")},
	{ID: "css.at.supports", IntroducedIn: 1, Detect: detectAtRule("supports")},
	{ID: "css.at.layer", IntroducedIn: 1, Detect: detectAtRule("layer")},
	{ID: "css.at.font-face", IntroducedIn: 1, Detect: detectAtRule("font-face")},
	{ID: "css.at.keyframes", IntroducedIn: 1, Detect: detectAtRule("keyframes")},
	{ID: "compono.builtin.link", IntroducedIn: 1, Detect: detectBuiltin("LINK")},
	{ID: "compono.builtin.image", IntroducedIn: 1, Detect: detectBuiltin("IMAGE")},
	{ID: "compono.builtin.nav", IntroducedIn: 1, Detect: detectBuiltin("NAV")},
	{ID: "compono.builtin.list", IntroducedIn: 1, Detect: detectBuiltin("LIST")},
	{ID: "compono.builtin.details", IntroducedIn: 1, Detect: detectBuiltin("DETAILS")},
	{ID: "compono.builtin.header", IntroducedIn: 1, Detect: detectBuiltin("HEADER")},
	{ID: "compono.builtin.main", IntroducedIn: 1, Detect: detectBuiltin("MAIN")},
	{ID: "compono.builtin.footer", IntroducedIn: 1, Detect: detectBuiltin("FOOTER")},
	{ID: "compono.builtin.article", IntroducedIn: 1, Detect: detectBuiltin("ARTICLE")},
	{ID: "compono.builtin.aside", IntroducedIn: 1, Detect: detectBuiltin("ASIDE")},
	{ID: "compono.markdown.heading", IntroducedIn: 1, Detect: detectMarkdown("heading")},
	{ID: "compono.markdown.p", IntroducedIn: 1, Detect: detectMarkdown("p")},
	{ID: "compono.markdown.br", IntroducedIn: 1, Detect: detectMarkdown("br")},
	{ID: "compono.markdown.em", IntroducedIn: 1, Detect: detectMarkdown("em")},
	{ID: "compono.markdown.strong", IntroducedIn: 1, Detect: detectMarkdown("strong")},
	{ID: "compono.markdown.link", IntroducedIn: 1, Detect: detectMarkdown("link")},
	{ID: "compono.markdown.inline-code", IntroducedIn: 1, Detect: detectMarkdown("inline-code")},
	{ID: "compono.markdown.code-block", IntroducedIn: 1, Detect: detectMarkdown("code-block")},
	{ID: "compono.markdown.list", IntroducedIn: 1, Detect: detectMarkdown("list")},
	{ID: "compono.markdown.blockquote", IntroducedIn: 1, Detect: detectMarkdown("blockquote")},
	{ID: "compono.markdown.hr", IntroducedIn: 1, Detect: detectMarkdown("hr")},
	{ID: "compono.markdown.table", IntroducedIn: 1, Detect: detectMarkdown("table")},
}

Features are the feature rows of the ledger.

Functions

func Builtins

func Builtins() []string

Builtins returns the names of the built-ins open to themes, from the compono.builtin.* rows of Features in ledger order: the last segment of the ID in upper case, with "-" as "_". Address validation, the detectors and the undefined call rule look only at this set, not at Compono's list. It returns a new slice on every call.

func CheckSource

func CheckSource(dir string, contract []byte) (Package, BuildReport, error)

CheckSource runs the checks of build and of the dev proxy on the development directory dir. contract is the file of the contract that theme.toml refers to, from the CLI's cache; themecore does not know the cache layout. The order is that of the static check table: the package limits and content, theme.toml, contract.toml, the assets, Transform, the files the CSS URLs point to, attributes.toml against the contract and LICENSE. The returned package is what goes into the built file, the canonical CSS included; the CLI adds built.toml and then checksums.json.

func Checksums

func Checksums(p Package) ([]byte, error)

Checksums returns the content of checksums.json for p: the sha256 of every file except checksums.json itself. The output is deterministic: the same files give the same bytes, and so the same release.

func DetectorCorpus

func DetectorCorpus() error

DetectorCorpus runs the detectors on every case of the detector corpus and returns an error for the first case whose signals differ from its golden file or whose contract does not parse. The detectors read contracts with Compono, so Umono and CLI call it in their tests: a Compono version that changes the signals fails there.

func FallbackCSS

func FallbackCSS() []byte

FallbackCSS returns the canonical CSS of the fallback theme: the Transform output of FallbackSource. Umono serves it at /_theme/fallback-{release}/theme.css.

func FallbackSource

func FallbackSource() []byte

FallbackSource returns the source of the fallback theme, the default look of pages without an active theme, in the author grammar. It is also the starting assets/theme.css of umono theme create.

func MarkdownElements

func MarkdownElements() []string

MarkdownElements returns the names of the Markdown elements open to themes, from the compono.markdown.* rows of Features in ledger order: the last segment of the ID, the name of the element's spec. It returns a new slice on every call.

func TOMLConformance

func TOMLConformance() error

TOMLConformance returns an error if the TOML decoder of this binary accepts a document that is not TOML v1.0.0 or rejects one that is. The go-toml version in a binary is chosen by the whole build, so Umono and CLI call it in their tests to check the decoder they are built with.

func ThemeAPICurrent

func ThemeAPICurrent() int

ThemeAPICurrent is the newest Theme API version: the largest IntroducedIn of Features and At of Breaks.

func ThemeAPIMin

func ThemeAPIMin() int

ThemeAPIMin is the oldest Theme API version still accepted: the largest At of Breaks.

func Transform

func Transform(css []byte) (out []byte, err error)

Transform turns the theme author's assets/theme.css into the canonical CSS that goes into the package (themecore, CSS Scoping). It drops a leading byte order mark and a leading @charset "UTF-8"; statement, parses the CSS strictly, checks the author grammar and serializes the canonical grammar, in which every style rule is inside an @scope (umono-root) block. The output always passes Verify; if it does not, Transform returns an error instead of the output, because that is a bug in themecore (Karar 11).

func Verify

func Verify(css []byte) error

Verify checks that css, the assets/theme.css of a package, is in the canonical grammar (themecore, Karar 9). It is structural: it does not compare bytes with Transform's output, so the serialization format is not part of the Theme API (Karar 10).

func VerifyChecksums

func VerifyChecksums(p Package) error

VerifyChecksums checks p against its checksums.json. It looks at the values, not the format: a reformatted file with the right values is accepted.

Types

type AttributeTable

type AttributeTable struct {
	// contains filtered or unexported fields
}

AttributeTable is attributes.toml compiled against a contract: the attributes of every address. It is a plain value; rendering reads no file.

func CompileAttributes

func CompileAttributes(b []byte, c Contract) (AttributeTable, error)

CompileAttributes validates attributes.toml against the contract c and compiles it into an AttributeTable. Every address is layout, layout BUILTIN or layout COMPONENT BUILTIN with parts separated by a single space, and its names are layouts and exposed components of c and built-ins of Builtins. A name c defines as a component is never a built-in. Only class and sizes are allowed, sizes only on an address that ends with IMAGE.

func (AttributeTable) Lookup

func (t AttributeTable) Lookup(layout, builtin string, chain []compono.Frame) map[string]string

Lookup returns the attributes of the built-in rendered with the frame chain chain on a page bound to layout, or nil. The scope is the innermost global frame whose scope path is [UMONO_LAYOUT] and whose name is an exposed component, otherwise the layout; the address is layout [SCOPE] BUILTIN. It fits compono.AttributeHookFunc once layout is bound, and returns a new map on every call.

func (AttributeTable) Root

func (t AttributeTable) Root(layout string) map[string]string

Root returns the attributes of the layout address, which the runtime writes on <umono-root>, or nil. It returns a new map on every call.

type Author

type Author struct {
	Name string  `toml:"name"`
	URL  *string `toml:"url"`
}

Author is an entry of the authors array.

type Break

type Break struct {
	At     int
	Reason string
}

Break is a narrowing of the Theme API. It raises both ThemeAPICurrent and ThemeAPIMin to At.

type BuildReport

type BuildReport struct {
	// Version is theme-api-version, from DetectThemeAPIVersion. The CLI
	// writes it to built.toml.
	Version  int
	Evidence []Signal
	// Warnings are those of Lint. They do not stop the build.
	Warnings []Warning
}

BuildReport is the outcome of CheckSource.

type Built

type Built struct {
	Manifest
	ThemeAPIVersion int       `toml:"theme-api-version"`
	BuiltAt         time.Time `toml:"built-at"`
}

Built is built.toml, the manifest of a package. The CLI writes it from theme.toml during build: the keys of theme.toml are copied unchanged and theme-api-version and built-at are added.

func ParseBuilt

func ParseBuilt(b []byte) (Built, error)

ParseBuilt parses and validates built.toml.

func (Built) CheckInstallDir

func (b Built) CheckInstallDir(dir string) error

CheckInstallDir checks that the install path dir, themes/{org}/{id}/{version} or themes-pending/{org}/{id}/{version}, matches id and version (built.toml, Karar 5). Only the last three elements of dir are compared.

type Component

type Component struct {
	Name   string `toml:"name"`
	Source string `toml:"source"`
	Expose bool   `toml:"expose"`
}

Component is an entry of the components array. Expose tells whether the component is a scope for addressing.

type Contract

type Contract struct {
	Schema      string      `toml:"schema"`
	ID          string      `toml:"id"`
	Version     string      `toml:"version"`
	Description *string     `toml:"description"`
	Layouts     []Layout    `toml:"layouts"`
	Components  []Component `toml:"components"`
}

Contract is contract.toml. Contracts are published on umono.dev and copied into every theme package unchanged.

func ParseContract

func ParseContract(b []byte) (Contract, error)

ParseContract parses and validates contract.toml: the structural layer (types, required keys, grammars and uniqueness), then the sources, which are parsed with Compono. A source with a grammar error, or a call that resolves to no local, contract component or built-in, is rejected with an error that wraps ErrOutdated.

type DetectorFunc

type DetectorFunc func(p Package) (where string, found bool)

DetectorFunc reports whether a package uses a feature. where is the first place in the package that shows it, for example "assets/icons/cart.svg". Detectors look at the package itself: the canonical CSS and the package's own contract.toml, never the author's sources.

type DevResult

type DevResult struct {
	// Built is the built.toml CheckDev validated, with the id dev/dev,
	// which ParseBuilt rejects.
	Built Built
	// ContractSource is the unchanged bytes of contract.toml. Umono adds
	// its contract record from it.
	ContractSource []byte
	Contract       Contract
	// Floor is DetectFeatureFloor of the dev package.
	Floor    int
	Evidence []Signal
	// CSS is the Transform output of assets/theme.css, nil if CSSErr is set.
	// Umono answers the theme.css request with an error status then.
	CSS    []byte
	CSSErr error
	// Attributes is the compiled attributes.toml, an empty table if there is
	// none or AttributesErr is set. Pages are rendered without injection then.
	Attributes    AttributeTable
	AttributesErr error
	// AssetErrs maps the path of every file under assets/ that breaks the
	// rules to its error. Umono answers its request with 404.
	AssetErrs map[string]error
}

DevResult is the outcome of CheckDev. The errors of one check are returned here and do not stop dev mode; Umono handles each on its own.

func CheckDev

func CheckDev(dir, root string) (DevResult, error)

CheckDev runs the checks of activation on the dev directory dir of Umono's dev mode, themes/dev/dev/0.0.0/, with the privileges of dev/dev, which exist only here. root is the theme project, UMONO_THEME_DEV. There is no checksums.json; assets/, attributes.toml and contract.toml may be symlinks into root; theme.css goes through Transform instead of Verify; built.toml has the id dev/dev and the version 0.0.0. The other checks run as in CheckPackage. The package limits and content, built.toml, contract.toml, LICENSE and the feature floor are fatal: the returned error. CheckDev keeps no cache; Umono calls it on every request.

type Feature

type Feature struct {
	ID           string
	IntroducedIn int
	Detect       DetectorFunc
}

Feature is a capability of the Theme API and the version that introduced it. Every feature has a detector.

type Layout

type Layout struct {
	ID          string  `toml:"id"`
	Description *string `toml:"description"`
	Source      string  `toml:"source"`
}

Layout is an entry of the layouts array.

type Manifest

type Manifest struct {
	Schema      string   `toml:"schema"`
	ID          string   `toml:"id"`
	Contract    string   `toml:"contract"`
	Name        string   `toml:"name"`
	Version     string   `toml:"version"`
	Description *string  `toml:"description"`
	License     *string  `toml:"license"`
	Homepage    *string  `toml:"homepage"`
	Repository  *string  `toml:"repository"`
	Authors     []Author `toml:"authors"`
}

Manifest is theme.toml, the manifest the theme author writes. The CLI reads it; it does not go into the package. Optional keys are nil when absent.

func ParseManifest

func ParseManifest(b []byte) (Manifest, error)

ParseManifest parses and validates theme.toml.

type Package

type Package struct {
	// Files maps the path of each file to its content. Paths are relative to
	// the package root and use "/" as the separator. Directories are not
	// listed; they follow from the paths.
	Files map[string][]byte
}

Package is the content of a theme package. During build it is the state that goes into the built file, canonical CSS included. During install and activation it is the package itself, read from the built file or from the installed directory.

func ReadDir

func ReadDir(dir string) (Package, error)

ReadDir reads the installed form of a package from dir. The package limits are checked with the sizes from the file system's metadata before any file is read. Symlinks are not followed: anything that is not a regular file or a directory is rejected, and so are hard links. Hard links can only be detected on Unix, so ReadDir fails on other platforms.

func ReadZip

func ReadZip(r io.ReaderAt, size int64) (Package, error)

ReadZip reads a built file. size is the file's size from the file system's metadata. The package limits are checked before anything is read: the built file's size first, then the sizes the entries declare. While an entry is decompressed its real bytes are counted, and reading stops as soon as a limit is exceeded. Entry names, types, encryption and compression methods are checked before any entry is decompressed.

type Result

type Result struct {
	Contract Contract
	// Attributes is the compiled attributes.toml, an empty table if the
	// package has none.
	Attributes AttributeTable
	// Floor is DetectFeatureFloor of the package.
	Floor    int
	Evidence []Signal
}

Result is the outcome of CheckPackage.

func CheckPackage

func CheckPackage(p Package) (Result, error)

CheckPackage runs the checks of install and activation on p, a package read with ReadZip or ReadDir, which check the package limits and content. The order is that of the static check table: the required files, built.toml, contract.toml and its match with built.toml, the assets, Verify of the canonical CSS, the files its URLs point to, attributes.toml against the contract, LICENSE and, last, the feature floor: a theme-api-version lower than the features the package uses is rejected. checksums.json and the Theme API range are checked by the caller before.

type Signal

type Signal struct {
	Feature      string // "assets.svg"
	IntroducedIn int    // 3
	Where        string // "assets/icons/cart.svg"
}

Signal is the evidence that a package uses a feature: the feature's ID, the version that introduced it and the first place in the package that shows it.

func DetectFeatureFloor

func DetectFeatureFloor(p Package) (floor int, evidence []Signal, err error)

DetectFeatureFloor is the first term of theme-api-version: the largest IntroducedIn of the features the package uses, or 0 if it uses none. evidence has one signal per detected feature, in ledger order. Install and activation call it to verify the version that built.toml declares.

func DetectThemeAPIVersion

func DetectThemeAPIVersion(p Package) (version int, evidence []Signal, err error)

DetectThemeAPIVersion is theme-api-version: the larger of DetectFeatureFloor and ThemeAPIMin. evidence is DetectFeatureFloor's. Build calls it to declare the version.

type Warning

type Warning struct {
	Message string
	Where   string
}

Warning is a finding of Lint. Where is a selector or an address, for example ".layout-post h1" or "post-list MAIN_MENU LINK".

func Lint

func Lint(p Package) []Warning

Lint returns warnings for mistakes that are valid CSS but never match. Only development tools call it; it is not part of the Theme API, and install and activation do not call it. It reports root classes used without :scope and addresses whose component the layout never renders (themecore, Kök Sınıfı Uyarısı and Ölü Adres Uyarısı). A package whose attributes.toml, contract.toml or assets/theme.css cannot be read gets no warnings; those errors are reported by the checks.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL