Implementations in goat gen
Registering, overriding, and executing implementations using the implement and implement:overwrite commands.
goat gen: implement and implement:overwrite
The goat gen subterminal provides two commands for registering generation implementations:
implementimplement: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
...
EOFExample
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
IMPLEOFThe 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
...
EOFregistering another implementation with the same name is invalid:
implement --name assets --body=<<EOF
...
EOFand 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
...
EOFExample
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
IMPLEOFEach implementation name may have at most one implement:overwrite definition.
For example:
implement:overwrite --name assets --body=<<EOF
...
EOFfollowed by another overwrite:
implement:overwrite --name assets --body=<<EOF
...
EOFresults 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
EOFeven if the original implementation is encountered later:
implement --name assets --body=<<EOF
# original implementation
EOFThe 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:
| Registration | Allowed |
|---|---|
One implement | Yes |
Two or more implement definitions | No |
One implement + one implement:overwrite | Yes |
One implement + multiple overwrites | No |
Multiple implement definitions + one overwrite | No |
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
EOFInvalid: implement:overwrite inside implement
implement --name assets --body=<<EOF
implement:overwrite --name nested --body=<<INNER
...
INNER
EOFInvalid: implement inside implement:overwrite
implement:overwrite --name assets --body=<<EOF
implement --name nested --body=<<INNER
...
INNER
EOFInvalid: implement:overwrite inside implement:overwrite
implement:overwrite --name assets --body=<<EOF
implement:overwrite --name nested --body=<<INNER
...
INNER
EOFAll 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:overwriteDuring 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
implementdefinition must exist; - zero or one
implement:overwritedefinition may exist; - duplicate
implementdefinitions are rejected; - duplicate
implement:overwritedefinitions 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:
implementor:
implement:overwritethe 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
EOFAnother 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
EOFIt 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
EOFimplement --name routes --body=<<EOF
# generate routes
EOFimplement --name models --body=<<EOF
# generate models
EOFAfter 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"
EOFThe second module customizes it:
implement:overwrite --name assets --body=<<EOF
echo "custom assets implementation"
EOFThe 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"
EOFDeclaration 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"
EOFThis 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"
EOFThis results in an error because assets has more than one overwrite.
Important behavior
Keep the following rules in mind when using implementations:
implementdefines the base implementation.- An implementation name must have exactly one base definition.
implement:overwritemay 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.
implementandimplement:overwritecannot be called from implementation bodies.- Resolved implementations execute concurrently.
- Execution order between implementations is not guaranteed.
goat genwaits until all implementations have completed.- Errors from implementation execution are propagated to
goat gen.