goat gen 中的实现
使用 implement 和 implement:overwrite 命令注册、覆盖和执行实现。
goat gen:implement 和 implement:overwrite
Subterminal goat gen 提供了两条用于注册生成实现的命令:
implementimplement:overwrite
实现可以在主生成脚本中声明,也可以在任意已加载的模块中声明。
注册阶段结束后,所有最终选定的实现将并发执行。
implement
命令 implement 会以唯一名称注册一个实现。
语法
implement --name <nazwa> --body=<<EOF
...
EOF示例
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通过参数 --name 传入的值用于标识该实现。
每个名称只能存在一个 implement 定义。
例如,注册后:
implement --name assets --body=<<EOF
...
EOF再次注册同名实现:
implement --name assets --body=<<EOF
...
EOF必须报错。
implement:overwrite
命令 implement:overwrite 会在最终解析实现注册表时替换实现主体。
语法
implement:overwrite --name <nazwa> --body=<<EOF
...
EOF示例
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每个实现名称最多只能存在一个 implement:overwrite 定义。
例如:
implement:overwrite --name assets --body=<<EOF
...
EOF随后再有:
implement:overwrite --name assets --body=<<EOF
...
EOF必须报错。
注册顺序无关紧要
生成模块可以并发加载。
因此,遇到 implement 和 implement:overwrite 命令的顺序无法保证。
因此,implement:overwrite 不要求基础实现必须先被注册。
例如,以下情况是有效的:
implement:overwrite --name assets --body=<<EOF
# 被覆盖的实现
EOF即使基础实现稍后才被遇到:
implement --name assets --body=<<EOF
# 基础实现
EOF只有在主生成脚本和所有已加载模块完成注册后,才会确定最终实现。
每个名称适用以下规则:
| 注册 | 允许 |
一个 implement | 是 |
两个或更多 implement | 否 |
一个 implement + 一个 implement:overwrite | 是 |
一个 implement + 多个 implement:overwrite | 否 |
多个 implement + 一个 implement:overwrite | 否 |
如果存在 implement:overwrite,其主体将替换由 implement 注册的主体。
实现不能嵌套
implement 和 implement:overwrite 是注册命令。
它们只能在实现注册阶段使用。
不能从另一个实现的主体内部调用它们。
无效:implement 位于 implement 内部
implement --name assets --body=<<EOF
implement --name nested --body=<<INNER
...
INNER
EOF无效:implement:overwrite 位于 implement 内部
implement --name assets --body=<<EOF
implement:overwrite --name nested --body=<<INNER
...
INNER
EOF无效:implement 位于 implement:overwrite 内部
implement:overwrite --name assets --body=<<EOF
implement --name nested --body=<<INNER
...
INNER
EOF无效:implement:overwrite 位于 implement:overwrite 内部
implement:overwrite --name assets --body=<<EOF
implement:overwrite --name nested --body=<<INNER
...
INNER
EOF上述所有情况都必须报错。
生命周期
goat gen 中的实现处理分为两个独立阶段。
1. 注册
主生成脚本及所有已加载模块均使用以下方式注册实现:
implement
implement:overwrite在此阶段,实现体会被保存,但尚不会执行。
注册完成后,将验证实现注册表,然后确定最终实现。
对于每个名称:
- 必须恰好存在一个
implement定义; - 可以存在零个或一个
implement:overwrite定义; - 重复的
implement定义会导致错误; - 重复的
implement:overwrite定义会导致错误; - 如果存在
implement:overwrite,其实现体将成为最终实现体。
2. 执行
注册完成后,所有最终实现都会并发执行。
goat gen 会等待所有实现完成后才结束。
在执行阶段,注册已关闭。
如果实现体尝试调用:
implement或:
implement:overwrite该调用必须以错误结束。
实现执行期间返回的错误必须被正确传递给 goat gen。
在模块之间使用实现
模块可以提供默认实现:
# 模块: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其他模块或主生成脚本可以替换该实现:
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无论先遇到哪一项声明都没有影响。
注册完成后,assets 的最终实现将是传递给 implement:overwrite 的实现体。
原始实现体不会被执行。
多个独立实现
不同的实现可以由不同模块注册。
示例:
implement --name assets --body=<<EOF
# 生成资源
EOFimplement --name routes --body=<<EOF
# 生成路由
EOFimplement --name models --body=<<EOF
# 生成模型
EOF注册完成后,这三个实现都会并发执行。
示意图:
注册
|
+-- assets
+-- routes
+-- models
|
v
解析注册表
|
+--> 执行 assets ----+
+--> 执行 routes ----+--> 等待全部完成 --> 结束
+--> 执行 models ----+独立实现之间不存在有保证的执行顺序。
因此,实现不应假定其他实现在它之前或之后执行。
覆盖示例
假设存在两个模块。
第一个提供默认实现:
implement --name assets --body=<<EOF
echo "default assets implementation"
EOF第二个将其替换:
implement:overwrite --name assets --body=<<EOF
echo "custom assets implementation"
EOF最终实现将是:
echo "custom assets implementation"仅会执行 implement:overwrite 中的实现体。
以下顺序是等价的,同样正确:
implement:overwrite --name assets --body=<<EOF
echo "custom assets implementation"
EOF
implement --name assets --body=<<EOF
echo "default assets implementation"
EOF声明顺序不会影响结果。
无效重复项
重复的 implement
implement --name assets --body=<<EOF
echo "first"
EOF
implement --name assets --body=<<EOF
echo "second"
EOF此情况必须以错误结束,因为 assets 存在多个基础实现。
重复的 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此情况必须以错误结束,因为 assets 存在多个覆盖实现。
核心规则
使用实现机制时,适用以下规则:
implement定义基础实现;- 每个名称必须恰好有一个基础定义;
implement:overwrite可以替换基础定义;- 同一个名称最多只能存在一个
implement:overwrite; implement:overwrite可以在其对应的implement之前注册;- 不应依赖模块之间的注册顺序;
- 实现体不会在注册期间执行;
- 不得从实现体中调用
implement和implement:overwrite; - 最终实现会并发执行;
- 不保证实现的执行顺序;
goat gen会等待所有实现完成;- 实现执行期间产生的错误会传播到
goat gen。