Advanced Topics
In this chapter you will learn:
- How the metadata system works
- How to use sets for unique collections
- How to search deep structures with
deep-findanddeep-query - Format specifiers and formatting techniques
- Type predicates and other utilities
The Metadata System
Every value in eucalypt can carry metadata: a block of additional information that does not appear in the rendered output but can be inspected and used programmatically.
Attaching Metadata
Use the // operator to attach metadata to a value:
answer: 42 // { note: "the answer to everything" }
The value 42 is rendered normally; the metadata is hidden:
answer: 42
Reading Metadata
Use meta to retrieve the metadata block:
answer: 42 // { note: "the answer to everything" }
note: meta(answer).note
answer: 42
note: the answer to everything
Deep Merge Metadata
Use //<< to deep-merge additional metadata onto existing metadata:
x: 1 // { a: 1 }
y: x //<< { b: 2 }
result: meta(y)
x: 1
y: 1
result:
a: 1
b: 2
Declaration Metadata
Declaration metadata (written with the backtick syntax) is separate from value metadata. It controls how eucalypt processes the declaration:
` { doc: "A helper" export: :suppress }
helper(x): x + 1
Key metadata properties:
doc-- documentation stringexport: :suppress-- hide from outputtarget-- mark as an export targetimport-- import other filesassociates/precedence-- operator fixity (see Operators for details)
YAML Tags
Metadata can carry a tag key which renders as a YAML tag:
ref: :my-resource // { tag: "!Ref" }
ref: !Ref my-resource
This is useful for generating CloudFormation, Kubernetes, and other tagged YAML formats.
Assertions with //= and //=>
The //= operator asserts equality at runtime:
result: 2 + 2 //= 4 # panics if not equal
The //=> operator additionally stores the assertion as metadata:
checked: 2 + 2 //=> 4
m: meta(checked) # contains the assertion
Sets
Sets are unordered collections of unique values, provided by the
set namespace.
Creating Sets
eu -e '[1, 2, 2, 3, 3, 3] set.from-list set.to-list'
- 1
- 2
- 3
Duplicates are removed and elements are sorted.
The empty set is written as ∅:
eu -e '∅ set.to-list'
[]
Membership and Size
eu -e '[1, 2, 3] set.from-list set.contains?(2)'
true
eu -e '[1, 2, 3] set.from-list set.size'
3
eu -e '∅ set.empty?'
true
Adding and Removing
eu -e '∅ set.add(1) set.add(2) set.add(1) set.to-list'
- 1
- 2
eu -e '[1, 2, 3] set.from-list set.remove(2) set.to-list'
- 1
- 3
Set Algebra
Union:
a: [1, 2] set.from-list
b: [2, 3] set.from-list
result: a set.union(b) set.to-list
result:
- 1
- 2
- 3
Intersection:
a: [1, 2, 3] set.from-list
b: [2, 3, 4] set.from-list
result: a set.intersect(b) set.to-list
result:
- 2
- 3
Difference:
a: [1, 2, 3] set.from-list
b: [2, 3] set.from-list
result: a set.diff(b) set.to-list
result:
- 1
Deep Find
deep-find recursively searches a nested block structure for all
values associated with a given key name:
eu -e 'deep-find("host", { server: { host: "10.0.0.1" db: { host: "10.0.0.2" } } })'
- 10.0.0.1
- 10.0.0.2
deep-find-first
Return just the first match, or a default:
eu -e 'deep-find-first("host", "unknown", { server: { host: "10.0.0.1" } })'
10.0.0.1
deep-find-paths
Return the key paths to each match:
eu -e 'deep-find-paths("host", { server: { host: "a" db: { host: "b" } } })'
- - server
- host
- - server
- db
- host
Deep Query
deep-query provides a more powerful pattern-based search using
dot-separated patterns with wildcards.
Bare Key (Recursive Search)
A bare key name searches recursively (equivalent to **.key):
eu -e 'deep-query("port", { web: { port: 80 } db: { port: 5432 } })'
- 80
- 5432
Dotted Path
A dotted path matches a specific path:
eu -e 'deep-query("server.host", { server: { host: "10.0.0.1" port: 80 } })'
- 10.0.0.1
Wildcard *
* matches exactly one level:
eu -e 'deep-query("*.port", { web: { port: 80 } db: { port: 5432 } name: "app" })'
- 80
- 5432
Double Wildcard **
** matches any depth:
eu -e 'deep-query("config.**.port", { config: { port: 9090 nested: { deep: { port: 3000 } } } })'
- 9090
- 3000
Variants
deep-query-first(pattern, default, block)-- first match or defaultdeep-query-paths(pattern, block)-- key paths of matches
Type Predicates
Test the type of a value:
eu -e '{ a: 1 } block?'
true
eu -e '[1, 2] list?'
true
Sorting
qsort
Sort with a custom comparison:
eu -e '["banana", "apple", "cherry"] qsort(<)'
- apple
- banana
- cherry
Sort by a derived key. Here str.letters ; count is a forward
composition (see
Functions and Combinators) that
extracts the letter count:
` :suppress
shorter(a, b): (a str.letters count) < (b str.letters count)
words: ["one", "two", "three", "four", "five", "six"]
by-length: words qsort(shorter)
words:
- one
- two
- three
- four
- five
- six
by-length:
- one
- two
- six
- four
- five
- three
The prelude also provides sort-by(key-fn, cmp) as a convenience for
this pattern:
by-length: words sort-by(str.letters ; count, <)
sort-nums
Sort numbers in ascending order:
eu -e '[30, 10, 20] sort-nums'
- 10
- 20
- 30
Grouping
group-by
Group list elements by a key function:
items: [
{ type: "fruit" name: "apple" }
{ type: "veg" name: "carrot" }
{ type: "fruit" name: "banana" }
]
grouped: items group-by(.type)
items:
- type: fruit
name: apple
- type: veg
name: carrot
- type: fruit
name: banana
grouped:
fruit:
- type: fruit
name: apple
- type: fruit
name: banana
veg:
- type: veg
name: carrot
Format Specifiers
Format specifiers in interpolation control output formatting. They use printf-style codes after a colon inside the interpolation braces.
results: {
n: 42
padded: "{n:%06d}"
pi: 3.14159
float: "{pi:%.2f}"
h: 255
hex: "{h:%x}"
}
results:
padded: '000042'
float: '3.14'
hex: ff
Available Format Codes
| Code | Description | Example |
|---|---|---|
%d, %i | Signed decimal integer | {42:%d} → 42 |
%u | Unsigned decimal integer | {42:%u} → 42 |
%o | Octal | {255:%o} → 377 |
%x, %X | Hexadecimal (lower/upper) | {255:%x} → ff |
%f, %F | Decimal notation | {3.14:%.1f} → 3.1 |
%e, %E | Scientific notation | {1000:%e} → 1e3 |
%g, %G | Auto (decimal or scientific) | {0.001:%g} → 0.001 |
%s | String | {:hello:%s} → hello |
Flags and Modifiers
%-— left-align%+— prepend+for positive numbers%0— zero-padding (e.g.%06d)%#— alternate form (e.g.0xprefix for hex)- Width:
%10s— minimum field width - Precision:
%.2f— decimal places for floats
See also the Strings and Text chapter for more on string interpolation.
Version Assertions
Assert a minimum version of eu:
_ : eu.requires(">=0.3.0")
Access build metadata:
info: {
version: eu.build.version
prelude: eu.prelude.version
}
Key Concepts
- Metadata (
//) attaches hidden information to values;metaretrieves it - Sets (
set.*) provide unique collections with union, intersection, and difference - Deep find recursively searches nested blocks by key name
- Deep query supports pattern-based search with
*and**wildcards - Type predicates (
block?,list?) test value types qsortsorts with custom comparators; format specifiers control numeric outputeu.requiresasserts a minimum version for compatibility