英文:
Syntax for creating struct map key in Go HTML template
问题
我正在编写一个简单的Go程序,用于显示每个环境中已部署服务版本的HTML表格。我的程序包含以下结构体:
type versionKey struct {
Environment string
Service string
}
type templateData struct {
Environments []string
Services []string
Versions map[versionKey]string
}
如你所见,Versions
映射使用versionKey
作为字符串值(例如"1.0.0"
)的键。
我将templateData
结构体传递给HTML模板,并在其Environments
和Services
切片上进行迭代,以构建HTML表格。问题是,我需要为给定的环境和服务交叉点构建一个versionKey
,以便我可以使用它在Versions
映射中查找版本并将该值输出到表格单元格中。
在模板中,我可以从迭代中获取$environment
和$service
变量,但我无法弄清楚Go模板语法来创建versionKey
结构体。
以下是省略了标记的模板代码:
{{$environments := .Environments}}
{{$services := .Services}}
{{$versions := .Versions}}
{{range $service := $services}}
...
{{range $environment := $environments}}
...
{{index $versions ...? }} // 如何在这里创建versionKey结构体映射键?
...
{{end}}
...
{{end}}
请注意,这只是模板代码的一部分,你需要根据实际需求填充省略的部分。
英文:
I'm writing a simple Go program to display an HTML table of deployed service versions per environment. My program contains the following structs:
type versionKey struct {
Environment string
Service string
}
type templateData struct {
Environments []string
Services []string
Versions map[versionKey]string
}
As you can see, the Versions
map uses a versionKey
as a key for a string value e.g. "1.0.0"
.
I'm passing the templateData
struct to an HTML template and ranging over its Environments
and Services
slices to build the HTML table. The problem is that I need to construct a versionKey
for any given intersection of environment and service so I can use it to look up the version from the Versions
map and output that value in the table cell.
Within the template I have $environment
and $service
variables available from the ranges, but I can't work out the Go template syntax to create the versionKey
struct.
Here's the template code with the markup omitted:
{{$environments := .Environments}}
{{$services := .Services}}
{{$versions := .Versions}}
{{range $service := $services}}
...
{{range $environment := $environments}}
...
{{index $versions ...? }} // How to create versionKey struct map key here?
...
{{end}}
...
{{end}}
答案1
得分: 4
只使用模板代码是无法实现的。你需要从执行的Go代码中获得一些支持来完成这个任务。根据设计原则,模板不应包含复杂的逻辑。你可以争论这是否复杂,但是模板语法本身不支持这个功能。
最简单的解决方案是在templateData
结构体中添加一个Version()
方法,该方法可以根据给定的环境和服务返回版本号:
func (t *templateData) Version(environment, service string) string {
return t.Versions[versionKey{
Environment: environment,
Service: service,
}]
}
在模板中使用该方法:
{{range $service := $services -}}
{{range $environment := $environments}}
{{$environment}} - {{$service}} version: {{$.Version $environment $service}}
{{end}}
{{end}}
进行测试:
t := template.Must(template.New("").Parse(templ))
td := &templateData{
Environments: []string{"EnvA", "EnvB"},
Services: []string{"ServA", "ServB"},
Versions: map[versionKey]string{
{"EnvA", "ServA"}: "1.0.0",
{"EnvA", "ServB"}: "1.0.1",
{"EnvB", "ServA"}: "1.0.2",
},
}
if err := t.Execute(os.Stdout, td); err != nil {
panic(err)
}
输出结果(在Go Playground上尝试):
EnvA - ServA version: 1.0.0
EnvB - ServA version: 1.0.2
EnvA - ServB version: 1.0.1
EnvB - ServB version:
其他方法
除了使用templateData.Version()
方法之外,你也可以注册一个函数,该函数可以根据给定的环境和服务创建并返回一个versionKey
类型的值。详细信息请参见Template.Funcs()
。虽然这种方法更复杂,但更灵活,因为它可以在其他地方重复使用。在这里可以找到一个示例:https://stackoverflow.com/questions/35550326/golang-templates-and-passing-funcs-to-template/35550730#35550730。稍微变化一下,你也可以将函数值作为其他模板数据传递,而不是将其注册为命名函数,然后可以调用它。
另一种方法是将你的Versions
字段“转换”为一个嵌套的map,例如:
Versions map[string]map[string]string
首先可以按环境索引,然后按服务索引,在模板中可以通过两个{{index}}
操作实现。不过,你需要检查第一个索引是否返回任何结果。
英文:
Using only template code you can't. You need some kind of support from the executing Go code to do that. By design philosophy, templates should not contain complex logic. You may argue whether this is complex, but the template syntax has no support for this.
Simplest solution would be to add a Version()
method to the templateData
struct, which would simply return the version for a given environment and service:
func (t *templateData) Version(environment, service string) string {
return t.Versions[versionKey{
Environment: environment,
Service: service,
}]
}
Using this from the template:
{{range $service := $services -}}
{{range $environment := $environments}}
{{$environment}} - {{$service}} version: {{$.Version $environment $service}}
{{end}}
{{end}}
Testing it:
t := template.Must(template.New("").Parse(templ))
td := &templateData{
Environments: []string{"EnvA", "EnvB"},
Services: []string{"ServA", "ServB"},
Versions: map[versionKey]string{
{"EnvA", "ServA"}: "1.0.0",
{"EnvA", "ServB"}: "1.0.1",
{"EnvB", "ServA"}: "1.0.2",
},
}
if err := t.Execute(os.Stdout, td); err != nil {
panic(err)
}
Output (try it on the Go Playground):
EnvA - ServA version: 1.0.0
EnvB - ServA version: 1.0.2
EnvA - ServB version: 1.0.1
EnvB - ServB version:
Alternatives
Instead of the templateData.Version()
method you could just as easily register a function which could create and return a value of type versionKey
from a given environment and service. See Template.Funcs()
for details. This would be more complicated though, but more flexible as this could be reused elsewhere. See an example of this here: https://stackoverflow.com/questions/35550326/golang-templates-and-passing-funcs-to-template/35550730#35550730. A slight variation of this would be to pass a function value as any other template data instead of registering it as a named function, which can be called.
Another alternative would be to "transform" your Versions
field into a map of maps, e.g.:
Versions map[string]map[string]string
Which first could be indexed by environment, then by service, which in the template you can achieve by 2 {{index}}
actions. You would have to check if the first indexing yields any results though.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论