diff --git a/README.md b/README.md index e926f79..fd05af9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ hatmill - HTML generation DSL for Go [![GoDoc](https://godoc.org/gitlab.codemonkeysoftware.net/b/hatmill?status.svg)](https://godoc.org/gitlab.codemonkeysoftware.net/b/hatmill) ![Badge count](https://img.shields.io/badge/badges-5-yellow.svg) +`hatmill` provides a simple set of types and helper functions for writing HTML in plain Go code, without having to deal with any template languages. It is not spectacularly performant, but is comparable to the `html/template` package (at least in simple cases; run `go test -bench=. -benchmem` for proof). `hatmill` “templates” are arguably easier to read and write than many template languages. + Installation --------------- There are three necessary packages: diff --git a/benchmark_test.go b/benchmark_test.go new file mode 100644 index 0000000..4106623 --- /dev/null +++ b/benchmark_test.go @@ -0,0 +1,104 @@ +package hatmill_test + +import ( + "bytes" + h "gitlab.codemonkeysoftware.net/b/hatmill" + he "gitlab.codemonkeysoftware.net/b/hatmill/element" + "html/template" + "io" + "testing" +) + +type benchPerson struct { + Name string + FavoriteAnimals []string +} + +type benchModel struct { + Title string + Persons []benchPerson +} + +var benchData = benchModel{ + Title: "People I know", + Persons: []benchPerson{ + {Name: "Malter Witty", FavoriteAnimals: []string{"porpoise"}}, + {Name: "Perry Moppins", FavoriteAnimals: []string{"penguin"}}, + {Name: "Ralbus Fumblemore", FavoriteAnimals: []string{"owl", "cat", "toad"}}, + {Name: "Wuce Brayne", FavoriteAnimals: []string{"bat", "robin"}}, + }, +} + +type testingT interface { + Helper() + Error(args ...interface{}) +} + +func checkError(t testingT, err error) { + t.Helper() + if err != nil { + t.Error(err) + } +} + +func stdHtmlTemplate(t testingT) func(testingT, io.Writer, benchModel) { + // hatmill does not pretty-print its output, so neither must we here. + const tpl = `{{.Title}}` + + `{{range .Persons}}

{{.Name}}

` + + `` + + `
{{end}}` + compiled, err := template.New("page").Parse(tpl) + checkError(t, err) + return func(t testingT, w io.Writer, model benchModel) { + checkError(t, compiled.Execute(w, model)) + } +} + +func hatmillTemplate(t testingT, w io.Writer, model benchModel) { + var personHtml h.Terms + for _, p := range model.Persons { + var animalHtml h.Terms + for _, a := range p.FavoriteAnimals { + animalHtml = append(animalHtml, he.Li()(h.Text(a))) + } + personHtml = append(personHtml, he.Div()( + he.H1()(h.Text(p.Name)), + he.Ul()(animalHtml), + )) + } + html := he.Html()( + he.Head()( + he.Title()(h.Text(model.Title)), + ), + he.Body()(personHtml), + ) + _, err := h.WriteDocument(w, html) + checkError(t, err) +} + +func TestBenchmarkEquivalence(t *testing.T) { + var stdHtmlOutput bytes.Buffer + stdHtmlTemplate(t)(t, &stdHtmlOutput, benchData) + var hatmillOutput bytes.Buffer + hatmillTemplate(t, &hatmillOutput, benchData) + if stdHtmlOutput.String() != hatmillOutput.String() { + t.Fatalf("Output does not match\nhtml/template: %s\nhatmill: %s", + &stdHtmlOutput, &hatmillOutput) + } +} + +func doBenchmark(b *testing.B, tpl func(testingT, io.Writer, benchModel)) { + var buf bytes.Buffer + for i := 0; i < b.N; i++ { + buf.Reset() + tpl(b, &buf, benchData) + } +} + +func BenchmarkStdHtmlTemplate(b *testing.B) { + doBenchmark(b, stdHtmlTemplate(b)) +} + +func BenchmarkHatmill(b *testing.B) { + doBenchmark(b, hatmillTemplate) +}