yoast variable template snippet sitename sep plugin page examples description templates go go-templates

templates - variable - Llamando a una plantilla con varios parámetros de pipeline



yoast meta description (9)

A veces, los mapas son una solución rápida y fácil para situaciones como esta, como se menciona en algunas de las otras respuestas. Ya que estás usando mucho Gophers (y dado que, en base a tu otra pregunta, te importa si el actual Gopher es un administrador), creo que merece su propia estructura:

type Gopher struct { Name string IsCurrent bool IsAdmin bool }

Aquí hay una actualización de su código de Playground: http://play.golang.org/p/NAyZMn9Pep

Obviamente, la codificación de las estructuras de ejemplo con un nivel adicional de profundidad resulta un poco complicada, pero como en la práctica serán generadas por una máquina, es sencillo marcar IsCurrent e IsAdmin según sea necesario.

En una plantilla Go, a veces la forma de pasar los datos correctos a la plantilla correcta me resulta incómoda. Llamar a una plantilla con un parámetro de canalización se parece a llamar a una función con un solo parámetro.

Digamos que tengo un sitio para Gophers sobre Gophers. Tiene una plantilla principal de la página de inicio y una plantilla de utilidad para imprimir una lista de Gophers.

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

Salida:

*The great GopherBook* (logged in as Dewey) [Most popular] >> Huey >> Dewey >> Louie [Most active] >> Huey >> Louie [Most recent] >> Louie

Ahora quiero agregar un poco de contexto en el subtítulo: formatear el nombre "Dewey" de manera diferente dentro de la lista porque es el nombre del usuario registrado actualmente. ¡Pero no puedo pasar el nombre directamente porque solo hay un posible canal de argumento de "punto"! ¿Que puedo hacer?

  • Obviamente, puedo copiar y pegar el código de subtemplate en la plantilla principal (no quiero porque deja de lado todo el interés de tener una subtemplate).
  • O puedo hacer malabares con algún tipo de variables globales con los accesores (tampoco quiero hacerlo).
  • O puedo crear un nuevo tipo de estructura específica para cada lista de parámetros de la plantilla (no excelente).

El anuncio "... parece que se llama a una función con un solo parámetro.":

En cierto sentido, cada función toma un parámetro: un registro de invocación multivalor. Con las plantillas es lo mismo, ese registro de "invocación" puede ser un valor primitivo, o un {map, struct, array, slice} multivalor. La plantilla puede seleccionar qué {clave, campo, índice} utilizará desde el parámetro de tubería "único" en cualquier lugar.

IOW, uno es suficiente en este caso.


El método más directo (aunque no el más elegante), especialmente para alguien relativamente nuevo, es usar las estructuras anon "sobre la marcha". Esto fue documentado / sugerido ya en la excelente presentación de Andrew Gerrand en 2012, "10 cosas que probablemente no se conocen".

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

Ejemplo trivial a continuación:

// 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)

Tenga en cuenta que esto técnicamente no se ejecutará como está, ya que la plantilla necesita una limpieza menor (es decir, deshacerse de la coma en la última línea del bucle de rango), pero eso es bastante trivial. El ajuste de los parámetros de su plantilla en una estructura anónima puede parecer tedioso y detallado, pero tiene el beneficio adicional de indicar explícitamente qué se usará una vez que se ejecute la plantilla. Definitivamente menos tedioso que tener que definir una estructura con nombre para cada nueva plantilla que escriba.


Implementé una biblioteca para este problema que admite el paso y comprobación de argumentos de tipo tubería.

Manifestación

{{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

Repositorio de github


La forma en que me acerco a esto es decorar la tubería general:

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

creando un canal específico para el contexto:

type HomeDataContext struct { *HomeData I interface{} }

La asignación de la tubería específica del contexto es muy barata. Puede acceder a los HomeData potencialmente grandes copiando el puntero:

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

Dado que HomeData está incrustado en HomeDataContext , su plantilla accederá directamente a ella (por ejemplo, aún puede hacer .Popular y no .HomeData.Popular ). Además, ahora tiene acceso a un campo de forma libre ( .I ).

Finalmente, hago una función Using para HomeDataContext como tal.

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 }

Esto me permite mantener un estado ( HomeData ) pero pasar un valor arbitrario a la sub-plantilla.

Ver http://play.golang.org/p/8tJz2qYHbZ .


Lo mejor que he encontrado hasta ahora (y realmente no me gusta) es muxing y demuxing parámetros con un contenedor de par "genérico":

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, } }

Utilizo mucho este truco para construir mi sitio web, y me pregunto si existe alguna forma más idiomática para lograr lo mismo.


Podría registrar una función "dict" en sus plantillas que puede usar para pasar múltiples valores a una llamada de plantilla. La llamada en sí se vería así:

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

El código para el pequeño ayudante "dict", incluido el registro como una función de plantilla, está aquí:

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")


Puede definir funciones en su plantilla y hacer que estas funciones sean cierres definidos en sus datos de la siguiente manera:

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

Entonces, simplemente puede llamar a esta función en su plantilla:

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

Esta versión actualizada en el patio de juegos es bonita !! alrededor del usuario actual:

*The great GopherBook* (logged in as Dewey) [Most popular] >> Huey >> !!Dewey!! >> Louie [Most active] >> Huey >> Louie [Most recent] >> Louie

EDITAR

Como puede anular las funciones al llamar a Funcs , en realidad puede rellenar previamente las funciones de la plantilla al compilar su plantilla, y actualizarlas con su cierre real de la siguiente manera:

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) }

Aunque no estoy seguro de cómo se juega cuando varios goroutines intentan acceder a la misma plantilla ...

El ejemplo de trabajo


basado en @ tux21b

He mejorado la función para que se pueda usar incluso sin especificar los índices (solo para mantener el camino, se adjuntan variables a la plantilla)

Así que ahora puedes hacerlo así:

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

o

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

o

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

pero si el parámetro (.CurrentUser.name) no es una matriz, definitivamente necesita poner un índice para pasar este valor a la plantilla

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

ver mi codigo

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")