diff options
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 4 | ||||
| -rw-r--r-- | pkg/ext/compression.go | 142 | ||||
| -rw-r--r-- | pkg/ext/compression_test.go | 42 | ||||
| -rw-r--r-- | pkg/ext/mime.go | 24 | ||||
| -rw-r--r-- | pkg/handler/git/handler.go | 7 | ||||
| -rw-r--r-- | pkg/handler/router.go | 25 | ||||
| -rw-r--r-- | pkg/handler/static/handler.go | 11 | ||||
| -rw-r--r-- | pkg/u/list.go | 8 | ||||
| -rw-r--r-- | pkg/u/list_test.go | 35 | ||||
| -rw-r--r-- | templates/base.qtpl | 2 | ||||
| -rw-r--r-- | templates/base.qtpl.go | 138 | 
12 files changed, 360 insertions, 80 deletions
| @@ -5,9 +5,11 @@ go 1.22.2  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  ) @@ -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:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4  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 0000000..92144b8 --- /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 0000000..6424378 --- /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 0000000..6da66e3 --- /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 f3e74c7..28cc99e 100644 --- a/pkg/handler/git/handler.go +++ b/pkg/handler/git/handler.go @@ -6,6 +6,7 @@ import (  	"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 @@ func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) {  }  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 @@ func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) {  }  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 @@ func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) {  }  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 @@ func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) {  }  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 @@ func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) {  }  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 @@ func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) {  }  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 ed782f7..de5117c 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 @@ func MountHandler(  	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 a8b4583..5155068 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 @@ func ServeStaticHandler() (func(w http.ResponseWriter, r *http.Request), error)  	}  	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 34eafd1..cf71909 100644 --- a/pkg/u/list.go +++ b/pkg/u/list.go @@ -8,6 +8,14 @@ func First[T any](v []T) (T, bool) {  	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 a6d84c7..805a209 100644 --- a/pkg/u/list_test.go +++ b/pkg/u/list_test.go @@ -94,3 +94,38 @@ func TestSubList(t *testing.T) {  		})  	}  } + +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 180b1ab..497aa6d 100644 --- a/templates/base.qtpl +++ b/templates/base.qtpl @@ -37,12 +37,14 @@ Page {  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 c5570c8..5f39e8d 100644 --- a/templates/base.qtpl.go +++ b/templates/base.qtpl.go @@ -82,168 +82,170 @@ func Ignore[T any](v T, _ error) T {  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  } | 
