Plantillas de generación de texto con Go

Visión general

El texto está a nuestro alrededor como desarrolladores de software. El código es texto, HTML es texto, XNL / JSON / YAML / TOML es texto, Markdown es texto, CSV es texto. Todos estos formatos de texto están diseñados para atender tanto a humanos como a máquinas. Los humanos deben poder leer y editar formatos de texto con editores de texto plano. 

Pero hay muchos casos en los que necesita generar texto en un formato determinado. Puede convertir de un formato a otro, crear su propio DSL, generar un código de ayuda automáticamente o simplemente personalizar un correo electrónico con información específica del usuario. Cualquiera que sea la necesidad, Go es más que capaz de ayudarlo en el camino con sus poderosas plantillas.. 

En este tutorial, aprenderá acerca de los entresijos de las plantillas Go y cómo usarlas para una generación de texto potente..

¿Qué son las plantillas Go??

Las plantillas Go son objetos que administran un texto con marcadores de posición especiales denominados acciones, que están encerrados entre llaves dobles: Algo de acción . Cuando ejecuta la plantilla, le proporciona una estructura Go que tiene los datos que los marcadores de posición necesitan. 

Aquí hay un ejemplo rápido que genera chistes de knock knock. Una broma de knock knock tiene un formato muy estricto. Las únicas cosas que cambian son la identidad de la aldaba y el remate..

paquete main import ("text / template" "os") tipo Joke struct Who string Punchline string func main () t: = template.New ("Knock Knock Joke") text: = 'Knock Knock \ n¿Quién está? .¿Quien quien quien? .Punchline 't.Parse (texto) chistes: = [] Broma "Etch", "¡Bendito seas!", "La vaca se va", "No, ¡la vaca se muere!", Para _, broma: = bromas de rango t.Execute (os.Stdout, broma) Salida: Knock Knock ¿Quién está ahí? Etch Etch quién? ¡Salud! ¿TOC Toc quién está ahí? La vaca va la vaca va quien? No, la vaca va muu!

Entender las acciones de plantilla

La sintaxis de la plantilla es muy poderosa y admite acciones como accesores de datos, funciones, tuberías, variables, condicionales y bucles..

Accesores de datos

Los accesores de datos son muy simples. Simplemente sacan los datos del inicio de la estructura. También pueden perforar estructuras anidadas:

func main () family: = Family Father: Person "Tarzan", Mother: Person "Jane", ChildrenCount: 2, t: = template.New ("Father") text: = "El padre el nombre es .Father.Name "t.Parse (texto) t.Execute (os.Stdout, family) 

Si los datos no son una estructura, puede usar solo . Para acceder directamente al valor:

func main () t: = template.New ("") t.Parse ("Todo vale: . \ n") t.Execute (os.Stdout, 1) t.Execute (os.Stdout, "two") t.Execute (os.Stdout, 3.0) t.Execute (os.Stdout, map [string] int "four": 4) Salida: Todo vale: 1 Todo vale: dos Todo vale: 3 Todo vale: mapa [cuatro: 4] 

Más adelante veremos cómo tratar con matrices, cortes y mapas..

Funciones

Las funciones realmente elevan lo que puedes hacer con las plantillas. Hay muchas funciones globales, e incluso puede agregar funciones específicas de la plantilla. La lista completa de funciones globales está disponible en el sitio web de Go.

Aquí hay un ejemplo de cómo usar el printf función en una plantilla:

func main () t: = template.New ("") t.Parse ('Manteniendo solo 2 decimales de π: printf "% .2f".') t.Execute (os.Stdout, math. Pi) Salida: Manteniendo solo 2 decimales de π: 3.14 

Oleoductos

Las tuberías le permiten aplicar varias funciones al valor actual. La combinación de diferentes funciones expande significativamente las formas en que puede dividir y cortar sus valores. 

En el siguiente código, encadeno tres funciones. Primero, la función de llamada ejecuta la función pasar a Ejecutar(). Entonces el len La función devuelve la longitud del resultado de la función de entrada, que es 3 en este caso. Finalmente, el printf La función imprime el número de artículos..

func main () t: = template.New ("") t.Parse ('call. | len | printf "% d items"') t.Execute (os.Stdout, func () string  return "abc") Salida: 3 artículos

Variables

A veces desea reutilizar el resultado de una tubería compleja varias veces. Con las plantillas Go, puede definir una variable y reutilizarla tantas veces como desee. El siguiente ejemplo extrae el nombre y apellido de la estructura de entrada, los cita y los almacena en las variables $ F y $ L. Luego los procesa en orden normal e inverso.. 

Otro buen truco aquí es que paso una estructura anónima a la plantilla para hacer que el código sea más conciso y evitar el desorden con los tipos que se usan solo en un lugar..

func main () t: = template.New ("") t.Parse ('$ F: = .FirstName | printf "% q" $ L: = .LastName | printf "% q"  Normal: $ F $ L Reverso: $ L $ F ') t.Execute (os.Stdout, struct FirstName string LastName string "Gigi "," Sayfan ",) Salida: Normal:" Gigi "" Sayfan "Reverso:" Sayfan "" Gigi "

Condicionales

Pero no nos detengamos aquí. Incluso puedes tener condiciones en tus plantillas. Hay un si-fin acción y if-else-end acción. La cláusula if se muestra si la salida de la tubería condicional no está vacía:

func main () t: = template.New ("") t.Parse ('if. - . else No hay datos disponibles end') t. Ejecutar (os.Stdout, "42") t.Execute (os.Stdout, "") Salida: 42 No hay datos disponibles 

Tenga en cuenta que la cláusula else provoca una nueva línea, y el texto "No hay datos disponibles" está significativamente sangrado.

Bucles

Ir plantillas tienen bucles también. Esto es muy útil cuando sus datos contienen segmentos, mapas u otros iterables. El objeto de datos para un bucle puede ser cualquier objeto Go iterable, como matriz, sector, mapa o canal. La función de rango le permite iterar sobre el objeto de datos y crear una salida para cada elemento. Veamos cómo iterar sobre un mapa:

func main () t: = template.New ("") e: = 'Name, Scores range $ k, $ v: =. $ k range $ s: = $ v , $ s end end 't.Parse (e) t.Execute (os.Stdout, map [string] [] int "Mike": 88, 77 , 99, "Betty": 54, 96, 78, "Jake": 89, 67, 93,) Salida: Nombre, puntajes Betty, 54,96,78 Jake, 89,67,93 Mike, 88,77,99 

Como puede ver, el espacio en blanco líder sigue siendo un problema. No pude encontrar una manera decente de abordarlo dentro de la sintaxis de la plantilla. Requerirá post-procesamiento. En teoría, puede colocar un guión para recortar los espacios en blanco que preceden o siguen las acciones, pero no funciona en presencia de distancia.

Plantillas de texto

Las plantillas de texto se implementan en el paquete de texto / plantilla. Además de todo lo que hemos visto hasta ahora, este paquete también puede cargar plantillas de archivos y componer varias plantillas utilizando la acción de la plantilla. El objeto Template en sí tiene muchos métodos para admitir tales casos de uso avanzados:

  • ParseFiles ()
  • ParseGlob ()
  • AddParseTree ()
  • Clon()
  • Plantillas definidas ()
  • Delims ()
  • ExecuteTemplate ()
  • Funciones ()
  • Buscar()
  • Opción()
  • Plantillas()

Debido a las limitaciones de espacio, no voy a entrar en más detalles (tal vez en otro tutorial).

Plantillas HTML 

Las plantillas HTML se definen en el paquete html / template. Tiene exactamente la misma interfaz que el paquete de plantillas de texto, pero está diseñado para generar HTML que esté a salvo de la inyección de código. Esto se hace limpiando cuidadosamente los datos antes de incrustarlos en la plantilla. El supuesto de trabajo es que los autores de la plantilla son de confianza, pero los datos proporcionados a la plantilla no pueden ser confiables. 

Esto es importante. Si aplica automáticamente las plantillas que recibe de fuentes que no son de confianza, el paquete html / template no lo protegerá. Es tu responsabilidad revisar las plantillas..

Veamos la diferencia entre la salida de texto / plantilla y html / plantilla. Al usar el texto / plantilla, es fácil inyectar código JavaScript en la salida generada.

paquete main import ("text / template" "os") func main () t, _: = template.New (""). Parse ("Hello, .!") d: = ""t.Execute (os.Stdout, d) Salida: Hola, ! 

Pero importando el html / plantilla en lugar de texto / plantilla evita este ataque, escapando de las etiquetas de script y los paréntesis:

Hola, !

Tratar con los errores

Hay dos tipos de errores: errores de análisis y errores de ejecución. los Analizar gramaticalmente() La función analiza el texto de la plantilla y devuelve un error, que ignoré en los ejemplos de código, pero en el código de producción desea detectar estos errores antes de tiempo y solucionarlos.. 

Si quieres una salida rápida y sucia, entonces la Debe() Método toma la salida de un método que devuelve (* Plantilla, error)-me gusta Clon(), Analizar gramaticalmente(), o ParseFiles ()-y pánico si el error no es nulo. Aquí es cómo se comprueba un error de análisis explícito:

func main () e: = "Soy una plantilla defectuosa, " _, err: = template.New (""). Parse (e) if err! = nil msg: = "Error al análisis: '% s'. \ n Error:% v \ n "fmt.Printf (msg, e, err) Salida: Error al analizar la plantilla: 'Soy una plantilla incorrecta, '. Error: plantilla:: 1: acción inesperada no cerrada en el comando 

Utilizando Debe() Sólo pánico si algo está mal con la plantilla:

func main () e: = "Soy una mala plantilla, " plantilla.Debe (plantilla.Nuevo (""). Parse (e)) Salida: panic: plantilla:: 1: inesperado sin cerrar acción al mando 

El otro tipo de error es un error de ejecución si los datos proporcionados no coinciden con la plantilla. Nuevamente, puedes verificar explícitamente o usar Debe() al pánico. En este caso, recomiendo que verifique y tenga instalado un mecanismo de recuperación.. 

Por lo general, no es necesario desactivar todo el sistema solo porque una entrada no cumple con los requisitos. En el siguiente ejemplo, la plantilla espera un campo llamado Nombre en la estructura de datos, pero proporciono una estructura con un campo llamado Nombre completo.

func main () e: = "Debe haber un nombre: .Name" t, _: = template.New (""). Parse (e) err: = t.Execute (os.Stdout, struct FullName string "Gigi Sayfan",) if err! = nil fmt.Println ("Fail to execute.", err) Output: Debe haber un nombre: Fail al ejecutar. plantilla:: 1: 24: ejecutando "" en <.Name>: no se puede evaluar el nombre del campo en tipo struct cadena de nombre completo 

Conclusión

Go tiene un potente y sofisticado sistema de plantillas. Se utiliza con gran efecto en muchos proyectos grandes como Kubernetes y Hugo. El paquete html / template proporciona una instalación segura y de gran potencia industrial para sanear la salida de los sistemas basados ​​en la web. En este tutorial, cubrimos todos los conceptos básicos y algunos casos de uso intermedios. 

Todavía hay características más avanzadas en los paquetes de plantillas que esperan ser desbloqueados. Juega con plantillas e incorpóralas a tus programas. Se sorprenderá gratamente de lo conciso y legible que se ve su código de generación de texto..