Textgenerering med Go Mallar

Översikt

Text är runt omkring oss som mjukvaruutvecklare. Koden är text, HTML är text, XNL / JSON / YAML / TOML är text, Markdown är text, CSV är text. Alla dessa textformat är utformade för att tillgodose både människor och maskiner. Människor ska kunna läsa och redigera textformat med vanliga textredigerare. 

Men det finns många fall där du behöver skapa text i ett visst format. Du kan konvertera från ett format till ett annat, skapa din egen DSL, generera någon hjälpenkod automatiskt eller bara anpassa ett e-postmeddelande med användarspecifik information. Vad som än är nödvändigt är Go mer än att hjälpa dig på vägen med sina mäktiga mallar. 

I den här handledningen lär du dig information om inlägg och utlägg i Go-mallar och hur du använder dem för kraftfull textgenerering.

Vad är Go Mallar?

Go mallar är objekt som hanterar lite text med speciella platshållare-kallade handlingar, som är inneslutna av dubbla krullningsparvar: en handling. När du kör mallen ger du den en Go-struktur som har de uppgifter som platshållarna behöver. 

Här är ett snabbt exempel som genererar knock knock vitsar. Ett knock knock joke har ett mycket strikt format. De enda saker som förändras är knockarens identitet och slaglinjen.

paket huvud import ("text / mall" "os") typ Joke struct Vilken sträng Punchline sträng func main () t: = template.New ("Knock Knock Joke") text: = 'Knock Knock \ nVem är där? .Vem vem vem? .Punchline 't.Parse (text) skämt: = [] Joke "Etch", "Bless you!", "Cow goes", "No, cow goes moo!" _, skämt: = intervju skämt t.Execute (os.Stdout, skämt) Output: Knock Knock Vem är det? Etch Etch vem? Välsigna dig! Knock Knock Vem är där? Cow går Cow går vem? Nej, ko går mamma!

Förstå Mallåtgärder

Mallsyntaxen är mycket kraftfull och stöder handlingar som datatillträdare, funktioner, pipelines, variabler, conditionals och loopar.

Datatillträdare

Data accessors är mycket enkla. De drar bara data ut ur struct start. De kan även borra in i kapslade strukturer:

func main () familj: = Familj Far: Person "Tarzan", Mor: Person "Jane", ChildrenCount: 2, t: = template.New ("Father") text: = "Faderns namnet är .Father.Name "t.Parse (text) t.Execute (os.Stdout, familj) 

Om data inte är en struktur, kan du bara använda . för att få tillgång till värdet direkt:

func main () t: = mall.Ny ("") t.Parse ("Allting går: . \ n") t.Execute (os.Stdout, 1) t.execute (os.Stdout, "två") t.execute (os.Stdout, 3.0) t.execute (os.Stdout, map [string] int "four": 4) Output: Allting går: 1 Allt går: två Allt går: 3 Allting går: karta [fyra: 4] 

Vi kommer senare se hur vi hanterar arrays, skivor och kartor.

funktioner

Funktioner höjer verkligen vad du kan göra med mallar. Det finns många globala funktioner, och du kan till och med lägga till mallspecifika funktioner. Den fullständiga listan över globala funktioner finns på Go-webbplatsen.

Här är ett exempel på hur du använder printf funktion i en mall:

func main () t: = template.New ("") t.Parse ('Håller bara 2 decimaler av π: printf "% .2f".') t.execute (os.Stdout, math. Pi) Utgång: Håller bara 2 decimaler av π: 3.14 

pipelines

Rörledningar låter dig tillämpa flera funktioner till det aktuella värdet. Kombinera olika funktioner expanderar väsentligt hur du kan skära och tärning dina värden. 

I följande kod kedjar jag tre funktioner. Först utförs samtalsfunktionen funktionspasset till Kör(). Sedan len funktionen returnerar längden på resultatet av inmatningsfunktionen, vilket är 3 i det här fallet. Slutligen, den printf funktionen skriver ut antalet objekt.

func main () t: = mall.Ny ("") t.Parse ('call. | len | printf'% d items '') t.execute (os.Stdout, func returnera "abc") Utgång: 3 objekt

variabler

Ibland vill du återanvända resultatet av en komplex rörledning flera gånger. Med Go-mallar kan du definiera en variabel och återanvända den så många gånger du vill. Följande exempel extraherar för- och efternamn från inmatningsstrukturen, citerar dem och lagrar dem i variablerna $ F och $ L. Då gör den dem i normal och omvänd ordning. 

Ett annat snyggt knep här är att jag skickar en anonym struktur till mallen för att göra koden mer koncis och förhindra att det klämmas med typer som endast används på ett ställe.

func main () t: = mall.Ny ("") t.Parse ('$ F: = .FirstName | printf "% q" $ L: = .LastName | printf "% q"  Normal: $ F $ L Omvänd: $ L $ F ') t.Execute (os.Stdout, struct Förnamnsträng LastName string "Gigi "," Sayfan ",) Utmatning: Normal:" Gigi "" Sayfan "Omvänd:" Sayfan "" Gigi "

villkors

Men låt oss inte stanna här. Du kan till och med ha villkor i dina mallar. Där är en om-end åtgärd och if-else-end verkan. Om-klausulen visas om utgången från den villkorliga rörledningen inte är tom:

func main () t: = template.New ("") t.Parse ('if. . else Inga data finns tillgängliga end') t. Execute (os.Stdout, "42") t.Execute (os.Stdout, "") Utgång: 42 Inga data finns 

Observera att den andra klausulen orsakar en ny rad, och texten "Ingen tillgänglig data" är signifikant inryckt.

Loops

Gå mallar har loopar också. Det här är super användbart när dina data innehåller skivor, kartor eller andra iterables. Datobjektet för en slinga kan vara något iterabelt Go-objekt som array, skiva, karta eller kanal. Områdesfunktionen låter dig iterera över dataobjektet och skapa en utgång för varje element. Låt oss se hur man repeterar över en karta:

func main () t: = mall.Ny ("") e: = 'Namn, poäng område $ k, $ v: =. $ k range $ s: = $ v , $ s end end 't.Parse (e) t.Execute (os.Stdout, karta [sträng] [] int "Mike": 88, 77 , 99, "Betty": 54, 96, 78, "Jake": 89, 67, 93,) Utgång: Namn, Scores Betty, 54,96,78 Jake, 89,67,93 Mike, 88,77,99 

Som du kan se är den ledande blankrummet fortfarande ett problem. Jag kunde inte hitta ett anständigt sätt att ta itu med i mallsyntaxen. Det kommer att kräva efterbehandling. I teorin kan du placera ett streck för att trimma whitespace före eller efterföljande handlingar, men det fungerar inte i närvaro av räckvidd.

Textmallar

Textmallar implementeras i text / mallpaketet. Förutom allt vi hittills har sett kan det här paketet också ladda mallar från filer och komponera flera mallar med hjälp av mallåtgärden. Själva mallobjektet har många metoder för att stödja sådana avancerade användningsfall:

  • ParseFiles ()
  • ParseGlob ()
  • AddParseTree ()
  • Klona()
  • DefinedTemplates ()
  • Delims ()
  • ExecuteTemplate ()
  • Funcs ()
  • Slå upp()
  • Alternativ()
  • Mallar ()

På grund av rymdbegränsningar kommer jag inte att gå in mer detaljerat (kanske i en annan handledning).

HTML-mallar 

HTML-mallar definieras i html / mallpaketet. Den har exakt samma gränssnitt som textmallpaketet, men det är utformat för att generera HTML som är säkert från kodinjektion. Detta görs genom noggrann sanering av data innan den läggs in i mallen. Arbetsantagandet är att mallförfattare är betroda, men de uppgifter som lämnas till mallen kan inte lita på. 

Det här är viktigt. Om du automatiskt ansöker mallar som du får från otillförlitliga källor kommer inte html / mallpaketet att skydda dig. Det är ditt ansvar att kontrollera mallarna.

Låt oss se skillnaden mellan produktionen av text / mall och html / mall. När du använder texten / mallen är det enkelt att injicera JavaScript-kod i den genererade produktionen.

Paketets huvudimport ("text / mall" "os") func main () t, _: = mall.Ny (""). Parse ("Hej, .!") d: = ""t.Execute (os.Stdout, d) Utgång: Hej, ! 

Men import av html / mall istället för text / mall förhindrar denna attack, genom att flyga skriptet och parenteserna:

Hej, !

Hantera fel

Det finns två typer av fel: analysera fel och körningsfel. De Parse () funktionen analyserar malltexten och returnerar ett fel som jag ignorerade i kodproverna men i produktionskoden vill du fånga dessa fel tidigt och adressera dem. 

Om du vill ha en snabb och smutsig utgång, då Måste() Metoden tar utgången från en metod som returnerar (* Mall, fel)-tycka om Klona(), Parse (), eller ParseFiles ()-och panikerar om felet inte är noll. Så här söker du efter ett explicit analyseringsfel:

func main () e: = "Jag är en dålig mall, " _, err: = mall.Ny (""). Parse (e) om err! = nil msg: = "Misslyckades parsing: '% s'. \ nFel:% v \ n "fmt.Printf (msg, e, err) Output: Misslyckades med att analysera mall:" Jag är en dålig mall, ' Fel: mall:: 1: oväntad oupplåst åtgärd i kommando 

Använder sig av Måste() bara panikerar om något är fel med mallen:

func main () e: = "Jag är en dålig mall, " template.Must (template.New (""). Parse (e)) Utmatning: panik: mall:: 1: oväntat åtgärd i kommando 

Den andra typen av fel är ett exekveringsfel om den angivna data inte matchar mallen. Återigen kan du kolla explicit eller använda Måste() att få panik. Jag rekommenderar i detta fall att du kontrollerar och har en återställningsmekanism på plats. 

Vanligtvis behöver man inte sätta ner hela systemet bara för att en insats inte uppfyller kraven. I följande exempel förväntar sig mallen ett fält som heter namn på datastrukturen, men jag ger en struktur med ett fält som heter Fullständiga namn.

func main () e: = "Det måste finnas ett namn: .Name" t, _: = mall.Ny (""). Parse (e) err: = t.execute (os.Stdout, struct FullName string "Gigi Sayfan",) om err! = nil fmt.Println ("Fail to execute.", err) Utdata: Det måste finnas ett namn: Underlåtenhet att exekvera. mall:: 1: 24: exekverar "" på <.Name>: kan inte utvärdera fält Namn i typ struct FullName string 

Slutsats

Go har ett kraftfullt och sofistikerat templerande system. Den har stor effekt i många stora projekt som Kubernetes och Hugo. Html / mallpaketet erbjuder en säker industriell styrka för att sanitera produktionen från webbaserade system. I den här handledningen täckte vi alla grunderna och några mellanliggande fall. 

Det finns fortfarande mer avancerade funktioner i mallpaketet som väntar på att låsa upp. Spela med mallar och införliva dem i dina program. Du kommer bli glatt överraskad hur koncist och läsbar din textgenereringskod ser ut.