cerrado @ c7a8aa113a914e70e027fea93265c7232b865b5e

feat: Add compression
diff --git a/go.mod b/go.mod
index 5bd4d7647b43cb737a873b5636a162a1ea25df69..cc63e5d26352e2c39488c00f139e5fdbe17cf13f 100644
--- a/go.mod
+++ b/go.mod
@@ -5,9 +5,11 @@
 require (
 	git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082
 	github.com/alecthomas/chroma/v2 v2.13.0
+	github.com/andybalholm/brotli v1.1.0
 	github.com/go-git/go-git/v5 v5.12.0
 	github.com/gomarkdown/markdown v0.0.0-20240419095408-642f0ee99ae2
 	github.com/google/go-cmp v0.6.0
+	github.com/klauspost/compress v1.17.8
 	github.com/valyala/quicktemplate v1.7.0
 	golang.org/x/sync v0.7.0
 )
diff --git a/go.sum b/go.sum
index 69c34b73d113fa1f0ae49c5068a2517c3ee91725..f010dc9d81e57367a63071c716ea39fe1f0960a3 100644
--- a/go.sum
+++ b/go.sum
@@ -15,6 +15,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
 github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 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/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
+github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -59,6 +61,8 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 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/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
+github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
diff --git a/pkg/ext/compression.go b/pkg/ext/compression.go
new file mode 100644
index 0000000000000000000000000000000000000000..92144b82132968aa7db6115aad78f37d71c8e354
--- /dev/null
+++ b/pkg/ext/compression.go
@@ -0,0 +1,142 @@
+package ext
+
+import (
+	"compress/gzip"
+	"compress/lzw"
+	"errors"
+	"io"
+	"log/slog"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"git.gabrielgio.me/cerrado/pkg/u"
+	"github.com/andybalholm/brotli"
+	"github.com/klauspost/compress/zstd"
+)
+
+var (
+	invalidParamErr = errors.New("Invalid weighted param")
+)
+
+type CompressionResponseWriter struct {
+	innerWriter    http.ResponseWriter
+	compressWriter io.Writer
+}
+
+func Compress(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
+	return func(w http.ResponseWriter, r *http.Request) {
+		if accept, ok := r.Header["Accept-Encoding"]; ok {
+			if compress, algo := GetCompressionWriter(u.FirstOrZero(accept), w); algo != "" {
+				defer compress.Close()
+				w.Header().Add("Content-Encoding", algo)
+				w = &CompressionResponseWriter{
+					innerWriter:    w,
+					compressWriter: compress,
+				}
+			}
+		}
+		next(w, r)
+	}
+}
+
+func GetCompressionWriter(header string, inner io.Writer) (io.WriteCloser, string) {
+	c := GetCompression(header)
+	switch c {
+	case "br":
+		return GetBrotliWriter(inner), c
+	case "gzip":
+		return GetGZIPWriter(inner), c
+	case "compress":
+		return GetLZWWriter(inner), c
+	case "zstd":
+		return GetZSTDWriter(inner), c
+	default:
+		return nil, ""
+	}
+
+}
+
+func (c *CompressionResponseWriter) Header() http.Header {
+	return c.innerWriter.Header()
+}
+func (c *CompressionResponseWriter) Write(b []byte) (int, error) {
+	return c.compressWriter.Write(b)
+}
+
+func (c *CompressionResponseWriter) WriteHeader(statusCode int) {
+	c.innerWriter.WriteHeader(statusCode)
+}
+
+func GetCompression(header string) string {
+	c := "*"
+	q := 0.0
+
+	if header == "" {
+		return c
+	}
+
+	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding
+	for _, e := range strings.Split(header, ",") {
+		ps := strings.Split(e, ";")
+		if len(ps) == 2 {
+			w, err := getWeighedValue(ps[1])
+			if err != nil {
+				slog.Error(
+					"Error parsing weighting from Accept-Encoding",
+					"error", err,
+				)
+				continue
+			}
+			// gettting weighting value
+			if w > q {
+				q = w
+				c = strings.Trim(ps[0], " ")
+			}
+		} else {
+			if 1 > q {
+				q = 1
+				c = strings.Trim(ps[0], " ")
+			}
+		}
+	}
+
+	return c
+}
+
+func GetGZIPWriter(w io.Writer) io.WriteCloser {
+	// error can be ignored here since it will only err when compression level
+	// is not valid
+	r, _ := gzip.NewWriterLevel(w, gzip.BestCompression)
+	return r
+}
+
+func GetBrotliWriter(w io.Writer) io.WriteCloser {
+	return brotli.NewWriterLevel(w, brotli.BestCompression)
+}
+
+func GetZSTDWriter(w io.Writer) io.WriteCloser {
+	// error can be ignored here since it will only opts are given
+	r, _ := zstd.NewWriter(w)
+	return r
+}
+
+func GetLZWWriter(w io.Writer) io.WriteCloser {
+	return lzw.NewWriter(w, lzw.LSB, 8)
+}
+
+func getWeighedValue(part string) (float64, error) {
+	ps := strings.SplitN(part, "=", 2)
+	if len(ps) != 2 {
+		return 0, invalidParamErr
+	}
+	if name := strings.TrimSpace(ps[0]); name == "q" {
+		w, err := strconv.ParseFloat(ps[1], 64)
+		if err != nil {
+			return 0, err
+		}
+		return w, nil
+	}
+
+	return 0, invalidParamErr
+}
diff --git a/pkg/ext/compression_test.go b/pkg/ext/compression_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..64243780d97c90cee130ae60d5652275dd7d9814
--- /dev/null
+++ b/pkg/ext/compression_test.go
@@ -0,0 +1,42 @@
+// go:build unit
+package ext
+
+import "testing"
+
+func TestGetCompression(t *testing.T) {
+	testCases := []struct {
+		name        string
+		header      string
+		compression string
+	}{
+		{
+			name:        "Empty",
+			header:      "",
+			compression: "*",
+		},
+		{
+			name:        "Weighted",
+			header:      "gzip;q=1.0, *;q=0.5",
+			compression: "gzip",
+		},
+		{
+			name:        "Mixed",
+			header:      "deflate, gzip;q=1.0, *;q=0.5",
+			compression: "deflate",
+		},
+		{
+			name:        "Not weighted",
+			header:      "zstd, deflate, gzip",
+			compression: "zstd",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := GetCompression(tc.header)
+			if got != tc.compression {
+				t.Errorf("Wrong compression returned: got %s want %s", got, tc.compression)
+			}
+		})
+	}
+}
diff --git a/pkg/ext/mime.go b/pkg/ext/mime.go
new file mode 100644
index 0000000000000000000000000000000000000000..6da66e3db03709439e1a8731116081dd5fe738bb
--- /dev/null
+++ b/pkg/ext/mime.go
@@ -0,0 +1,24 @@
+package ext
+
+import "net/http"
+
+type ContentType = string
+
+const (
+	TextHTML ContentType = "text/html"
+)
+
+func Html(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
+	return func(w http.ResponseWriter, r *http.Request) {
+		next(w, r)
+	}
+}
+
+func SetHTML(w http.ResponseWriter) {
+	SetMIME(w, TextHTML)
+
+}
+
+func SetMIME(w http.ResponseWriter, mime ContentType) {
+	w.Header().Add("Content-Type", mime)
+}
diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go
index f3e74c747e433b33f1b68d13473ef5ab507b5b95..28cc99e1ffe8564b7d63b31a3452134a51dc33aa 100644
--- a/pkg/handler/git/handler.go
+++ b/pkg/handler/git/handler.go
@@ -6,6 +6,7 @@ 	"log/slog"
 	"net/http"
 	"path/filepath"
 
+	"git.gabrielgio.me/cerrado/pkg/ext"
 	"git.gabrielgio.me/cerrado/pkg/service"
 	"git.gabrielgio.me/cerrado/templates"
 	"github.com/alecthomas/chroma/v2"
@@ -50,6 +51,7 @@ 	templates.WritePageTemplate(w, gitList)
 }
 
 func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) {
+	ext.SetHTML(w)
 	name := r.PathValue("name")
 	ref, err := g.gitService.GetHead(name)
 	if err != nil {
@@ -66,6 +68,7 @@ 	templates.WritePageTemplate(w, gitList)
 }
 
 func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) {
+	ext.SetHTML(w)
 	name := r.PathValue("name")
 	ref, err := g.gitService.GetHead(name)
 	if err != nil {
@@ -81,6 +84,7 @@ 	templates.WritePageTemplate(w, gitList)
 }
 
 func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) {
+	ext.SetHTML(w)
 	name := r.PathValue("name")
 
 	tags, err := g.gitService.ListTags(name)
@@ -113,6 +117,7 @@ 	templates.WritePageTemplate(w, gitList)
 }
 
 func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) {
+	ext.SetHTML(w)
 	name := r.PathValue("name")
 	ref := r.PathValue("ref")
 	rest := r.PathValue("rest")
@@ -137,6 +142,7 @@ 	templates.WritePageTemplate(w, gitList)
 }
 
 func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) {
+	ext.SetHTML(w)
 	name := r.PathValue("name")
 	ref := r.PathValue("ref")
 	rest := r.PathValue("rest")
@@ -178,6 +184,7 @@ 	templates.WritePageTemplate(w, gitList)
 }
 
 func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) {
+	ext.SetHTML(w)
 	name := r.PathValue("name")
 	ref := r.PathValue("ref")
 
diff --git a/pkg/handler/router.go b/pkg/handler/router.go
index ed782f75d525c637e539a4e6c07795cc0d8ac8d2..de5117c9e0a6b8836117e40c1b237bdb1afa6346 100644
--- a/pkg/handler/router.go
+++ b/pkg/handler/router.go
@@ -4,6 +4,7 @@ import (
 	"net/http"
 
 	serverconfig "git.gabrielgio.me/cerrado/pkg/config"
+	"git.gabrielgio.me/cerrado/pkg/ext"
 	"git.gabrielgio.me/cerrado/pkg/handler/about"
 	"git.gabrielgio.me/cerrado/pkg/handler/config"
 	"git.gabrielgio.me/cerrado/pkg/handler/git"
@@ -31,15 +32,19 @@ 	}
 
 	mux := http.NewServeMux()
 
-	mux.HandleFunc("/static/{file}", staticHandler)
-	mux.HandleFunc("/{name}/about/{$}", gitHandler.About)
-	mux.HandleFunc("/{name}", gitHandler.Summary)
-	mux.HandleFunc("/{name}/refs/{$}", gitHandler.Refs)
-	mux.HandleFunc("/{name}/tree/{ref}/{rest...}", gitHandler.Tree)
-	mux.HandleFunc("/{name}/blob/{ref}/{rest...}", gitHandler.Blob)
-	mux.HandleFunc("/{name}/log/{ref}", gitHandler.Log)
-	mux.HandleFunc("/config", configHander)
-	mux.HandleFunc("/about", aboutHandler.About)
-	mux.HandleFunc("/", gitHandler.List)
+	mux.HandleFunc("/static/{file}", m(staticHandler))
+	mux.HandleFunc("/{name}/about/{$}", m(gitHandler.About))
+	mux.HandleFunc("/{name}", m(gitHandler.Summary))
+	mux.HandleFunc("/{name}/refs/{$}", m(gitHandler.Refs))
+	mux.HandleFunc("/{name}/tree/{ref}/{rest...}", m(gitHandler.Tree))
+	mux.HandleFunc("/{name}/blob/{ref}/{rest...}", m(gitHandler.Blob))
+	mux.HandleFunc("/{name}/log/{ref}", m(gitHandler.Log))
+	mux.HandleFunc("/config", m(configHander))
+	mux.HandleFunc("/about", m(aboutHandler.About))
+	mux.HandleFunc("/", m(gitHandler.List))
 	return mux, nil
 }
+
+func m(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
+	return ext.Compress(next)
+}
diff --git a/pkg/handler/static/handler.go b/pkg/handler/static/handler.go
index a8b458324580082c2424b21a65d92c2849e6a836..5155068306666b2303de7f129585108f32ea5dfa 100644
--- a/pkg/handler/static/handler.go
+++ b/pkg/handler/static/handler.go
@@ -2,8 +2,11 @@ package static
 
 import (
 	"io/fs"
+	"mime"
 	"net/http"
+	"path/filepath"
 
+	"git.gabrielgio.me/cerrado/pkg/ext"
 	"git.gabrielgio.me/cerrado/static"
 )
 
@@ -14,8 +17,12 @@ 		return nil, err
 	}
 
 	return func(w http.ResponseWriter, r *http.Request) {
-		f := r.PathValue("file")
-
+		var (
+			f = r.PathValue("file")
+			e = filepath.Ext(f)
+			m = mime.TypeByExtension(e)
+		)
+		ext.SetMIME(w, m)
 		http.ServeFileFS(w, r, staticFs, f)
 	}, nil
 }
diff --git a/pkg/u/list.go b/pkg/u/list.go
index 34eafd11492cecb0eddfe8cc7ad4c4df8bf96511..cf71909e439d7726b4c6d9c6fcea771ad776a5bf 100644
--- a/pkg/u/list.go
+++ b/pkg/u/list.go
@@ -8,6 +8,14 @@ 	}
 	return v[0], true
 }
 
+func FirstOrZero[T any](v []T) T {
+	if len(v) == 0 {
+		var zero T
+		return zero
+	}
+	return v[0]
+}
+
 func ChunkBy[T any](items []T, chunkSize int) [][]T {
 	var chunks = make([][]T, 0, (len(items)/chunkSize)+1)
 	for chunkSize < len(items) {
diff --git a/pkg/u/list_test.go b/pkg/u/list_test.go
index a6d84c7eaa6ceb401da5f22978f0f285ea1b7478..805a2091b59ce4c644f748b4b6c49a28aec50cdb 100644
--- a/pkg/u/list_test.go
+++ b/pkg/u/list_test.go
@@ -94,3 +94,38 @@ 			}
 		})
 	}
 }
+
+func TestFirstOrZero(t *testing.T) {
+	testCases := []struct {
+		name  string
+		slice []int
+		first int
+	}{
+		{
+			name:  "multiple items slice",
+			slice: []int{1, 2, 3},
+			first: 1,
+		},
+		{
+			name:  "single item slice",
+			slice: []int{1},
+			first: 1,
+		},
+		{
+			name:  "empty slice",
+			slice: []int{},
+			first: 0,
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+
+			first := FirstOrZero(tc.slice)
+
+			if first != tc.first {
+				t.Errorf("Error first, want %d got %d", tc.first, first)
+			}
+
+		})
+	}
+}
diff --git a/templates/base.qtpl b/templates/base.qtpl
index 180b1ab428bc964511451c36569b198be2f5d3ee..497aa6d59d5fa49671e8ad4da524712bf62c40e8 100644
--- a/templates/base.qtpl
+++ b/templates/base.qtpl
@@ -37,12 +37,14 @@ %}
 
 Page prints a page implementing Page interface.
 {% func PageTemplate(p Page) %}
+<!DOCTYPE html>
 <html lang="en">
     <head>
         <meta charset="utf-8">
         <link rel="icon" href="data:,">
         <title>cerrado | {%= p.Title() %}</title> 
         <link rel="stylesheet" href="/static/main{%s Slug%}.css">
+        <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
         <meta name="viewport" content="width=device-width, initial-scale=1" />
     </head>
     <body>
diff --git a/templates/base.qtpl.go b/templates/base.qtpl.go
index c5570c8ee4119c1861d86e15aec1f7e24a4a35a9..5f39e8dd4040b5dbe896fc5c44dc5b423636f9eb 100644
--- a/templates/base.qtpl.go
+++ b/templates/base.qtpl.go
@@ -82,168 +82,170 @@ //line base.qtpl:39
 func StreamPageTemplate(qw422016 *qt422016.Writer, p Page) {
 //line base.qtpl:39
 	qw422016.N().S(`
+<!DOCTYPE html>
 <html lang="en">
     <head>
         <meta charset="utf-8">
         <link rel="icon" href="data:,">
         <title>cerrado | `)
-//line base.qtpl:44
+//line base.qtpl:45
 	p.StreamTitle(qw422016)
-//line base.qtpl:44
+//line base.qtpl:45
 	qw422016.N().S(`</title> 
         <link rel="stylesheet" href="/static/main`)
-//line base.qtpl:45
+//line base.qtpl:46
 	qw422016.E().S(Slug)
-//line base.qtpl:45
+//line base.qtpl:46
 	qw422016.N().S(`.css">
+        <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
         <meta name="viewport" content="width=device-width, initial-scale=1" />
     </head>
     <body>
         `)
-//line base.qtpl:49
+//line base.qtpl:51
 	p.StreamNavbar(qw422016)
-//line base.qtpl:49
+//line base.qtpl:51
 	qw422016.N().S(`
         <div class="container">
             `)
-//line base.qtpl:51
+//line base.qtpl:53
 	p.StreamContent(qw422016)
-//line base.qtpl:51
+//line base.qtpl:53
 	qw422016.N().S(`
         </div>
     </body>
     `)
-//line base.qtpl:54
+//line base.qtpl:56
 	p.StreamScript(qw422016)
-//line base.qtpl:54
+//line base.qtpl:56
 	qw422016.N().S(`
 </html>
 `)
-//line base.qtpl:56
+//line base.qtpl:58
 }
 
-//line base.qtpl:56
+//line base.qtpl:58
 func WritePageTemplate(qq422016 qtio422016.Writer, p Page) {
-//line base.qtpl:56
+//line base.qtpl:58
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line base.qtpl:56
+//line base.qtpl:58
 	StreamPageTemplate(qw422016, p)
-//line base.qtpl:56
+//line base.qtpl:58
 	qt422016.ReleaseWriter(qw422016)
-//line base.qtpl:56
+//line base.qtpl:58
 }
 
-//line base.qtpl:56
+//line base.qtpl:58
 func PageTemplate(p Page) string {
-//line base.qtpl:56
+//line base.qtpl:58
 	qb422016 := qt422016.AcquireByteBuffer()
-//line base.qtpl:56
+//line base.qtpl:58
 	WritePageTemplate(qb422016, p)
-//line base.qtpl:56
+//line base.qtpl:58
 	qs422016 := string(qb422016.B)
-//line base.qtpl:56
+//line base.qtpl:58
 	qt422016.ReleaseByteBuffer(qb422016)
-//line base.qtpl:56
+//line base.qtpl:58
 	return qs422016
-//line base.qtpl:56
+//line base.qtpl:58
 }
 
-//line base.qtpl:58
+//line base.qtpl:60
 type BasePage struct{}
 
-//line base.qtpl:59
+//line base.qtpl:61
 func (p *BasePage) StreamTitle(qw422016 *qt422016.Writer) {
-//line base.qtpl:59
+//line base.qtpl:61
 	qw422016.N().S(`Empty`)
-//line base.qtpl:59
+//line base.qtpl:61
 }
 
-//line base.qtpl:59
+//line base.qtpl:61
 func (p *BasePage) WriteTitle(qq422016 qtio422016.Writer) {
-//line base.qtpl:59
+//line base.qtpl:61
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line base.qtpl:59
+//line base.qtpl:61
 	p.StreamTitle(qw422016)
-//line base.qtpl:59
+//line base.qtpl:61
 	qt422016.ReleaseWriter(qw422016)
-//line base.qtpl:59
+//line base.qtpl:61
 }
 
-//line base.qtpl:59
+//line base.qtpl:61
 func (p *BasePage) Title() string {
-//line base.qtpl:59
+//line base.qtpl:61
 	qb422016 := qt422016.AcquireByteBuffer()
-//line base.qtpl:59
+//line base.qtpl:61
 	p.WriteTitle(qb422016)
-//line base.qtpl:59
+//line base.qtpl:61
 	qs422016 := string(qb422016.B)
-//line base.qtpl:59
+//line base.qtpl:61
 	qt422016.ReleaseByteBuffer(qb422016)
-//line base.qtpl:59
+//line base.qtpl:61
 	return qs422016
-//line base.qtpl:59
+//line base.qtpl:61
 }
 
-//line base.qtpl:60
+//line base.qtpl:62
 func (p *BasePage) StreamBody(qw422016 *qt422016.Writer) {
-//line base.qtpl:60
+//line base.qtpl:62
 	qw422016.N().S(`HelloWorld`)
-//line base.qtpl:60
+//line base.qtpl:62
 }
 
-//line base.qtpl:60
+//line base.qtpl:62
 func (p *BasePage) WriteBody(qq422016 qtio422016.Writer) {
-//line base.qtpl:60
+//line base.qtpl:62
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line base.qtpl:60
+//line base.qtpl:62
 	p.StreamBody(qw422016)
-//line base.qtpl:60
+//line base.qtpl:62
 	qt422016.ReleaseWriter(qw422016)
-//line base.qtpl:60
+//line base.qtpl:62
 }
 
-//line base.qtpl:60
+//line base.qtpl:62
 func (p *BasePage) Body() string {
-//line base.qtpl:60
+//line base.qtpl:62
 	qb422016 := qt422016.AcquireByteBuffer()
-//line base.qtpl:60
+//line base.qtpl:62
 	p.WriteBody(qb422016)
-//line base.qtpl:60
+//line base.qtpl:62
 	qs422016 := string(qb422016.B)
-//line base.qtpl:60
+//line base.qtpl:62
 	qt422016.ReleaseByteBuffer(qb422016)
-//line base.qtpl:60
+//line base.qtpl:62
 	return qs422016
-//line base.qtpl:60
+//line base.qtpl:62
 }
 
-//line base.qtpl:61
+//line base.qtpl:63
 func (p *BasePage) StreamScript(qw422016 *qt422016.Writer) {
-//line base.qtpl:61
+//line base.qtpl:63
 }
 
-//line base.qtpl:61
+//line base.qtpl:63
 func (p *BasePage) WriteScript(qq422016 qtio422016.Writer) {
-//line base.qtpl:61
+//line base.qtpl:63
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line base.qtpl:61
+//line base.qtpl:63
 	p.StreamScript(qw422016)
-//line base.qtpl:61
+//line base.qtpl:63
 	qt422016.ReleaseWriter(qw422016)
-//line base.qtpl:61
+//line base.qtpl:63
 }
 
-//line base.qtpl:61
+//line base.qtpl:63
 func (p *BasePage) Script() string {
-//line base.qtpl:61
+//line base.qtpl:63
 	qb422016 := qt422016.AcquireByteBuffer()
-//line base.qtpl:61
+//line base.qtpl:63
 	p.WriteScript(qb422016)
-//line base.qtpl:61
+//line base.qtpl:63
 	qs422016 := string(qb422016.B)
-//line base.qtpl:61
+//line base.qtpl:63
 	qt422016.ReleaseByteBuffer(qb422016)
-//line base.qtpl:61
+//line base.qtpl:63
 	return qs422016
-//line base.qtpl:61
+//line base.qtpl:63
 }