pax_global_header 0000666 0000000 0000000 00000000064 14053770275 0014524 g ustar 00root root 0000000 0000000 52 comment=a3f5fc8c4d24c8a09720094b04ffa433f6d05eab
render-1.4.0/ 0000775 0000000 0000000 00000000000 14053770275 0013005 5 ustar 00root root 0000000 0000000 render-1.4.0/.github/ 0000775 0000000 0000000 00000000000 14053770275 0014345 5 ustar 00root root 0000000 0000000 render-1.4.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14053770275 0016402 5 ustar 00root root 0000000 0000000 render-1.4.0/.github/workflows/test.yaml 0000664 0000000 0000000 00000001204 14053770275 0020242 0 ustar 00root root 0000000 0000000 on:
push:
branches:
- master
- v1
pull_request:
branches:
- "**"
name: Test
jobs:
tests:
strategy:
matrix:
go-version: [1.14.x, 1.15.x, 1.16.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: make ci
golangci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
render-1.4.0/.golangci.yaml 0000664 0000000 0000000 00000001523 14053770275 0015533 0 ustar 00root root 0000000 0000000 issues:
exclude:
- G203
linters:
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
- exhaustive
- goconst
- gocritic
- gocyclo
- goimports
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- nakedret
- nolintlint
- rowserrcheck
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace
- noctx
- misspell
# - golint
# - gochecknoinits
# - funlen
# - gofmt
# - scopelint
# - interfacer
# - gomnd
# - lll
# - asciicheck
# - gochecknoglobals
# - gocognit
# - godot
# - godox
# - goerr113
# - maligned
# - nestif
# - prealloc
# - testpackage
# - wsl
render-1.4.0/LICENSE 0000664 0000000 0000000 00000002070 14053770275 0014011 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2014 Cory Jacobsen
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
render-1.4.0/Makefile 0000664 0000000 0000000 00000000637 14053770275 0014453 0 ustar 00root root 0000000 0000000 .PHONY: help test
.DEFAULT_GOAL := help
help: ## Displays this help message.
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
test: ## Runs the linter, tests, and vetting.
golangci-lint run ./...
go test -cover -race -count=1 ./...
go vet ./...
ci: ## Runs on the tests and vetting for CI.
go test -cover -race -count=1 ./...
go vet ./...
render-1.4.0/README.md 0000664 0000000 0000000 00000037752 14053770275 0014302 0 ustar 00root root 0000000 0000000 # Render [](http://godoc.org/github.com/unrolled/render) [](https://github.com/unrolled/render/actions)
Render is a package that provides functionality for easily rendering JSON, XML, text, binary data, and HTML templates.
## Usage
Render can be used with pretty much any web framework providing you can access the `http.ResponseWriter` from your handler. The rendering functions simply wraps Go's existing functionality for marshaling and rendering data.
- HTML: Uses the [html/template](http://golang.org/pkg/html/template/) package to render HTML templates.
- JSON: Uses the [encoding/json](http://golang.org/pkg/encoding/json/) package to marshal data into a JSON-encoded response.
- XML: Uses the [encoding/xml](http://golang.org/pkg/encoding/xml/) package to marshal data into an XML-encoded response.
- Binary data: Passes the incoming data straight through to the `http.ResponseWriter`.
- Text: Passes the incoming string straight through to the `http.ResponseWriter`.
~~~ go
// main.go
package main
import (
"encoding/xml"
"net/http"
"github.com/unrolled/render"
)
type ExampleXml struct {
XMLName xml.Name `xml:"example"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func main() {
r := render.New()
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Welcome, visit sub pages now."))
})
mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
r.Data(w, http.StatusOK, []byte("Some binary data here."))
})
mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
r.Text(w, http.StatusOK, "Plain text here")
})
mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
})
mux.HandleFunc("/jsonp", func(w http.ResponseWriter, req *http.Request) {
r.JSONP(w, http.StatusOK, "callbackName", map[string]string{"hello": "jsonp"})
})
mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
})
mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
// Assumes you have a template in ./templates called "example.tmpl"
// $ mkdir -p templates && echo "
Hello {{.}}.
" > templates/example.tmpl
r.HTML(w, http.StatusOK, "example", "World")
})
http.ListenAndServe("127.0.0.1:3000", mux)
}
~~~
~~~ html
Hello {{.}}.
~~~
### Available Options
Render comes with a variety of configuration options _(Note: these are not the default option values. See the defaults below.)_:
~~~ go
// ...
r := render.New(render.Options{
Directory: "templates", // Specify what path to load the templates from.
FileSystem: &LocalFileSystem{}, // Specify filesystem from where files are loaded.
Asset: func(name string) ([]byte, error) { // Load from an Asset function instead of file.
return []byte("template content"), nil
},
AssetNames: func() []string { // Return a list of asset names for the Asset function
return []string{"filename.tmpl"}
},
Layout: "layout", // Specify a layout template. Layouts can call {{ yield }} to render the current template or {{ partial "css" }} to render a partial from the current template.
Extensions: []string{".tmpl", ".html"}, // Specify extensions to load for templates.
Funcs: []template.FuncMap{AppHelpers}, // Specify helper function maps for templates to access.
Delims: render.Delims{"{[{", "}]}"}, // Sets delimiters to the specified strings.
Charset: "UTF-8", // Sets encoding for content-types. Default is "UTF-8".
DisableCharset: true, // Prevents the charset from being appended to the content type header.
IndentJSON: true, // Output human readable JSON.
IndentXML: true, // Output human readable XML.
PrefixJSON: []byte(")]}',\n"), // Prefixes JSON responses with the given bytes.
PrefixXML: []byte(""), // Prefixes XML responses with the given bytes.
HTMLContentType: "application/xhtml+xml", // Output XHTML content type instead of default "text/html".
IsDevelopment: true, // Render will now recompile the templates on every HTML response.
UseMutexLock: true, // Overrides the default no lock implementation and uses the standard `sync.RWMutex` lock.
UnEscapeHTML: true, // Replace ensure '&<>' are output correctly (JSON only).
StreamingJSON: true, // Streams the JSON response via json.Encoder.
RequirePartials: true, // Return an error if a template is missing a partial used in a layout.
DisableHTTPErrorRendering: true, // Disables automatic rendering of http.StatusInternalServerError when an error occurs.
})
// ...
~~~
### Default Options
These are the preset options for Render:
~~~ go
r := render.New()
// Is the same as the default configuration options:
r := render.New(render.Options{
Directory: "templates",
FileSystem: &LocalFileSystem{},
Asset: nil,
AssetNames: nil,
Layout: "",
Extensions: []string{".tmpl"},
Funcs: []template.FuncMap{},
Delims: render.Delims{"{{", "}}"},
Charset: "UTF-8",
DisableCharset: false,
IndentJSON: false,
IndentXML: false,
PrefixJSON: []byte(""),
PrefixXML: []byte(""),
BinaryContentType: "application/octet-stream",
HTMLContentType: "text/html",
JSONContentType: "application/json",
JSONPContentType: "application/javascript",
TextContentType: "text/plain",
XMLContentType: "application/xhtml+xml",
IsDevelopment: false,
UseMutexLock: false,
UnEscapeHTML: false,
StreamingJSON: false,
RequirePartials: false,
DisableHTTPErrorRendering: false,
RenderPartialsWithoutPrefix: false,
BufferPool: GenericBufferPool,
})
~~~
### JSON vs Streaming JSON
By default, Render does **not** stream JSON to the `http.ResponseWriter`. It instead marshalls your object into a byte array, and if no errors occurred, writes that byte array to the `http.ResponseWriter`. If you would like to use the built it in streaming functionality (`json.Encoder`), you can set the `StreamingJSON` setting to `true`. This will stream the output directly to the `http.ResponseWriter`. Also note that streaming is only implemented in `render.JSON` and not `render.JSONP`, and the `UnEscapeHTML` and `Indent` options are ignored when streaming.
### Loading Templates
By default Render will attempt to load templates with a '.tmpl' extension from the "templates" directory. Templates are found by traversing the templates directory and are named by path and basename. For instance, the following directory structure:
~~~
templates/
|
|__ admin/
| |
| |__ index.tmpl
| |
| |__ edit.tmpl
|
|__ home.tmpl
~~~
Will provide the following templates:
~~~
admin/index
admin/edit
home
~~~
Templates can be loaded from an `embed.FS`.
~~~ go
// ...
//go:embed templates/*.html templates/*.tmpl
var embeddedTemplates embed.FS
// ...
r := render.New(render.Options{
Directory: "templates",
FileSystem: &render.EmbedFileSystem{
FS: embeddedTemplates,
},
Extensions: []string{".html", ".tmpl"},
})
// ...
~~~
You can also load templates from memory by providing the `Asset` and `AssetNames` options,
e.g. when generating an asset file using [go-bindata](https://github.com/jteeuwen/go-bindata).
### Layouts
Render provides `yield` and `partial` functions for layouts to access:
~~~ go
// ...
r := render.New(render.Options{
Layout: "layout",
})
// ...
~~~
~~~ html
My Layout
{{ partial "css" }}
{{ partial "header" }}
{{ yield }}
{{ partial "footer" }}
~~~
`current` can also be called to get the current template being rendered.
~~~ html
My Layout
This is the {{ current }} page.
~~~
Partials are defined by individual templates as seen below. The partial template's
name needs to be defined as "{partial name}-{template name}".
~~~ html
{{ define "header-home" }}
Home
{{ end }}
{{ define "footer-home"}}
The End
{{ end }}
~~~
By default, the template is not required to define all partials referenced in the
layout. If you want an error to be returned when a template does not define a
partial, set `Options.RequirePartials = true`.
### Character Encodings
Render will automatically set the proper Content-Type header based on which function you call. See below for an example of what the default settings would output (note that UTF-8 is the default, and binary data does not output the charset):
~~~ go
// main.go
package main
import (
"encoding/xml"
"net/http"
"github.com/unrolled/render"
)
type ExampleXml struct {
XMLName xml.Name `xml:"example"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func main() {
r := render.New(render.Options{})
mux := http.NewServeMux()
// This will set the Content-Type header to "application/octet-stream".
// Note that this does not receive a charset value.
mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
r.Data(w, http.StatusOK, []byte("Some binary data here."))
})
// This will set the Content-Type header to "application/json; charset=UTF-8".
mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
})
// This will set the Content-Type header to "text/xml; charset=UTF-8".
mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
})
// This will set the Content-Type header to "text/plain; charset=UTF-8".
mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
r.Text(w, http.StatusOK, "Plain text here")
})
// This will set the Content-Type header to "text/html; charset=UTF-8".
mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
// Assumes you have a template in ./templates called "example.tmpl"
// $ mkdir -p templates && echo "Hello {{.}}.
" > templates/example.tmpl
r.HTML(w, http.StatusOK, "example", "World")
})
http.ListenAndServe("127.0.0.1:3000", mux)
}
~~~
In order to change the charset, you can set the `Charset` within the `render.Options` to your encoding value:
~~~ go
// main.go
package main
import (
"encoding/xml"
"net/http"
"github.com/unrolled/render"
)
type ExampleXml struct {
XMLName xml.Name `xml:"example"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func main() {
r := render.New(render.Options{
Charset: "ISO-8859-1",
})
mux := http.NewServeMux()
// This will set the Content-Type header to "application/octet-stream".
// Note that this does not receive a charset value.
mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
r.Data(w, http.StatusOK, []byte("Some binary data here."))
})
// This will set the Content-Type header to "application/json; charset=ISO-8859-1".
mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
})
// This will set the Content-Type header to "text/xml; charset=ISO-8859-1".
mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
})
// This will set the Content-Type header to "text/plain; charset=ISO-8859-1".
mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
r.Text(w, http.StatusOK, "Plain text here")
})
// This will set the Content-Type header to "text/html; charset=ISO-8859-1".
mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
// Assumes you have a template in ./templates called "example.tmpl"
// $ mkdir -p templates && echo "Hello {{.}}.
" > templates/example.tmpl
r.HTML(w, http.StatusOK, "example", "World")
})
http.ListenAndServe("127.0.0.1:3000", mux)
}
~~~
### Error Handling
The rendering functions return any errors from the rendering engine.
By default, they will also write the error to the HTTP response and set the status code to 500. You can disable
this behavior so that you can handle errors yourself by setting
`Options.DisableHTTPErrorRendering: true`.
~~~go
r := render.New(render.Options{
DisableHTTPErrorRendering: true,
})
//...
err := r.HTML(w, http.StatusOK, "example", "World")
if err != nil{
http.Redirect(w, r, "/my-custom-500", http.StatusFound)
}
~~~
## Integration Examples
### [Echo](https://github.com/labstack/echo)
~~~ go
// main.go
package main
import (
"io"
"net/http"
"github.com/labstack/echo"
"github.com/unrolled/render"
)
type RenderWrapper struct { // We need to wrap the renderer because we need a different signature for echo.
rnd *render.Render
}
func (r *RenderWrapper) Render(w io.Writer, name string, data interface{},c echo.Context) error {
return r.rnd.HTML(w, 0, name, data) // The zero status code is overwritten by echo.
}
func main() {
r := &RenderWrapper{render.New()}
e := echo.New()
e.Renderer = r
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "TemplateName", "TemplateData")
})
e.Logger.Fatal(e.Start("127.0.0.1:8080"))
}
~~~
### [Gin](https://github.com/gin-gonic/gin)
~~~ go
// main.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/unrolled/render"
)
func main() {
r := render.New(render.Options{
IndentJSON: true,
})
router := gin.Default()
router.GET("/", func(c *gin.Context) {
r.JSON(c.Writer, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
})
router.Run("127.0.0.1:8080")
}
~~~
### [Goji](https://github.com/zenazn/goji)
~~~ go
// main.go
package main
import (
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
"github.com/unrolled/render"
)
func main() {
r := render.New(render.Options{
IndentJSON: true,
})
goji.Get("/", func(c web.C, w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
})
goji.Serve() // Defaults to ":8000".
}
~~~
### [Negroni](https://github.com/codegangsta/negroni)
~~~ go
// main.go
package main
import (
"net/http"
"github.com/urfave/negroni"
"github.com/unrolled/render"
)
func main() {
r := render.New(render.Options{
IndentJSON: true,
})
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
})
n := negroni.Classic()
n.UseHandler(mux)
n.Run("127.0.0.1:8080")
}
~~~
### [Traffic](https://github.com/pilu/traffic)
~~~ go
// main.go
package main
import (
"net/http"
"github.com/pilu/traffic"
"github.com/unrolled/render"
)
func main() {
r := render.New(render.Options{
IndentJSON: true,
})
router := traffic.New()
router.Get("/", func(w traffic.ResponseWriter, req *traffic.Request) {
r.JSON(w, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
})
router.Run() // Defaults to "127.0.0.1:3000".
}
~~~
render-1.4.0/doc.go 0000664 0000000 0000000 00000003370 14053770275 0014104 0 ustar 00root root 0000000 0000000 /*Package render is a package that provides functionality for easily rendering JSON, XML, binary data, and HTML templates.
package main
import (
"encoding/xml"
"net/http"
"github.com/unrolled/render"
)
type ExampleXml struct {
XMLName xml.Name `xml:"example"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func main() {
r := render.New()
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Welcome, visit sub pages now."))
})
mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
r.Data(w, http.StatusOK, []byte("Some binary data here."))
})
mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
r.Text(w, http.StatusOK, "Plain text here")
})
mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
})
mux.HandleFunc("/jsonp", func(w http.ResponseWriter, req *http.Request) {
r.JSONP(w, http.StatusOK, "callbackName", map[string]string{"hello": "jsonp"})
})
mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
})
mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
// Assumes you have a template in ./templates called "example.tmpl".
// $ mkdir -p templates && echo "Hello HTML world.
" > templates/example.tmpl
r.HTML(w, http.StatusOK, "example", nil)
})
http.ListenAndServe("127.0.0.1:3000", mux)
}
*/
package render
render-1.4.0/engine.go 0000664 0000000 0000000 00000010046 14053770275 0014602 0 ustar 00root root 0000000 0000000 package render
import (
"bytes"
"encoding/json"
"encoding/xml"
"html/template"
"io"
"net/http"
)
// Engine is the generic interface for all responses.
type Engine interface {
Render(io.Writer, interface{}) error
}
// Head defines the basic ContentType and Status fields.
type Head struct {
ContentType string
Status int
}
// Data built-in renderer.
type Data struct {
Head
}
// HTML built-in renderer.
type HTML struct {
Head
Name string
Templates *template.Template
bp GenericBufferPool
}
// JSON built-in renderer.
type JSON struct {
Head
Indent bool
UnEscapeHTML bool
Prefix []byte
StreamingJSON bool
}
// JSONP built-in renderer.
type JSONP struct {
Head
Indent bool
Callback string
}
// Text built-in renderer.
type Text struct {
Head
}
// XML built-in renderer.
type XML struct {
Head
Indent bool
Prefix []byte
}
// Write outputs the header content.
func (h Head) Write(w http.ResponseWriter) {
w.Header().Set(ContentType, h.ContentType)
w.WriteHeader(h.Status)
}
// Render a data response.
func (d Data) Render(w io.Writer, v interface{}) error {
if hw, ok := w.(http.ResponseWriter); ok {
c := hw.Header().Get(ContentType)
if c != "" {
d.Head.ContentType = c
}
d.Head.Write(hw)
}
_, _ = w.Write(v.([]byte))
return nil
}
// Render a HTML response.
func (h HTML) Render(w io.Writer, binding interface{}) error {
var buf *bytes.Buffer
if h.bp != nil {
// If we have a bufferpool, allocate from it
buf = h.bp.Get()
defer h.bp.Put(buf)
}
err := h.Templates.ExecuteTemplate(buf, h.Name, binding)
if err != nil {
return err
}
if hw, ok := w.(http.ResponseWriter); ok {
h.Head.Write(hw)
}
_, _ = buf.WriteTo(w)
return nil
}
// Render a JSON response.
func (j JSON) Render(w io.Writer, v interface{}) error {
if j.StreamingJSON {
return j.renderStreamingJSON(w, v)
}
var result []byte
var err error
if j.Indent {
result, err = json.MarshalIndent(v, "", " ")
result = append(result, '\n')
} else {
result, err = json.Marshal(v)
}
if err != nil {
return err
}
// Unescape HTML if needed.
if j.UnEscapeHTML {
result = bytes.ReplaceAll(result, []byte("\\u003c"), []byte("<"))
result = bytes.ReplaceAll(result, []byte("\\u003e"), []byte(">"))
result = bytes.ReplaceAll(result, []byte("\\u0026"), []byte("&"))
}
// JSON marshaled fine, write out the result.
if hw, ok := w.(http.ResponseWriter); ok {
j.Head.Write(hw)
}
if len(j.Prefix) > 0 {
_, _ = w.Write(j.Prefix)
}
_, _ = w.Write(result)
return nil
}
func (j JSON) renderStreamingJSON(w io.Writer, v interface{}) error {
if hw, ok := w.(http.ResponseWriter); ok {
j.Head.Write(hw)
}
if len(j.Prefix) > 0 {
_, _ = w.Write(j.Prefix)
}
return json.NewEncoder(w).Encode(v)
}
// Render a JSONP response.
func (j JSONP) Render(w io.Writer, v interface{}) error {
var result []byte
var err error
if j.Indent {
result, err = json.MarshalIndent(v, "", " ")
} else {
result, err = json.Marshal(v)
}
if err != nil {
return err
}
// JSON marshaled fine, write out the result.
if hw, ok := w.(http.ResponseWriter); ok {
j.Head.Write(hw)
}
_, _ = w.Write([]byte(j.Callback + "("))
_, _ = w.Write(result)
_, _ = w.Write([]byte(");"))
// If indenting, append a new line.
if j.Indent {
_, _ = w.Write([]byte("\n"))
}
return nil
}
// Render a text response.
func (t Text) Render(w io.Writer, v interface{}) error {
if hw, ok := w.(http.ResponseWriter); ok {
c := hw.Header().Get(ContentType)
if c != "" {
t.Head.ContentType = c
}
t.Head.Write(hw)
}
_, _ = w.Write([]byte(v.(string)))
return nil
}
// Render an XML response.
func (x XML) Render(w io.Writer, v interface{}) error {
var result []byte
var err error
if x.Indent {
result, err = xml.MarshalIndent(v, "", " ")
result = append(result, '\n')
} else {
result, err = xml.Marshal(v)
}
if err != nil {
return err
}
// XML marshaled fine, write out the result.
if hw, ok := w.(http.ResponseWriter); ok {
x.Head.Write(hw)
}
if len(x.Prefix) > 0 {
_, _ = w.Write(x.Prefix)
}
_, _ = w.Write(result)
return nil
}
render-1.4.0/fs.go 0000664 0000000 0000000 00000000657 14053770275 0013754 0 ustar 00root root 0000000 0000000 package render
import (
"io/ioutil"
"path/filepath"
)
type FileSystem interface {
Walk(root string, walkFn filepath.WalkFunc) error
ReadFile(filename string) ([]byte, error)
}
type LocalFileSystem struct{}
func (LocalFileSystem) Walk(root string, walkFn filepath.WalkFunc) error {
return filepath.Walk(root, walkFn)
}
func (LocalFileSystem) ReadFile(filename string) ([]byte, error) {
return ioutil.ReadFile(filename)
}
render-1.4.0/fs_embed.go 0000664 0000000 0000000 00000001010 14053770275 0015070 0 ustar 00root root 0000000 0000000 // +build go1.16
package render
import (
"embed"
"io/fs"
"path/filepath"
)
// EmbedFileSystem implements FileSystem on top of an embed.FS
type EmbedFileSystem struct {
embed.FS
}
var _ FileSystem = &EmbedFileSystem{}
func (e *EmbedFileSystem) Walk(root string, walkFn filepath.WalkFunc) error {
return fs.WalkDir(e.FS, root, func(path string, d fs.DirEntry, _ error) error {
if d == nil {
return nil
}
info, err := d.Info()
if err != nil {
return err
}
return walkFn(path, info, err)
})
}
render-1.4.0/fs_embed_test.go 0000664 0000000 0000000 00000002614 14053770275 0016142 0 ustar 00root root 0000000 0000000 // +build go1.16
package render
import (
"embed"
"net/http"
"net/http/httptest"
"testing"
)
//go:embed testdata/*/*.html testdata/*/*.tmpl testdata/*/*/*.tmpl
var EmbedFixtures embed.FS
func TestEmbedFileSystemTemplateLookup(t *testing.T) {
baseDir := "testdata/template-dir-test"
fname0Rel := "0"
fname1Rel := "subdir/1"
fnameShouldParsedRel := "dedicated.tmpl/notbad"
dirShouldNotParsedRel := "dedicated"
r := New(Options{
Directory: baseDir,
Extensions: []string{".tmpl", ".html"},
FileSystem: &EmbedFileSystem{
FS: EmbedFixtures,
},
})
expect(t, r.TemplateLookup(fname1Rel) != nil, true)
expect(t, r.TemplateLookup(fname0Rel) != nil, true)
expect(t, r.TemplateLookup(fnameShouldParsedRel) != nil, true)
expect(t, r.TemplateLookup(dirShouldNotParsedRel) == nil, true)
}
func TestEmbedFileSystemHTMLBasic(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
FileSystem: &EmbedFileSystem{
FS: EmbedFixtures,
},
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "hello", "gophers")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 200)
expect(t, res.Header().Get(ContentType), ContentHTML+"; charset=UTF-8")
expect(t, res.Body.String(), "Hello gophers
\n")
}
render-1.4.0/genericbufferpool.go 0000664 0000000 0000000 00000000251 14053770275 0017032 0 ustar 00root root 0000000 0000000 package render
import "bytes"
// GenericBufferPool abstracts buffer pool implementations
type GenericBufferPool interface {
Get() *bytes.Buffer
Put(*bytes.Buffer)
}
render-1.4.0/go.mod 0000664 0000000 0000000 00000000236 14053770275 0014114 0 ustar 00root root 0000000 0000000 module github.com/unrolled/render
go 1.16
require (
github.com/fsnotify/fsnotify v1.4.9
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea // indirect
)
render-1.4.0/go.sum 0000664 0000000 0000000 00000000751 14053770275 0014143 0 ustar 00root root 0000000 0000000 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea h1:+WiDlPBBaO+h9vPNZi8uJ3k4BkKQB7Iow3aqwHVA5hI=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
render-1.4.0/helpers.go 0000664 0000000 0000000 00000000634 14053770275 0015001 0 ustar 00root root 0000000 0000000 package render
import (
"fmt"
"html/template"
)
// Included helper functions for use when rendering HTML.
var helperFuncs = template.FuncMap{
"yield": func() (string, error) {
return "", fmt.Errorf("yield called with no layout defined")
},
"partial": func() (string, error) {
return "", fmt.Errorf("block called with no layout defined")
},
"current": func() (string, error) {
return "", nil
},
}
render-1.4.0/helpers_test.go 0000664 0000000 0000000 00000003625 14053770275 0016043 0 ustar 00root root 0000000 0000000 package render
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestRenderPartial(t *testing.T) {
render := New(Options{
Directory: "testdata/partials",
Layout: "layout",
})
var renErr error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
renErr = render.HTML(w, http.StatusOK, "content", "gophers")
})
res := httptest.NewRecorder()
req, err := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
if err != nil {
t.Fatalf("couldn't create a request. err = %s", err)
}
h.ServeHTTP(res, req)
expectNil(t, renErr)
expect(t, res.Body.String(), "before gophers\nduring
\nafter gophers\n")
}
func TestRenderPartialRequirePartialsOff(t *testing.T) {
render := New(Options{
Directory: "testdata/partials",
Layout: "layout",
RequirePartials: false,
})
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = render.HTML(w, http.StatusOK, "content-partial", "gophers")
})
res := httptest.NewRecorder()
req, err := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
if err != nil {
t.Fatalf("couldn't create a request. err = %s", err)
}
h.ServeHTTP(res, req)
expect(t, res.Body.String(), "\nduring
\nafter gophers\n")
}
func TestRenderPartialRequirePartialsOn(t *testing.T) {
render := New(Options{
Directory: "testdata/partials",
Layout: "layout",
RequirePartials: true,
})
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = render.HTML(w, http.StatusOK, "content-partial", "gophers")
})
res := httptest.NewRecorder()
req, err := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
if err != nil {
t.Fatalf("couldn't create a request. err = %s", err)
}
h.ServeHTTP(res, req)
expect(t, res.Body.String(), "template: layout:1:3: executing \"layout\" at : error calling partial: html/template: \"before-content-partial\" is undefined\n")
}
render-1.4.0/lock.go 0000664 0000000 0000000 00000000665 14053770275 0014273 0 ustar 00root root 0000000 0000000 package render
import "sync"
// rwLock represents an interface for sync.RWMutex.
type rwLock interface {
Lock()
Unlock()
RLock()
RUnlock()
}
var (
// Ensure our interface is correct.
_ rwLock = &sync.RWMutex{}
_ rwLock = emptyLock{}
)
// emptyLock is a noop RWLock implementation.
type emptyLock struct{}
func (emptyLock) Lock() {}
func (emptyLock) Unlock() {}
func (emptyLock) RLock() {}
func (emptyLock) RUnlock() {}
render-1.4.0/render.go 0000664 0000000 0000000 00000036211 14053770275 0014616 0 ustar 00root root 0000000 0000000 package render
import (
"bytes"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"github.com/fsnotify/fsnotify"
)
const (
// ContentBinary header value for binary data.
ContentBinary = "application/octet-stream"
// ContentHTML header value for HTML data.
ContentHTML = "text/html"
// ContentJSON header value for JSON data.
ContentJSON = "application/json"
// ContentJSONP header value for JSONP data.
ContentJSONP = "application/javascript"
// ContentLength header constant.
ContentLength = "Content-Length"
// ContentText header value for Text data.
ContentText = "text/plain"
// ContentType header constant.
ContentType = "Content-Type"
// ContentXHTML header value for XHTML data.
ContentXHTML = "application/xhtml+xml"
// ContentXML header value for XML data.
ContentXML = "text/xml"
// Default character encoding.
defaultCharset = "UTF-8"
)
// helperFuncs had to be moved out. See helpers.go|helpers_pre16.go files.
// Delims represents a set of Left and Right delimiters for HTML template rendering.
type Delims struct {
// Left delimiter, defaults to {{.
Left string
// Right delimiter, defaults to }}.
Right string
}
// Options is a struct for specifying configuration options for the render.Render object.
type Options struct {
// Directory to load templates. Default is "templates".
Directory string
// FileSystem to access files
FileSystem FileSystem
// Asset function to use in place of directory. Defaults to nil.
Asset func(name string) ([]byte, error)
// AssetNames function to use in place of directory. Defaults to nil.
AssetNames func() []string
// Layout template name. Will not render a layout if blank (""). Defaults to blank ("").
Layout string
// Extensions to parse template files from. Defaults to [".tmpl"].
Extensions []string
// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Defaults to empty map.
Funcs []template.FuncMap
// Delims sets the action delimiters to the specified strings in the Delims struct.
Delims Delims
// Appends the given character set to the Content-Type header. Default is "UTF-8".
Charset string
// If DisableCharset is set to true, it will not append the above Charset value to the Content-Type header. Default is false.
DisableCharset bool
// Outputs human readable JSON.
IndentJSON bool
// Outputs human readable XML. Default is false.
IndentXML bool
// Prefixes the JSON output with the given bytes. Default is false.
PrefixJSON []byte
// Prefixes the XML output with the given bytes.
PrefixXML []byte
// Allows changing the binary content type.
BinaryContentType string
// Allows changing the HTML content type.
HTMLContentType string
// Allows changing the JSON content type.
JSONContentType string
// Allows changing the JSONP content type.
JSONPContentType string
// Allows changing the Text content type.
TextContentType string
// Allows changing the XML content type.
XMLContentType string
// If IsDevelopment is set to true, this will recompile the templates on every request. Default is false.
IsDevelopment bool
// If UseMutexLock is set to true, the standard `sync.RWMutex` lock will be used instead of the lock free implementation. Default is false.
// Note that when `IsDevelopment` is true, the standard `sync.RWMutex` lock is always used. Lock free is only a production feature.
UseMutexLock bool
// Unescape HTML characters "&<>" to their original values. Default is false.
UnEscapeHTML bool
// Streams JSON responses instead of marshalling prior to sending. Default is false.
StreamingJSON bool
// Require that all partials executed in the layout are implemented in all templates using the layout. Default is false.
RequirePartials bool
// Deprecated: Use the above `RequirePartials` instead of this. As of Go 1.6, blocks are built in. Default is false.
RequireBlocks bool
// Disables automatic rendering of http.StatusInternalServerError when an error occurs. Default is false.
DisableHTTPErrorRendering bool
// Enables using partials without the current filename suffix which allows use of the same template in multiple files. e.g {{ partial "carosuel" }} inside the home template will match carosel-home or carosel.
// ***NOTE*** - This option should be named RenderPartialsWithoutSuffix as that is what it does. "Prefix" is a typo. Maintaining the existing name for backwards compatibility.
RenderPartialsWithoutPrefix bool
// BufferPool to use when rendering HTML templates. If none is supplied
// defaults to SizedBufferPool of size 32 with 512KiB buffers.
BufferPool GenericBufferPool
}
// HTMLOptions is a struct for overriding some rendering Options for specific HTML call.
type HTMLOptions struct {
// Layout template name. Overrides Options.Layout.
Layout string
// Funcs added to Options.Funcs.
Funcs template.FuncMap
}
// Render is a service that provides functions for easily writing JSON, XML,
// binary data, and HTML templates out to a HTTP Response.
type Render struct {
lock rwLock
// Customize Secure with an Options struct.
opt Options
templates *template.Template
compiledCharset string
hasWatcher bool
}
// New constructs a new Render instance with the supplied options.
func New(options ...Options) *Render {
var o Options
if len(options) > 0 {
o = options[0]
}
r := Render{opt: o}
r.prepareOptions()
r.CompileTemplates()
return &r
}
func (r *Render) prepareOptions() {
// Fill in the defaults if need be.
if len(r.opt.Charset) == 0 {
r.opt.Charset = defaultCharset
}
if !r.opt.DisableCharset {
r.compiledCharset = "; charset=" + r.opt.Charset
}
if len(r.opt.Directory) == 0 {
r.opt.Directory = "templates"
}
if r.opt.FileSystem == nil {
r.opt.FileSystem = &LocalFileSystem{}
}
if len(r.opt.Extensions) == 0 {
r.opt.Extensions = []string{".tmpl"}
}
if len(r.opt.BinaryContentType) == 0 {
r.opt.BinaryContentType = ContentBinary
}
if len(r.opt.HTMLContentType) == 0 {
r.opt.HTMLContentType = ContentHTML
}
if len(r.opt.JSONContentType) == 0 {
r.opt.JSONContentType = ContentJSON
}
if len(r.opt.JSONPContentType) == 0 {
r.opt.JSONPContentType = ContentJSONP
}
if len(r.opt.TextContentType) == 0 {
r.opt.TextContentType = ContentText
}
if len(r.opt.XMLContentType) == 0 {
r.opt.XMLContentType = ContentXML
}
if r.opt.BufferPool == nil {
r.opt.BufferPool = NewSizedBufferPool(32, 1<<19) // 32 buffers of size 512KiB each
}
if r.opt.IsDevelopment || r.opt.UseMutexLock {
r.lock = &sync.RWMutex{}
} else {
r.lock = &emptyLock{}
}
}
func (r *Render) CompileTemplates() {
if r.opt.Asset == nil || r.opt.AssetNames == nil {
r.compileTemplatesFromDir()
return
}
r.compileTemplatesFromAsset()
}
func (r *Render) compileTemplatesFromDir() {
dir := r.opt.Directory
tmpTemplates := template.New(dir)
tmpTemplates.Delims(r.opt.Delims.Left, r.opt.Delims.Right)
var watcher *fsnotify.Watcher
if r.opt.IsDevelopment {
var err error
watcher, err = fsnotify.NewWatcher()
if err != nil {
log.Printf("Unable to create new watcher for template files. Templates will be recompiled on every render. Error: %v\n", err)
}
}
// Walk the supplied directory and compile any files that match our extension list.
_ = r.opt.FileSystem.Walk(dir, func(path string, info os.FileInfo, _ error) error {
// Fix same-extension-dirs bug: some dir might be named to: "users.tmpl", "local.html".
// These dirs should be excluded as they are not valid golang templates, but files under
// them should be treat as normal.
// If is a dir, return immediately (dir is not a valid golang template).
if info != nil && watcher != nil {
_ = watcher.Add(path)
}
if info == nil || info.IsDir() {
return nil
}
rel, err := filepath.Rel(dir, path)
if err != nil {
return err
}
ext := ""
if strings.Contains(rel, ".") {
ext = filepath.Ext(rel)
}
for _, extension := range r.opt.Extensions {
if ext == extension {
buf, err := r.opt.FileSystem.ReadFile(path)
if err != nil {
panic(err)
}
name := (rel[0 : len(rel)-len(ext)])
tmpl := tmpTemplates.New(filepath.ToSlash(name))
// Add our funcmaps.
for _, funcs := range r.opt.Funcs {
tmpl.Funcs(funcs)
}
// Break out if this parsing fails. We don't want any silent server starts.
template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
break
}
}
return nil
})
r.lock.Lock()
defer r.lock.Unlock()
r.templates = tmpTemplates
if r.hasWatcher = watcher != nil; r.hasWatcher {
go func() {
select {
case _, ok := <-watcher.Events:
if !ok {
return
}
case _, ok := <-watcher.Errors:
if !ok {
return
}
}
watcher.Close()
r.CompileTemplates()
}()
}
}
func (r *Render) compileTemplatesFromAsset() {
dir := r.opt.Directory
tmpTemplates := template.New(dir)
tmpTemplates.Delims(r.opt.Delims.Left, r.opt.Delims.Right)
for _, path := range r.opt.AssetNames() {
if !strings.HasPrefix(path, dir) {
continue
}
rel, err := filepath.Rel(dir, path)
if err != nil {
panic(err)
}
ext := ""
if strings.Contains(rel, ".") {
ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".")
}
for _, extension := range r.opt.Extensions {
if ext == extension {
buf, err := r.opt.Asset(path)
if err != nil {
panic(err)
}
name := (rel[0 : len(rel)-len(ext)])
tmpl := tmpTemplates.New(filepath.ToSlash(name))
// Add our funcmaps.
for _, funcs := range r.opt.Funcs {
tmpl.Funcs(funcs)
}
// Break out if this parsing fails. We don't want any silent server starts.
template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
break
}
}
}
r.lock.Lock()
defer r.lock.Unlock()
r.templates = tmpTemplates
}
// TemplateLookup is a wrapper around template.Lookup and returns
// the template with the given name that is associated with t, or nil
// if there is no such template.
func (r *Render) TemplateLookup(t string) *template.Template {
r.lock.RLock()
defer r.lock.RUnlock()
return r.templates.Lookup(t)
}
func (r *Render) execute(templates *template.Template, name string, binding interface{}) (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
return buf, templates.ExecuteTemplate(buf, name, binding)
}
func (r *Render) layoutFuncs(templates *template.Template, name string, binding interface{}) template.FuncMap {
return template.FuncMap{
"yield": func() (template.HTML, error) {
buf, err := r.execute(templates, name, binding)
// Return safe HTML here since we are rendering our own template.
return template.HTML(buf.String()), err
},
"current": func() (string, error) {
return name, nil
},
"block": func(partialName string) (template.HTML, error) {
log.Println("Render's `block` implementation is now depericated. Use `partial` as a drop in replacement.")
fullPartialName := fmt.Sprintf("%s-%s", partialName, name)
if templates.Lookup(fullPartialName) == nil && r.opt.RenderPartialsWithoutPrefix {
fullPartialName = partialName
}
if r.opt.RequireBlocks || templates.Lookup(fullPartialName) != nil {
buf, err := r.execute(templates, fullPartialName, binding)
// Return safe HTML here since we are rendering our own template.
return template.HTML(buf.String()), err
}
return "", nil
},
"partial": func(partialName string) (template.HTML, error) {
fullPartialName := fmt.Sprintf("%s-%s", partialName, name)
if templates.Lookup(fullPartialName) == nil && r.opt.RenderPartialsWithoutPrefix {
fullPartialName = partialName
}
if r.opt.RequirePartials || templates.Lookup(fullPartialName) != nil {
buf, err := r.execute(templates, fullPartialName, binding)
// Return safe HTML here since we are rendering our own template.
return template.HTML(buf.String()), err
}
return "", nil
},
}
}
func (r *Render) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
layout := r.opt.Layout
funcs := template.FuncMap{}
for _, tmp := range r.opt.Funcs {
for k, v := range tmp {
funcs[k] = v
}
}
if len(htmlOpt) > 0 {
opt := htmlOpt[0]
if len(opt.Layout) > 0 {
layout = opt.Layout
}
for k, v := range opt.Funcs {
funcs[k] = v
}
}
return HTMLOptions{
Layout: layout,
Funcs: funcs,
}
}
// Render is the generic function called by XML, JSON, Data, HTML, and can be called by custom implementations.
func (r *Render) Render(w io.Writer, e Engine, data interface{}) error {
err := e.Render(w, data)
if hw, ok := w.(http.ResponseWriter); err != nil && !r.opt.DisableHTTPErrorRendering && ok {
http.Error(hw, err.Error(), http.StatusInternalServerError)
}
return err
}
// Data writes out the raw bytes as binary data.
func (r *Render) Data(w io.Writer, status int, v []byte) error {
head := Head{
ContentType: r.opt.BinaryContentType,
Status: status,
}
d := Data{
Head: head,
}
return r.Render(w, d, v)
}
// HTML builds up the response from the specified template and bindings.
func (r *Render) HTML(w io.Writer, status int, name string, binding interface{}, htmlOpt ...HTMLOptions) error {
// If we are in development mode, recompile the templates on every HTML request.
r.lock.RLock() // rlock here because we're reading the hasWatcher
if r.opt.IsDevelopment && !r.hasWatcher {
r.lock.RUnlock() // runlock here because CompileTemplates will lock
r.CompileTemplates()
r.lock.RLock()
}
templates := r.templates
r.lock.RUnlock()
opt := r.prepareHTMLOptions(htmlOpt)
if tpl := templates.Lookup(name); tpl != nil {
if len(opt.Layout) > 0 {
tpl.Funcs(r.layoutFuncs(templates, name, binding))
name = opt.Layout
}
if len(opt.Funcs) > 0 {
tpl.Funcs(opt.Funcs)
}
}
head := Head{
ContentType: r.opt.HTMLContentType + r.compiledCharset,
Status: status,
}
h := HTML{
Head: head,
Name: name,
Templates: templates,
bp: r.opt.BufferPool,
}
return r.Render(w, h, binding)
}
// JSON marshals the given interface object and writes the JSON response.
func (r *Render) JSON(w io.Writer, status int, v interface{}) error {
head := Head{
ContentType: r.opt.JSONContentType + r.compiledCharset,
Status: status,
}
j := JSON{
Head: head,
Indent: r.opt.IndentJSON,
Prefix: r.opt.PrefixJSON,
UnEscapeHTML: r.opt.UnEscapeHTML,
StreamingJSON: r.opt.StreamingJSON,
}
return r.Render(w, j, v)
}
// JSONP marshals the given interface object and writes the JSON response.
func (r *Render) JSONP(w io.Writer, status int, callback string, v interface{}) error {
head := Head{
ContentType: r.opt.JSONPContentType + r.compiledCharset,
Status: status,
}
j := JSONP{
Head: head,
Indent: r.opt.IndentJSON,
Callback: callback,
}
return r.Render(w, j, v)
}
// Text writes out a string as plain text.
func (r *Render) Text(w io.Writer, status int, v string) error {
head := Head{
ContentType: r.opt.TextContentType + r.compiledCharset,
Status: status,
}
t := Text{
Head: head,
}
return r.Render(w, t, v)
}
// XML marshals the given interface object and writes the XML response.
func (r *Render) XML(w io.Writer, status int, v interface{}) error {
head := Head{
ContentType: r.opt.XMLContentType + r.compiledCharset,
Status: status,
}
x := XML{
Head: head,
Indent: r.opt.IndentXML,
Prefix: r.opt.PrefixXML,
}
return r.Render(w, x, v)
}
render-1.4.0/render_data_test.go 0000664 0000000 0000000 00000003244 14053770275 0016646 0 ustar 00root root 0000000 0000000 package render
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestDataBinaryBasic(t *testing.T) {
render := New(Options{
// nothing here to configure
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.Data(w, 299, []byte("hello there"))
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 299)
expect(t, res.Header().Get(ContentType), ContentBinary)
expect(t, res.Body.String(), "hello there")
}
func TestDataCustomMimeType(t *testing.T) {
render := New(Options{
// nothing here to configure
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(ContentType, "image/jpeg")
err = render.Data(w, http.StatusOK, []byte("..jpeg data.."))
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "image/jpeg")
expect(t, res.Body.String(), "..jpeg data..")
}
func TestDataCustomContentType(t *testing.T) {
render := New(Options{
BinaryContentType: "image/png",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.Data(w, http.StatusOK, []byte("..png data.."))
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "image/png")
expect(t, res.Body.String(), "..png data..")
}
render-1.4.0/render_html_test.go 0000664 0000000 0000000 00000025150 14053770275 0016701 0 ustar 00root root 0000000 0000000 package render
import (
"bytes"
"errors"
"html/template"
"net/http"
"net/http/httptest"
"testing"
)
func TestHTMLBad(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "nope", nil)
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNotNil(t, err)
expect(t, res.Code, 500)
expect(t, res.Body.String(), "html/template: \"nope\" is undefined\n")
}
func TestHTMLBadDisableHTTPErrorRendering(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
DisableHTTPErrorRendering: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "nope", nil)
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNotNil(t, err)
expect(t, res.Code, 200)
expect(t, res.Body.String(), "")
}
func TestHTMLBasic(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "hello", "gophers")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 200)
expect(t, res.Header().Get(ContentType), ContentHTML+"; charset=UTF-8")
expect(t, res.Body.String(), "Hello gophers
\n")
}
func BenchmarkBigHTMLBuffers(b *testing.B) {
b.ReportAllocs()
render := New(Options{
Directory: "testdata/basic",
})
var buf = new(bytes.Buffer)
for i := 0; i < b.N; i++ {
_ = render.HTML(buf, http.StatusOK, "hello", "gophers")
buf.Reset()
}
}
func BenchmarkSmallHTMLBuffers(b *testing.B) {
b.ReportAllocs()
render := New(Options{
Directory: "testdata/basic",
// Tiny 8 bytes buffers -> should lead to allocations
// on every template render
BufferPool: NewSizedBufferPool(32, 8),
})
var buf = new(bytes.Buffer)
for i := 0; i < b.N; i++ {
_ = render.HTML(buf, http.StatusOK, "hello", "gophers")
buf.Reset()
}
}
func TestHTMLXHTML(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
HTMLContentType: ContentXHTML,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "hello", "gophers")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 200)
expect(t, res.Header().Get(ContentType), ContentXHTML+"; charset=UTF-8")
expect(t, res.Body.String(), "Hello gophers
\n")
}
func TestHTMLExtensions(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
Extensions: []string{".tmpl", ".html"},
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "hypertext", nil)
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 200)
expect(t, res.Header().Get(ContentType), ContentHTML+"; charset=UTF-8")
expect(t, res.Body.String(), "Hypertext!\n")
}
func TestHTMLFuncs(t *testing.T) {
render := New(Options{
Directory: "testdata/custom_funcs",
Funcs: []template.FuncMap{{
"myCustomFunc": func() string {
return "My custom function"
},
}},
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "index", "gophers")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Body.String(), "My custom function\n")
}
func TestRenderLayout(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
Layout: "layout",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "content", "gophers")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Body.String(), "head\ngophers
\n\nfoot\n")
}
func TestHTMLLayoutCurrent(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
Layout: "current_layout",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "content", "gophers")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Body.String(), "content head\ngophers
\n\ncontent foot\n")
}
func TestHTMLNested(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "admin/index", "gophers")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 200)
expect(t, res.Header().Get(ContentType), ContentHTML+"; charset=UTF-8")
expect(t, res.Body.String(), "Admin gophers
\n")
}
func TestHTMLBadPath(t *testing.T) {
render := New(Options{
Directory: "../../../../../../../../../../../../../../../../testdata/basic",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "hello", "gophers")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNotNil(t, err)
expect(t, res.Code, 500)
}
func TestHTMLDelimiters(t *testing.T) {
render := New(Options{
Delims: Delims{"{[{", "}]}"},
Directory: "testdata/basic",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "delims", "gophers")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 200)
expect(t, res.Header().Get(ContentType), ContentHTML+"; charset=UTF-8")
expect(t, res.Body.String(), "Hello gophers
")
}
func TestHTMLDefaultCharset(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "hello", "gophers")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 200)
expect(t, res.Header().Get(ContentType), ContentHTML+"; charset=UTF-8")
// ContentLength should be deferred to the ResponseWriter and not Render
expect(t, res.Header().Get(ContentLength), "")
expect(t, res.Body.String(), "Hello gophers
\n")
}
func TestHTMLOverrideLayout(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
Layout: "layout",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "content", "gophers", HTMLOptions{
Layout: "another_layout",
})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 200)
expect(t, res.Header().Get(ContentType), ContentHTML+"; charset=UTF-8")
expect(t, res.Body.String(), "another head\ngophers
\n\nanother foot\n")
}
func TestHTMLNoRace(t *testing.T) {
// This test used to fail if run with -race
render := New(Options{
Directory: "testdata/basic",
})
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := render.HTML(w, http.StatusOK, "hello", "gophers")
expectNil(t, err)
})
done := make(chan bool)
doreq := func() {
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expect(t, res.Code, 200)
expect(t, res.Header().Get(ContentType), ContentHTML+"; charset=UTF-8")
// ContentLength should be deferred to the ResponseWriter and not Render
expect(t, res.Header().Get(ContentLength), "")
expect(t, res.Body.String(), "Hello gophers
\n")
done <- true
}
// Run two requests to check there is no race condition
go doreq()
go doreq()
<-done
<-done
}
func TestHTMLLoadFromAssets(t *testing.T) {
render := New(Options{
Asset: func(file string) ([]byte, error) {
switch file {
case "templates/test.tmpl":
return []byte("gophers
\n"), nil
case "templates/layout.tmpl":
return []byte("head\n{{ yield }}\nfoot\n"), nil
default:
return nil, errors.New("file not found: " + file)
}
},
AssetNames: func() []string {
return []string{"templates/test.tmpl", "templates/layout.tmpl"}
},
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "test", "gophers", HTMLOptions{
Layout: "layout",
})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 200)
expect(t, res.Header().Get(ContentType), ContentHTML+"; charset=UTF-8")
expect(t, res.Body.String(), "head\ngophers
\n\nfoot\n")
}
func TestCompileTemplatesFromDir(t *testing.T) {
baseDir := "testdata/template-dir-test"
fname0Rel := "0"
fname1Rel := "subdir/1"
fnameShouldParsedRel := "dedicated.tmpl/notbad"
dirShouldNotParsedRel := "dedicated"
r := New(Options{
Directory: baseDir,
Extensions: []string{".tmpl", ".html"},
})
r.compileTemplatesFromDir()
expect(t, r.TemplateLookup(fname1Rel) != nil, true)
expect(t, r.TemplateLookup(fname0Rel) != nil, true)
expect(t, r.TemplateLookup(fnameShouldParsedRel) != nil, true)
expect(t, r.TemplateLookup(dirShouldNotParsedRel) == nil, true)
}
func TestHTMLDisabledCharset(t *testing.T) {
render := New(Options{
Directory: "testdata/basic",
DisableCharset: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.HTML(w, http.StatusOK, "hello", "gophers")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), ContentHTML)
expect(t, res.Body.String(), "Hello gophers
\n")
}
render-1.4.0/render_json_test.go 0000664 0000000 0000000 00000016333 14053770275 0016711 0 ustar 00root root 0000000 0000000 package render
import (
"encoding/json"
"math"
"net/http"
"net/http/httptest"
"testing"
)
type Greeting struct {
One string `json:"one"`
Two string `json:"two"`
}
func TestJSONBasic(t *testing.T) {
render := New()
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, 299, Greeting{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 299)
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8")
expect(t, res.Body.String(), "{\"one\":\"hello\",\"two\":\"world\"}")
}
func TestJSONPrefix(t *testing.T) {
prefix := ")]}',\n"
render := New(Options{
PrefixJSON: []byte(prefix),
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, 300, Greeting{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 300)
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8")
expect(t, res.Body.String(), prefix+"{\"one\":\"hello\",\"two\":\"world\"}")
}
func TestJSONIndented(t *testing.T) {
render := New(Options{
IndentJSON: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, http.StatusOK, Greeting{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8")
expect(t, res.Body.String(), "{\n \"one\": \"hello\",\n \"two\": \"world\"\n}\n")
}
func TestJSONConsumeIndented(t *testing.T) {
render := New(Options{
IndentJSON: true,
})
var renErr error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
renErr = render.JSON(w, http.StatusOK, Greeting{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
var output Greeting
err := json.Unmarshal(res.Body.Bytes(), &output)
expectNil(t, err)
expectNil(t, renErr)
expect(t, output.One, "hello")
expect(t, output.Two, "world")
}
func TestJSONWithError(t *testing.T) {
render := New(Options{}, Options{}, Options{}, Options{})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, 299, math.NaN())
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNotNil(t, err)
expect(t, res.Code, 500)
}
func TestJSONWithOutUnEscapeHTML(t *testing.T) {
render := New(Options{
UnEscapeHTML: false,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, http.StatusOK, Greeting{"test&test", "test&test
"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Body.String(), `{"one":"\u003cspan\u003etest\u0026test\u003c/span\u003e","two":"\u003cdiv\u003etest\u0026test\u003c/div\u003e"}`)
}
func TestJSONWithUnEscapeHTML(t *testing.T) {
render := New(Options{
UnEscapeHTML: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, http.StatusOK, Greeting{"test&test", "test&test
"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Body.String(), "{\"one\":\"test&test\",\"two\":\"test&test
\"}")
}
func TestJSONStream(t *testing.T) {
render := New(Options{
StreamingJSON: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, 299, Greeting{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 299)
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8")
expect(t, res.Body.String(), "{\"one\":\"hello\",\"two\":\"world\"}\n")
}
func TestJSONStreamPrefix(t *testing.T) {
prefix := ")]}',\n"
render := New(Options{
PrefixJSON: []byte(prefix),
StreamingJSON: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, 300, Greeting{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 300)
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=UTF-8")
expect(t, res.Body.String(), prefix+"{\"one\":\"hello\",\"two\":\"world\"}\n")
}
func TestJSONStreamWithError(t *testing.T) {
render := New(Options{
StreamingJSON: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, 299, math.NaN())
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNotNil(t, err)
expect(t, res.Code, 299)
// Because this is streaming, we can not catch the error.
expect(t, res.Body.String(), "json: unsupported value: NaN\n")
// Also the header will be incorrect.
expect(t, res.Header().Get(ContentType), "text/plain; charset=utf-8")
}
func TestJSONCharset(t *testing.T) {
render := New(Options{
Charset: "foobar",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, 300, Greeting{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 300)
expect(t, res.Header().Get(ContentType), ContentJSON+"; charset=foobar")
expect(t, res.Body.String(), "{\"one\":\"hello\",\"two\":\"world\"}")
}
func TestJSONCustomContentType(t *testing.T) {
render := New(Options{
JSONContentType: "application/vnd.api+json",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, http.StatusOK, Greeting{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "application/vnd.api+json; charset=UTF-8")
expect(t, res.Body.String(), "{\"one\":\"hello\",\"two\":\"world\"}")
}
func TestJSONDisabledCharset(t *testing.T) {
render := New(Options{
DisableCharset: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSON(w, http.StatusOK, Greeting{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), ContentJSON)
expect(t, res.Body.String(), "{\"one\":\"hello\",\"two\":\"world\"}")
}
render-1.4.0/render_jsonp_test.go 0000664 0000000 0000000 00000005572 14053770275 0017074 0 ustar 00root root 0000000 0000000 package render
import (
"math"
"net/http"
"net/http/httptest"
"testing"
)
type GreetingP struct {
One string `json:"one"`
Two string `json:"two"`
}
func TestJSONPBasic(t *testing.T) {
render := New()
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSONP(w, 299, "helloCallback", GreetingP{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 299)
expect(t, res.Header().Get(ContentType), ContentJSONP+"; charset=UTF-8")
expect(t, res.Body.String(), "helloCallback({\"one\":\"hello\",\"two\":\"world\"});")
}
func TestJSONPRenderIndented(t *testing.T) {
render := New(Options{
IndentJSON: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSONP(w, http.StatusOK, "helloCallback", GreetingP{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), ContentJSONP+"; charset=UTF-8")
expect(t, res.Body.String(), "helloCallback({\n \"one\": \"hello\",\n \"two\": \"world\"\n});\n")
}
func TestJSONPWithError(t *testing.T) {
render := New()
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSONP(w, 299, "helloCallback", math.NaN())
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNotNil(t, err)
expect(t, res.Code, 500)
}
func TestJSONPCustomContentType(t *testing.T) {
render := New(Options{
JSONPContentType: "application/vnd.api+json",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSONP(w, http.StatusOK, "helloCallback", GreetingP{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "application/vnd.api+json; charset=UTF-8")
expect(t, res.Body.String(), "helloCallback({\"one\":\"hello\",\"two\":\"world\"});")
}
func TestJSONPDisabledCharset(t *testing.T) {
render := New(Options{
DisableCharset: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.JSONP(w, http.StatusOK, "helloCallback", GreetingP{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), ContentJSONP)
expect(t, res.Body.String(), "helloCallback({\"one\":\"hello\",\"two\":\"world\"});")
}
render-1.4.0/render_test.go 0000664 0000000 0000000 00000004543 14053770275 0015660 0 ustar 00root root 0000000 0000000 package render
import (
"context"
"net/http"
"net/http/httptest"
"reflect"
"sync"
"testing"
)
var ctx = context.Background()
func TestLockConfig(t *testing.T) {
mutex := reflect.TypeOf(&sync.RWMutex{}).Kind()
empty := reflect.TypeOf(&emptyLock{}).Kind()
r1 := New(Options{
IsDevelopment: true,
UseMutexLock: false,
})
expect(t, reflect.TypeOf(r1.lock).Kind(), mutex)
r2 := New(Options{
IsDevelopment: true,
UseMutexLock: true,
})
expect(t, reflect.TypeOf(r2.lock).Kind(), mutex)
r3 := New(Options{
IsDevelopment: false,
UseMutexLock: true,
})
expect(t, reflect.TypeOf(r3.lock).Kind(), mutex)
r4 := New(Options{
IsDevelopment: false,
UseMutexLock: false,
})
expect(t, reflect.TypeOf(r4.lock).Kind(), empty)
}
/* Benchmarks */
func BenchmarkNormalJSON(b *testing.B) {
render := New()
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = render.JSON(w, 200, Greeting{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
for i := 0; i < b.N; i++ {
h.ServeHTTP(res, req)
}
}
func BenchmarkStreamingJSON(b *testing.B) {
render := New(Options{
StreamingJSON: true,
})
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = render.JSON(w, 200, Greeting{"hello", "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
for i := 0; i < b.N; i++ {
h.ServeHTTP(res, req)
}
}
func BenchmarkHTML(b *testing.B) {
render := New(Options{
Directory: "testdata/basic",
})
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = render.HTML(w, http.StatusOK, "hello", "gophers")
})
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
h.ServeHTTP(httptest.NewRecorder(), req)
}
})
}
/* Test Helper */
func expect(t *testing.T, a interface{}, b interface{}) {
if a != b {
t.Errorf("Expected ||%#v|| (type %v) - Got ||%#v|| (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}
func expectNil(t *testing.T, a interface{}) {
if a != nil {
t.Errorf("Expected ||nil|| - Got ||%#v|| (type %v)", a, reflect.TypeOf(a))
}
}
func expectNotNil(t *testing.T, a interface{}) {
if a == nil {
t.Errorf("Expected ||not nil|| - Got ||nil|| (type %v)", reflect.TypeOf(a))
}
}
render-1.4.0/render_text_test.go 0000664 0000000 0000000 00000005234 14053770275 0016722 0 ustar 00root root 0000000 0000000 package render
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestTextBasic(t *testing.T) {
render := New(Options{
// nothing here to configure
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.Text(w, 299, "Hello Text!")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 299)
expect(t, res.Header().Get(ContentType), ContentText+"; charset=UTF-8")
expect(t, res.Body.String(), "Hello Text!")
}
func TestTextCharset(t *testing.T) {
render := New(Options{
Charset: "foobar",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.Text(w, 299, "Hello Text!")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 299)
expect(t, res.Header().Get(ContentType), ContentText+"; charset=foobar")
expect(t, res.Body.String(), "Hello Text!")
}
func TestTextSuppliedCharset(t *testing.T) {
render := New(Options{
Charset: "foobar",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(ContentType, "text/css")
err = render.Text(w, 200, "html{color:red}")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 200)
expect(t, res.Header().Get(ContentType), "text/css")
expect(t, res.Body.String(), "html{color:red}")
}
func TestTextCustomContentType(t *testing.T) {
render := New(Options{
TextContentType: "application/customtext",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.Text(w, http.StatusOK, "Hello Text!")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "application/customtext; charset=UTF-8")
expect(t, res.Body.String(), "Hello Text!")
}
func TestTextDisabledCharset(t *testing.T) {
render := New(Options{
DisableCharset: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.Text(w, http.StatusOK, "Hello Text!")
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), ContentText)
expect(t, res.Body.String(), "Hello Text!")
}
render-1.4.0/render_xml_test.go 0000664 0000000 0000000 00000007063 14053770275 0016540 0 ustar 00root root 0000000 0000000 package render
import (
"encoding/xml"
"net/http"
"net/http/httptest"
"testing"
)
type GreetingXML struct {
XMLName xml.Name `xml:"greeting"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func TestXMLBasic(t *testing.T) {
render := New(Options{
// nothing here to configure
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.XML(w, 299, GreetingXML{One: "hello", Two: "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 299)
expect(t, res.Header().Get(ContentType), ContentXML+"; charset=UTF-8")
expect(t, res.Body.String(), "")
}
func TestXMLPrefix(t *testing.T) {
prefix := "\n"
render := New(Options{
PrefixXML: []byte(prefix),
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.XML(w, 300, GreetingXML{One: "hello", Two: "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, 300)
expect(t, res.Header().Get(ContentType), ContentXML+"; charset=UTF-8")
expect(t, res.Body.String(), prefix+"")
}
func TestXMLIndented(t *testing.T) {
render := New(Options{
IndentXML: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.XML(w, http.StatusOK, GreetingXML{One: "hello", Two: "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), ContentXML+"; charset=UTF-8")
expect(t, res.Body.String(), "\n")
}
func TestXMLWithError(t *testing.T) {
render := New(Options{
// nothing here to configure
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.XML(w, 299, map[string]string{"foo": "bar"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNotNil(t, err)
expect(t, res.Code, 500)
}
func TestXMLCustomContentType(t *testing.T) {
render := New(Options{
XMLContentType: "application/customxml",
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.XML(w, http.StatusOK, GreetingXML{One: "hello", Two: "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), "application/customxml; charset=UTF-8")
expect(t, res.Body.String(), "")
}
func TestXMLDisabledCharset(t *testing.T) {
render := New(Options{
DisableCharset: true,
})
var err error
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err = render.XML(w, http.StatusOK, GreetingXML{One: "hello", Two: "world"})
})
res := httptest.NewRecorder()
req, _ := http.NewRequestWithContext(ctx, "GET", "/foo", nil)
h.ServeHTTP(res, req)
expectNil(t, err)
expect(t, res.Code, http.StatusOK)
expect(t, res.Header().Get(ContentType), ContentXML)
expect(t, res.Body.String(), "")
}
render-1.4.0/sizedbufferpool.go 0000664 0000000 0000000 00000003701 14053770275 0016537 0 ustar 00root root 0000000 0000000 package render
import (
"bytes"
)
// Pulled from the github.com/oxtoacart/bpool package (Apache licensed).
// SizedBufferPool implements a pool of bytes.Buffers in the form of a bounded
// channel. Buffers are pre-allocated to the requested size.
type SizedBufferPool struct {
c chan *bytes.Buffer
a int
}
// NewSizedBufferPool creates a new BufferPool bounded to the given size.
// size defines the number of buffers to be retained in the pool and alloc sets
// the initial capacity of new buffers to minimize calls to make().
//
// The value of alloc should seek to provide a buffer that is representative of
// most data written to the the buffer (i.e. 95th percentile) without being
// overly large (which will increase static memory consumption). You may wish to
// track the capacity of your last N buffers (i.e. using an []int) prior to
// returning them to the pool as input into calculating a suitable alloc value.
func NewSizedBufferPool(size int, alloc int) (bp *SizedBufferPool) {
return &SizedBufferPool{
c: make(chan *bytes.Buffer, size),
a: alloc,
}
}
// Get gets a Buffer from the SizedBufferPool, or creates a new one if none are
// available in the pool. Buffers have a pre-allocated capacity.
func (bp *SizedBufferPool) Get() (b *bytes.Buffer) {
select {
case b = <-bp.c:
// reuse existing buffer
default:
// create new buffer
b = bytes.NewBuffer(make([]byte, 0, bp.a))
}
return
}
// Put returns the given Buffer to the SizedBufferPool.
func (bp *SizedBufferPool) Put(b *bytes.Buffer) {
b.Reset()
// Release buffers over our maximum capacity and re-create a pre-sized
// buffer to replace it.
// Note that the cap(b.Bytes()) provides the capacity from the read off-set
// only, but as we've called b.Reset() the full capacity of the underlying
// byte slice is returned.
if cap(b.Bytes()) > bp.a {
b = bytes.NewBuffer(make([]byte, 0, bp.a))
}
select {
case bp.c <- b:
default: // Discard the buffer if the pool is full.
}
}
render-1.4.0/testdata/ 0000775 0000000 0000000 00000000000 14053770275 0014616 5 ustar 00root root 0000000 0000000 render-1.4.0/testdata/basic/ 0000775 0000000 0000000 00000000000 14053770275 0015677 5 ustar 00root root 0000000 0000000 render-1.4.0/testdata/basic/admin/ 0000775 0000000 0000000 00000000000 14053770275 0016767 5 ustar 00root root 0000000 0000000 render-1.4.0/testdata/basic/admin/index.tmpl 0000664 0000000 0000000 00000000025 14053770275 0020771 0 ustar 00root root 0000000 0000000 Admin {{.}}
render-1.4.0/testdata/basic/another_layout.tmpl 0000664 0000000 0000000 00000000046 14053770275 0021632 0 ustar 00root root 0000000 0000000 another head
{{ yield }}
another foot
render-1.4.0/testdata/basic/content.tmpl 0000664 0000000 0000000 00000000021 14053770275 0020240 0 ustar 00root root 0000000 0000000 {{ . }}
render-1.4.0/testdata/basic/current_layout.tmpl 0000664 0000000 0000000 00000000062 14053770275 0021652 0 ustar 00root root 0000000 0000000 {{ current }} head
{{ yield }}
{{ current }} foot
render-1.4.0/testdata/basic/delims.tmpl 0000664 0000000 0000000 00000000026 14053770275 0020050 0 ustar 00root root 0000000 0000000 Hello {[{.}]}