繁体   English   中英

使用多个管道参数调用模板

[英]Calling a template with several pipeline parameters

在 Go 模板中,有时将正确的数据传递给正确的模板的方式让我感觉很尴尬。 调用带有管道参数的模板看起来就像调用只有一个参数的函数。

假设我有一个关于 Gophers 的 Gophers 网站。 它有一个主页主模板和一个打印 Gophers 列表的实用程序模板。

http://play.golang.org/p/Jivy_WPh16

输出 :

*The great GopherBook*    (logged in as Dewey)

    [Most popular]  
        >> Huey
        >> Dewey
        >> Louie

    [Most active]   
        >> Huey
        >> Louie

    [Most recent]   
        >> Louie

现在我想在子模板中添加一些上下文:在列表中以不同的方式格式化名称“Dewey”,因为它是当前登录用户的名称。 但我不能直接传递名称,因为只有一个可能的“点”参数管道! 我能做些什么?

  • 显然,我可以将子模板代码复制粘贴到主模板中(我不想这样做,因为它放弃了拥有子模板的所有兴趣)。
  • 或者我可以使用访问器处理某种全局变量(我也不想这样做)。
  • 或者我可以为每个模板参数列表创建一个新的特定结构类型(不是很好)。

您可以在模板中注册一个“dict”函数,您可以使用该函数将多个值传递给模板调用。 调用本身将如下所示:

{{template "userlist" dict "Users" .MostPopular "Current" .CurrentUser}}

小“dict”助手的代码,包括将其注册为模板 func 在这里:

var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
    "dict": func(values ...interface{}) (map[string]interface{}, error) {
        if len(values)%2 != 0 {
            return nil, errors.New("invalid dict call")
        }
        dict := make(map[string]interface{}, len(values)/2)
        for i := 0; i < len(values); i+=2 {
            key, ok := values[i].(string)
            if !ok {
                return nil, errors.New("dict keys must be strings")
            }
            dict[key] = values[i+1]
        }
        return dict, nil
    },
}).ParseGlob("templates/*.html")

您可以在模板中定义函数,并将这些函数作为在您的数据上定义的闭包,如下所示:

template.FuncMap{"isUser": func(g Gopher) bool { return string(g) == string(data.User);},}

然后,您可以简单地在模板中调用此函数:

{{define "sub"}}

    {{range $y := .}}>> {{if isUser $y}}!!{{$y}}!!{{else}}{{$y}}{{end}}
    {{end}}
{{end}}

操场上的这个更新版本输出漂亮!! 当前用户周围:

*The great GopherBook*    (logged in as Dewey)

[Most popular]  

>> Huey
>> !!Dewey!!
>> Louie



[Most active]   

>> Huey
>> Louie



[Most recent]   

>> Louie

编辑

由于您可以在调用Funcs时覆盖函数,因此您实际上可以在编译模板时预先填充模板函数,并使用您的实际闭包更新它们,如下所示:

var defaultfuncs = map[string]interface{} {
    "isUser": func(g Gopher) bool { return false;},
}

func init() {
    // Default value returns `false` (only need the correct type)
    t = template.New("home").Funcs(defaultfuncs)
    t, _ = t.Parse(subtmpl)
    t, _ = t.Parse(hometmpl)
}

func main() {
    // When actually serving, we update the closure:
    data := &HomeData{
        User:    "Dewey",
        Popular: []Gopher{"Huey", "Dewey", "Louie"},
        Active:  []Gopher{"Huey", "Louie"},
        Recent:  []Gopher{"Louie"},
    }
    t.Funcs(template.FuncMap{"isUser": func(g Gopher) bool { return string(g) == string(data.User); },})
    t.ExecuteTemplate(os.Stdout, "home", data)
}

虽然我不确定当几个 goroutine 尝试访问同一个模板时它是如何发挥作用的......

工作示例

最直接的方法(尽管不是最优雅的)——尤其是对于新手来说——是“即时”使用匿名结构。 早在 Andrew Gerrand 2012 年出色的演讲“你可能不知道的 10 件事”中就记录/建议了这一点

https://talks.golang.org/2012/10things.slide#1

下面的简单示例:

// define the template

const someTemplate = `insert into {{.Schema}}.{{.Table}} (field1, field2)
values
   {{ range .Rows }}
       ({{.Field1}}, {{.Field2}}),
   {{end}};`

// wrap your values and execute the template

    data := struct {
        Schema string
        Table string
        Rows   []MyCustomType
    }{
        schema,
        table,
        someListOfMyCustomType,
    }

    t, err := template.New("new_tmpl").Parse(someTemplate)
    if err != nil {
        panic(err)
    }

    // working buffer
    buf := &bytes.Buffer{}

    err = t.Execute(buf, data)

请注意,这在技术上不会按原样运行,因为模板需要进行一些小的清理(即去掉范围循环最后一行的逗号),但这相当简单。 将模板的参数包装在匿名结构中可能看起来乏味且冗长,但它具有明确说明模板执行后将使用的内容的额外好处。 绝对比必须为您编写的每个新模板定义一个命名结构更乏味。

我为此问题实现了一个库,它支持类似管道的参数传递和检查。

演示

{{define "foo"}}
    {{if $args := . | require "arg1" | require "arg2" "int" | args }}
        {{with .Origin }} // Original dot
            {{.Bar}}
            {{$args.arg1}}
        {{ end }}
    {{ end }}
{{ end }}

{{ template "foo" . | arg "arg1" "Arg1" | arg "arg2" 42 }}
{{ template "foo" . | arg "arg1" "Arg1" | arg "arg2" "42" }} // will raise an error

Github 仓库

基于@tux21b

我已经改进了该功能,因此即使不指定索引也可以使用它(只是为了保持将变量附加到模板的方式)

所以现在你可以这样做:

{{template "userlist" dict "Users" .MostPopular "Current" .CurrentUser}}

或者

{{template "userlist" dict .MostPopular .CurrentUser}}

或者

{{template "userlist" dict .MostPopular "Current" .CurrentUser}}

但是如果参数 (.CurrentUser.name) 不是一个数组,您肯定需要放置一个索引才能将此值传递给模板

{{template "userlist" dict .MostPopular "Name" .CurrentUser.name}}

看我的代码:

var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
    "dict": func(values ...interface{}) (map[string]interface{}, error) {
        if len(values) == 0 {
            return nil, errors.New("invalid dict call")
        }

        dict := make(map[string]interface{})

        for i := 0; i < len(values); i ++ {
            key, isset := values[i].(string)
            if !isset {
                if reflect.TypeOf(values[i]).Kind() == reflect.Map {
                    m := values[i].(map[string]interface{})
                    for i, v := range m {
                        dict[i] = v
                    }
                }else{
                    return nil, errors.New("dict values must be maps")
               }
            }else{
                i++
                if i == len(values) {
                    return nil, errors.New("specify the key for non array values")
                }
                dict[key] = values[i]
            }

        }
        return dict, nil
    },
}).ParseGlob("templates/*.html")

到目前为止,我发现的最好的(我不是很喜欢)是使用“通用”对容器对参数进行复用和解复用:

http://play.golang.org/p/ri3wMAubPX

type PipelineDecorator struct {
    // The actual pipeline
    Data interface{}
    // Some helper data passed as "second pipeline"
    Deco interface{}
}

func decorate(data interface{}, deco interface{}) *PipelineDecorator {
    return &PipelineDecorator{
        Data: data,
        Deco: deco,
    }
}

我经常使用这个技巧来构建我的网站,我想知道是否存在一些更惯用的方法来实现同样的目标。

广告“......看起来像调用一个只有一个参数的函数。”:

从某种意义上说,每个函数都有一个参数——一个多值调用记录。 使用模板也是一样的,“调用”记录可以是原始值,也可以是多值 {map,struct,array,slice}。 模板可以从任何位置的“单个”管道参数中选择要使用的 {key,field,index}。

IOW,在这种情况下,一个就足够了。

正如其他几个答案中提到的那样,有时地图是解决此类情况的快速简便的解决方案。 由于您经常使用 Gophers(并且基于您的其他问题,您关心当前的 Gopher 是否是管理员),我认为它值得拥有自己的结构:

type Gopher struct {
    Name string
    IsCurrent bool
    IsAdmin bool
}

这是您的 Playground 代码的更新: http ://play.golang.org/p/NAyZMn9Pep

显然,用额外的深度手动编码示例结构有点麻烦,但由于实际上它们将是机器生成的,因此根据需要标记IsCurrentIsAdmin很简单。

我处理这个的方法是装饰一般管道:

type HomeData struct {
    User    Gopher
    Popular []Gopher
    Active  []Gopher
    Recent  []Gopher
}

通过创建特定于上下文的管道:

type HomeDataContext struct {
    *HomeData
    I interface{}
}

分配特定于上下文的管道非常便宜。 您可以通过复制指向它的指针来访问可能很大的HomeData

t.ExecuteTemplate(os.Stdout, "home", &HomeDataContext{
    HomeData: data,
})

由于HomeData嵌入在HomeDataContext中,您的模板将直接访问它(例如,您仍然可以使用.Popular而不是.HomeData.Popular )。 此外,您现在可以访问自由格式字段 ( .I )。

最后,我像这样为HomeDataContext创建了一个Using函数。

func (ctx *HomeDataContext) Using(data interface{}) *HomeDataContext {
    c := *ctx // make a copy, so we don't actually alter the original
    c.I = data
    return &c
}

这允许我保持状态( HomeData ),但将任意值传递给子模板。

请参阅http://play.golang.org/p/8tJz2qYHbZ

根据您的目标, https://github.com/josharian/tstruct博客文章)可能会有所帮助。 您将定义一个名为UserList的 Go 结构,使用 tstruct 为其自动生成 FuncMap 助手,然后编写如下内容:

{{ template "userlist" UserList (Users .MostPopular) (Current .CurrentUser) }}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM