opentofu/vendor/github.com/Unknwon/macaron/render.go
Martin Atkins 158a90b25b Grafana Provider, with Data Source and Dashboard resources (#6206)
* Grafana provider

* grafana_data_source resource.

Allows data sources to be created in Grafana. Supports all data source
types that are accepted in the current version of Grafana, and will
support any future ones that fit into the existing structure.

* Vendoring of apparentlymart/go-grafana-api

This is in anticipation of adding a Grafana provider plugin.

* grafana_dashboard resource

* Website documentation for the Grafana provider.
2016-05-20 10:20:17 +01:00

599 lines
15 KiB
Go

// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package macaron
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
"github.com/Unknwon/com"
)
const (
ContentType = "Content-Type"
ContentLength = "Content-Length"
ContentBinary = "application/octet-stream"
ContentJSON = "application/json"
ContentHTML = "text/html"
CONTENT_PLAIN = "text/plain"
ContentXHTML = "application/xhtml+xml"
ContentXML = "text/xml"
defaultCharset = "UTF-8"
)
var (
// Provides a temporary buffer to execute templates into and catch errors.
bufpool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
// Included helper functions for use when rendering html
helperFuncs = template.FuncMap{
"yield": func() (string, error) {
return "", fmt.Errorf("yield called with no layout defined")
},
"current": func() (string, error) {
return "", nil
},
}
)
type (
// TemplateFile represents a interface of template file that has name and can be read.
TemplateFile interface {
Name() string
Data() []byte
Ext() string
}
// TemplateFileSystem represents a interface of template file system that able to list all files.
TemplateFileSystem interface {
ListFiles() []TemplateFile
}
// Delims represents a set of Left and Right delimiters for HTML template rendering
Delims struct {
// Left delimiter, defaults to {{
Left string
// Right delimiter, defaults to }}
Right string
}
// RenderOptions represents a struct for specifying configuration options for the Render middleware.
RenderOptions struct {
// Directory to load templates. Default is "templates".
Directory string
// Layout template name. Will not render a layout if "". Default is to "".
Layout string
// Extensions to parse template files from. Defaults are [".tmpl", ".html"].
Extensions []string
// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
Funcs []template.FuncMap
// Delims sets the action delimiters to the specified strings in the Delims struct.
Delims Delims
// Appends the given charset to the Content-Type header. Default is "UTF-8".
Charset string
// Outputs human readable JSON.
IndentJSON bool
// Outputs human readable XML.
IndentXML bool
// Prefixes the JSON output with the given bytes.
PrefixJSON []byte
// Prefixes the XML output with the given bytes.
PrefixXML []byte
// Allows changing of output to XHTML instead of HTML. Default is "text/html"
HTMLContentType string
// TemplateFileSystem is the interface for supporting any implmentation of template file system.
TemplateFileSystem
}
// HTMLOptions is a struct for overriding some rendering Options for specific HTML call
HTMLOptions struct {
// Layout template name. Overrides Options.Layout.
Layout string
}
Render interface {
http.ResponseWriter
SetResponseWriter(http.ResponseWriter)
RW() http.ResponseWriter
JSON(int, interface{})
JSONString(interface{}) (string, error)
RawData(int, []byte)
RenderData(int, []byte)
HTML(int, string, interface{}, ...HTMLOptions)
HTMLSet(int, string, string, interface{}, ...HTMLOptions)
HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
HTMLString(string, interface{}, ...HTMLOptions) (string, error)
HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error)
HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error)
XML(int, interface{})
Error(int, ...string)
Status(int)
SetTemplatePath(string, string)
HasTemplateSet(string) bool
}
)
// TplFile implements TemplateFile interface.
type TplFile struct {
name string
data []byte
ext string
}
// NewTplFile cerates new template file with given name and data.
func NewTplFile(name string, data []byte, ext string) *TplFile {
return &TplFile{name, data, ext}
}
func (f *TplFile) Name() string {
return f.name
}
func (f *TplFile) Data() []byte {
return f.data
}
func (f *TplFile) Ext() string {
return f.ext
}
// TplFileSystem implements TemplateFileSystem interface.
type TplFileSystem struct {
files []TemplateFile
}
// NewTemplateFileSystem creates new template file system with given options.
func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem {
fs := TplFileSystem{}
fs.files = make([]TemplateFile, 0, 10)
if err := filepath.Walk(opt.Directory, func(path string, info os.FileInfo, err error) error {
r, err := filepath.Rel(opt.Directory, path)
if err != nil {
return err
}
ext := GetExt(r)
for _, extension := range opt.Extensions {
if ext == extension {
var data []byte
if !omitData {
data, err = ioutil.ReadFile(path)
if err != nil {
return err
}
}
name := filepath.ToSlash((r[0 : len(r)-len(ext)]))
fs.files = append(fs.files, NewTplFile(name, data, ext))
break
}
}
return nil
}); err != nil {
panic("NewTemplateFileSystem: " + err.Error())
}
return fs
}
func (fs TplFileSystem) ListFiles() []TemplateFile {
return fs.files
}
func PrepareCharset(charset string) string {
if len(charset) != 0 {
return "; charset=" + charset
}
return "; charset=" + defaultCharset
}
func GetExt(s string) string {
index := strings.Index(s, ".")
if index == -1 {
return ""
}
return s[index:]
}
func compile(opt RenderOptions) *template.Template {
dir := opt.Directory
t := template.New(dir)
t.Delims(opt.Delims.Left, opt.Delims.Right)
// Parse an initial template in case we don't have any.
template.Must(t.Parse("Macaron"))
if opt.TemplateFileSystem == nil {
opt.TemplateFileSystem = NewTemplateFileSystem(opt, false)
}
for _, f := range opt.TemplateFileSystem.ListFiles() {
tmpl := t.New(f.Name())
for _, funcs := range opt.Funcs {
tmpl.Funcs(funcs)
}
// Bomb out if parse fails. We don't want any silent server starts.
template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data())))
}
return t
}
const (
_DEFAULT_TPL_SET_NAME = "DEFAULT"
)
// templateSet represents a template set of type *template.Template.
type templateSet struct {
lock sync.RWMutex
sets map[string]*template.Template
dirs map[string]string
}
func newTemplateSet() *templateSet {
return &templateSet{
sets: make(map[string]*template.Template),
dirs: make(map[string]string),
}
}
func (ts *templateSet) Set(name string, opt *RenderOptions) *template.Template {
t := compile(*opt)
ts.lock.Lock()
defer ts.lock.Unlock()
ts.sets[name] = t
ts.dirs[name] = opt.Directory
return t
}
func (ts *templateSet) Get(name string) *template.Template {
ts.lock.RLock()
defer ts.lock.RUnlock()
return ts.sets[name]
}
func (ts *templateSet) GetDir(name string) string {
ts.lock.RLock()
defer ts.lock.RUnlock()
return ts.dirs[name]
}
func prepareRenderOptions(options []RenderOptions) RenderOptions {
var opt RenderOptions
if len(options) > 0 {
opt = options[0]
}
// Defaults.
if len(opt.Directory) == 0 {
opt.Directory = "templates"
}
if len(opt.Extensions) == 0 {
opt.Extensions = []string{".tmpl", ".html"}
}
if len(opt.HTMLContentType) == 0 {
opt.HTMLContentType = ContentHTML
}
return opt
}
func ParseTplSet(tplSet string) (tplName string, tplDir string) {
tplSet = strings.TrimSpace(tplSet)
if len(tplSet) == 0 {
panic("empty template set argument")
}
infos := strings.Split(tplSet, ":")
if len(infos) == 1 {
tplDir = infos[0]
tplName = path.Base(tplDir)
} else {
tplName = infos[0]
tplDir = infos[1]
}
if !com.IsDir(tplDir) {
panic("template set path does not exist or is not a directory")
}
return tplName, tplDir
}
func renderHandler(opt RenderOptions, tplSets []string) Handler {
cs := PrepareCharset(opt.Charset)
ts := newTemplateSet()
ts.Set(_DEFAULT_TPL_SET_NAME, &opt)
var tmpOpt RenderOptions
for _, tplSet := range tplSets {
tplName, tplDir := ParseTplSet(tplSet)
tmpOpt = opt
tmpOpt.Directory = tplDir
ts.Set(tplName, &tmpOpt)
}
return func(ctx *Context) {
r := &TplRender{
ResponseWriter: ctx.Resp,
templateSet: ts,
Opt: &opt,
CompiledCharset: cs,
}
ctx.Data["TmplLoadTimes"] = func() string {
if r.startTime.IsZero() {
return ""
}
return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
}
ctx.Render = r
ctx.MapTo(r, (*Render)(nil))
}
}
// Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
// An single variadic macaron.RenderOptions struct can be optionally provided to configure
// HTML rendering. The default directory for templates is "templates" and the default
// file extension is ".tmpl" and ".html".
//
// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
// MACARON_ENV environment variable to "production".
func Renderer(options ...RenderOptions) Handler {
return renderHandler(prepareRenderOptions(options), []string{})
}
func Renderers(options RenderOptions, tplSets ...string) Handler {
return renderHandler(prepareRenderOptions([]RenderOptions{options}), tplSets)
}
type TplRender struct {
http.ResponseWriter
*templateSet
Opt *RenderOptions
CompiledCharset string
startTime time.Time
}
func (r *TplRender) SetResponseWriter(rw http.ResponseWriter) {
r.ResponseWriter = rw
}
func (r *TplRender) RW() http.ResponseWriter {
return r.ResponseWriter
}
func (r *TplRender) JSON(status int, v interface{}) {
var (
result []byte
err error
)
if r.Opt.IndentJSON {
result, err = json.MarshalIndent(v, "", " ")
} else {
result, err = json.Marshal(v)
}
if err != nil {
http.Error(r, err.Error(), 500)
return
}
// json rendered fine, write out the result
r.Header().Set(ContentType, ContentJSON+r.CompiledCharset)
r.WriteHeader(status)
if len(r.Opt.PrefixJSON) > 0 {
r.Write(r.Opt.PrefixJSON)
}
r.Write(result)
}
func (r *TplRender) JSONString(v interface{}) (string, error) {
var result []byte
var err error
if r.Opt.IndentJSON {
result, err = json.MarshalIndent(v, "", " ")
} else {
result, err = json.Marshal(v)
}
if err != nil {
return "", err
}
return string(result), nil
}
func (r *TplRender) XML(status int, v interface{}) {
var result []byte
var err error
if r.Opt.IndentXML {
result, err = xml.MarshalIndent(v, "", " ")
} else {
result, err = xml.Marshal(v)
}
if err != nil {
http.Error(r, err.Error(), 500)
return
}
// XML rendered fine, write out the result
r.Header().Set(ContentType, ContentXML+r.CompiledCharset)
r.WriteHeader(status)
if len(r.Opt.PrefixXML) > 0 {
r.Write(r.Opt.PrefixXML)
}
r.Write(result)
}
func (r *TplRender) data(status int, contentType string, v []byte) {
if r.Header().Get(ContentType) == "" {
r.Header().Set(ContentType, contentType)
}
r.WriteHeader(status)
r.Write(v)
}
func (r *TplRender) RawData(status int, v []byte) {
r.data(status, ContentBinary, v)
}
func (r *TplRender) RenderData(status int, v []byte) {
r.data(status, CONTENT_PLAIN, v)
}
func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) {
buf := bufpool.Get().(*bytes.Buffer)
return buf, t.ExecuteTemplate(buf, name, data)
}
func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) {
funcs := template.FuncMap{
"yield": func() (template.HTML, error) {
buf, err := r.execute(t, tplName, data)
// return safe html here since we are rendering our own template
return template.HTML(buf.String()), err
},
"current": func() (string, error) {
return tplName, nil
},
}
t.Funcs(funcs)
}
func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
t := r.templateSet.Get(setName)
if Env == DEV {
opt := *r.Opt
opt.Directory = r.templateSet.GetDir(setName)
t = r.templateSet.Set(setName, &opt)
}
if t == nil {
return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName)
}
opt := r.prepareHTMLOptions(htmlOpt)
if len(opt.Layout) > 0 {
r.addYield(t, tplName, data)
tplName = opt.Layout
}
out, err := r.execute(t, tplName, data)
if err != nil {
return nil, err
}
return out, nil
}
func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
r.startTime = time.Now()
out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
if err != nil {
http.Error(r, err.Error(), http.StatusInternalServerError)
return
}
r.Header().Set(ContentType, r.Opt.HTMLContentType+r.CompiledCharset)
r.WriteHeader(status)
out.WriteTo(r)
bufpool.Put(out)
}
func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) {
r.renderHTML(status, _DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
}
func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
r.renderHTML(status, setName, tplName, data, htmlOpt...)
}
func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
if err != nil {
return []byte(""), err
}
return out.Bytes(), nil
}
func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
return r.HTMLSetBytes(_DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
}
func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...)
return string(p), err
}
func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
p, err := r.HTMLBytes(name, data, htmlOpt...)
return string(p), err
}
// Error writes the given HTTP status to the current ResponseWriter
func (r *TplRender) Error(status int, message ...string) {
r.WriteHeader(status)
if len(message) > 0 {
r.Write([]byte(message[0]))
}
}
func (r *TplRender) Status(status int) {
r.WriteHeader(status)
}
func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
if len(htmlOpt) > 0 {
return htmlOpt[0]
}
return HTMLOptions{
Layout: r.Opt.Layout,
}
}
func (r *TplRender) SetTemplatePath(setName, dir string) {
if len(setName) == 0 {
setName = _DEFAULT_TPL_SET_NAME
}
opt := *r.Opt
opt.Directory = dir
r.templateSet.Set(setName, &opt)
}
func (r *TplRender) HasTemplateSet(name string) bool {
return r.templateSet.Get(name) != nil
}