Templatewrapper

Go template uitbreiden met een eigen functie

Het Probleem

Ik heb een simpele webservice in golang gemaakt, waarmee ik via de web brouwser lokaal journals, todo’s en url’s in simpele markdown bestanden kan bewerken en lokaal kan opslaan. goproductive. Voor mij heel handig want die lokale bestanden synchroniseer ik dan weer met Resilio naar al mijn machines en een paar Vps-en. Hierdoor kan ik deze locale bestandjes op al mijn machienes bekijken en bewerken via mijn gezellige webservice-je.

Maar zoals iedere webservice heeft mijn webservice-je naast programma (.go) bestanden natuurlijk ook .html, .ccs en .script bestanden. En die waren de oorzaak van het niet zo uitwisselbaar zijn van mijn gecompileerde applicatie. Op mijn thuis laptop deed ik go build. En daarna goproductive --path ../Documents/UrlAndNote en dan kon ik aan de slag. Maar op mijn werk laptop kon ik dan niet gewoon goproductive --path ../Documents/UrlAndNote doen. De templates en andere bestanden werden niet gevonden, want op beide pc heb ik verschillende home directorys. (bij compileren worden de root paden gebruikt, dat is wel fraai)

De oorzaak van dit probleem is dat de .html, .script, .css en .jpg enz, de assets, niet in de binary mee gecompileerd worden. go build of go install kijken alleen naar de .go bestanden en maken daar een applicatie van. Wanneer de .html, .css enz in eenst ergens anders staan kan de applicatie deze niet meer vinden.

Oplossingen

Er zijn een aantal oplossingen voor dit probleem. Je kan bijvoorbeeld een aparte repo maken met je statische bestanden en dan een script bouwen waarmee je eerst een checkout doet van de statische bestanden repo en daarna de webservice opstart. Het voordeel hiervan is dat je de statisch bestanden (.html, .css, enz) kan wijzigen en gebruiken, zonder dat je steeds opnieuwe de webservice hoeft to compileren. Het nadeel is dus dat je geen uitwisselbare applicatie hebt.

Een andere oplossing zou kunnen zijn om alle template inhoud, in de gofiles te typen. func getTemplate(return "<html><body>...</body></html>") Maar daarmee ga je het niet redden als je ook plaatje wil gebruiken. En daarnaast is het veel fijner om html in een .html bestand te hebben, want dan werkt je editor lekker mee met syntax herkenning. (geld ook voor .css, en .java)

De oplossing die ik koos was om al die assets mee te compileren in mijn binary. Daar zijn natuurlijk tools voor. De tool die ik daarvoor wilde gebruiken is: packer V2 en dat was het begin van een nieuw probleem. Ik kon de html/Template.ParseFiles(filenames …string) niet meer gebruiken.

NOTE: Het voordeel van packer is dat wanneer je gewoon een go build -v doet, nog steeds de locale assets worden gebuikt. Pas wanneer je packer build -v doet worden de assets mee gecompileerd. NOTE: Nadeel van packer is dat je binary groter wordt. Als je heel veel plaatjes enze hebt is dit niet de ideale oplossing.

Uitwerking

In mijn applicatie gebruik ik de html/template package. Daarmee kan ik .html bestanden schrijven die programmatisch worden gevuld met variabelen uit het programma. Meestal noemen ze dit parsen. Met html.Template.ParseFiles(filenames …string) kan je meerder templates parsen. Ik gebruikte die functie om mijn teplates uit delen samen te stellen als volgt: ParseFiles("./templates/index.html", "./templates/header.html", "./templates/contendHeadLess.html","./templates/footer.html") De index.html ziet er dan als volgt uit:

{{template "header.html" .}}
{{template "contendHeadLess.html"}}
{{template footer.html}}

Maar het probleem is dat ik door packer V2 te gebruiken, de functie ParseFiles niet meer kon gebruiken. De functie ParseFiles(filename …string) verwacht bestand namen waarvan de bestanden echt geopend kunnen worden. En de manier om een file via packer te lezen is met box.FindString("index.html") alleen daar komt direct de text van het bestand uit als string. Dus om een handige manier te hebben om mijn templates op te bouwen moest ik een functie hebben,

  1. waar ik bestandnamen als parameters kon meegeven,
  2. die door box.FindString(name) werden gelezen,
  3. en om daar vervolgens een template aan de template stack toe te voegen,
  4. en om vervolgens de text van het .html te parsen met template.Parse(text string).

Opzich niet zo heel ingewikkeld. Als ik de beheerder zou zijn van de html.Template package, zou ik de volgende methode maken:

func(t *Template) Template.ParseFilesWithPackerV2  (filenames ...string) *Template {
  for _, filename := range filenames {
    nt := t.New(filename)
    nt.Parse(box.FindString(filename)) // Ik check de error die terug kan komen even niet
  }
  return t
}

Maar omdat ik niet de beheerder ben van het package html/template werkt dat niet. De oplossing is om zelf een Type struct te maken die als een ware een wrapper is om de Template type:

type MyTemplate struct{ *html.Template }

Alle methods van de struct die in mijn struct zit, zijn dan direct benaderbaar. En voor je eigen type kan je dan wel een method schrijven:

func (t * MyTemplate) ParseFiles(filename ...string) { 
  // Hier de code
}

En daar begind een wonderlijk pointer spel!

Want, je moet de type Template die je terugkrijgt van een html/Template.New(“name” string) en bij Template.Parse(text), blijven wrappen in je eigen type. Het resultaat zag er bij mij uiteindelijk zo uit:

import (
  "html/template"
  "github.com/gobuffalo/packr/v2"
)
func main(){
  box := packr.New("myBox", "./templates")
  type MyTemplate struct{ *html/Template }
  func NewMyTemplate(name string) *MyTemplate {return &MyTemplate{Template.New(name)}}
  func (mTpl *MyTemplate) ParseFilesWithPackerV2(filenames ...string) (*MyTemplate, error) {
    for _, filename := range filenames {
      if mTpl.name == filename {
        tTpl := mTpl
      } else {
        tTpl := &MyTemplate{mTpl.New(filename)}
      }
      _,err := tTpl.Parse(box.FindString(filename))
      if err != nil {
        return nil, err
      }
    }
    return mTpl, nil
  }
}

En zo kan je dus een soort van extensie maken op een package, en wordt het refactoren van mijn originele code zo makkelijk als het vervangen van Template.ParseFiles() naar MyTemplate.ParseFilesWithPackerV2(). Een simpele zoek en vervang aktie dus.

Wanneer ik nu mijn go programma compileer met packr2 build -v of packr2 install -v, dan zitten mijn assets in de binary, die wel iets groter is geworden, die nu op al mijn machienes (linux) te gebruiken is.

 
comments powered by Disqus