1diff --git a/.gitignore b/.gitignore
2index e660fd93d3196215552065b1e63bf6a2f393ed86..776d7a2c9749474ff5daa02e3b6842d4c1d8a36d 100644
3--- a/.gitignore
4+++ b/.gitignore
5@@ -1 +1,2 @@
6 bin/
7+static/*.css
8diff --git a/Makefile b/Makefile
9index c947b684f1eaa54cd28a07d37e68eee5ad7f79fc..38db3a9c283ce4cd2538f1fc454111ed9ff12035 100644
10--- a/Makefile
11+++ b/Makefile
12@@ -1,12 +1,19 @@
13-build: sass
14+.ONESHELL:
15+
16+build: sass tmpl
17 go build -o bin/cerrado
18
19-run: sass
20+run: sass tmpl
21 go run .
22
23 test:
24 go test -v --tags=unit ./...
25
26 sass:
27+ @make -p static
28 sassc \
29- -I scss scss/main.scss bin/main.css
30+ -I scss scss/main.scss static/main.css
31+
32+tmpl:
33+ cd ./templates
34+ qtc *
35diff --git a/go.mod b/go.mod
36index bfbe03ad8f3b8502d8d8f9ba9d3967552b1171e1..b8b36b4cc85febdf92f178dea6604511c034b56d 100644
37--- a/go.mod
38+++ b/go.mod
39@@ -5,5 +5,8 @@
40 require (
41 git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082
42 github.com/google/go-cmp v0.6.0
43+ github.com/valyala/quicktemplate v1.7.0
44 golang.org/x/sync v0.7.0
45 )
46+
47+require github.com/valyala/bytebufferpool v1.0.0 // indirect
48diff --git a/go.sum b/go.sum
49index a98204454c0ef47546d171a1dada140655d0b45e..0ba6fdb880c9cf8d7bef209163294c73990aab69 100644
50--- a/go.sum
51+++ b/go.sum
52@@ -1,8 +1,29 @@
53 git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082 h1:9Udx5fm4vRtmgDIBjy2ef5QioHbzpw5oHabbhpAUyEw=
54 git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082/go.mod h1:ybgvEJTIx5XbaspSviB3KNa6OdPmAZqDoSud7z8fFlw=
55+github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
56+github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
57 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
58 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
59+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
60 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
61 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
62+github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
63+github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
64+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
65+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
66+github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
67+github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
68+github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
69+github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
70+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
71+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
72+golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
73 golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
74 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
75+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
76+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
77+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
78+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
79+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
80+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
81+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
82diff --git a/main.go b/main.go
83index ba441fe71b8c6c0ea4aff7a8805d2e4729059e27..55f866e18d0497c7d8c44ff21ea0adf1517b90be 100644
84--- a/main.go
85+++ b/main.go
86@@ -12,6 +12,7 @@ "time"
87
88 "git.gabrielgio.me/cerrado/pkg/config"
89 "git.gabrielgio.me/cerrado/pkg/worker"
90+ "git.gabrielgio.me/cerrado/templates"
91 )
92
93 func main() {
94@@ -51,10 +52,11 @@ slog.Error("Error parsing json", "error", err)
95 return
96 }
97
98- if _, err := w.Write(b); err != nil {
99- slog.Error("Error handling index", "error", err)
100- return
101+ hello := &templates.HelloPage{
102+ Body: string(b),
103 }
104+
105+ templates.WritePageTemplate(w, hello)
106 })
107 serverTask := worker.NewServerTask(&http.Server{Handler: mux, Addr: "0.0.0.0:8080"})
108
109diff --git a/static/static.go b/static/static.go
110new file mode 100644
111index 0000000000000000000000000000000000000000..ada043472dff4bf3bb13f74d3e2bdb5fdbc10763
112--- /dev/null
113+++ b/static/static.go
114@@ -0,0 +1,6 @@
115+package static
116+
117+import "embed"
118+
119+//go:embed *
120+var Static embed.FS
121diff --git a/templates/base.qtpl b/templates/base.qtpl
122new file mode 100644
123index 0000000000000000000000000000000000000000..1683981b506d0ed3e46c9331cc1841189422929a
124--- /dev/null
125+++ b/templates/base.qtpl
126@@ -0,0 +1,44 @@
127+This is a base page template. All the other template pages implement this interface.
128+
129+{% import "strconv" %}
130+
131+{% interface
132+Page {
133+ Title()
134+ Content()
135+ Script()
136+}
137+
138+%}
139+
140+{% code func FromUInttoString(u *uint) string {
141+ if u != nil {
142+ return strconv.FormatUint(uint64(*u), 10)
143+ }
144+ return ""
145+ }
146+%}
147+
148+
149+Page prints a page implementing Page interface.
150+{% func PageTemplate(p Page) %}
151+<html lang="en">
152+ <head>
153+ <meta charset="utf-8">
154+ <title>img | {%= p.Title() %}</title>
155+ <link rel="stylesheet" href="/static/main.css">
156+ <meta name="viewport" content="width=device-width, initial-scale=1" />
157+ </head>
158+ <body>
159+ <div>
160+ {%= p.Content() %}
161+ </div>
162+ </body>
163+ {%= p.Script() %}
164+</html>
165+{% endfunc %}
166+
167+{% code type BasePage struct {} %}
168+{% func (p *BasePage) Title() %}Empty{% endfunc %}
169+{% func (p *BasePage) Body() %}HelloWorld{% endfunc %}
170+{% func (p *BasePage) Script() %}{% endfunc %}
171diff --git a/templates/base.qtpl.go b/templates/base.qtpl.go
172new file mode 100644
173index 0000000000000000000000000000000000000000..8d93fa82b1607475b09d4077628d6598b628af6e
174--- /dev/null
175+++ b/templates/base.qtpl.go
176@@ -0,0 +1,217 @@
177+// Code generated by qtc from "base.qtpl". DO NOT EDIT.
178+// See https://github.com/valyala/quicktemplate for details.
179+
180+// This is a base page template. All the other template pages implement this interface.
181+//
182+
183+//line base.qtpl:3
184+package templates
185+
186+//line base.qtpl:3
187+import "strconv"
188+
189+//line base.qtpl:5
190+import (
191+ qtio422016 "io"
192+
193+ qt422016 "github.com/valyala/quicktemplate"
194+)
195+
196+//line base.qtpl:5
197+var (
198+ _ = qtio422016.Copy
199+ _ = qt422016.AcquireByteBuffer
200+)
201+
202+//line base.qtpl:6
203+type Page interface {
204+//line base.qtpl:6
205+ Title() string
206+//line base.qtpl:6
207+ StreamTitle(qw422016 *qt422016.Writer)
208+//line base.qtpl:6
209+ WriteTitle(qq422016 qtio422016.Writer)
210+//line base.qtpl:6
211+ Content() string
212+//line base.qtpl:6
213+ StreamContent(qw422016 *qt422016.Writer)
214+//line base.qtpl:6
215+ WriteContent(qq422016 qtio422016.Writer)
216+//line base.qtpl:6
217+ Script() string
218+//line base.qtpl:6
219+ StreamScript(qw422016 *qt422016.Writer)
220+//line base.qtpl:6
221+ WriteScript(qq422016 qtio422016.Writer)
222+//line base.qtpl:6
223+}
224+
225+//line base.qtpl:14
226+func FromUInttoString(u *uint) string {
227+ if u != nil {
228+ return strconv.FormatUint(uint64(*u), 10)
229+ }
230+ return ""
231+}
232+
233+// Page prints a page implementing Page interface.
234+
235+//line base.qtpl:24
236+func StreamPageTemplate(qw422016 *qt422016.Writer, p Page) {
237+//line base.qtpl:24
238+ qw422016.N().S(`
239+<html lang="en">
240+ <head>
241+ <meta charset="utf-8">
242+ <title>img | `)
243+//line base.qtpl:28
244+ p.StreamTitle(qw422016)
245+//line base.qtpl:28
246+ qw422016.N().S(`</title>
247+ <link rel="stylesheet" href="/static/main.css">
248+ <meta name="viewport" content="width=device-width, initial-scale=1" />
249+ </head>
250+ <body>
251+ <div>
252+ `)
253+//line base.qtpl:34
254+ p.StreamContent(qw422016)
255+//line base.qtpl:34
256+ qw422016.N().S(`
257+ </div>
258+ </body>
259+ `)
260+//line base.qtpl:37
261+ p.StreamScript(qw422016)
262+//line base.qtpl:37
263+ qw422016.N().S(`
264+</html>
265+`)
266+//line base.qtpl:39
267+}
268+
269+//line base.qtpl:39
270+func WritePageTemplate(qq422016 qtio422016.Writer, p Page) {
271+//line base.qtpl:39
272+ qw422016 := qt422016.AcquireWriter(qq422016)
273+//line base.qtpl:39
274+ StreamPageTemplate(qw422016, p)
275+//line base.qtpl:39
276+ qt422016.ReleaseWriter(qw422016)
277+//line base.qtpl:39
278+}
279+
280+//line base.qtpl:39
281+func PageTemplate(p Page) string {
282+//line base.qtpl:39
283+ qb422016 := qt422016.AcquireByteBuffer()
284+//line base.qtpl:39
285+ WritePageTemplate(qb422016, p)
286+//line base.qtpl:39
287+ qs422016 := string(qb422016.B)
288+//line base.qtpl:39
289+ qt422016.ReleaseByteBuffer(qb422016)
290+//line base.qtpl:39
291+ return qs422016
292+//line base.qtpl:39
293+}
294+
295+//line base.qtpl:41
296+type BasePage struct{}
297+
298+//line base.qtpl:42
299+func (p *BasePage) StreamTitle(qw422016 *qt422016.Writer) {
300+//line base.qtpl:42
301+ qw422016.N().S(`Empty`)
302+//line base.qtpl:42
303+}
304+
305+//line base.qtpl:42
306+func (p *BasePage) WriteTitle(qq422016 qtio422016.Writer) {
307+//line base.qtpl:42
308+ qw422016 := qt422016.AcquireWriter(qq422016)
309+//line base.qtpl:42
310+ p.StreamTitle(qw422016)
311+//line base.qtpl:42
312+ qt422016.ReleaseWriter(qw422016)
313+//line base.qtpl:42
314+}
315+
316+//line base.qtpl:42
317+func (p *BasePage) Title() string {
318+//line base.qtpl:42
319+ qb422016 := qt422016.AcquireByteBuffer()
320+//line base.qtpl:42
321+ p.WriteTitle(qb422016)
322+//line base.qtpl:42
323+ qs422016 := string(qb422016.B)
324+//line base.qtpl:42
325+ qt422016.ReleaseByteBuffer(qb422016)
326+//line base.qtpl:42
327+ return qs422016
328+//line base.qtpl:42
329+}
330+
331+//line base.qtpl:43
332+func (p *BasePage) StreamBody(qw422016 *qt422016.Writer) {
333+//line base.qtpl:43
334+ qw422016.N().S(`HelloWorld`)
335+//line base.qtpl:43
336+}
337+
338+//line base.qtpl:43
339+func (p *BasePage) WriteBody(qq422016 qtio422016.Writer) {
340+//line base.qtpl:43
341+ qw422016 := qt422016.AcquireWriter(qq422016)
342+//line base.qtpl:43
343+ p.StreamBody(qw422016)
344+//line base.qtpl:43
345+ qt422016.ReleaseWriter(qw422016)
346+//line base.qtpl:43
347+}
348+
349+//line base.qtpl:43
350+func (p *BasePage) Body() string {
351+//line base.qtpl:43
352+ qb422016 := qt422016.AcquireByteBuffer()
353+//line base.qtpl:43
354+ p.WriteBody(qb422016)
355+//line base.qtpl:43
356+ qs422016 := string(qb422016.B)
357+//line base.qtpl:43
358+ qt422016.ReleaseByteBuffer(qb422016)
359+//line base.qtpl:43
360+ return qs422016
361+//line base.qtpl:43
362+}
363+
364+//line base.qtpl:44
365+func (p *BasePage) StreamScript(qw422016 *qt422016.Writer) {
366+//line base.qtpl:44
367+}
368+
369+//line base.qtpl:44
370+func (p *BasePage) WriteScript(qq422016 qtio422016.Writer) {
371+//line base.qtpl:44
372+ qw422016 := qt422016.AcquireWriter(qq422016)
373+//line base.qtpl:44
374+ p.StreamScript(qw422016)
375+//line base.qtpl:44
376+ qt422016.ReleaseWriter(qw422016)
377+//line base.qtpl:44
378+}
379+
380+//line base.qtpl:44
381+func (p *BasePage) Script() string {
382+//line base.qtpl:44
383+ qb422016 := qt422016.AcquireByteBuffer()
384+//line base.qtpl:44
385+ p.WriteScript(qb422016)
386+//line base.qtpl:44
387+ qs422016 := string(qb422016.B)
388+//line base.qtpl:44
389+ qt422016.ReleaseByteBuffer(qb422016)
390+//line base.qtpl:44
391+ return qs422016
392+//line base.qtpl:44
393+}
394diff --git a/templates/helloworld.qtpl b/templates/helloworld.qtpl
395new file mode 100644
396index 0000000000000000000000000000000000000000..02c09681378f5fad6de6fa1ec9550d59233cf887
397--- /dev/null
398+++ b/templates/helloworld.qtpl
399@@ -0,0 +1,16 @@
400+{% code
401+type HelloPage struct {
402+ Body string
403+}
404+%}
405+
406+{% func (p *HelloPage) Title() %}Hello{% endfunc %}
407+
408+{% func (p *HelloPage) Content() %}
409+HelloWorld
410+
411+{%s p.Body %}
412+{% endfunc %}
413+
414+{% func (p *HelloPage) Script() %}
415+{% endfunc %}
416diff --git a/templates/helloworld.qtpl.go b/templates/helloworld.qtpl.go
417new file mode 100644
418index 0000000000000000000000000000000000000000..9ef78f55e201f7d50d7d373dc40fa30c682169bf
419--- /dev/null
420+++ b/templates/helloworld.qtpl.go
421@@ -0,0 +1,131 @@
422+// Code generated by qtc from "helloworld.qtpl". DO NOT EDIT.
423+// See https://github.com/valyala/quicktemplate for details.
424+
425+//line helloworld.qtpl:1
426+package templates
427+
428+//line helloworld.qtpl:1
429+import (
430+ qtio422016 "io"
431+
432+ qt422016 "github.com/valyala/quicktemplate"
433+)
434+
435+//line helloworld.qtpl:1
436+var (
437+ _ = qtio422016.Copy
438+ _ = qt422016.AcquireByteBuffer
439+)
440+
441+//line helloworld.qtpl:2
442+type HelloPage struct {
443+ Body string
444+}
445+
446+//line helloworld.qtpl:7
447+func (p *HelloPage) StreamTitle(qw422016 *qt422016.Writer) {
448+//line helloworld.qtpl:7
449+ qw422016.N().S(`Hello`)
450+//line helloworld.qtpl:7
451+}
452+
453+//line helloworld.qtpl:7
454+func (p *HelloPage) WriteTitle(qq422016 qtio422016.Writer) {
455+//line helloworld.qtpl:7
456+ qw422016 := qt422016.AcquireWriter(qq422016)
457+//line helloworld.qtpl:7
458+ p.StreamTitle(qw422016)
459+//line helloworld.qtpl:7
460+ qt422016.ReleaseWriter(qw422016)
461+//line helloworld.qtpl:7
462+}
463+
464+//line helloworld.qtpl:7
465+func (p *HelloPage) Title() string {
466+//line helloworld.qtpl:7
467+ qb422016 := qt422016.AcquireByteBuffer()
468+//line helloworld.qtpl:7
469+ p.WriteTitle(qb422016)
470+//line helloworld.qtpl:7
471+ qs422016 := string(qb422016.B)
472+//line helloworld.qtpl:7
473+ qt422016.ReleaseByteBuffer(qb422016)
474+//line helloworld.qtpl:7
475+ return qs422016
476+//line helloworld.qtpl:7
477+}
478+
479+//line helloworld.qtpl:9
480+func (p *HelloPage) StreamContent(qw422016 *qt422016.Writer) {
481+//line helloworld.qtpl:9
482+ qw422016.N().S(`
483+HelloWorld
484+
485+`)
486+//line helloworld.qtpl:12
487+ qw422016.E().S(p.Body)
488+//line helloworld.qtpl:12
489+ qw422016.N().S(`
490+`)
491+//line helloworld.qtpl:13
492+}
493+
494+//line helloworld.qtpl:13
495+func (p *HelloPage) WriteContent(qq422016 qtio422016.Writer) {
496+//line helloworld.qtpl:13
497+ qw422016 := qt422016.AcquireWriter(qq422016)
498+//line helloworld.qtpl:13
499+ p.StreamContent(qw422016)
500+//line helloworld.qtpl:13
501+ qt422016.ReleaseWriter(qw422016)
502+//line helloworld.qtpl:13
503+}
504+
505+//line helloworld.qtpl:13
506+func (p *HelloPage) Content() string {
507+//line helloworld.qtpl:13
508+ qb422016 := qt422016.AcquireByteBuffer()
509+//line helloworld.qtpl:13
510+ p.WriteContent(qb422016)
511+//line helloworld.qtpl:13
512+ qs422016 := string(qb422016.B)
513+//line helloworld.qtpl:13
514+ qt422016.ReleaseByteBuffer(qb422016)
515+//line helloworld.qtpl:13
516+ return qs422016
517+//line helloworld.qtpl:13
518+}
519+
520+//line helloworld.qtpl:15
521+func (p *HelloPage) StreamScript(qw422016 *qt422016.Writer) {
522+//line helloworld.qtpl:15
523+ qw422016.N().S(`
524+`)
525+//line helloworld.qtpl:16
526+}
527+
528+//line helloworld.qtpl:16
529+func (p *HelloPage) WriteScript(qq422016 qtio422016.Writer) {
530+//line helloworld.qtpl:16
531+ qw422016 := qt422016.AcquireWriter(qq422016)
532+//line helloworld.qtpl:16
533+ p.StreamScript(qw422016)
534+//line helloworld.qtpl:16
535+ qt422016.ReleaseWriter(qw422016)
536+//line helloworld.qtpl:16
537+}
538+
539+//line helloworld.qtpl:16
540+func (p *HelloPage) Script() string {
541+//line helloworld.qtpl:16
542+ qb422016 := qt422016.AcquireByteBuffer()
543+//line helloworld.qtpl:16
544+ p.WriteScript(qb422016)
545+//line helloworld.qtpl:16
546+ qs422016 := string(qb422016.B)
547+//line helloworld.qtpl:16
548+ qt422016.ReleaseByteBuffer(qb422016)
549+//line helloworld.qtpl:16
550+ return qs422016
551+//line helloworld.qtpl:16
552+}