Generazione del testo con modelli Go

Panoramica

Il testo è tutto intorno a noi come sviluppatori di software. Il codice è testo, HTML è testo, XNL / JSON / YAML / TOML è testo, Markdown è testo, CSV è testo. Tutti questi formati di testo sono progettati per soddisfare sia gli uomini che le macchine. Gli esseri umani dovrebbero essere in grado di leggere e modificare i formati testuali con editor di testo semplice. 

Ma ci sono molti casi in cui è necessario generare testo in un determinato formato. È possibile convertire da un formato all'altro, creare la propria DSL, generare automaticamente un codice di supporto o semplicemente personalizzare un'e-mail con informazioni specifiche dell'utente. Qualunque sia la necessità, Go è più che in grado di assisterti lungo il percorso con i suoi potenti modelli. 

In questo tutorial, imparerai a conoscere i dettagli di modelli Go e come utilizzarli per generare un testo potente.

Cosa sono i modelli?

I modelli Go sono oggetti che gestiscono del testo con segnaposti speciali chiamati azioni, racchiusi tra doppie parentesi graffe: qualche azione. Quando si esegue il modello, viene fornito con una struttura Go che contiene i dati necessari ai segnaposto. 

Ecco un rapido esempio che genera battute a colpi di martello. Una battuta di knock knock ha un formato molto severo. Le uniche cose che cambiano sono l'identità del knocker e la battuta finale.

pacchetto main import ("text / template" "os") tipo Joke struct Who stringa Punchline string func main () t: = template.New ("Knock Knock Joke") text: = 'Knock Knock \ nChi è lì? .Chi chi chi? .Punchline 't.Parse (testo) barzellette: = [] Joke "Etch", "Bless you!", "Cow goes", "No, cow goes moo!", Per _, joke: = range jokes t.Execute (os.Stdout, joke) Output: Knock Knock Chi c'è? Etch Etch chi? Salute! Knock Knock Chi c'è? Cow va Cow va chi? No, la mucca va moo!

Comprendere le azioni del modello

La sintassi del template è molto potente e supporta azioni come accessor di dati, funzioni, pipeline, variabili, condizionali e loop.

Accessors di dati

Gli accessor di dati sono molto semplici. Tirano fuori i dati dalla struttura che inizia. Possono scavare anche nelle strutture nidificate:

func main () family: = Famiglia Padre: Person "Tarzan", Madre: Person "Jane", ChildrenCount: 2, t: = template.New ("Padre") text: = "Il padre nome è .Father.Name "t.Parse (testo) t.Execute (os.Stdout, famiglia) 

Se i dati non sono una struttura, puoi usare solo . per accedere direttamente al valore:

func main () t: = template.New ("") t.Parse ("Anything goes: . \ 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) Output: Qualsiasi cosa va: 1 Qualche cosa va: due Qualche cosa va: 3 Qualche cosa va: mappa [quattro: 4] 

Vedremo in seguito come gestire array, slice e mappe.

funzioni

Le funzioni elevano davvero ciò che puoi fare con i modelli. Esistono molte funzioni globali e puoi persino aggiungere funzioni specifiche del modello. L'elenco completo delle funzioni globali è disponibile sul sito web Go.

Ecco un esempio di come usare il printf funzione in un modello:

func main () t: = template.New ("") t.Parse ('Mantenendo solo 2 decimali di π: printf "% .2f".') t.Execute (os.Stdout, math. Pi) Output: mantenendo solo 2 decimali di π: 3.14 

Condotte

Le pipeline consentono di applicare più funzioni al valore corrente. La combinazione di diverse funzioni espande in modo significativo i modi in cui è possibile suddividere e azzerare i valori. 

Nel seguente codice, concateno tre funzioni. Innanzitutto, la funzione di chiamata esegue la funzione passa a Eseguire(). Poi il len la funzione restituisce la lunghezza del risultato della funzione di input, che in questo caso è 3. Finalmente, il printf funzione stampa il numero di elementi.

func main () t: = template.New ("") t.Parse ('call. | len | printf "% d items"') t.Execute (os.Stdout, func () stringa  return "abc") Output: 3 elementi

variabili

A volte si desidera riutilizzare il risultato di una pipeline complessa più volte. Con i modelli Go, puoi definire una variabile e riutilizzarla tutte le volte che vuoi. L'esempio seguente estrae il nome e il cognome dalla struttura di input, li cita e li memorizza nelle variabili $ F e $ L. Quindi li rende in ordine normale e inverso. 

Un altro trucco chiaro qui è che passo una struttura anonima al modello per rendere il codice più conciso ed evitare di ingombrarlo con tipi che sono usati solo in un posto.

func main () t: = template.New ("") t.Parse ('$ F: = .FirstName | printf "% q" $ L: = .LastName | printf "% q"  Normale: $ F $ L Reverse: $ L $ F ') t.Execute (os.Stdout, struct FirstName stringa LastName stringa "Gigi "," Sayfan ",) Uscita: Normale:" Gigi "" Sayfan "Al contrario:" Sayfan "" Gigi "

Condizionali

Ma non fermiamoci qui. Puoi persino avere condizioni nei tuoi modelli. C'è un se-end azione e if-else-end azione. La clausola if viene visualizzata se l'output della pipeline condizionale non è vuoto:

func main () t: = template.New ("") t.Parse ('if. - . else Nessun dato disponibile end') t. Execute (os.Stdout, "42") t.Execute (os.Stdout, "") Output: 42 Nessun dato disponibile 

Si noti che la clausola else causa una nuova riga e il testo "Nessun dato disponibile" è notevolmente rientrato.

Loops

Anche i modelli hanno loop. Questo è super utile quando i tuoi dati contengono sezioni, mappe o altri iterabili. L'oggetto dati per un loop può essere qualsiasi oggetto Go iterable come array, slice, map o channel. La funzione intervallo consente di eseguire iterazioni sull'oggetto dati e creare un output per ciascun elemento. Vediamo come iterare su una mappa:

func main () t: = template.New ("") e: = 'Nome, Punteggi 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,) Output: Nome, punteggi Betty, 54,96,78 Jake, 89,67,93 Mike, 88,77,99 

Come puoi vedere, lo spazio bianco iniziale è ancora un problema. Non ero in grado di trovare un modo decente per affrontarlo all'interno della sintassi del template. Richiederà la post-elaborazione. In teoria, puoi posizionare un trattino per tagliare lo spazio bianco precedente o successivo alle azioni, ma non funziona in presenza di gamma.

Modelli di testo

I modelli di testo sono implementati nel pacchetto testo / modello. Oltre a tutto ciò che abbiamo visto finora, questo pacchetto può anche caricare modelli da file e comporre più modelli utilizzando l'azione del modello. L'oggetto Template stesso ha molti metodi per supportare casi d'uso avanzati:

  • ParseFiles ()
  • ParseGlob ()
  • AddParseTree ()
  • Clone()
  • DefinedTemplates ()
  • Delims ()
  • ExecuteTemplate ()
  • Funcs ()
  • Consultare()
  • Opzione()
  • Modelli ()

A causa delle limitazioni di spazio, non andrò più in dettaglio (forse in un altro tutorial).

Modelli HTML 

I modelli HTML sono definiti nel pacchetto html / template. Ha esattamente la stessa interfaccia del pacchetto di modelli di testo, ma è progettato per generare codice HTML sicuro dall'iniezione di codice. Ciò avviene disinfettando accuratamente i dati prima di incorporarli nel modello. L'ipotesi di lavoro è che gli autori dei modelli siano attendibili, ma i dati forniti al modello non possono essere considerati attendibili. 

Questo è importante. Se applichi automaticamente i modelli che ricevi da fonti non attendibili, il pacchetto html / template non ti proteggerà. È tua responsabilità controllare i modelli.

Vediamo la differenza tra l'output di text / template e html / template. Quando si utilizza il testo / modello, è facile inserire il codice JavaScript nell'output generato.

pacchetto main import ("text / template" "os") func main () t, _: = template.New (""). Parse ("Hello, .!") d: = ""t.Execute (os.Stdout, d) Output: Ciao, ! 

Ma importando il html / template invece di text / template impedisce questo attacco, evitando i tag dello script e le parentesi:

Ciao, !

Gestire gli errori

Esistono due tipi di errori: errori di analisi e errori di esecuzione. Il Parse () funzione analizza il testo del modello e restituisce un errore, che ho ignorato negli esempi di codice, ma nel codice di produzione si desidera rilevare questi errori in anticipo e affrontarli. 

Se vuoi un'uscita rapida e sporca, allora il Dovere() metodo prende l'output di un metodo che restituisce (* Modello, errore)-piace Clone(), Parse (), o ParseFiles ()-e panico se l'errore non è nullo. Ecco come viene verificato un errore di analisi esplicito:

func main () e: = "I'm a bad template, " _, err: = template.New (""). Parse (e) se err! = nil msg: = "Non riuscito analisi: '% s'. \ nErrore:% v \ n "fmt.Printf (msg, e, err) Output: modello di analisi non riuscito: 'I'm a bad template, '. Errore: modello:: 1: azione inaspettata non chiusa nel comando 

utilizzando Dovere() semplicemente panico se qualcosa non va con il modello:

func main () e: = "I'm a bad template, " template.Must (template.New (""). Parse (e)) Output: panic: template:: 1: inaspettato non chiuso azione al comando 

L'altro tipo di errore è un errore di esecuzione se i dati forniti non corrispondono al modello. Di nuovo, puoi controllare esplicitamente o usare Dovere() farsi prendere dal panico. In questo caso, ti consiglio di verificare e di avere un meccanismo di recupero in atto. 

Di solito, non è necessario portare giù l'intero sistema solo perché un input non soddisfa i requisiti. Nell'esempio seguente, il modello si aspetta un campo chiamato Nome sulla struttura dei dati, ma fornisco una struttura con un campo chiamato Nome e cognome.

func main () e: = "Deve esserci un nome: .Name" t, _: = template.New (""). Parse (e) err: = t.Execute (os.Stdout, struct FullName string "Gigi Sayfan",) se err! = nil fmt.Println ("Impossibile eseguire.", err) Output: deve esserci un nome: impossibile eseguire. template:: 1: 24: eseguendo "" a <.Name>: impossibile valutare il campo Name in type struct FullName string 

Conclusione

Go ha un sistema di templating potente e sofisticato. È usato con grande efficacia in molti grandi progetti come Kubernetes e Hugo. Il pacchetto html / template offre una struttura sicura e industriale per sanificare l'output dei sistemi basati sul web. In questo tutorial, abbiamo trattato tutte le basi e alcuni casi d'uso intermedi. 

Ci sono ancora funzioni più avanzate nei pacchetti di modelli che attendono di essere sbloccati. Gioca con i modelli e inseriscili nei tuoi programmi. Sarai piacevolmente sorpreso di quanto sia sintetico e leggibile il tuo codice di generazione del testo.