关于ruby:如何基于表达式在Go模板中有条件地设置变量,如果没有用if语句包装可能会导致错误

How can I conditionally set a variable in a Go template based on an expression which may cause an error if not wrapped with an if statement

问题

我该怎么做?

1
{{ $use_ssl := (ne $.Env.CERT_NAME"") }}

其中,$.Env.CERT_NAME可以为零/未定义。如果为零,则会出现以下错误:

1
at <ne $.Env.CERT_NAME"">: error calling ne: invalid type for comparison

注意:我无法控制传入Go模板的对象,因此必须完全在模板本身内解决这个问题。

我试过什么

我试着先检查一下它是否是非空的:

1
{{ $use_ssl := ( ($.Env.CERT_NAME) && (ne $.Env.CERT_NAME"") ) }}

但它给出了这个错误:

1
unexpected"&" in operand

所以我切换到这个,它在语法上是允许的:

1
{{ $use_ssl := (and ($.Env.CERT_NAME) (ne $.Env.CERT_NAME"") ) }}

但是,我失去了用&&操作符得到的短路评估,我又回到了得到这个错误的地方:

1
at <ne $.Env.CERT_NAME ...>: error calling ne: invalid type for comparison

好吧,我想,如果我不能用一条漂亮的单行线来做,而且Go没有三元运算符,那很好,我就用惯用的Go-Way来做,显然是if/else

1
2
3
4
5
{{ if $.Env.CERT_NAME }}
  {{ $use_ssl := (ne $.Env.CERT_NAME"") }}
{{ else }}
  {{ $use_ssl := false }}
{{ end }}

但是,当然,我遇到了范围问题,因为if莫名其妙地(或者至少令人恼火地)创建了一个新的变量范围(与我更习惯的Ruby/erb模板不同),所以显然我甚至不能这样做:

1
2
3
4
5
6
{{ if $.Env.CERT_NAME }}
  {{ $use_ssl := true }}
{{ else }}
  {{ $use_ssl := false }}
{{ end }}
# $use_ssl: {{ $use_ssl }}

现在不出现此错误:

1
undefined variable"$use_ssl"

"不出汗",我想。我将在外部作用域中声明变量,这样它将具有正确的作用域,并且内部作用域仍然能够更改该变量(具有外部作用域的变量)。(顺便说一下,这就是它在Ruby中的工作原理。)

不,很明显,只创建了两个同名但作用域不同的变量(这怎么会让人困惑!):

1
2
3
4
5
6
7
8
9
10
11
{{ $use_ssl := false }}
{{ if $.Env.CERT_NAME }}
  {{ $use_ssl := true }}
  # $use_ssl: {{ $use_ssl }}
{{ else }}
  {{ $use_ssl := false }}
{{ end }}
# $use_ssl: {{ $use_ssl }}

  # $use_ssl: true
# $use_ssl: false

我也试过将if语句嵌入如下:

1
{{ $use_ssl := if $.Env.CERT_NAME { true } }}

但这就产生了这个错误:

1
unexpected <if> in command

显然,if语句不能像在ruby中那样用作go中的表达式?

如果我尝试用C的三元运算符的惯用go等价物中建议的三元运算符的各种替代方法,我也会遇到语法错误。,像:

1
c := map[bool]int{true: 1, false: 0} [5 > 4]

当然,使用$.something可以从内部作用域的外部作用域中读取变量,但是如何在内部作用域中设置$.something

如果不是这样,那怎么办??

我错过了什么?这在Go模板中是可能的吗?

Go模板(我是新手)似乎非常有限。在一个模板中,几乎你能在Go本身中做的所有事情都是不可能的。但希望有个解决办法…

http://play.golang.org/p/sufzdsx-1v有一个巧妙的解决方法,包括用{{$hasFemale := cell false}}创建一个新对象,然后用$hasFemale.Set true设置一个值,但是我如何才能在没有访问评估模板的代码的情况下(在他们称为template.FuncMap的地方)执行类似的操作?

其他尝试过却失败了的人

https://groups.google.com/forum/!主题/Golang Nuts/Muznz9dbhrg

This is the biggest (and only) problem I have with Go. I simply do not understand why this functionality has not been implemented yet.

When the template package is used in a generic context (eg. Executing
an arbitrary template file with an arbitrary json file), you might not
have the luxury of doing precalculations.

网址:https://github.com/golang/go/issues/10608

This is probably as designed, but it would be really nice if there was a way to get the changed $v outside of the conditional.

This is the #1 template question we get over at Hugo (https://github.com/spf13/hugo). We have added a hack [$.Scratch.Set"v1" 123] to work around it for now ...


当前正在使用的解决方案

如果更新作用域而不是声明变量,无论是使用set还是使用merge都可以在if子句之外访问它。

1
2
3
4
5
6
7
8
{{- if eq .podKind"core" -}}
{{- $dummy := set ."matchNodePurpose" .Values.scheduling.corePods.nodeAffinity.matchNodePurpose }}
{{- else if eq .podKind"user" -}}
{{- $dummy := set ."matchNodePurpose" .Values.scheduling.userPods.nodeAffinity.matchNodePurpose }}
{{- end -}}

# Will now output what you decided within an if/else if clause
{{- .matchNodePurpose }}

未来解决方案(可与Helm 2.10一起使用)

我在helm模板的上下文中遇到了同样的问题,这些模板是带有一些预先安装的附加功能(如sprig)的go模板。

2018年3月28日,在本次公关中,一位Terenary运营商加入了Sprig,到时候,Helm会让他们解决您和我都有的问题。

万亿进制

true | ternary"b""c"将返回"b"

false | ternary"b""c"将返回"c"


我想我可能已经为我的特定问题找到了一个解决方法,尽管我希望听到其他更普遍地解决这个问题的答案。

在https://github.com/jwilder/nginx proxy/blob/1e0b930/nginx.tmpl_l91中遇到这一行:

1
{{ $default_host := or ($.Env.DEFAULT_HOST)"" }}

这给了我另一个尝试的想法。我想解决方案是使用or的组合并设置额外的临时变量…

1
2
{{ $cert_name := or $.Env.CERT_NAME"" }}
{{ $use_ssl := ne $cert_name"" }}