Syntax for creating struct map key in Go HTML template

huangapple go评论71阅读模式
英文:

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模板,并在其EnvironmentsServices切片上进行迭代,以构建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.

huangapple
  • 本文由 发表于 2017年8月10日 18:36:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/45611344.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定