Library-authoring keyword cookbook¶
These are the keywords you write inside a .capy library to define a
source language — function, arg literal, arg capture, the
block_* openers, write, type, and the file-level directives. They
are the vocabulary of the author.
Looking for the
${ … }helpers you call inside a template (pascalCase,escapeHtml,indent, …)? Those are a different set — see the Built-in function cookbook.
Every keyword below is dispatched by the library parser in
infra/capy_lib_parser.go; the canonical if … end example is mirrored
by the CI-checked sample
samples/library-keywords/.
Maintenance rule. This list must stay in lock-step with the directive switches in
infra/capy_lib_parser.go. Add/rename/remove a directive → update this page in the same change. SeeCLAUDE.md.
The shape of a library¶
extension py # ← file-level directives
function if # ← define a source keyword
arg literal "if"
arg capture cond any
block_closer end
write `if ${cond}:
${indent 4 body}`
end
function end # ← a silent block closer
end
A .capy file is a flat list of top-level directives. The two that
do the heavy lifting are function (define a keyword users can write) and
type (define a custom capture type). Everything is opt-in: with an empty
library, every script is rejected — Capy ships zero grammar.
File-level directives¶
Written at column 0.
| Keyword | Form | What it does |
|---|---|---|
extension |
extension py |
Output file extension (sets the default output name / fenced-block hint). |
output_file |
output_file "dist/app.js" |
Fixed output path instead of stdout. |
description |
description "…" |
Library summary; shown by capy docs. |
name / version |
name "mylib" / version "1.0.0" |
Manifest fields. |
function |
function NAME … end |
Define a source keyword. See below. |
type |
type NAME … end |
Define a custom capture type. See Types. |
context |
context … end |
Seed initial context values (state the inner DSL reads/writes). |
comments |
comments … end |
Declare line-comment markers (# …). With none, scripts have no comment syntax. |
import |
import "other.capy" |
Merge another library's functions/types. |
preprocess |
preprocess + include "@import" … end |
Opt into text-level include directives (off by default). |
command |
command "NAME" … end |
Define a capy <lib> NAME CLI subcommand. |
file / file_template |
file "PATH" … end |
Multi-file output. See Multi-file output. |
impl / default_impl |
impl "d3" "impl/d3.capy" … end |
Swappable interface implementations. |
Defining a function¶
A function NAME … end block defines one source keyword. Inside it:
Matching the source — arg¶
The arg lines declare, in order, what the keyword matches on the
source line.
arg literal¶
arg literal "if" # match the exact token `if`
arg literal "->" "arrow" # optional 2nd token = a description
Matches a fixed token. A function with no arg literal (and not
declared bare) gets its own name auto-prepended as a leading literal —
so function say implicitly requires the token say first.
arg capture¶
Binds a named value you can interpolate in the template as ${NAME}.
arg capture cond any # capture an expression as `cond`
arg capture name ident # capture a single identifier
arg capture attrs attribute* # repeat a function-typed capture (0+)
arg capture params param+ sep "," join ", " # 1+, comma in, ", " out
arg capture label string default "Submit" # optional, trailing
The optional modifiers (all independent):
| Modifier | Role |
|---|---|
TYPE |
the capture type — a built-in or another function/type name (a nonterminal). |
* / + suffix |
repetition: * = zero-or-more, + = one-or-more (function-typed captures only). |
sep "X" |
input separator consumed between repetitions while parsing. |
join "Y" |
output separator inserted between rendered sub-results. |
default "V" |
makes a trailing capture optional; binds V when omitted. |
Opening a body — the block_* directives¶
This is how you know a function "can have a body": it declares exactly one of the directives below. A function with none of them is a single-line statement and takes no body. The loader rejects a function that sets more than one.
When a function opens a body, the rendered inner statements are exposed to
its template as the automatic local ${body} (see
block functions).
| Directive | Body delimited by | Use it for |
|---|---|---|
block_closer end |
indentation; closed by the named closer function (end) |
Python/YAML-style indented blocks |
block_open "{" close "}" |
explicit open/close delimiters | { … } brace blocks |
block_dedent |
the first DEDENT (no closer keyword) | CSS selectors, YAML sections |
block_verbatim end |
raw source bytes until the closer (no nested parsing) | code blocks, embedded HTML/SVG |
block_close_seq "</" name ">" |
an exact multi-token sequence (literals + capture refs) | matched-pair HTML/XML tags |
block_sections rescue finally closer end |
a main body plus named sub-sections | try/rescue/finally |
function if
arg literal "if"
arg capture cond any
block_closer end
write `if ${cond}:
${indent 4 body}`
end
The closer (end here) is itself a function — often silent
(function end … end), but it may emit text (e.g. a }).
Emitting output — write and template¶
The function body is inner-DSL statements. The two you'll use most:
| Statement | What it does |
|---|---|
write `…${x}…` |
Append a template string to this function's output. Backtick strings are multi-line; ${…} interpolates captures, ${body}, and helpers. |
template … end |
Multi-line template sugar — same ${…} rules, no backtick bookkeeping. Desugars to write. |
Other inner-DSL statements (for accumulating state across calls) include
set, append, prepend, merge, delete, if, for/loop, let,
and exec. See the Inner DSL reference.
Function modifiers¶
| Keyword | Effect |
|---|---|
description "…" |
Per-function doc, surfaced by capy docs and Introspect(). |
priority N |
Disambiguates overlapping matches — higher wins. |
bare |
Opt out of the auto-prepended name keyword (for pure-capture nonterminals like param NAME TYPE). |
when_followed_by indent / when_not_followed_by indent |
Context-sensitive matching: pick this function only when the next line is / isn't indented. |
Built-in capture types¶
The TYPE in arg capture NAME TYPE. Any function or type name also
works as a type (a nonterminal — the capture matches that construct and
renders its template).
| Type | Matches |
|---|---|
any |
one expression (default if no type given) |
ident |
a single identifier |
dotted_ident |
a dotted path (a.b.c) |
raw |
exactly one token, verbatim |
word |
adjacent tokens with no source whitespace joined (--oneline, k8s/deploy.yaml) |
tail |
every remaining token on the line, original spacing preserved |
string |
a quoted string (runs through the expression parser) |
int / float / bool |
a numeric / boolean literal |
Trailing-capture tip: a nonterminal's last capture has no following literal to stop on, so prefer single-token types (
raw,ident,word) there — astringcan swallow a following delimiter like>as a comparison operator.
Defining a type¶
type NAME … end defines a reusable, validated capture type.
| Keyword | Form | What it does |
|---|---|---|
base |
base ident |
Build on a built-in type. |
pattern |
pattern "^[A-Z][A-Za-z0-9]*$" |
Regex the captured token must match. |
options |
options GET POST PUT |
Restrict to an enumerated set. |
group_open / group_close |
group_open "[" / group_close "]" |
Make the type a delimited inline group ([label]). |
description |
description "…" |
Doc string. |
See Types for the full treatment.
See also¶
- .capy library syntax — the formal grammar of a library file.
- Library authoring — a guided walkthrough.
- Block functions — every body mode in depth.
- Built-in function cookbook — the
${ … }template helpers. samples/library-keywords/— the runnable source for theif … endexample.