joetjen.net
EN DE
Konfigurationssprache

Das Konfigurationsformat CASC

CASC ist eine Konfigurationssprache mit echter Spezifikation: verschachtelte Blöcke, typisierte Werte von Zeitdauern bis zu IP-Bereichen, Variablen, Umgebungszugriffe, Verweise auf andere Schlüssel, Imports und Schleifen — in einer Syntax, die als Klartext lesbar bleibt.

Format 1.0

Ein erster Blick

Eine CASC-Datei ist eine Folge von Anweisungen, die zusammen einen Baum benannter Werte ergeben. Diese hier liest eine Umgebungsdatei, einige Umgebungsvariablen und eine gemeinsame Variable und verweist von einem Schlüssel auf zwei andere:

config.casc
#@version = 1.0

import "env/${COOPER_ENV}.casc"

@region = ${REGION:"eu-west"}

server {
  host = "0.0.0.0"
  port = !int(${PORT:8080})
}

database {
  host = "db.@{region}.internal"
  *password = ${DB_PASSWORD:?"DB_PASSWORD is required"}
  timeout = 500ms
  cache = 512MiB
}

health.url = "http://%{server.host}:%{server.port}/health"
  • #@version = 1.0 — jede Datei beginnt mit ihrer Formatversion.
  • import holt eine weitere Datei hinzu; ihr Pfad darf die Umgebung lesen. ${COOPER_ENV} nennt in jeder Implementierung die aktuelle Umgebung, etwa dev oder prod.
  • @region ist eine Variable, ${…} liest die Umgebung, %{…} verweist auf einen anderen Schlüssel des fertigen Baums.
  • 500ms und 512MiB sind Werte eigenen Typs, keine Zeichenketten, die später noch geparst werden müssen.
  • *password ist als geheim markiert: der Wert bleibt überall geschwärzt, wo er landet.
  • :?"…" macht eine fehlende Umgebungsvariable zu einem Ladefehler mit eigener Meldung.

Gestaltung

  • Eine Art von Map. Block und Punkt-Pfad sind zwei Schreibweisen desselben Baums, und doppelt geschriebene Blöcke werden zusammengeführt.
  • Typisierte Literale. Zeitdauern, Bytegrößen, Datum und Uhrzeit, IP-Adressen und Tupel sind Werte der Sprache selbst.
  • Drei Verweisarten, eine Grammatik. @{} für Variablen, ${} für die Umgebung und %{} für Konfigurationspfade teilen sich Defaults, Wächter, Indizes und Filter.
  • Ausdrückliches Zusammenführen. Spätere Zuweisungen gewinnen, Blöcke werden tief gemischt; die Sigils ~, + und - ersetzen, hängen an und entfernen, wenn genau das gemeint ist.
  • Nichts Unbekanntes rutscht durch. Ein Tag, Resolver oder Import-Schema, das niemand registriert hat, ist ein Ladefehler, der es beim Namen nennt.
  • Geheimnisse in der Syntax. Ein * am Schlüssel markiert den Wert als geheim, und wer ihn interpoliert, erhält ein ebenfalls geschwärztes Ergebnis.
  • Überall dieselbe Bedeutung. Ein Dokument ergibt in jeder Implementierung dieselbe Konfiguration, und ein abgelehntes scheitert mit derselben Fehlerkategorie.

Der Name ist ein Wortspiel: Ein Cooper ist ein Böttcher, und ein „casc“ ist ein Fass. Das Format ist das, was im Fass steckt.

Eckdaten

EigenschaftWert
Endung.casc
KodierungUTF-8
Kopfzeile#@version = 1.0 — Pflicht, in jeder Datei; davor dürfen nur Kommentare und Leerzeilen stehen
Kommentare# gefolgt von einem Leerzeichen, bis zum Zeilenende
ErgebnisEin Baum: Blöcke sind Maps, Blätter typisierte Werte
GrammatikPEG, geschrieben in Aether — siehe Grammatik
ImplementierungenElixir (die Referenz), Node.js, PHP, Praxis

Coopers README beginnt, wie die der anderen Implementierungen, mit einer Warnung: Die Bibliothek ist in starker Entwicklung und vor 1.0.0 nicht für den Produktiveinsatz gedacht. Bis dahin können sich die Bibliotheken und die Spezifikation zwischen zwei beliebigen Releases ändern; deshalb ist CASC hier als in Entwicklung markiert. Die Formatversion bleibt 1.0.

Implementierungen und Werkzeuge

Diese Seiten beschreiben das Format. Vier Implementierungen lesen es — in Elixir, Node.js, PHP und Praxis —, und zu jeder gehören Bibliotheken, die das Ergebnis an eine Anwendung oder ein Framework weitergeben. Wie sie das Format laden oder bereitstellen, steht jeweils in deren eigener Dokumentation.

NameSpracheRolle
cooperElixirDie Referenzimplementierung (Hex: cooper 0.7.0)
cooper_configElixirNutzt eine CASC-Datei als Konfiguration eines Mix-Projekts oder Releases (Hex: cooper_config 0.5.1)
node-cooperNode.jsDer Port für Node.js (npm: @joetjen/cooper 0.2.2)
node-cooper-configNode.jsLädt die Konfiguration einer Anwendung einmal beim Start und gibt dem Programm Funktionen, um sie zu lesen (npm: @joetjen/cooper-config 0.2.1)
node-cooper-fastifyNode.jsFastify-Plugin: die Konfiguration unter fastify.config, die Optionen des Servers selbst aus demselben Dokument (npm: @joetjen/cooper-fastify 0.2.1)
node-cooper-nestjsNode.jsNestJS-Modul, das die Konfiguration an @nestjs/config übergibt (npm: @joetjen/cooper-nestjs 0.2.1)
php-cooperPHPDer Port für PHP (Packagist: joetjen/cooper 0.2.1)
php-cooper-configPHPLädt die Konfiguration einer Anwendung einmal beim Start und gibt ihr Funktionen, um sie zu lesen (Packagist: joetjen/cooper-config 0.3.1)
php-cooper-laravelPHPLaravel-Integration: config/config.casc ersetzt config/*.php (Packagist: joetjen/cooper-laravel 0.2.1)
php-cooper-symfonyPHPSymfony-Integration: ein CASC-Dokument ersetzt config/packages/*.yaml für Bundle-Konfiguration und Container-Parameter (Packagist: joetjen/cooper-symfony 0.2.1)
cooperPraxisDer Port für Praxis, aus derselben Grammatik erzeugt; Teil des Praxis-Repositorys, noch nicht eigenständig veröffentlicht
cooper_prxPraxisLiest die config.casc eines Praxis-Projekts in die Anwendungsumgebung
cooper_conformancePraxisDie Konformitätssuite: dieselben Dokumente durch jede Implementierung, jede Abweichung benannt
CASC for VS CodeTypeScriptSyntaxhervorhebung, Snippets und ein Language Server — Definitionen, Hover, Vervollständigung, Gliederung und Diagnosen über Imports hinweg. Version 0.2.0, noch nicht im VS Code Marketplace oder bei Open VSX veröffentlicht
ichorElixirDer Grammatik-Compiler, mit dem die Parser für Elixir und Praxis erzeugt werden

Die Parser für Node.js und PHP sind von Hand geschrieben und übertragen die Grammatik Regel für Regel. Die Konformitätssuite schickt einen gemeinsamen Satz von Dokumenten durch alle vier Implementierungen und vergleicht jedes Ergebnis exakt, jede Ablehnung anhand ihrer Fehlerkategorie.

Auf diesen Seiten

  • Aufbau — Kopfzeile, Kommentare, Schlüssel, Blöcke, Zusammenführen.
  • Werte — alle Literal-Typen.
  • Verweise — Variablen, Umgebung, Konfigurationspfade, Filter, Tags.
  • Imports und Schleifen — Konfiguration aufteilen und erzeugen, mit einem vollständigen Beispiel.
  • Grammatik — die formale Grammatik, die Fehlerkategorien und wo die Implementierungen noch von der Spezifikation abweichen.