goat gen: implement and implement:overwrite

The goat gen subterminal provides two commands for registering generation implementations:

  • implement
  • implement:overwrite

Implementations may be declared in the main generation script or in any loaded module.

After registration is complete, all resolved implementations are executed concurrently.


implement

The implement command registers an implementation under a unique name.

Syntax

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

Example

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

The value passed to --name identifies the implementation.

Each implementation name may have exactly one implement definition.

For example, after registering:

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

registering another implementation with the same name is invalid:

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

and results in an error.


implement:overwrite

The implement:overwrite command replaces the body of an implementation when the final implementation registry is resolved.

Syntax

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

Example

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

Each implementation name may have at most one implement:overwrite definition.

For example:

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

followed by another overwrite:

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

results in an error.


Registration order does not matter

Generation modules may be loaded concurrently.

Because of this, the order in which implement and implement:overwrite commands are encountered is not guaranteed.

An overwrite therefore does not require the original implementation to have already been registered.

This is valid:

implement:overwrite --name assets --body=<<EOF
    # overridden implementation
EOF

even if the original implementation is encountered later:

implement --name assets --body=<<EOF
    # original implementation
EOF

The final implementation is resolved only after the main generation script and all loaded modules have finished registering their implementations.

For every implementation name, the following rules apply:

RegistrationAllowed
One implementYes
Two or more implement definitionsNo
One implement + one implement:overwriteYes
One implement + multiple overwritesNo
Multiple implement definitions + one overwriteNo

When an overwrite exists, its body replaces the body registered with implement.


Implementations cannot be nested

implement and implement:overwrite are registration commands.

They may only be used while implementations are being registered.

Neither command is available from inside an implementation body.

Invalid: implement inside implement

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

Invalid: implement:overwrite inside implement

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

Invalid: implement inside implement:overwrite

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

Invalid: implement:overwrite inside implement:overwrite

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

All of the examples above result in an error.


Execution lifecycle

goat gen processes implementations in two distinct phases.

1. Registration

The main generation script and all loaded modules register their implementations using:

implement
implement:overwrite

During this phase, implementation bodies are stored but are not executed.

Once all scripts and modules finish registration, the implementation registry is validated and resolved.

For every implementation name:

  • exactly one implement definition must exist;
  • zero or one implement:overwrite definition may exist;
  • duplicate implement definitions are rejected;
  • duplicate implement:overwrite definitions are rejected;
  • when an overwrite exists, its body becomes the resolved implementation body.

2. Execution

After registration is complete, all resolved implementation bodies are executed concurrently.

goat gen waits for every implementation to finish before exiting.

Registration is closed during this phase.

If an implementation body attempts to call either:

implement

or:

implement:overwrite

the command fails with an error.

Errors produced while executing implementation bodies are propagated to goat gen.


Using implementations across modules

A module may provide a default implementation:

# module: 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

Another module, or the main generation script, may replace that implementation:

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

It does not matter which declaration is encountered first.

After registration finishes, the resolved implementation for assets is the body supplied by implement:overwrite.

The original body is not executed.


Multiple independent implementations

Different implementations may be registered by different modules.

For example:

implement --name assets --body=<<EOF
    # generate assets
EOF
implement --name routes --body=<<EOF
    # generate routes
EOF
implement --name models --body=<<EOF
    # generate models
EOF

After registration is complete, all three resolved implementations are executed concurrently.

Conceptually:

registration
    |
    +-- assets
    +-- routes
    +-- models
    |
    v
resolve registry
    |
    +--> execute assets ----+
    +--> execute routes ----+--> wait for all --> exit
    +--> execute models ----+

There is no guaranteed execution order between independent implementations.

An implementation must therefore not rely on another implementation being executed before or after it.


Overwrite example

Consider two modules.

The first module provides a default implementation:

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

The second module customizes it:

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

The resolved implementation is:

echo "custom assets implementation"

Only the overwritten body is executed.

The following is equivalent and also valid:

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

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

Declaration order does not affect the result.


Invalid duplicate registrations

Duplicate implementation

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

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

This results in an error because assets has more than one base implementation.

Duplicate 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

This results in an error because assets has more than one overwrite.


Important behavior

Keep the following rules in mind when using implementations:

  • implement defines the base implementation.
  • An implementation name must have exactly one base definition.
  • implement:overwrite may replace that definition.
  • Each implementation name may have at most one overwrite.
  • An overwrite may be registered before the corresponding base implementation.
  • Registration order across modules must not be relied upon.
  • Implementation bodies are not executed during registration.
  • implement and implement:overwrite cannot be called from implementation bodies.
  • Resolved implementations execute concurrently.
  • Execution order between implementations is not guaranteed.
  • goat gen waits until all implementations have completed.
  • Errors from implementation execution are propagated to goat gen.