Modèles de génération de texte avec Go

Vue d'ensemble

Le texte nous entoure en tant que développeur de logiciels. Le code est un texte, HTML est un texte, XNL / JSON / YAML / TOML est un texte, Markdown est un texte, CSV est un texte. Tous ces formats de texte sont conçus pour répondre aux besoins des hommes et des machines. Les humains doivent pouvoir lire et éditer des formats de texte avec des éditeurs de texte brut. 

Mais il existe de nombreux cas où vous devez générer du texte dans un certain format. Vous pouvez convertir d'un format à un autre, créer votre propre DSL, générer automatiquement un code d'assistance ou tout simplement personnaliser un courrier électronique avec des informations spécifiques à l'utilisateur. Quel que soit le besoin, Go est plus que capable de vous aider tout au long de son parcours avec ses puissants modèles. 

Dans ce tutoriel, vous en apprendrez plus sur les tenants et les aboutissants des modèles Go et sur leur utilisation pour générer de puissants textes..

Quels sont les modèles Go?

Les modèles Go sont des objets qui gèrent du texte avec des espaces réservés spéciaux appelés actions, qui sont entourés de doubles accolades: une action. Lorsque vous exécutez le modèle, vous lui fournissez une structure Go contenant les données nécessaires aux espaces réservés.. 

Voici un exemple rapide qui génère des blagues. Toc, blague a un format très strict. Les seules choses qui changent sont l'identité du heurteur et du punchline..

package principal d'importation ("text / template" "os") type Joke struct chaîne string Punchline chaîne func main () t: = template.New ("Knock Knock Joke") text: = 'Knock Knock \ nQui est-ce? .Qui qui qui? .Punchline 't.Parse (text) blokes: = [] Joke "Etch", "À tes souhaits!", "La vache va", "Non, la vache va moo!", Pour _, blague: = blagues sur la plage t.Execute (os.Stdout, blague) Sortie: Knock Knock Qui est là? Etch Etch qui? Soyez bénis! Knock Knock qui est là? Vache va Vache va qui? Non, la vache va moo!

Comprendre les actions de modèle

La syntaxe du modèle est très puissante et prend en charge des actions telles que des accesseurs de données, des fonctions, des pipelines, des variables, des conditions et des boucles..

Accesseurs de données

Les accesseurs de données sont très simples. Ils extraient simplement les données du début de la structure. Ils peuvent aussi explorer les structures imbriquées:

func main () family: = Family Père: Personne "Tarzan", Mère: Personne "Jane", ChildrenCount: 2, t: = template.New ("Father") text: = "Le père nom est .Father.Name "t.Parse (texte) t.Execute (os.Stdout, famille) 

Si les données ne sont pas une structure, vous pouvez simplement utiliser . pour accéder directement à la valeur:

func main () t: = template.New ("") t.Parse ("Tout est permis: . \ n") t.Execute (os.Stdout, 1) t.Execute (os.Stdout, "deux") t.Execute (os.Stdout, 3.0) t.Execute (os.Stdout, map [chaîne] int "quatre": 4) Sortie: Tout est compris: 1 Tout est correct: deux Tout est correct: 3 Tout va bien: carte [quatre: 4] 

Nous verrons plus tard comment traiter les tableaux, les tranches et les cartes.

Les fonctions

Les fonctions élèvent vraiment ce que vous pouvez faire avec des modèles. Il existe de nombreuses fonctions globales et vous pouvez même ajouter des fonctions spécifiques à un modèle. La liste complète des fonctions globales est disponible sur le site Web de Go.

Voici un exemple d'utilisation de la printf fonction dans un modèle:

func main () t: = template.New ("") t.Parse ('Ne conserve que 2 décimales de π: printf "% .2f".') t.Execute (os.Stdout, math. Pi) Sortie: ne garder que 2 décimales de π: 3.14 

Pipelines

Les pipelines vous permettent d'appliquer plusieurs fonctions à la valeur actuelle. La combinaison de différentes fonctions élargit considérablement la façon dont vous pouvez trancher et dés vos valeurs. 

Dans le code suivant, je chaîne trois fonctions. Tout d’abord, la fonction d’appel exécute la fonction passée à Exécuter(). Puis le len function renvoie la longueur du résultat de la fonction d'entrée, qui est 3 dans ce cas. Finalement, le printf fonction imprime le nombre d'éléments.

func main () t: = template.New ("") t.Parse ('call. | len | printf "% d items"') t.Execute (os.Stdout, func () chaîne  retourne "abc") Sortie: 3 éléments

Variables

Parfois, vous souhaitez réutiliser plusieurs fois le résultat d'un pipeline complexe. Avec les modèles Go, vous pouvez définir une variable et la réutiliser autant de fois que vous le souhaitez. L'exemple suivant extrait le nom et le prénom de la structure en entrée, les cite et les stocke dans les variables $ F et $ L. Ensuite, il les rend dans l'ordre normal et inverse. 

Une autre astuce intéressante consiste ici à transmettre une structure anonyme au modèle afin de rendre le code plus concis et d'éviter de l'encombrer de types utilisés uniquement à un seul endroit..

func main () t: = template.New ("") t.Parse ('$ F: = .FirstName | printf "% q" L: = .LastName | printf "% q"  Normal: $ F $ L Inverser: $ L $ F ') t.Execute (os.Stdout, struct chaîne FirstName Nom chaîne "Gigi "," Sayfan ",) Sortie: Normal:" Gigi "" Sayfan "Inverse:" Sayfan "" Gigi "

Les conditions

Mais ne nous arrêtons pas ici. Vous pouvez même avoir des conditions dans vos modèles. Il y a un si-fin action et si-sinon-fin action. La clause if est affichée si la sortie du pipeline conditionnel n'est pas vide:

func main () t: = template.New ("") t.Parse ('if. - . else Aucune donnée n'est disponible fin') t. Execute (os.Stdout, "42") t.Execute (os.Stdout, "") Sortie: 42 Aucune donnée disponible. 

Notez que la clause else crée une nouvelle ligne et que le texte "Aucune donnée disponible" est indenté de manière significative..

Boucles

Les modèles de go ont aussi des boucles. Ceci est très utile lorsque vos données contiennent des tranches, des cartes ou d’autres itérables. L'objet de données d'une boucle peut être n'importe quel objet Go éditable, tel qu'un tableau, une tranche, une carte ou un canal. La fonction de plage vous permet de parcourir l'objet de données et de créer une sortie pour chaque élément. Voyons comment itérer sur une carte:

func main () t: = template.New ("") e: = 'Nom, scores plage $ k, $ v: =. $ k plage $ s: = $ v , $ s fin fin 't.Parse (e) t.Execute (os.Stdout, map [chaîne] [] int "Mike": 88, 77 , 99, "Betty": 54, 96, 78, "Jake": 89, 67, 93,) Sortie: Nom, Scores Betty, 54,96,78 Jake, 89,67,93 Mike, 88,77,99 

Comme vous pouvez le constater, l’espace blanc reste un problème. Je n'ai pas réussi à trouver un moyen convenable de l'aborder dans la syntaxe du modèle. Cela nécessitera un post-traitement. En théorie, vous pouvez placer un tiret pour couper les espaces précédant ou suivant les actions, mais cela ne fonctionne pas en présence de intervalle.

Modèles de texte

Les modèles de texte sont implémentés dans le package texte / modèle. En plus de tout ce que nous avons vu jusqu'à présent, ce paquet peut également charger des modèles à partir de fichiers et composer plusieurs modèles à l'aide de l'action modèle. L'objet Template lui-même dispose de nombreuses méthodes pour prendre en charge de tels cas d'utilisation avancés:

  • ParseFiles ()
  • ParseGlob ()
  • AddParseTree ()
  • Cloner()
  • DefinedTemplates ()
  • Delims ()
  • ExecuteTemplate ()
  • Funcs ()
  • Chercher()
  • Option()
  • Modèles ()

En raison de contraintes d'espace, je ne vais pas entrer dans les détails (peut-être dans un autre tutoriel).

Modèles HTML 

Les modèles HTML sont définis dans le package html / template. Il a exactement la même interface que le paquet de modèle de texte, mais il est conçu pour générer du HTML qui est à l'abri de l'injection de code. Cela se fait en nettoyant soigneusement les données avant de les incorporer dans le modèle. L'hypothèse de travail est que les auteurs de modèles sont approuvés, mais que les données fournies au modèle ne sont pas fiables.. 

C'est important. Si vous appliquez automatiquement les modèles que vous recevez de sources non fiables, le package html / template ne vous protégera pas. Il est de votre responsabilité de vérifier les modèles.

Voyons la différence entre la sortie de texte / modèle et html / template. Lorsque vous utilisez le texte / modèle, il est facile d'injecter du code JavaScript dans la sortie générée.

package main import ("text / template" "os") func main () t, _: = template.New (""). Parse ("Bonjour, .!") d: = ""t.Execute (os.Stdout, d) Sortie: Bonjour, ! 

Mais en important html / template au lieu de texte / modèle empêche cette attaque, en échappant les balises de script et les parenthèses:

Bonjour, !

Traiter les erreurs

Il existe deux types d’erreurs: les erreurs d’analyse et les erreurs d’exécution. le Parse () La fonction analyse le texte du modèle et renvoie une erreur, que j'ai ignorée dans les exemples de code, mais dans le code de production, vous souhaitez détecter ces erreurs plus tôt et les résoudre.. 

Si vous voulez une sortie rapide et sale, le Doit() méthode prend la sortie d'une méthode qui retourne (* Modèle, erreur)-comme Cloner(), Parse (), ou ParseFiles ()-et panique si l'erreur n'est pas nulle. Voici comment vérifier une erreur d’analyse explicite:

func main () e: = "Je suis un mauvais modèle, " _, err: = template.New (""). Parse (e) si err! = nil msg: = "Echec de Analyse: '% s'. \ nErreur:% v \ n "fmt.Printf (msg, e, err) Sortie: Échec de l'analyse du modèle: 'Je suis un mauvais modèle, '. Erreur: modèle:: 1: action non fermée inattendue dans la commande 

En utilisant Doit() panique juste si quelque chose ne va pas avec le modèle:

func main () e: = "Je ne suis pas un bon modèle " modèle.Must (modèle.Nouveau (""). Parse (e)) Sortie: panique: modèle:: 1: inattendu non fermé action en commande 

L'autre type d'erreur est une erreur d'exécution si les données fournies ne correspondent pas au modèle. Encore une fois, vous pouvez vérifier explicitement ou utiliser Doit() paniquer. Je recommande dans ce cas de vérifier et de mettre en place un mécanisme de récupération. 

En règle générale, il n'est pas nécessaire de mettre tout le système hors service simplement parce qu'une entrée ne répond pas aux exigences. Dans l'exemple suivant, le modèle attend un champ appelé prénom sur la structure de données, mais je fournis une structure avec un champ appelé Nom complet.

func main () e: = "Il doit exister un nom: .Name" t, _: = template.New (""). Parse (e) err: = t.Execute (os.Stdout, struct FullName string "Gigi Sayfan",) si err! = nil fmt.Println ("Echec de l'exécution.", err) Sortie: Il doit exister un nom: Echec de l'exécution. template:: 1: 24: exécutant "" dans <.Name>: impossible d'évaluer le nom du champ dans le type struct FullName string 

Conclusion

Go a un système de templates puissant et sophistiqué. Il est utilisé avec succès dans de nombreux grands projets comme Kubernetes et Hugo. Le package html / template fournit une installation sécurisée de niveau industriel pour assainir la sortie des systèmes Web. Dans ce tutoriel, nous avons couvert toutes les bases et quelques cas d’utilisation intermédiaires. 

Il existe encore des fonctionnalités plus avancées dans les packages de modèles en attente de déverrouillage. Jouez avec des modèles et intégrez-les dans vos programmes. Vous serez agréablement surpris de la concision et de la lisibilité de votre code de génération de texte..