goat gen: implement und implement:overwrite

Das Subterminal goat gen stellt zwei Befehle zur Registrierung von Generierungsimplementierungen bereit:

  • implement
  • implement:overwrite

Implementierungen können sowohl im Haupt-Generierungsskript als auch in jedem geladenen Modul deklariert werden.

Nach Abschluss der Registrierungsphase werden alle final aufgelösten Implementierungen parallel ausgeführt.


implement

Der Befehl implement registriert eine Implementierung unter einem eindeutigen Namen.

Syntax

implement --name <name> --body=<<EOF
    ...
EOF

Beispiel

implement --name assets --body=<<IMPLEOF
    gen:files:onchange \
        --from="assets" \
        --cache="cache/assets-mtimes.json" \
        --foreach=<<EOF
            gen:force \
                --tmpl="asset.txt.tmpl" \
                --out="generated/{{.Data.Name.SnakeCase}}.txt"
        EOF \
        --index=<<EOF
            gen:force \
                --tmpl="index.txt.tmpl" \
                --out="generated/index.txt"
        EOF
IMPLEOF

Der über --name übergebene Wert identifiziert die Implementierung.

Für jeden Implementierungsnamen darf genau eine implement-Definition existieren.

Wenn beispielsweise bereits folgende Implementierung registriert wurde:

implement --name assets --body=<<EOF
    ...
EOF

ist eine erneute Registrierung mit demselben Namen ungültig:

implement --name assets --body=<<EOF
    ...
EOF

und muss zu einem Fehler führen.


implement:overwrite

Der Befehl implement:overwrite ersetzt den Body einer Implementierung bei der finalen Auflösung der Implementierungs-Registry.

Syntax

implement:overwrite --name <name> --body=<<EOF
    ...
EOF

Beispiel

implement:overwrite --name assets --body=<<IMPLEOF
    gen:files:onchange \
        --from="assets" \
        --cache="cache/assets-mtimes.json" \
        --foreach=<<EOF
            gen:force \
                --tmpl="other_asset.txt.tmpl" \
                --out="generated/{{.Data.Name.SnakeCase}}.txt"
        EOF \
        --index=<<EOF
            gen:force \
                --tmpl="other_index.txt.tmpl" \
                --out="generated/index.txt"
        EOF
IMPLEOF

Für jeden Implementierungsnamen darf höchstens eine implement:overwrite-Definition existieren.

Beispielsweise:

implement:overwrite --name assets --body=<<EOF
    ...
EOF

gefolgt von:

implement:overwrite --name assets --body=<<EOF
    ...
EOF

muss zu einem Fehler führen.


Die Registrierungsreihenfolge spielt keine Rolle

Generierungsmodule können parallel geladen werden.

Daher ist die Reihenfolge, in der implement und implement:overwrite verarbeitet werden, nicht garantiert.

implement:overwrite setzt deshalb nicht voraus, dass die zugehörige Basisimplementierung bereits registriert wurde.

Folgendes ist gültig:

implement:overwrite --name assets --body=<<EOF
    # überschriebene Implementierung
EOF

auch wenn die Basisimplementierung erst später registriert wird:

implement --name assets --body=<<EOF
    # Basisimplementierung
EOF

Die finale Implementierung wird erst bestimmt, nachdem das Haupt-Generierungsskript und alle geladenen Module ihre Implementierungen registriert haben.

Für jeden Implementierungsnamen gelten folgende Regeln:

RegistrierungZulässig
Ein implementJa
Zwei oder mehr implement-DefinitionenNein
Ein implement + ein implement:overwriteJa
Ein implement + mehrere implement:overwriteNein
Mehrere implement-Definitionen + ein implement:overwriteNein

Wenn ein implement:overwrite vorhanden ist, ersetzt dessen Body den durch implement registrierten Body.


Implementierungen dürfen nicht verschachtelt werden

implement und implement:overwrite sind Registrierungsbefehle.

Sie dürfen ausschließlich während der Registrierungsphase verwendet werden.

Keiner der beiden Befehle darf innerhalb des Bodys einer anderen Implementierung aufgerufen werden.

Ungültig: implement innerhalb von implement

implement --name assets --body=<<EOF
    implement --name nested --body=<<INNER
        ...
    INNER
EOF

Ungültig: implement:overwrite innerhalb von implement

implement --name assets --body=<<EOF
    implement:overwrite --name nested --body=<<INNER
        ...
    INNER
EOF

Ungültig: implement innerhalb von implement:overwrite

implement:overwrite --name assets --body=<<EOF
    implement --name nested --body=<<INNER
        ...
    INNER
EOF

Ungültig: implement:overwrite innerhalb von implement:overwrite

implement:overwrite --name assets --body=<<EOF
    implement:overwrite --name nested --body=<<INNER
        ...
    INNER
EOF

Alle oben genannten Fälle müssen zu einem Fehler führen.


Ausführungszyklus

Die Verarbeitung von Implementierungen in goat gen erfolgt in zwei klar getrennten Phasen.

1. Registrierung

Das Haupt-Generierungsskript und alle geladenen Module registrieren ihre Implementierungen mit:

implement
implement:overwrite

In dieser Phase werden die Implementierungs-Bodys gespeichert, aber noch nicht ausgeführt.

Nach Abschluss der Registrierung wird die Implementierungs-Registry validiert und anschließend final aufgelöst.

Für jeden Implementierungsnamen gilt:

  • Es muss genau eine implement-Definition existieren.
  • Es darf null oder eine implement:overwrite-Definition geben.
  • Doppelte implement-Definitionen führen zu einem Fehler.
  • Doppelte implement:overwrite-Definitionen führen zu einem Fehler.
  • Wenn ein implement:overwrite existiert, wird dessen Body zum finalen Implementierungs-Body.

2. Ausführung

Nach Abschluss der Registrierung werden alle final aufgelösten Implementierungen parallel ausgeführt.

goat gen wartet auf den Abschluss aller Implementierungen, bevor der Befehl beendet wird.

Während dieser Phase ist die Registrierung geschlossen.

Wenn der Body einer Implementierung versucht, einen der folgenden Befehle aufzurufen:

implement

oder:

implement:overwrite

muss der Aufruf mit einem Fehler fehlschlagen.

Fehler, die während der Ausführung von Implementierungen auftreten, müssen korrekt an goat gen weitergegeben werden.


Verwendung von Implementierungen über mehrere Module hinweg

Ein Modul kann eine Standardimplementierung bereitstellen:

# Modul: assets

implement --name assets --body=<<EOF
    gen:files:onchange \
        --from="assets" \
        --cache="cache/assets-mtimes.json" \
        --foreach=<<FILE
            gen:force \
                --tmpl="asset.txt.tmpl" \
                --out="generated/{{.Data.Name.SnakeCase}}.txt"
        FILE
EOF

Ein anderes Modul oder das Haupt-Generierungsskript kann diese Implementierung ersetzen:

implement:overwrite --name assets --body=<<EOF
    gen:files:onchange \
        --from="assets" \
        --cache="cache/assets-mtimes.json" \
        --foreach=<<FILE
            gen:force \
                --tmpl="custom-asset.txt.tmpl" \
                --out="generated/{{.Data.Name.SnakeCase}}.txt"
        FILE
EOF

Es spielt keine Rolle, welche Deklaration zuerst verarbeitet wird.

Nach Abschluss der Registrierung ist der über implement:overwrite angegebene Body die finale Implementierung für assets.

Der ursprüngliche Body wird nicht ausgeführt.


Mehrere unabhängige Implementierungen

Unterschiedliche Implementierungen können von unterschiedlichen Modulen registriert werden.

Beispiel:

implement --name assets --body=<<EOF
    # Assets generieren
EOF
implement --name routes --body=<<EOF
    # Routen generieren
EOF
implement --name models --body=<<EOF
    # Modelle generieren
EOF

Nach Abschluss der Registrierung werden alle drei Implementierungen parallel ausgeführt.

Schematisch:

Registrierung
    |
    +-- assets
    +-- routes
    +-- models
    |
    v
Registry auflösen
    |
    +--> assets ausführen ----+
    +--> routes ausführen ----+--> auf alle warten --> beenden
    +--> models ausführen ----+

Es gibt keine garantierte Ausführungsreihenfolge zwischen unabhängigen Implementierungen.

Eine Implementierung darf daher nicht davon ausgehen, dass eine andere Implementierung vor oder nach ihr ausgeführt wird.


Beispiel für ein Überschreiben

Angenommen, es existieren zwei Module.

Das erste stellt eine Standardimplementierung bereit:

implement --name assets --body=<<EOF
    echo "default assets implementation"
EOF

Das zweite ersetzt diese Implementierung:

implement:overwrite --name assets --body=<<EOF
    echo "custom assets implementation"
EOF

Die final aufgelöste Implementierung lautet:

echo "custom assets implementation"

Nur der Body aus implement:overwrite wird ausgeführt.

Die folgende Reihenfolge ist gleichwertig und ebenfalls gültig:

implement:overwrite --name assets --body=<<EOF
    echo "custom assets implementation"
EOF

implement --name assets --body=<<EOF
    echo "default assets implementation"
EOF

Die Reihenfolge der Deklarationen hat keinen Einfluss auf das Ergebnis.


Ungültige doppelte Registrierungen

Doppeltes implement

implement --name assets --body=<<EOF
    echo "first"
EOF

implement --name assets --body=<<EOF
    echo "second"
EOF

Dieser Fall muss zu einem Fehler führen, da für assets mehr als eine Basisimplementierung existiert.

Doppeltes implement:overwrite

implement --name assets --body=<<EOF
    echo "default"
EOF

implement:overwrite --name assets --body=<<EOF
    echo "first overwrite"
EOF

implement:overwrite --name assets --body=<<EOF
    echo "second overwrite"
EOF

Dieser Fall muss zu einem Fehler führen, da für assets mehr als ein Overwrite registriert wurde.


Wichtigste Regeln

Bei der Verwendung von Implementierungen gelten folgende Regeln:

  • implement definiert die Basisimplementierung.
  • Jeder Implementierungsname muss genau eine Basisdefinition besitzen.
  • implement:overwrite kann die Basisdefinition ersetzen.
  • Für einen Implementierungsnamen darf höchstens ein implement:overwrite existieren.
  • implement:overwrite darf vor dem zugehörigen implement registriert werden.
  • Auf die Registrierungsreihenfolge zwischen Modulen darf man sich nicht verlassen.
  • Implementierungs-Bodys werden während der Registrierungsphase nicht ausgeführt.
  • implement und implement:overwrite dürfen nicht innerhalb von Implementierungs-Bodys aufgerufen werden.
  • Final aufgelöste Implementierungen werden parallel ausgeführt.
  • Die Ausführungsreihenfolge der Implementierungen ist nicht garantiert.
  • goat gen wartet auf den Abschluss aller Implementierungen.
  • Fehler aus der Implementierungsausführung werden an goat gen weitergegeben.