joetjen.net
EN DE
Configuration language

File structure

The header, comments, keys and blocks — and what happens when the same key is written more than once.

Format 1.0

Whitespace and comments

Statements are separated by whitespace; one per line is the convention. Inside a block, commas may separate statements as well. A # followed by a space starts a comment that runs to the end of the line. There are no block comments.

#@version = 1.0
# A comment runs to the end of the line.
name = "api"   # so does a trailing one
limits { soft = 10, hard = 20 }
%{"name" => "api", "limits" => %{"soft" => 10, "hard" => 20}}

Keys

A bare key starts with a letter and continues with letters, digits, _, + and -; it may end in ? or !. So logger, handle-otp-reports, enabled? and ready! are all keys. The same rule names variables and atoms.

Reserved everywhere: nil, true, false, inf, +inf, -inf. The words for, in, from, as and import only mean something where a loop or an import expects them, and are ordinary keys elsewhere.

Quoted keys

Anything else — spaces, dots, a reserved word — goes in quotes. Double quotes allow escapes, single quotes are literal. A quoted key can be one segment of a dotted path.

headers { "Content-Type" = "application/json" }
foo."bar baz".dronf = "fnord"
"true" = 1
%{"headers" => %{"Content-Type" => "application/json"},
  "foo" => %{"bar baz" => %{"dronf" => "fnord"}},
  "true" => 1}

Secret keys

A * in front of any key segment marks the value as secret. The value itself is unchanged; an implementation wraps it so that it is redacted when printed or logged. Secrecy follows the value: a string that interpolates a secret is itself secret, and prints with that part redacted.

*password = "hunter2"
db.*token = "t0k3n"
url = "postgres://app:%{password}@db.internal"
%{"password" => [~~REDACTED~~],
  "db" => %{"token" => [~~REDACTED~~]},
  "url" => "postgres://app:[~~REDACTED~~]@db.internal"}

Assignment

A statement assigns a value to a key: key = value. The specification lets you drop the = (port 8080), but the reference implementation misreads a single bare key followed by a string as an import, so writing = is the portable choice. A colon is never an assignment.

Paths and blocks

A block { … } groups statements under a key; a dotted path reaches into nested blocks directly. Both build the same tree, and they mix freely. These four lines produce the identical result:

foo { bar { baz = "dronf" } }
foo { bar.baz = "dronf" }
foo = { bar = { baz = "dronf" } }
foo.bar.baz = "dronf"
%{"foo" => %{"bar" => %{"baz" => "dronf"}}}

Blocks are CASC’s only map type, and a block stands only on the right-hand side of a statement. A list can hold lists and tuples, but not blocks.

Disabled statements

A # written directly against a statement switches it off, however many lines it spans. The key is absent from the result — not nil. It is the quick way to try a configuration without a setting and keep it at hand.

section {
  #ignored = 1
  #sub { remove = yes }
  kept = true
}
%{"section" => %{"kept" => true}}

Merging and overrides

A key may be written any number of times, in one file or across imports. Statements apply in order:

  • Blocks deep-merge, at every depth.
  • Scalars, lists and tuples are replaced: the last write wins.
  • A change of type replaces too: a block after a scalar is a block.

When the default is not what you want, a sigil in front of the key says so:

SigilEffect
~key { … }Replace the block instead of merging into it
+key = [ … ]Append to a list (a plain assignment if the key is new)
-key = [ … ]Remove these elements from a list, by equality
-keyDelete the key; deleting a missing key does nothing

Tuples are never merged: + or - against a tuple is an error. When several prefixes combine, the order is #, then the sigil, then *, as in #~*database { … }.

base.casc, then override.casc
# base.casc
server { host = "0.0.0.0", port = 8080 }
tags = ["a", "b", "c"]
feature.legacy_mode = true

# override.casc
~server { port = 9090 }
+tags = ["d"]
-tags = ["b"]
-feature.legacy_mode
%{"server" => %{"port" => 9090},
  "tags" => ["a", "c", "d"],
  "feature" => %{}}