goat gen: implement i implement:overwrite

Subterminal goat gen udostępnia dwa polecenia służące do rejestrowania implementacji generowania:

  • implement
  • implement:overwrite

Implementacje mogą być deklarowane zarówno w głównym skrypcie generującym, jak i w dowolnym załadowanym module.

Po zakończeniu fazy rejestracji wszystkie finalnie wybrane implementacje są wykonywane współbieżnie.


implement

Polecenie implement rejestruje implementację pod unikalną nazwą.

Składnia

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

Przykład

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

Wartość przekazana w parametrze --name identyfikuje implementację.

Dla każdej nazwy może istnieć dokładnie jedna definicja implement.

Przykładowo, po zarejestrowaniu:

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

ponowna rejestracja implementacji o tej samej nazwie:

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

musi zakończyć się błędem.


implement:overwrite

Polecenie implement:overwrite zastępuje ciało implementacji podczas finalnego rozwiązywania rejestru implementacji.

Składnia

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

Przykład

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

Dla każdej nazwy implementacji może istnieć maksymalnie jedna definicja implement:overwrite.

Przykładowo:

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

a następnie kolejne:

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

musi zakończyć się błędem.


Kolejność rejestracji nie ma znaczenia

Moduły generujące mogą być ładowane współbieżnie.

Z tego powodu kolejność, w której napotykane są polecenia implement i implement:overwrite, nie jest gwarantowana.

implement:overwrite nie wymaga więc, aby podstawowa implementacja została wcześniej zarejestrowana.

Poprawny jest na przykład taki przypadek:

implement:overwrite --name assets --body=<<EOF
    # nadpisana implementacja
EOF

nawet jeśli podstawowa implementacja zostanie napotkana dopiero później:

implement --name assets --body=<<EOF
    # podstawowa implementacja
EOF

Finalna implementacja jest ustalana dopiero po zakończeniu rejestracji przez główny skrypt generujący oraz wszystkie załadowane moduły.

Dla każdej nazwy obowiązują następujące zasady:

RejestracjaDozwolona
Jedno implementTak
Dwa lub więcej implementNie
Jedno implement + jedno implement:overwriteTak
Jedno implement + wiele implement:overwriteNie
Wiele implement + jedno implement:overwriteNie

Jeżeli istnieje implement:overwrite, jego ciało zastępuje ciało zarejestrowane przez implement.


Implementacje nie mogą być zagnieżdżane

implement i implement:overwrite są poleceniami rejestracyjnymi.

Mogą być używane wyłącznie podczas fazy rejestracji implementacji.

Nie mogą być wywoływane z wnętrza ciała innej implementacji.

Niepoprawne: implement wewnątrz implement

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

Niepoprawne: implement:overwrite wewnątrz implement

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

Niepoprawne: implement wewnątrz implement:overwrite

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

Niepoprawne: implement:overwrite wewnątrz implement:overwrite

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

Wszystkie powyższe przypadki muszą zakończyć się błędem.


Cykl działania

Obsługa implementacji w goat gen przebiega w dwóch oddzielnych fazach.

1. Rejestracja

Główny skrypt generujący oraz wszystkie załadowane moduły rejestrują implementacje przy użyciu:

implement
implement:overwrite

W tej fazie ciała implementacji są zapisywane, ale nie są jeszcze wykonywane.

Po zakończeniu rejestracji rejestr implementacji jest walidowany, a następnie wyznaczane są finalne implementacje.

Dla każdej nazwy:

  • musi istnieć dokładnie jedna definicja implement;
  • może istnieć zero lub jedna definicja implement:overwrite;
  • zduplikowane definicje implement powodują błąd;
  • zduplikowane definicje implement:overwrite powodują błąd;
  • jeśli istnieje implement:overwrite, jego ciało staje się finalnym ciałem implementacji.

2. Wykonanie

Po zakończeniu rejestracji wszystkie finalne implementacje są wykonywane współbieżnie.

goat gen czeka na zakończenie wszystkich implementacji przed zakończeniem działania.

W fazie wykonania rejestracja jest zamknięta.

Jeżeli ciało implementacji spróbuje wywołać:

implement

lub:

implement:overwrite

wywołanie musi zakończyć się błędem.

Błędy zwracane podczas wykonywania implementacji muszą zostać poprawnie przekazane do goat gen.


Używanie implementacji pomiędzy modułami

Moduł może dostarczyć domyślną implementację:

# moduł: 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

Inny moduł albo główny skrypt generujący może tę implementację zastąpić:

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

Nie ma znaczenia, która deklaracja zostanie napotkana jako pierwsza.

Po zakończeniu rejestracji finalną implementacją dla assets będzie ciało przekazane do implement:overwrite.

Oryginalne ciało nie zostanie wykonane.


Wiele niezależnych implementacji

Różne implementacje mogą być rejestrowane przez różne moduły.

Przykład:

implement --name assets --body=<<EOF
    # generowanie assetów
EOF
implement --name routes --body=<<EOF
    # generowanie routingu
EOF
implement --name models --body=<<EOF
    # generowanie modeli
EOF

Po zakończeniu rejestracji wszystkie trzy implementacje zostaną wykonane współbieżnie.

Schematycznie:

rejestracja
    |
    +-- assets
    +-- routes
    +-- models
    |
    v
rozwiązanie rejestru
    |
    +--> wykonaj assets ----+
    +--> wykonaj routes ----+--> czekaj na wszystkie --> zakończ
    +--> wykonaj models ----+

Nie istnieje gwarantowana kolejność wykonywania niezależnych implementacji.

Implementacja nie powinna więc zakładać, że inna implementacja zostanie wykonana przed nią albo po niej.


Przykład nadpisania

Załóżmy, że istnieją dwa moduły.

Pierwszy dostarcza domyślną implementację:

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

Drugi ją zastępuje:

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

Finalną implementacją będzie:

echo "custom assets implementation"

Wykonane zostanie wyłącznie ciało z implement:overwrite.

Poniższa kolejność jest równoważna i również poprawna:

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

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

Kolejność deklaracji nie wpływa na wynik.


Niepoprawne duplikaty

Zduplikowane implement

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

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

Ten przypadek musi zakończyć się błędem, ponieważ dla assets istnieje więcej niż jedna podstawowa implementacja.

Zduplikowane 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

Ten przypadek musi zakończyć się błędem, ponieważ dla assets istnieje więcej niż jedno nadpisanie.


Najważniejsze zasady

Podczas korzystania z implementacji obowiązują następujące reguły:

  • implement definiuje podstawową implementację;
  • każda nazwa musi mieć dokładnie jedną podstawową definicję;
  • implement:overwrite może zastąpić podstawową definicję;
  • dla jednej nazwy może istnieć maksymalnie jedno implement:overwrite;
  • implement:overwrite może zostać zarejestrowane przed odpowiadającym mu implement;
  • nie należy polegać na kolejności rejestracji pomiędzy modułami;
  • ciała implementacji nie są wykonywane podczas rejestracji;
  • implement i implement:overwrite nie mogą być wywoływane z ciał implementacji;
  • finalne implementacje są wykonywane współbieżnie;
  • kolejność wykonywania implementacji nie jest gwarantowana;
  • goat gen czeka na zakończenie wszystkich implementacji;
  • błędy powstałe podczas wykonania implementacji są propagowane do goat gen.