cerrado @ f5b5f72f7eaf14692b8036c3698037c647b2423a

feat: Add qtc templating
diff --git a/.gitignore b/.gitignore
index e660fd93d3196215552065b1e63bf6a2f393ed86..776d7a2c9749474ff5daa02e3b6842d4c1d8a36d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 bin/
+static/*.css
diff --git a/Makefile b/Makefile
index c947b684f1eaa54cd28a07d37e68eee5ad7f79fc..38db3a9c283ce4cd2538f1fc454111ed9ff12035 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,19 @@
-build: sass
+.ONESHELL:
+
+build: sass tmpl
 	go build -o bin/cerrado
 
-run: sass
+run: sass tmpl
 	go run .
 
 test:
 	go test -v --tags=unit ./...
 
 sass:
+	@make -p static
 	sassc \
-		-I scss scss/main.scss bin/main.css
+		-I scss scss/main.scss static/main.css
+
+tmpl:
+	cd ./templates
+	qtc *
diff --git a/go.mod b/go.mod
index bfbe03ad8f3b8502d8d8f9ba9d3967552b1171e1..b8b36b4cc85febdf92f178dea6604511c034b56d 100644
--- a/go.mod
+++ b/go.mod
@@ -5,5 +5,8 @@
 require (
 	git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082
 	github.com/google/go-cmp v0.6.0
+	github.com/valyala/quicktemplate v1.7.0
 	golang.org/x/sync v0.7.0
 )
+
+require github.com/valyala/bytebufferpool v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index a98204454c0ef47546d171a1dada140655d0b45e..0ba6fdb880c9cf8d7bef209163294c73990aab69 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,29 @@
 git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082 h1:9Udx5fm4vRtmgDIBjy2ef5QioHbzpw5oHabbhpAUyEw=
 git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082/go.mod h1:ybgvEJTIx5XbaspSviB3KNa6OdPmAZqDoSud7z8fFlw=
+github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
+github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
+github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
+github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
+github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/main.go b/main.go
index ba441fe71b8c6c0ea4aff7a8805d2e4729059e27..55f866e18d0497c7d8c44ff21ea0adf1517b90be 100644
--- a/main.go
+++ b/main.go
@@ -12,6 +12,7 @@ 	"time"
 
 	"git.gabrielgio.me/cerrado/pkg/config"
 	"git.gabrielgio.me/cerrado/pkg/worker"
+	"git.gabrielgio.me/cerrado/templates"
 )
 
 func main() {
@@ -51,10 +52,11 @@ 			slog.Error("Error parsing json", "error", err)
 			return
 		}
 
-		if _, err := w.Write(b); err != nil {
-			slog.Error("Error handling index", "error", err)
-			return
+		hello := &templates.HelloPage{
+			Body: string(b),
 		}
+
+		templates.WritePageTemplate(w, hello)
 	})
 	serverTask := worker.NewServerTask(&http.Server{Handler: mux, Addr: "0.0.0.0:8080"})
 
diff --git a/static/static.go b/static/static.go
new file mode 100644
index 0000000000000000000000000000000000000000..ada043472dff4bf3bb13f74d3e2bdb5fdbc10763
--- /dev/null
+++ b/static/static.go
@@ -0,0 +1,6 @@
+package static
+
+import "embed"
+
+//go:embed *
+var Static embed.FS
diff --git a/templates/base.qtpl b/templates/base.qtpl
new file mode 100644
index 0000000000000000000000000000000000000000..1683981b506d0ed3e46c9331cc1841189422929a
--- /dev/null
+++ b/templates/base.qtpl
@@ -0,0 +1,44 @@
+This is a base page template. All the other template pages implement this interface.
+
+{% import "strconv" %}
+
+{% interface
+Page {
+	Title()
+	Content()
+    Script()
+}
+
+%}
+
+{% code func FromUInttoString(u *uint) string {
+        if u != nil {
+            return strconv.FormatUint(uint64(*u), 10)
+        }
+        return ""
+    }
+%}
+
+
+Page prints a page implementing Page interface.
+{% func PageTemplate(p Page) %}
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <title>img | {%= p.Title() %}</title> 
+        <link rel="stylesheet" href="/static/main.css">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+    </head>
+    <body>
+        <div>
+            {%= p.Content() %}
+        </div>
+    </body>
+    {%= p.Script() %}
+</html>
+{% endfunc %}
+
+{% code type BasePage struct {} %}
+{% func (p *BasePage) Title() %}Empty{% endfunc %}
+{% func (p *BasePage) Body() %}HelloWorld{% endfunc %}
+{% func (p *BasePage) Script() %}{% endfunc %}
diff --git a/templates/base.qtpl.go b/templates/base.qtpl.go
new file mode 100644
index 0000000000000000000000000000000000000000..8d93fa82b1607475b09d4077628d6598b628af6e
--- /dev/null
+++ b/templates/base.qtpl.go
@@ -0,0 +1,217 @@
+// Code generated by qtc from "base.qtpl". DO NOT EDIT.
+// See https://github.com/valyala/quicktemplate for details.
+
+// This is a base page template. All the other template pages implement this interface.
+//
+
+//line base.qtpl:3
+package templates
+
+//line base.qtpl:3
+import "strconv"
+
+//line base.qtpl:5
+import (
+	qtio422016 "io"
+
+	qt422016 "github.com/valyala/quicktemplate"
+)
+
+//line base.qtpl:5
+var (
+	_ = qtio422016.Copy
+	_ = qt422016.AcquireByteBuffer
+)
+
+//line base.qtpl:6
+type Page interface {
+//line base.qtpl:6
+	Title() string
+//line base.qtpl:6
+	StreamTitle(qw422016 *qt422016.Writer)
+//line base.qtpl:6
+	WriteTitle(qq422016 qtio422016.Writer)
+//line base.qtpl:6
+	Content() string
+//line base.qtpl:6
+	StreamContent(qw422016 *qt422016.Writer)
+//line base.qtpl:6
+	WriteContent(qq422016 qtio422016.Writer)
+//line base.qtpl:6
+	Script() string
+//line base.qtpl:6
+	StreamScript(qw422016 *qt422016.Writer)
+//line base.qtpl:6
+	WriteScript(qq422016 qtio422016.Writer)
+//line base.qtpl:6
+}
+
+//line base.qtpl:14
+func FromUInttoString(u *uint) string {
+	if u != nil {
+		return strconv.FormatUint(uint64(*u), 10)
+	}
+	return ""
+}
+
+// Page prints a page implementing Page interface.
+
+//line base.qtpl:24
+func StreamPageTemplate(qw422016 *qt422016.Writer, p Page) {
+//line base.qtpl:24
+	qw422016.N().S(`
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <title>img | `)
+//line base.qtpl:28
+	p.StreamTitle(qw422016)
+//line base.qtpl:28
+	qw422016.N().S(`</title> 
+        <link rel="stylesheet" href="/static/main.css">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+    </head>
+    <body>
+        <div>
+            `)
+//line base.qtpl:34
+	p.StreamContent(qw422016)
+//line base.qtpl:34
+	qw422016.N().S(`
+        </div>
+    </body>
+    `)
+//line base.qtpl:37
+	p.StreamScript(qw422016)
+//line base.qtpl:37
+	qw422016.N().S(`
+</html>
+`)
+//line base.qtpl:39
+}
+
+//line base.qtpl:39
+func WritePageTemplate(qq422016 qtio422016.Writer, p Page) {
+//line base.qtpl:39
+	qw422016 := qt422016.AcquireWriter(qq422016)
+//line base.qtpl:39
+	StreamPageTemplate(qw422016, p)
+//line base.qtpl:39
+	qt422016.ReleaseWriter(qw422016)
+//line base.qtpl:39
+}
+
+//line base.qtpl:39
+func PageTemplate(p Page) string {
+//line base.qtpl:39
+	qb422016 := qt422016.AcquireByteBuffer()
+//line base.qtpl:39
+	WritePageTemplate(qb422016, p)
+//line base.qtpl:39
+	qs422016 := string(qb422016.B)
+//line base.qtpl:39
+	qt422016.ReleaseByteBuffer(qb422016)
+//line base.qtpl:39
+	return qs422016
+//line base.qtpl:39
+}
+
+//line base.qtpl:41
+type BasePage struct{}
+
+//line base.qtpl:42
+func (p *BasePage) StreamTitle(qw422016 *qt422016.Writer) {
+//line base.qtpl:42
+	qw422016.N().S(`Empty`)
+//line base.qtpl:42
+}
+
+//line base.qtpl:42
+func (p *BasePage) WriteTitle(qq422016 qtio422016.Writer) {
+//line base.qtpl:42
+	qw422016 := qt422016.AcquireWriter(qq422016)
+//line base.qtpl:42
+	p.StreamTitle(qw422016)
+//line base.qtpl:42
+	qt422016.ReleaseWriter(qw422016)
+//line base.qtpl:42
+}
+
+//line base.qtpl:42
+func (p *BasePage) Title() string {
+//line base.qtpl:42
+	qb422016 := qt422016.AcquireByteBuffer()
+//line base.qtpl:42
+	p.WriteTitle(qb422016)
+//line base.qtpl:42
+	qs422016 := string(qb422016.B)
+//line base.qtpl:42
+	qt422016.ReleaseByteBuffer(qb422016)
+//line base.qtpl:42
+	return qs422016
+//line base.qtpl:42
+}
+
+//line base.qtpl:43
+func (p *BasePage) StreamBody(qw422016 *qt422016.Writer) {
+//line base.qtpl:43
+	qw422016.N().S(`HelloWorld`)
+//line base.qtpl:43
+}
+
+//line base.qtpl:43
+func (p *BasePage) WriteBody(qq422016 qtio422016.Writer) {
+//line base.qtpl:43
+	qw422016 := qt422016.AcquireWriter(qq422016)
+//line base.qtpl:43
+	p.StreamBody(qw422016)
+//line base.qtpl:43
+	qt422016.ReleaseWriter(qw422016)
+//line base.qtpl:43
+}
+
+//line base.qtpl:43
+func (p *BasePage) Body() string {
+//line base.qtpl:43
+	qb422016 := qt422016.AcquireByteBuffer()
+//line base.qtpl:43
+	p.WriteBody(qb422016)
+//line base.qtpl:43
+	qs422016 := string(qb422016.B)
+//line base.qtpl:43
+	qt422016.ReleaseByteBuffer(qb422016)
+//line base.qtpl:43
+	return qs422016
+//line base.qtpl:43
+}
+
+//line base.qtpl:44
+func (p *BasePage) StreamScript(qw422016 *qt422016.Writer) {
+//line base.qtpl:44
+}
+
+//line base.qtpl:44
+func (p *BasePage) WriteScript(qq422016 qtio422016.Writer) {
+//line base.qtpl:44
+	qw422016 := qt422016.AcquireWriter(qq422016)
+//line base.qtpl:44
+	p.StreamScript(qw422016)
+//line base.qtpl:44
+	qt422016.ReleaseWriter(qw422016)
+//line base.qtpl:44
+}
+
+//line base.qtpl:44
+func (p *BasePage) Script() string {
+//line base.qtpl:44
+	qb422016 := qt422016.AcquireByteBuffer()
+//line base.qtpl:44
+	p.WriteScript(qb422016)
+//line base.qtpl:44
+	qs422016 := string(qb422016.B)
+//line base.qtpl:44
+	qt422016.ReleaseByteBuffer(qb422016)
+//line base.qtpl:44
+	return qs422016
+//line base.qtpl:44
+}
diff --git a/templates/helloworld.qtpl b/templates/helloworld.qtpl
new file mode 100644
index 0000000000000000000000000000000000000000..02c09681378f5fad6de6fa1ec9550d59233cf887
--- /dev/null
+++ b/templates/helloworld.qtpl
@@ -0,0 +1,16 @@
+{% code
+type HelloPage struct {
+    Body string
+}
+%}
+
+{% func (p *HelloPage) Title() %}Hello{% endfunc %}
+
+{% func (p *HelloPage) Content() %}
+HelloWorld
+
+{%s p.Body %}
+{% endfunc %}
+
+{% func (p *HelloPage) Script() %}
+{% endfunc %}
diff --git a/templates/helloworld.qtpl.go b/templates/helloworld.qtpl.go
new file mode 100644
index 0000000000000000000000000000000000000000..9ef78f55e201f7d50d7d373dc40fa30c682169bf
--- /dev/null
+++ b/templates/helloworld.qtpl.go
@@ -0,0 +1,131 @@
+// Code generated by qtc from "helloworld.qtpl". DO NOT EDIT.
+// See https://github.com/valyala/quicktemplate for details.
+
+//line helloworld.qtpl:1
+package templates
+
+//line helloworld.qtpl:1
+import (
+	qtio422016 "io"
+
+	qt422016 "github.com/valyala/quicktemplate"
+)
+
+//line helloworld.qtpl:1
+var (
+	_ = qtio422016.Copy
+	_ = qt422016.AcquireByteBuffer
+)
+
+//line helloworld.qtpl:2
+type HelloPage struct {
+	Body string
+}
+
+//line helloworld.qtpl:7
+func (p *HelloPage) StreamTitle(qw422016 *qt422016.Writer) {
+//line helloworld.qtpl:7
+	qw422016.N().S(`Hello`)
+//line helloworld.qtpl:7
+}
+
+//line helloworld.qtpl:7
+func (p *HelloPage) WriteTitle(qq422016 qtio422016.Writer) {
+//line helloworld.qtpl:7
+	qw422016 := qt422016.AcquireWriter(qq422016)
+//line helloworld.qtpl:7
+	p.StreamTitle(qw422016)
+//line helloworld.qtpl:7
+	qt422016.ReleaseWriter(qw422016)
+//line helloworld.qtpl:7
+}
+
+//line helloworld.qtpl:7
+func (p *HelloPage) Title() string {
+//line helloworld.qtpl:7
+	qb422016 := qt422016.AcquireByteBuffer()
+//line helloworld.qtpl:7
+	p.WriteTitle(qb422016)
+//line helloworld.qtpl:7
+	qs422016 := string(qb422016.B)
+//line helloworld.qtpl:7
+	qt422016.ReleaseByteBuffer(qb422016)
+//line helloworld.qtpl:7
+	return qs422016
+//line helloworld.qtpl:7
+}
+
+//line helloworld.qtpl:9
+func (p *HelloPage) StreamContent(qw422016 *qt422016.Writer) {
+//line helloworld.qtpl:9
+	qw422016.N().S(`
+HelloWorld
+
+`)
+//line helloworld.qtpl:12
+	qw422016.E().S(p.Body)
+//line helloworld.qtpl:12
+	qw422016.N().S(`
+`)
+//line helloworld.qtpl:13
+}
+
+//line helloworld.qtpl:13
+func (p *HelloPage) WriteContent(qq422016 qtio422016.Writer) {
+//line helloworld.qtpl:13
+	qw422016 := qt422016.AcquireWriter(qq422016)
+//line helloworld.qtpl:13
+	p.StreamContent(qw422016)
+//line helloworld.qtpl:13
+	qt422016.ReleaseWriter(qw422016)
+//line helloworld.qtpl:13
+}
+
+//line helloworld.qtpl:13
+func (p *HelloPage) Content() string {
+//line helloworld.qtpl:13
+	qb422016 := qt422016.AcquireByteBuffer()
+//line helloworld.qtpl:13
+	p.WriteContent(qb422016)
+//line helloworld.qtpl:13
+	qs422016 := string(qb422016.B)
+//line helloworld.qtpl:13
+	qt422016.ReleaseByteBuffer(qb422016)
+//line helloworld.qtpl:13
+	return qs422016
+//line helloworld.qtpl:13
+}
+
+//line helloworld.qtpl:15
+func (p *HelloPage) StreamScript(qw422016 *qt422016.Writer) {
+//line helloworld.qtpl:15
+	qw422016.N().S(`
+`)
+//line helloworld.qtpl:16
+}
+
+//line helloworld.qtpl:16
+func (p *HelloPage) WriteScript(qq422016 qtio422016.Writer) {
+//line helloworld.qtpl:16
+	qw422016 := qt422016.AcquireWriter(qq422016)
+//line helloworld.qtpl:16
+	p.StreamScript(qw422016)
+//line helloworld.qtpl:16
+	qt422016.ReleaseWriter(qw422016)
+//line helloworld.qtpl:16
+}
+
+//line helloworld.qtpl:16
+func (p *HelloPage) Script() string {
+//line helloworld.qtpl:16
+	qb422016 := qt422016.AcquireByteBuffer()
+//line helloworld.qtpl:16
+	p.WriteScript(qb422016)
+//line helloworld.qtpl:16
+	qs422016 := string(qb422016.B)
+//line helloworld.qtpl:16
+	qt422016.ReleaseByteBuffer(qb422016)
+//line helloworld.qtpl:16
+	return qs422016
+//line helloworld.qtpl:16
+}