Aufbau einer Datei
Kopfzeile, Kommentare, Schlüssel und Blöcke — und was passiert, wenn derselbe Schlüssel mehrfach geschrieben wird.
Die Versionszeile
Jede Datei — auch eine importierte — beginnt mit #@version und der Formatversion, gegen die sie geschrieben ist. Das = ist optional, die Version folgt SemVer. Die kleinste gültige Datei besteht nur aus dieser Zeile:
#@version = 1.0
%{}Leerraum und Kommentare
Anweisungen werden durch Leerraum getrennt; eine pro Zeile ist die Konvention. Innerhalb eines Blocks dürfen auch Kommas trennen. Ein # mit folgendem Leerzeichen beginnt einen Kommentar bis zum Zeilenende. Blockkommentare gibt es nicht.
#@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}}Schlüssel
Ein nackter Schlüssel beginnt mit einem Buchstaben und setzt sich mit Buchstaben, Ziffern, _, + und - fort; am Ende darf ? oder ! stehen. logger, handle-otp-reports, enabled? und ready! sind also Schlüssel. Dieselbe Regel gilt für Variablen- und Atom-Namen.
Überall reserviert: nil, true, false, inf, +inf, -inf. Die Wörter for, in, from, as und import haben nur dort Bedeutung, wo eine Schleife oder ein Import sie erwartet, und sind sonst gewöhnliche Schlüssel.
Schlüssel in Anführungszeichen
Alles andere — Leerzeichen, Punkte, ein reserviertes Wort — steht in Anführungszeichen. Doppelte Anführungszeichen erlauben Escapes, einfache sind wörtlich. Ein Schlüssel in Anführungszeichen kann ein Segment eines Punkt-Pfads sein.
headers { "Content-Type" = "application/json" }
foo."bar baz".dronf = "fnord"
"true" = 1%{"headers" => %{"Content-Type" => "application/json"},
"foo" => %{"bar baz" => %{"dronf" => "fnord"}},
"true" => 1}Geheime Schlüssel
Ein * vor einem beliebigen Schlüsselsegment markiert den Wert als geheim. Der Wert selbst bleibt unverändert; eine Implementierung verpackt ihn so, dass er beim Ausgeben oder Loggen geschwärzt wird. Die Geheimhaltung wandert mit: Eine Zeichenkette, die ein Geheimnis interpoliert, ist selbst geheim und wird mit geschwärzter Stelle ausgegeben.
*password = "hunter2"
db.*token = "t0k3n"
url = "postgres://app:%{password}@db.internal"%{"password" => [~~REDACTED~~],
"db" => %{"token" => [~~REDACTED~~]},
"url" => "postgres://app:[~~REDACTED~~]@db.internal"}Zuweisung
Eine Anweisung weist einem Schlüssel einen Wert zu: key = value. Laut Spezifikation darf das = entfallen (port 8080), die Referenzimplementierung liest einen einzelnen nackten Schlüssel mit folgender Zeichenkette aber als Import — = zu schreiben ist daher die portable Wahl. Ein Doppelpunkt ist nie eine Zuweisung.
Pfade und Blöcke
Ein Block { … } fasst Anweisungen unter einem Schlüssel zusammen; ein Punkt-Pfad greift direkt in verschachtelte Blöcke. Beides erzeugt denselben Baum, und beides lässt sich frei mischen. Diese vier Zeilen ergeben dasselbe:
foo { bar { baz = "dronf" } }
foo { bar.baz = "dronf" }
foo = { bar = { baz = "dronf" } }
foo.bar.baz = "dronf"%{"foo" => %{"bar" => %{"baz" => "dronf"}}}Blöcke sind CASCs einziger Map-Typ, und ein Block steht nur auf der rechten Seite einer Anweisung. Eine Liste kann Listen und Tupel enthalten, aber keine Blöcke.
Deaktivierte Anweisungen
Ein # direkt vor einer Anweisung schaltet sie ab, egal über wie viele Zeilen sie reicht. Der Schlüssel fehlt im Ergebnis — er ist nicht nil. So lässt sich eine Konfiguration ohne eine Einstellung ausprobieren, ohne sie zu verlieren.
section {
#ignored = 1
#sub { remove = yes }
kept = true
}%{"section" => %{"kept" => true}}Zusammenführen und Überschreiben
Ein Schlüssel darf beliebig oft geschrieben werden, in einer Datei oder über Imports hinweg. Die Anweisungen wirken der Reihe nach:
- Blöcke werden tief zusammengeführt, auf jeder Ebene.
- Skalare, Listen und Tupel werden ersetzt: die letzte Zuweisung gewinnt.
- Ein Typwechsel ersetzt ebenfalls: ein Block nach einem Skalar ist ein Block.
Wo der Normalfall nicht passt, sagt ein Sigil vor dem Schlüssel, was gemeint ist:
| Sigil | Wirkung |
|---|---|
~key { … } | Den Block ersetzen statt hineinzumischen |
+key = [ … ] | An eine Liste anhängen (bei neuem Schlüssel eine normale Zuweisung) |
-key = [ … ] | Diese Elemente aus einer Liste entfernen, nach Gleichheit |
-key | Den Schlüssel löschen; ein fehlender Schlüssel bleibt ohne Wirkung |
Tupel werden nie gemischt: + oder - auf ein Tupel ist ein Fehler. Kommen mehrere Präfixe zusammen, ist die Reihenfolge #, dann das Sigil, dann *, etwa #~*database { … }.
# 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" => %{}}