joetjen.net
EN DE
Konfigurationssprache

Aufbau einer Datei

Kopfzeile, Kommentare, Schlüssel und Blöcke — und was passiert, wenn derselbe Schlüssel mehrfach geschrieben wird.

Format 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:

SigilWirkung
~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
-keyDen 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, danach 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" => %{}}