diff --git a/Gopkg.lock b/Gopkg.lock index ee3f8b6..5d2dc25 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -20,10 +20,10 @@ revision = "3a771d992973f24aa725d07868b467d1ddfceafb" [[projects]] + branch = "master" name = "github.com/flosch/pongo2" packages = ["."] - revision = "5e81b817a0c48c1c57cdf1a9056cf76bdee02ca9" - version = "v3.0" + revision = "e7cf9ea5ca9c574f3fd5f83f7eed4a6162a67dea" [[projects]] name = "github.com/golang/protobuf" @@ -32,16 +32,10 @@ version = "v1.1.0" [[projects]] - name = "github.com/gorilla/context" + branch = "master" + name = "github.com/juju/errors" packages = ["."] - revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42" - version = "v1.1.1" - -[[projects]] - name = "github.com/gorilla/mux" - packages = ["."] - revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf" - version = "v1.6.2" + revision = "c7d06af17c68cd34c835053720b21f6549d9b0ee" [[projects]] name = "github.com/matttproud/golang_protobuf_extensions" @@ -130,6 +124,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "e443e1ce42c6da5794decc9e50a89f0cda1f1e191c2c5582f2443c0342cb6e37" + inputs-digest = "7219340891e29105af1c44377ba63a2c742e75ae42d2453c879d334541234b0d" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 9952641..21bbdf8 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -35,11 +35,7 @@ [[constraint]] name = "github.com/flosch/pongo2" - version = "3.0.0" - -[[constraint]] - name = "github.com/gorilla/mux" - version = "1.6.2" + branch = "master" # Use v4 which is not yet properly tagged [[constraint]] name = "github.com/prometheus/client_golang" diff --git a/main.go b/main.go index 45d7277..76d5275 100644 --- a/main.go +++ b/main.go @@ -7,14 +7,12 @@ import ( "fmt" "io/ioutil" "net/http" - "net/url" "os" "path/filepath" "strings" "time" "github.com/Luzifer/rconfig" - "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus" "github.com/robfig/cron" log "github.com/sirupsen/logrus" @@ -22,6 +20,7 @@ import ( var ( cfg struct { + Listen string `flag:"listen" default:":3000" description:"Port/IP to listen on"` ExpireWarning time.Duration `flag:"expire-warning" default:"744h" description:"When to warn about a soon expiring certificate"` RootsDir string `flag:"roots-dir" default:"" description:"Directory to load custom RootCA certs from to be trusted (*.pem)"` LogLevel string `flag:"log-level" default:"info" description:"Verbosity of logs to use (debug, info, warning, error, ...)"` @@ -31,40 +30,12 @@ var ( version = "dev" - probeMonitors = map[string]*probeMonitor{} + probeMonitors = map[string]*probe{} rootPool *x509.CertPool redirectFoundError = errors.New("Found a redirect") ) -type probeMonitor struct { - IsValid prometheus.Gauge `json:"-"` - Expires prometheus.Gauge `json:"-"` - Status probeResult - Certificate *x509.Certificate -} - -func (p *probeMonitor) Update(status probeResult, cert *x509.Certificate) error { - p.Status = status - p.Certificate = cert - - p.updatePrometheus(status, cert) - - return nil -} - -func (p probeMonitor) updatePrometheus(status probeResult, cert *x509.Certificate) { - if cert != nil { - p.Expires.Set(float64(cert.NotAfter.UTC().Unix())) - } - - if status == certificateExpiresSoon || status == certificateOK { - p.IsValid.Set(1) - } else { - p.IsValid.Set(0) - } -} - func init() { if err := rconfig.Parse(&cfg); err != nil { log.Fatalf("Unable to parse CLI parameters: %s", err) @@ -83,6 +54,7 @@ func init() { } func main() { + // Configuration to receive redirects and TLS errors http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { return redirectFoundError } @@ -90,33 +62,35 @@ func main() { TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } + // Load valid CAs from system and specified folder var err error if rootPool, err = x509.SystemCertPool(); err != nil { log.WithError(err).Fatal("Unable to load system RootCA pool") } - if err = loadAdditionalRootCAPool(); err != nil { + if err = loadAdditionalRootCAPool(rootPool); err != nil { log.WithError(err).Fatal("Could not load intermediate certificates") } registerProbes() refreshCertificateStatus() - fmt.Printf("PromCertcheck %s...\nStarting to listen on 0.0.0.0:3000\n", version) + log.WithFields(log.Fields{ + "version": version, + }).Info("PromCertcheck started to listen on 0.0.0.0:3000") c := cron.New() c.AddFunc("0 0 * * * *", refreshCertificateStatus) c.Start() - r := mux.NewRouter() - r.Handle("/metrics", prometheus.Handler()) - r.HandleFunc("/", htmlHandler) - r.HandleFunc("/httpStatus", httpStatusHandler) - r.HandleFunc("/results.json", jsonHandler) - http.ListenAndServe(":3000", r) + http.Handle("/metrics", prometheus.Handler()) + http.HandleFunc("/", htmlHandler) + http.HandleFunc("/httpStatus", httpStatusHandler) + http.HandleFunc("/results.json", jsonHandler) + http.ListenAndServe(cfg.Listen, nil) } -func loadAdditionalRootCAPool() error { +func loadAdditionalRootCAPool(pool *x509.CertPool) error { if cfg.RootsDir == "" { // Nothing specified, not loading anything but sys certs return nil @@ -137,7 +111,7 @@ func loadAdditionalRootCAPool() error { return err } - if ok := rootPool.AppendCertsFromPEM(pem); !ok { + if ok := pool.AppendCertsFromPEM(pem); !ok { return fmt.Errorf("Failed to load certificate %q", path) } @@ -148,56 +122,35 @@ func loadAdditionalRootCAPool() error { } func registerProbes() { - for _, probe := range cfg.Probes { - probeURL, _ := url.Parse(probe) + for _, probeURL := range cfg.Probes { + p, err := probeFromURL(probeURL) + if err != nil { + log.WithError(err).Error("Unable to create probe") + continue + } - monitors := &probeMonitor{} - monitors.Expires = prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "certcheck_expires", - Help: "Expiration date in unix timestamp (UTC)", - ConstLabels: prometheus.Labels{ - "host": probeURL.Host, - }, - }) - monitors.IsValid = prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "certcheck_valid", - Help: "Validity of the certificate (0/1)", - ConstLabels: prometheus.Labels{ - "host": probeURL.Host, - }, - }) - - prometheus.MustRegister(monitors.Expires) - prometheus.MustRegister(monitors.IsValid) - - probeMonitors[probeURL.Host] = monitors + probeMonitors[p.url.Host] = p + log.WithFields(log.Fields{ + "host": p.url.Host, + }).Info("Probe registered") } } func refreshCertificateStatus() { - for _, probe := range cfg.Probes { - probeURL, _ := url.Parse(probe) - verificationResult, verifyCert := checkCertificate(probeURL) + for _, p := range probeMonitors { - probeLog := log.WithFields(log.Fields{ - "host": probeURL.Host, - "result": verificationResult, - }) - if verifyCert != nil { - probeLog = probeLog.WithFields(log.Fields{ - "version": verifyCert.Version, - "serial": verifyCert.SerialNumber, - "subject": verifyCert.Subject.CommonName, - "expires": verifyCert.NotAfter, - "issuer": verifyCert.Issuer.CommonName, - "alt_names": strings.Join(verifyCert.DNSNames, ", "), + go func(p *probe) { + logger := log.WithFields(log.Fields{ + "host": p.url.Host, }) - } - probeLog.Debug("Probe finished") - if err := probeMonitors[probeURL.Host].Update(verificationResult, verifyCert); err != nil { - probeLog.WithError(err).Error("Unable to update probe state") - return - } + if err := p.refresh(); err != nil { + logger.WithError(err).Error("Unable to refresh probe status") + return + } + + logger.Debug("Probe refreshed") + }(p) + } } diff --git a/probe.go b/probe.go new file mode 100644 index 0000000..87706de --- /dev/null +++ b/probe.go @@ -0,0 +1,97 @@ +package main + +import ( + "crypto/x509" + "fmt" + "net/url" + "strings" + + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" +) + +type probe struct { + Status probeResult + Certificate *x509.Certificate + + isValid prometheus.Gauge `json:"-"` + expires prometheus.Gauge `json:"-"` + url *url.URL `json:"-"` +} + +func probeFromURL(u string) (*probe, error) { + probeURL, err := url.Parse(u) + if err != nil { + return nil, err + } + + p := &probe{ + url: probeURL, + expires: prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "certcheck_expires", + Help: "Expiration date in unix timestamp (UTC)", + ConstLabels: prometheus.Labels{ + "host": probeURL.Host, + }, + }), + isValid: prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "certcheck_valid", + Help: "Validity of the certificate (0/1)", + ConstLabels: prometheus.Labels{ + "host": probeURL.Host, + }, + }), + } + + prometheus.MustRegister(p.expires) + prometheus.MustRegister(p.isValid) + + return p, nil +} + +func (p *probe) refresh() error { + verificationResult, verifyCert := checkCertificate(p.url) + + probeLog := log.WithFields(log.Fields{ + "host": p.url.Host, + "result": verificationResult, + }) + if verifyCert != nil { + probeLog = probeLog.WithFields(log.Fields{ + "version": verifyCert.Version, + "serial": verifyCert.SerialNumber, + "subject": verifyCert.Subject.CommonName, + "expires": verifyCert.NotAfter, + "issuer": verifyCert.Issuer.CommonName, + "alt_names": strings.Join(verifyCert.DNSNames, ", "), + }) + } + probeLog.Debug("Probe finished") + + if err := p.update(verificationResult, verifyCert); err != nil { + return fmt.Errorf("Unable to update probe state: %s", err) + } + + return nil +} + +func (p *probe) update(status probeResult, cert *x509.Certificate) error { + p.Status = status + p.Certificate = cert + + p.updatePrometheus(status, cert) + + return nil +} + +func (p probe) updatePrometheus(status probeResult, cert *x509.Certificate) { + if cert != nil { + p.expires.Set(float64(cert.NotAfter.UTC().Unix())) + } + + if status == certificateExpiresSoon || status == certificateOK { + p.isValid.Set(1) + } else { + p.isValid.Set(0) + } +} diff --git a/vendor/github.com/flosch/pongo2/.gitignore b/vendor/github.com/flosch/pongo2/.gitignore index 37eaf44..1346be5 100644 --- a/vendor/github.com/flosch/pongo2/.gitignore +++ b/vendor/github.com/flosch/pongo2/.gitignore @@ -7,6 +7,7 @@ _obj _test .idea +.vscode # Architecture specific extensions/prefixes *.[568vq] diff --git a/vendor/github.com/flosch/pongo2/.travis.yml b/vendor/github.com/flosch/pongo2/.travis.yml index a22ad21..0445a45 100644 --- a/vendor/github.com/flosch/pongo2/.travis.yml +++ b/vendor/github.com/flosch/pongo2/.travis.yml @@ -1,12 +1,13 @@ language: go go: - - 1.3 + - 1.7 - tip install: - - go get code.google.com/p/go.tools/cmd/cover + - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls - go get gopkg.in/check.v1 + - go get github.com/juju/errors script: - go test -v -covermode=count -coverprofile=coverage.out -bench . -cpu 1,4 - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN || true' diff --git a/vendor/github.com/flosch/pongo2/README.md b/vendor/github.com/flosch/pongo2/README.md index 7c61e9e..33def30 100644 --- a/vendor/github.com/flosch/pongo2/README.md +++ b/vendor/github.com/flosch/pongo2/README.md @@ -1,8 +1,9 @@ # [pongo](https://en.wikipedia.org/wiki/Pongo_%28genus%29)2 -[![GoDoc](https://godoc.org/github.com/flosch/pongo2?status.png)](https://godoc.org/github.com/flosch/pongo2) +[![Join the chat at https://gitter.im/flosch/pongo2](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/flosch/pongo2) +[![GoDoc](https://godoc.org/github.com/flosch/pongo2?status.svg)](https://godoc.org/github.com/flosch/pongo2) [![Build Status](https://travis-ci.org/flosch/pongo2.svg?branch=master)](https://travis-ci.org/flosch/pongo2) -[![Coverage Status](https://coveralls.io/repos/flosch/pongo2/badge.png?branch=master)](https://coveralls.io/r/flosch/pongo2?branch=master) +[![Coverage Status](https://coveralls.io/repos/flosch/pongo2/badge.svg?branch=master)](https://coveralls.io/r/flosch/pongo2?branch=master) [![gratipay](http://img.shields.io/badge/gratipay-support%20pongo-brightgreen.svg)](https://gratipay.com/flosch/) [![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=3654947)](https://www.bountysource.com/trackers/3654947-pongo2?utm_source=3654947&utm_medium=shield&utm_campaign=TRACKER_BADGE) @@ -95,6 +96,7 @@ Please also have a look on the [caveats](https://github.com/flosch/pongo2#caveat If you're using the `master`-branch of pongo2, you might be interested in this section. Since pongo2 is still in development (even though there is a first stable release!), there could be (backwards-incompatible) API changes over time. To keep track of these and therefore make it painless for you to adapt your codebase, I'll list them here. + * Function signature for tag execution changed: not taking a `bytes.Buffer` anymore; instead `Execute()`-functions are now taking a `TemplateWriter` interface. * Function signature for tag and filter parsing/execution changed (`error` return type changed to `*Error`). * `INodeEvaluator` has been removed and got replaced by `IEvaluator`. You can change your existing tags/filters by simply replacing the interface. * Two new helper functions: [`RenderTemplateFile()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateFile) and [`RenderTemplateString()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateString). @@ -104,7 +106,7 @@ If you're using the `master`-branch of pongo2, you might be interested in this s ## How you can help * Write [filters](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3) / [tags](https://github.com/flosch/pongo2/blob/master/tags.go#L4) (see [tutorial](https://www.florian-schlachter.de/post/pongo2/)) by forking pongo2 and sending pull requests - * Write/improve code tests (use the following command to see what tests are missing: `go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out`) + * Write/improve code tests (use the following command to see what tests are missing: `go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out` or have a look on [gocover.io/github.com/flosch/pongo2](http://gocover.io/github.com/flosch/pongo2)) * Write/improve template tests (see the `template_tests/` directory) * Write middleware, libraries and websites using pongo2. :-) @@ -115,7 +117,8 @@ For a documentation on how the templating language works you can [head over to t You can access pongo2's API documentation on [godoc](https://godoc.org/github.com/flosch/pongo2). ## Blog post series - + + * [pongo2 v3 released](https://www.florian-schlachter.de/post/pongo2-v3/) * [pongo2 v2 released](https://www.florian-schlachter.de/post/pongo2-v2/) * [pongo2 1.0 released](https://www.florian-schlachter.de/post/pongo2-10/) [August 8th 2014] * [pongo2 playground](https://www.florian-schlachter.de/post/pongo2-playground/) [August 1st 2014] @@ -154,8 +157,12 @@ You can access pongo2's API documentation on [godoc](https://godoc.org/github.co * [beego-pongo2.v2](https://github.com/ipfans/beego-pongo2.v2) - Same as `beego-pongo2`, but for pongo2 v2. * [macaron-pongo2](https://github.com/macaron-contrib/pongo2) - pongo2 support for [Macaron](https://github.com/Unknwon/macaron), a modular web framework. * [ginpongo2](https://github.com/ngerakines/ginpongo2) - middleware for [gin](github.com/gin-gonic/gin) to use pongo2 templates - * [pongo2-trans](https://github.com/fromYukki/pongo2trans) - `trans`-tag implementation for internationalization - + * [Build'n support for Iris' template engine](https://github.com/kataras/iris) + * [pongo2gin](https://github.com/robvdl/pongo2gin) - alternative renderer for [gin](github.com/gin-gonic/gin) to use pongo2 templates + * [pongo2-trans](https://github.com/digitalcrab/pongo2trans) - `trans`-tag implementation for internationalization + * [tpongo2](https://github.com/tango-contrib/tpongo2) - pongo2 support for [Tango](https://github.com/lunny/tango), a micro-kernel & pluggable web framework. + * [p2cli](https://github.com/wrouesnel/p2cli) - command line templating utility based on pongo2 + Please add your project to this list and send me a pull request when you've developed something nice for pongo2. # API-usage examples diff --git a/vendor/github.com/flosch/pongo2/context.go b/vendor/github.com/flosch/pongo2/context.go index df587c8..2934d70 100644 --- a/vendor/github.com/flosch/pongo2/context.go +++ b/vendor/github.com/flosch/pongo2/context.go @@ -1,13 +1,20 @@ package pongo2 import ( - "fmt" "regexp" + + "github.com/juju/errors" ) var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$") -// Use this Context type to provide constants, variables, instances or functions to your template. +var autoescape = true + +func SetAutoescape(newValue bool) { + autoescape = newValue +} + +// A Context type provides constants, variables, instances or functions to a template. // // pongo2 automatically provides meta-information or functions through the "pongo2"-key. // Currently, context["pongo2"] contains the following keys: @@ -24,14 +31,15 @@ func (c Context) checkForValidIdentifiers() *Error { for k, v := range c { if !reIdentifiers.MatchString(k) { return &Error{ - Sender: "checkForValidIdentifiers", - ErrorMsg: fmt.Sprintf("Context-key '%s' (value: '%+v') is not a valid identifier.", k, v), + Sender: "checkForValidIdentifiers", + OrigError: errors.Errorf("context-key '%s' (value: '%+v') is not a valid identifier", k, v), } } } return nil } +// Update updates this context with the key/value-pairs from another context. func (c Context) Update(other Context) Context { for k, v := range other { c[k] = v @@ -39,6 +47,8 @@ func (c Context) Update(other Context) Context { return c } +// ExecutionContext contains all data important for the current rendering state. +// // If you're writing a custom tag, your tag's Execute()-function will // have access to the ExecutionContext. This struct stores anything // about the current rendering process's Context including @@ -76,7 +86,7 @@ func newExecutionContext(tpl *Template, ctx Context) *ExecutionContext { Public: ctx, Private: privateCtx, - Autoescape: true, + Autoescape: autoescape, } } @@ -97,6 +107,10 @@ func NewChildExecutionContext(parent *ExecutionContext) *ExecutionContext { } func (ctx *ExecutionContext) Error(msg string, token *Token) *Error { + return ctx.OrigError(errors.New(msg), token) +} + +func (ctx *ExecutionContext) OrigError(err error, token *Token) *Error { filename := ctx.template.name var line, col int if token != nil { @@ -107,13 +121,13 @@ func (ctx *ExecutionContext) Error(msg string, token *Token) *Error { col = token.Col } return &Error{ - Template: ctx.template, - Filename: filename, - Line: line, - Column: col, - Token: token, - Sender: "execution", - ErrorMsg: msg, + Template: ctx.template, + Filename: filename, + Line: line, + Column: col, + Token: token, + Sender: "execution", + OrigError: err, } } diff --git a/vendor/github.com/flosch/pongo2/error.go b/vendor/github.com/flosch/pongo2/error.go index c1ee86e..8aec8c1 100644 --- a/vendor/github.com/flosch/pongo2/error.go +++ b/vendor/github.com/flosch/pongo2/error.go @@ -6,20 +6,20 @@ import ( "os" ) -// This Error type is being used to address an error during lexing, parsing or +// The Error type is being used to address an error during lexing, parsing or // execution. If you want to return an error object (for example in your own // tag or filter) fill this object with as much information as you have. // Make sure "Sender" is always given (if you're returning an error within // a filter, make Sender equals 'filter:yourfilter'; same goes for tags: 'tag:mytag'). // It's okay if you only fill in ErrorMsg if you don't have any other details at hand. type Error struct { - Template *Template - Filename string - Line int - Column int - Token *Token - Sender string - ErrorMsg string + Template *Template + Filename string + Line int + Column int + Token *Token + Sender string + OrigError error } func (e *Error) updateFromTokenIfNeeded(template *Template, t *Token) *Error { @@ -54,14 +54,14 @@ func (e *Error) Error() string { } } s += "] " - s += e.ErrorMsg + s += e.OrigError.Error() return s } -// Returns the affected line from the original template, if available. -func (e *Error) RawLine() (line string, available bool) { +// RawLine returns the affected line from the original template, if available. +func (e *Error) RawLine() (line string, available bool, outErr error) { if e.Line <= 0 || e.Filename == "" { - return "", false + return "", false, nil } filename := e.Filename @@ -70,17 +70,22 @@ func (e *Error) RawLine() (line string, available bool) { } file, err := os.Open(filename) if err != nil { - panic(err) + return "", false, err } - defer file.Close() + defer func() { + err := file.Close() + if err != nil && outErr == nil { + outErr = err + } + }() scanner := bufio.NewScanner(file) l := 0 for scanner.Scan() { l++ if l == e.Line { - return scanner.Text(), true + return scanner.Text(), true, nil } } - return "", false + return "", false, nil } diff --git a/vendor/github.com/flosch/pongo2/filters.go b/vendor/github.com/flosch/pongo2/filters.go index 229f7fe..1092705 100644 --- a/vendor/github.com/flosch/pongo2/filters.go +++ b/vendor/github.com/flosch/pongo2/filters.go @@ -2,8 +2,11 @@ package pongo2 import ( "fmt" + + "github.com/juju/errors" ) +// FilterFunction is the type filter functions must fulfil type FilterFunction func(in *Value, param *Value) (out *Value, err *Error) var filters map[string]FilterFunction @@ -12,32 +15,38 @@ func init() { filters = make(map[string]FilterFunction) } -// Registers a new filter. If there's already a filter with the same +// FilterExists returns true if the given filter is already registered +func FilterExists(name string) bool { + _, existing := filters[name] + return existing +} + +// RegisterFilter registers a new filter. If there's already a filter with the same // name, RegisterFilter will panic. You usually want to call this // function in the filter's init() function: // http://golang.org/doc/effective_go.html#init // // See http://www.florian-schlachter.de/post/pongo2/ for more about // writing filters and tags. -func RegisterFilter(name string, fn FilterFunction) { - _, existing := filters[name] - if existing { - panic(fmt.Sprintf("Filter with name '%s' is already registered.", name)) +func RegisterFilter(name string, fn FilterFunction) error { + if FilterExists(name) { + return errors.Errorf("filter with name '%s' is already registered", name) } filters[name] = fn + return nil } -// Replaces an already registered filter with a new implementation. Use this +// ReplaceFilter replaces an already registered filter with a new implementation. Use this // function with caution since it allows you to change existing filter behaviour. -func ReplaceFilter(name string, fn FilterFunction) { - _, existing := filters[name] - if !existing { - panic(fmt.Sprintf("Filter with name '%s' does not exist (therefore cannot be overridden).", name)) +func ReplaceFilter(name string, fn FilterFunction) error { + if !FilterExists(name) { + return errors.Errorf("filter with name '%s' does not exist (therefore cannot be overridden)", name) } filters[name] = fn + return nil } -// Like ApplyFilter, but panics on an error +// MustApplyFilter behaves like ApplyFilter, but panics on an error. func MustApplyFilter(name string, value *Value, param *Value) *Value { val, err := ApplyFilter(name, value, param) if err != nil { @@ -46,13 +55,14 @@ func MustApplyFilter(name string, value *Value, param *Value) *Value { return val } -// Applies a filter to a given value using the given parameters. Returns a *pongo2.Value or an error. +// ApplyFilter applies a filter to a given value using the given parameters. +// Returns a *pongo2.Value or an error. func ApplyFilter(name string, value *Value, param *Value) (*Value, *Error) { fn, existing := filters[name] if !existing { return nil, &Error{ - Sender: "applyfilter", - ErrorMsg: fmt.Sprintf("Filter with name '%s' not found.", name), + Sender: "applyfilter", + OrigError: errors.Errorf("Filter with name '%s' not found.", name), } } @@ -86,31 +96,31 @@ func (fc *filterCall) Execute(v *Value, ctx *ExecutionContext) (*Value, *Error) param = AsValue(nil) } - filtered_value, err := fc.filterFunc(v, param) + filteredValue, err := fc.filterFunc(v, param) if err != nil { return nil, err.updateFromTokenIfNeeded(ctx.template, fc.token) } - return filtered_value, nil + return filteredValue, nil } // Filter = IDENT | IDENT ":" FilterArg | IDENT "|" Filter func (p *Parser) parseFilter() (*filterCall, *Error) { - ident_token := p.MatchType(TokenIdentifier) + identToken := p.MatchType(TokenIdentifier) // Check filter ident - if ident_token == nil { + if identToken == nil { return nil, p.Error("Filter name must be an identifier.", nil) } filter := &filterCall{ - token: ident_token, - name: ident_token.Val, + token: identToken, + name: identToken.Val, } // Get the appropriate filter function and bind it - filterFn, exists := filters[ident_token.Val] + filterFn, exists := filters[identToken.Val] if !exists { - return nil, p.Error(fmt.Sprintf("Filter '%s' does not exist.", ident_token.Val), ident_token) + return nil, p.Error(fmt.Sprintf("Filter '%s' does not exist.", identToken.Val), identToken) } filter.filterFunc = filterFn diff --git a/vendor/github.com/flosch/pongo2/filters_builtin.go b/vendor/github.com/flosch/pongo2/filters_builtin.go index aaa68b1..f02b491 100644 --- a/vendor/github.com/flosch/pongo2/filters_builtin.go +++ b/vendor/github.com/flosch/pongo2/filters_builtin.go @@ -35,6 +35,8 @@ import ( "strings" "time" "unicode/utf8" + + "github.com/juju/errors" ) func init() { @@ -73,14 +75,15 @@ func init() { RegisterFilter("removetags", filterRemovetags) RegisterFilter("rjust", filterRjust) RegisterFilter("slice", filterSlice) + RegisterFilter("split", filterSplit) RegisterFilter("stringformat", filterStringformat) RegisterFilter("striptags", filterStriptags) RegisterFilter("time", filterDate) // time uses filterDate (same golang-format) RegisterFilter("title", filterTitle) RegisterFilter("truncatechars", filterTruncatechars) - RegisterFilter("truncatechars_html", filterTruncatecharsHtml) + RegisterFilter("truncatechars_html", filterTruncatecharsHTML) RegisterFilter("truncatewords", filterTruncatewords) - RegisterFilter("truncatewords_html", filterTruncatewordsHtml) + RegisterFilter("truncatewords_html", filterTruncatewordsHTML) RegisterFilter("upper", filterUpper) RegisterFilter("urlencode", filterUrlencode) RegisterFilter("urlize", filterUrlize) @@ -105,9 +108,9 @@ func filterTruncatecharsHelper(s string, newLen int) string { return string(runes) } -func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func() bool, fn func(c rune, s int, idx int) int, finalize func()) { +func filterTruncateHTMLHelper(value string, newOutput *bytes.Buffer, cond func() bool, fn func(c rune, s int, idx int) int, finalize func()) { vLen := len(value) - tag_stack := make([]string, 0) + var tagStack []string idx := 0 for idx < vLen && !cond() { @@ -118,17 +121,17 @@ func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func( } if c == '<' { - new_output.WriteRune(c) + newOutput.WriteRune(c) idx += s // consume "<" if idx+1 < vLen { if value[idx] == '/' { // Close tag - new_output.WriteString("/") + newOutput.WriteString("/") tag := "" - idx += 1 // consume "/" + idx++ // consume "/" for idx < vLen { c2, size2 := utf8.DecodeRuneInString(value[idx:]) @@ -146,21 +149,21 @@ func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func( idx += size2 } - if len(tag_stack) > 0 { + if len(tagStack) > 0 { // Ideally, the close tag is TOP of tag stack // In malformed HTML, it must not be, so iterate through the stack and remove the tag - for i := len(tag_stack) - 1; i >= 0; i-- { - if tag_stack[i] == tag { + for i := len(tagStack) - 1; i >= 0; i-- { + if tagStack[i] == tag { // Found the tag - tag_stack[i] = tag_stack[len(tag_stack)-1] - tag_stack = tag_stack[:len(tag_stack)-1] + tagStack[i] = tagStack[len(tagStack)-1] + tagStack = tagStack[:len(tagStack)-1] break } } } - new_output.WriteString(tag) - new_output.WriteString(">") + newOutput.WriteString(tag) + newOutput.WriteString(">") } else { // Open tag @@ -174,7 +177,7 @@ func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func( continue } - new_output.WriteRune(c2) + newOutput.WriteRune(c2) // End of tag found if c2 == '>' { @@ -194,7 +197,7 @@ func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func( } // Add tag to stack - tag_stack = append(tag_stack, tag) + tagStack = append(tagStack, tag) } } } else { @@ -204,10 +207,10 @@ func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func( finalize() - for i := len(tag_stack) - 1; i >= 0; i-- { - tag := tag_stack[i] + for i := len(tagStack) - 1; i >= 0; i-- { + tag := tagStack[i] // Close everything from the regular tag stack - new_output.WriteString(fmt.Sprintf("", tag)) + newOutput.WriteString(fmt.Sprintf("", tag)) } } @@ -217,28 +220,28 @@ func filterTruncatechars(in *Value, param *Value) (*Value, *Error) { return AsValue(filterTruncatecharsHelper(s, newLen)), nil } -func filterTruncatecharsHtml(in *Value, param *Value) (*Value, *Error) { +func filterTruncatecharsHTML(in *Value, param *Value) (*Value, *Error) { value := in.String() newLen := max(param.Integer()-3, 0) - new_output := bytes.NewBuffer(nil) + newOutput := bytes.NewBuffer(nil) textcounter := 0 - filterTruncateHtmlHelper(value, new_output, func() bool { + filterTruncateHTMLHelper(value, newOutput, func() bool { return textcounter >= newLen }, func(c rune, s int, idx int) int { textcounter++ - new_output.WriteRune(c) + newOutput.WriteRune(c) return idx + s }, func() { if textcounter >= newLen && textcounter < len(value) { - new_output.WriteString("...") + newOutput.WriteString("...") } }) - return AsSafeValue(new_output.String()), nil + return AsSafeValue(newOutput.String()), nil } func filterTruncatewords(in *Value, param *Value) (*Value, *Error) { @@ -260,19 +263,19 @@ func filterTruncatewords(in *Value, param *Value) (*Value, *Error) { return AsValue(strings.Join(out, " ")), nil } -func filterTruncatewordsHtml(in *Value, param *Value) (*Value, *Error) { +func filterTruncatewordsHTML(in *Value, param *Value) (*Value, *Error) { value := in.String() newLen := max(param.Integer(), 0) - new_output := bytes.NewBuffer(nil) + newOutput := bytes.NewBuffer(nil) wordcounter := 0 - filterTruncateHtmlHelper(value, new_output, func() bool { + filterTruncateHTMLHelper(value, newOutput, func() bool { return wordcounter >= newLen }, func(_ rune, _ int, idx int) int { // Get next word - word_found := false + wordFound := false for idx < len(value) { c2, size2 := utf8.DecodeRuneInString(value[idx:]) @@ -286,29 +289,29 @@ func filterTruncatewordsHtml(in *Value, param *Value) (*Value, *Error) { return idx } - new_output.WriteRune(c2) + newOutput.WriteRune(c2) idx += size2 if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' { // Word ends here, stop capturing it now break } else { - word_found = true + wordFound = true } } - if word_found { + if wordFound { wordcounter++ } return idx }, func() { if wordcounter >= newLen { - new_output.WriteString("...") + newOutput.WriteString("...") } }) - return AsSafeValue(new_output.String()), nil + return AsSafeValue(newOutput.String()), nil } func filterEscape(in *Value, param *Value) (*Value, *Error) { @@ -377,12 +380,11 @@ func filterAdd(in *Value, param *Value) (*Value, *Error) { if in.IsNumber() && param.IsNumber() { if in.IsFloat() || param.IsFloat() { return AsValue(in.Float() + param.Float()), nil - } else { - return AsValue(in.Integer() + param.Integer()), nil } + return AsValue(in.Integer() + param.Integer()), nil } // If in/param is not a number, we're relying on the - // Value's String() convertion and just add them both together + // Value's String() conversion and just add them both together return AsValue(in.String() + param.String()), nil } @@ -550,11 +552,11 @@ func filterCenter(in *Value, param *Value) (*Value, *Error) { } func filterDate(in *Value, param *Value) (*Value, *Error) { - t, is_time := in.Interface().(time.Time) - if !is_time { + t, isTime := in.Interface().(time.Time) + if !isTime { return nil, &Error{ - Sender: "filter:date", - ErrorMsg: "Filter input argument must be of type 'time.Time'.", + Sender: "filter:date", + OrigError: errors.New("filter input argument must be of type 'time.Time'"), } } return AsValue(t.Format(param.String())), nil @@ -612,6 +614,12 @@ func filterLinebreaks(in *Value, param *Value) (*Value, *Error) { return AsValue(b.String()), nil } +func filterSplit(in *Value, param *Value) (*Value, *Error) { + chunks := strings.Split(in.String(), param.String()) + + return AsValue(chunks), nil +} + func filterLinebreaksbr(in *Value, param *Value) (*Value, *Error) { return AsValue(strings.Replace(in.String(), "\n", "
", -1)), nil } @@ -641,7 +649,8 @@ func filterUrlencode(in *Value, param *Value) (*Value, *Error) { var filterUrlizeURLRegexp = regexp.MustCompile(`((((http|https)://)|www\.|((^|[ ])[0-9A-Za-z_\-]+(\.com|\.net|\.org|\.info|\.biz|\.de))))(?U:.*)([ ]+|$)`) var filterUrlizeEmailRegexp = regexp.MustCompile(`(\w+@\w+\.\w{2,4})`) -func filterUrlizeHelper(input string, autoescape bool, trunc int) string { +func filterUrlizeHelper(input string, autoescape bool, trunc int) (string, error) { + var soutErr error sout := filterUrlizeURLRegexp.ReplaceAllStringFunc(input, func(raw_url string) string { var prefix string var suffix string @@ -656,7 +665,8 @@ func filterUrlizeHelper(input string, autoescape bool, trunc int) string { t, err := ApplyFilter("iriencode", AsValue(raw_url), nil) if err != nil { - panic(err) + soutErr = err + return "" } url := t.String() @@ -673,16 +683,19 @@ func filterUrlizeHelper(input string, autoescape bool, trunc int) string { if autoescape { t, err := ApplyFilter("escape", AsValue(title), nil) if err != nil { - panic(err) + soutErr = err + return "" } title = t.String() } return fmt.Sprintf(`%s%s%s`, prefix, url, title, suffix) }) + if soutErr != nil { + return "", soutErr + } sout = filterUrlizeEmailRegexp.ReplaceAllStringFunc(sout, func(mail string) string { - title := mail if trunc > 3 && len(title) > trunc { @@ -692,7 +705,7 @@ func filterUrlizeHelper(input string, autoescape bool, trunc int) string { return fmt.Sprintf(`%s`, mail, title) }) - return sout + return sout, nil } func filterUrlize(in *Value, param *Value) (*Value, *Error) { @@ -701,24 +714,36 @@ func filterUrlize(in *Value, param *Value) (*Value, *Error) { autoescape = param.Bool() } - return AsValue(filterUrlizeHelper(in.String(), autoescape, -1)), nil + s, err := filterUrlizeHelper(in.String(), autoescape, -1) + if err != nil { + + } + + return AsValue(s), nil } func filterUrlizetrunc(in *Value, param *Value) (*Value, *Error) { - return AsValue(filterUrlizeHelper(in.String(), true, param.Integer())), nil + s, err := filterUrlizeHelper(in.String(), true, param.Integer()) + if err != nil { + return nil, &Error{ + Sender: "filter:urlizetrunc", + OrigError: errors.New("you cannot pass more than 2 arguments to filter 'pluralize'"), + } + } + return AsValue(s), nil } func filterStringformat(in *Value, param *Value) (*Value, *Error) { return AsValue(fmt.Sprintf(param.String(), in.Interface())), nil } -var re_striptags = regexp.MustCompile("<[^>]*?>") +var reStriptags = regexp.MustCompile("<[^>]*?>") func filterStriptags(in *Value, param *Value) (*Value, *Error) { s := in.String() // Strip all tags - s = re_striptags.ReplaceAllString(s, "") + s = reStriptags.ReplaceAllString(s, "") return AsValue(strings.TrimSpace(s)), nil } @@ -746,8 +771,8 @@ func filterPluralize(in *Value, param *Value) (*Value, *Error) { endings := strings.Split(param.String(), ",") if len(endings) > 2 { return nil, &Error{ - Sender: "filter:pluralize", - ErrorMsg: "You cannot pass more than 2 arguments to filter 'pluralize'.", + Sender: "filter:pluralize", + OrigError: errors.New("you cannot pass more than 2 arguments to filter 'pluralize'"), } } if len(endings) == 1 { @@ -770,11 +795,10 @@ func filterPluralize(in *Value, param *Value) (*Value, *Error) { } return AsValue(""), nil - } else { - return nil, &Error{ - Sender: "filter:pluralize", - ErrorMsg: "Filter 'pluralize' does only work on numbers.", - } + } + return nil, &Error{ + Sender: "filter:pluralize", + OrigError: errors.New("filter 'pluralize' does only work on numbers"), } } @@ -807,8 +831,8 @@ func filterSlice(in *Value, param *Value) (*Value, *Error) { comp := strings.Split(param.String(), ":") if len(comp) != 2 { return nil, &Error{ - Sender: "filter:slice", - ErrorMsg: "Slice string must have the format 'from:to' [from/to can be omitted, but the ':' is required]", + Sender: "filter:slice", + OrigError: errors.New("Slice string must have the format 'from:to' [from/to can be omitted, but the ':' is required]"), } } @@ -844,16 +868,16 @@ func filterWordcount(in *Value, param *Value) (*Value, *Error) { func filterWordwrap(in *Value, param *Value) (*Value, *Error) { words := strings.Fields(in.String()) - words_len := len(words) - wrap_at := param.Integer() - if wrap_at <= 0 { + wordsLen := len(words) + wrapAt := param.Integer() + if wrapAt <= 0 { return in, nil } - linecount := words_len/wrap_at + words_len%wrap_at + linecount := wordsLen/wrapAt + wordsLen%wrapAt lines := make([]string, 0, linecount) for i := 0; i < linecount; i++ { - lines = append(lines, strings.Join(words[wrap_at*i:min(wrap_at*(i+1), words_len)], " ")) + lines = append(lines, strings.Join(words[wrapAt*i:min(wrapAt*(i+1), wordsLen)], " ")) } return AsValue(strings.Join(lines, "\n")), nil } @@ -864,27 +888,27 @@ func filterYesno(in *Value, param *Value) (*Value, *Error) { 1: "no", 2: "maybe", } - param_string := param.String() - custom_choices := strings.Split(param_string, ",") - if len(param_string) > 0 { - if len(custom_choices) > 3 { + paramString := param.String() + customChoices := strings.Split(paramString, ",") + if len(paramString) > 0 { + if len(customChoices) > 3 { return nil, &Error{ - Sender: "filter:yesno", - ErrorMsg: fmt.Sprintf("You cannot pass more than 3 options to the 'yesno'-filter (got: '%s').", param_string), + Sender: "filter:yesno", + OrigError: errors.Errorf("You cannot pass more than 3 options to the 'yesno'-filter (got: '%s').", paramString), } } - if len(custom_choices) < 2 { + if len(customChoices) < 2 { return nil, &Error{ - Sender: "filter:yesno", - ErrorMsg: fmt.Sprintf("You must pass either no or at least 2 arguments to the 'yesno'-filter (got: '%s').", param_string), + Sender: "filter:yesno", + OrigError: errors.Errorf("You must pass either no or at least 2 arguments to the 'yesno'-filter (got: '%s').", paramString), } } // Map to the options now - choices[0] = custom_choices[0] - choices[1] = custom_choices[1] - if len(custom_choices) == 3 { - choices[2] = custom_choices[2] + choices[0] = customChoices[0] + choices[1] = customChoices[1] + if len(customChoices) == 3 { + choices[2] = customChoices[2] } } diff --git a/vendor/github.com/flosch/pongo2/lexer.go b/vendor/github.com/flosch/pongo2/lexer.go index 8956f9c..67b0b95 100644 --- a/vendor/github.com/flosch/pongo2/lexer.go +++ b/vendor/github.com/flosch/pongo2/lexer.go @@ -4,6 +4,8 @@ import ( "fmt" "strings" "unicode/utf8" + + "github.com/juju/errors" ) const ( @@ -28,6 +30,7 @@ var ( // Available symbols in pongo2 (within filters/tag) TokenSymbols = []string{ // 3-Char symbols + "{{-", "-}}", "{%-", "-%}", // 2-Char symbols "==", ">=", "<=", "&&", "||", "{{", "}}", "{%", "%}", "!=", "<>", @@ -42,11 +45,12 @@ var ( type TokenType int type Token struct { - Filename string - Typ TokenType - Val string - Line int - Col int + Filename string + Typ TokenType + Val string + Line int + Col int + TrimWhitespaces bool } type lexerStateFn func() lexerStateFn @@ -63,8 +67,8 @@ type lexer struct { line int col int - in_verbatim bool - verbatim_name string + inVerbatim bool + verbatimName string } func (t *Token) String() string { @@ -93,8 +97,8 @@ func (t *Token) String() string { typ = "Unknown" } - return fmt.Sprintf("", - typ, t.Typ, val, t.Line, t.Col) + return fmt.Sprintf("", + typ, t.Typ, val, t.Line, t.Col, t.TrimWhitespaces) } func lex(name string, input string) ([]*Token, *Error) { @@ -111,11 +115,11 @@ func lex(name string, input string) ([]*Token, *Error) { if l.errored { errtoken := l.tokens[len(l.tokens)-1] return nil, &Error{ - Filename: name, - Line: errtoken.Line, - Column: errtoken.Col, - Sender: "lexer", - ErrorMsg: errtoken.Val, + Filename: name, + Line: errtoken.Line, + Column: errtoken.Col, + Sender: "lexer", + OrigError: errors.New(errtoken.Val), } } return l.tokens, nil @@ -144,6 +148,11 @@ func (l *lexer) emit(t TokenType) { tok.Val = strings.Replace(tok.Val, `\\`, `\`, -1) } + if t == TokenSymbol && len(tok.Val) == 3 && (strings.HasSuffix(tok.Val, "-") || strings.HasPrefix(tok.Val, "-")) { + tok.TrimWhitespaces = true + tok.Val = strings.Replace(tok.Val, "-", "", -1) + } + l.tokens = append(l.tokens, tok) l.start = l.pos l.startline = l.line @@ -216,8 +225,8 @@ func (l *lexer) run() { for { // TODO: Support verbatim tag names // https://docs.djangoproject.com/en/dev/ref/templates/builtins/#verbatim - if l.in_verbatim { - name := l.verbatim_name + if l.inVerbatim { + name := l.verbatimName if name != "" { name += " " } @@ -229,20 +238,20 @@ func (l *lexer) run() { l.pos += w l.col += w l.ignore() - l.in_verbatim = false + l.inVerbatim = false } } else if strings.HasPrefix(l.input[l.pos:], "{% verbatim %}") { // tag if l.pos > l.start { l.emit(TokenHTML) } - l.in_verbatim = true + l.inVerbatim = true w := len("{% verbatim %}") l.pos += w l.col += w l.ignore() } - if !l.in_verbatim { + if !l.inVerbatim { // Ignore single-line comments {# ... #} if strings.HasPrefix(l.input[l.pos:], "{#") { if l.pos > l.start { @@ -303,7 +312,7 @@ func (l *lexer) run() { l.emit(TokenHTML) } - if l.in_verbatim { + if l.inVerbatim { l.errorf("verbatim-tag not closed, got EOF.") } } @@ -328,7 +337,7 @@ outer_loop: return l.stateIdentifier case l.accept(tokenDigits): return l.stateNumber - case l.accept(`"`): + case l.accept(`"'`): return l.stateString } @@ -339,7 +348,7 @@ outer_loop: l.col += l.length() l.emit(TokenSymbol) - if sym == "%}" || sym == "}}" { + if sym == "%}" || sym == "-%}" || sym == "}}" || sym == "-}}" { // Tag/variable end, return after emit return nil } @@ -348,10 +357,6 @@ outer_loop: } } - if l.pos < len(l.input) { - return l.errorf("Unknown character: %q (%d)", l.peek(), l.peek()) - } - break } @@ -374,6 +379,11 @@ func (l *lexer) stateIdentifier() lexerStateFn { func (l *lexer) stateNumber() lexerStateFn { l.acceptRun(tokenDigits) + if l.accept(tokenIdentifierCharsWithDigits) { + // This seems to be an identifier starting with a number. + // See https://github.com/flosch/pongo2/issues/151 + return l.stateIdentifier() + } /* Maybe context-sensitive number lexing? * comments.0.Text // first comment @@ -393,9 +403,10 @@ func (l *lexer) stateNumber() lexerStateFn { } func (l *lexer) stateString() lexerStateFn { + quotationMark := l.value() l.ignore() - l.startcol -= 1 // we're starting the position at the first " - for !l.accept(`"`) { + l.startcol-- // we're starting the position at the first " + for !l.accept(quotationMark) { switch l.next() { case '\\': // escape sequence diff --git a/vendor/github.com/flosch/pongo2/nodes.go b/vendor/github.com/flosch/pongo2/nodes.go index 5fd5a6c..5b039cd 100644 --- a/vendor/github.com/flosch/pongo2/nodes.go +++ b/vendor/github.com/flosch/pongo2/nodes.go @@ -1,17 +1,13 @@ package pongo2 -import ( - "bytes" -) - // The root document type nodeDocument struct { Nodes []INode } -func (doc *nodeDocument) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (doc *nodeDocument) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { for _, n := range doc.Nodes { - err := n.Execute(ctx, buffer) + err := n.Execute(ctx, writer) if err != nil { return err } diff --git a/vendor/github.com/flosch/pongo2/nodes_html.go b/vendor/github.com/flosch/pongo2/nodes_html.go index 9aa630c..c735def 100644 --- a/vendor/github.com/flosch/pongo2/nodes_html.go +++ b/vendor/github.com/flosch/pongo2/nodes_html.go @@ -1,14 +1,23 @@ package pongo2 import ( - "bytes" + "strings" ) type nodeHTML struct { token *Token + trimLeft bool + trimRight bool } -func (n *nodeHTML) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { - buffer.WriteString(n.token.Val) +func (n *nodeHTML) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + res := n.token.Val + if n.trimLeft { + res = strings.TrimLeft(res, tokenSpaceChars) + } + if n.trimRight { + res = strings.TrimRight(res, tokenSpaceChars) + } + writer.WriteString(res) return nil } diff --git a/vendor/github.com/flosch/pongo2/nodes_wrapper.go b/vendor/github.com/flosch/pongo2/nodes_wrapper.go index 9180dc7..d1bcb8d 100644 --- a/vendor/github.com/flosch/pongo2/nodes_wrapper.go +++ b/vendor/github.com/flosch/pongo2/nodes_wrapper.go @@ -1,17 +1,13 @@ package pongo2 -import ( - "bytes" -) - type NodeWrapper struct { Endtag string nodes []INode } -func (wrapper *NodeWrapper) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (wrapper *NodeWrapper) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { for _, n := range wrapper.nodes { - err := n.Execute(ctx, buffer) + err := n.Execute(ctx, writer) if err != nil { return err } diff --git a/vendor/github.com/flosch/pongo2/parser.go b/vendor/github.com/flosch/pongo2/parser.go index c85e2d2..2279e3c 100644 --- a/vendor/github.com/flosch/pongo2/parser.go +++ b/vendor/github.com/flosch/pongo2/parser.go @@ -1,13 +1,14 @@ package pongo2 import ( - "bytes" "fmt" "strings" + + "github.com/juju/errors" ) type INode interface { - Execute(*ExecutionContext, *bytes.Buffer) *Error + Execute(*ExecutionContext, TemplateWriter) *Error } type IEvaluator interface { @@ -27,10 +28,10 @@ type IEvaluator interface { // // (See Token's documentation for more about tokens) type Parser struct { - name string - idx int - tokens []*Token - last_token *Token + name string + idx int + tokens []*Token + lastToken *Token // if the parser parses a template document, here will be // a reference to it (needed to access the template through Tags) @@ -47,7 +48,7 @@ func newParser(name string, tokens []*Token, template *Template) *Parser { template: template, } if len(tokens) > 0 { - p.last_token = tokens[len(tokens)-1] + p.lastToken = tokens[len(tokens)-1] } return p } @@ -162,7 +163,7 @@ func (p *Parser) Count() int { // Returns tokens[i] or NIL (if i >= len(tokens)) func (p *Parser) Get(i int) *Token { - if i < len(p.tokens) { + if i < len(p.tokens) && i >= 0 { return p.tokens[i] } return nil @@ -175,7 +176,7 @@ func (p *Parser) GetR(shift int) *Token { return p.Get(i) } -// Produces a nice error message and returns an error-object. +// Error produces a nice error message and returns an error-object. // The 'token'-argument is optional. If provided, it will take // the token's position information. If not provided, it will // automatically use the CURRENT token's position information. @@ -196,13 +197,13 @@ func (p *Parser) Error(msg string, token *Token) *Error { col = token.Col } return &Error{ - Template: p.template, - Filename: p.name, - Sender: "parser", - Line: line, - Column: col, - Token: token, - ErrorMsg: msg, + Template: p.template, + Filename: p.name, + Sender: "parser", + Line: line, + Column: col, + Token: token, + OrigError: errors.New(msg), } } @@ -212,19 +213,19 @@ func (p *Parser) Error(msg string, token *Token) *Error { func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) { wrapper := &NodeWrapper{} - tagArgs := make([]*Token, 0) + var tagArgs []*Token for p.Remaining() > 0 { // New tag, check whether we have to stop wrapping here if p.Peek(TokenSymbol, "{%") != nil { - tag_ident := p.PeekTypeN(1, TokenIdentifier) + tagIdent := p.PeekTypeN(1, TokenIdentifier) - if tag_ident != nil { + if tagIdent != nil { // We've found a (!) end-tag found := false for _, n := range names { - if tag_ident.Val == n { + if tagIdent.Val == n { found = true break } @@ -238,16 +239,15 @@ func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) { for { if p.Match(TokenSymbol, "%}") != nil { // Okay, end the wrapping here - wrapper.Endtag = tag_ident.Val + wrapper.Endtag = tagIdent.Val return wrapper, newParser(p.template.name, tagArgs, p.template), nil - } else { - t := p.Current() - p.Consume() - if t == nil { - return nil, nil, p.Error("Unexpected EOF.", p.last_token) - } - tagArgs = append(tagArgs, t) } + t := p.Current() + p.Consume() + if t == nil { + return nil, nil, p.Error("Unexpected EOF.", p.lastToken) + } + tagArgs = append(tagArgs, t) } } } @@ -263,5 +263,47 @@ func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) { } return nil, nil, p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")), - p.last_token) + p.lastToken) +} + +// Skips all nodes between starting tag and "{% endtag %}" +func (p *Parser) SkipUntilTag(names ...string) *Error { + for p.Remaining() > 0 { + // New tag, check whether we have to stop wrapping here + if p.Peek(TokenSymbol, "{%") != nil { + tagIdent := p.PeekTypeN(1, TokenIdentifier) + + if tagIdent != nil { + // We've found a (!) end-tag + + found := false + for _, n := range names { + if tagIdent.Val == n { + found = true + break + } + } + + // We only process the tag if we've found an end tag + if found { + // Okay, endtag found. + p.ConsumeN(2) // '{%' tagname + + for { + if p.Match(TokenSymbol, "%}") != nil { + // Done skipping, exit. + return nil + } + } + } + } + } + t := p.Current() + p.Consume() + if t == nil { + return p.Error("Unexpected EOF.", p.lastToken) + } + } + + return p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")), p.lastToken) } diff --git a/vendor/github.com/flosch/pongo2/parser_document.go b/vendor/github.com/flosch/pongo2/parser_document.go index 4ab8b93..e3ac2c8 100644 --- a/vendor/github.com/flosch/pongo2/parser_document.go +++ b/vendor/github.com/flosch/pongo2/parser_document.go @@ -6,8 +6,13 @@ func (p *Parser) parseDocElement() (INode, *Error) { switch t.Typ { case TokenHTML: + n := &nodeHTML{token: t} + left := p.PeekTypeN(-1, TokenSymbol) + right := p.PeekTypeN(1, TokenSymbol) + n.trimLeft = left != nil && left.TrimWhitespaces + n.trimRight = right != nil && right.TrimWhitespaces p.Consume() // consume HTML element - return &nodeHTML{token: t}, nil + return n, nil case TokenSymbol: switch t.Val { case "{{": diff --git a/vendor/github.com/flosch/pongo2/parser_expression.go b/vendor/github.com/flosch/pongo2/parser_expression.go index c1002de..988468e 100644 --- a/vendor/github.com/flosch/pongo2/parser_expression.go +++ b/vendor/github.com/flosch/pongo2/parser_expression.go @@ -1,38 +1,37 @@ package pongo2 import ( - "bytes" "fmt" "math" ) type Expression struct { // TODO: Add location token? - expr1 IEvaluator - expr2 IEvaluator - op_token *Token + expr1 IEvaluator + expr2 IEvaluator + opToken *Token } type relationalExpression struct { // TODO: Add location token? - expr1 IEvaluator - expr2 IEvaluator - op_token *Token + expr1 IEvaluator + expr2 IEvaluator + opToken *Token } type simpleExpression struct { - negate bool - negative_sign bool - term1 IEvaluator - term2 IEvaluator - op_token *Token + negate bool + negativeSign bool + term1 IEvaluator + term2 IEvaluator + opToken *Token } type term struct { // TODO: Add location token? - factor1 IEvaluator - factor2 IEvaluator - op_token *Token + factor1 IEvaluator + factor2 IEvaluator + opToken *Token } type power struct { @@ -56,14 +55,14 @@ func (expr *simpleExpression) FilterApplied(name string) bool { (expr.term2 != nil && expr.term2.FilterApplied(name))) } -func (t *term) FilterApplied(name string) bool { - return t.factor1.FilterApplied(name) && (t.factor2 == nil || - (t.factor2 != nil && t.factor2.FilterApplied(name))) +func (expr *term) FilterApplied(name string) bool { + return expr.factor1.FilterApplied(name) && (expr.factor2 == nil || + (expr.factor2 != nil && expr.factor2.FilterApplied(name))) } -func (p *power) FilterApplied(name string) bool { - return p.power1.FilterApplied(name) && (p.power2 == nil || - (p.power2 != nil && p.power2.FilterApplied(name))) +func (expr *power) FilterApplied(name string) bool { + return expr.power1.FilterApplied(name) && (expr.power2 == nil || + (expr.power2 != nil && expr.power2.FilterApplied(name))) } func (expr *Expression) GetPositionToken() *Token { @@ -86,48 +85,48 @@ func (expr *power) GetPositionToken() *Token { return expr.power1.GetPositionToken() } -func (expr *Expression) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (expr *Expression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { value, err := expr.Evaluate(ctx) if err != nil { return err } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } -func (expr *relationalExpression) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (expr *relationalExpression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { value, err := expr.Evaluate(ctx) if err != nil { return err } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } -func (expr *simpleExpression) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (expr *simpleExpression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { value, err := expr.Evaluate(ctx) if err != nil { return err } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } -func (expr *term) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (expr *term) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { value, err := expr.Evaluate(ctx) if err != nil { return err } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } -func (expr *power) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (expr *power) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { value, err := expr.Evaluate(ctx) if err != nil { return err } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } @@ -141,13 +140,13 @@ func (expr *Expression) Evaluate(ctx *ExecutionContext) (*Value, *Error) { if err != nil { return nil, err } - switch expr.op_token.Val { + switch expr.opToken.Val { case "and", "&&": return AsValue(v1.IsTrue() && v2.IsTrue()), nil case "or", "||": return AsValue(v1.IsTrue() || v2.IsTrue()), nil default: - panic(fmt.Sprintf("unimplemented: %s", expr.op_token.Val)) + return nil, ctx.Error(fmt.Sprintf("unimplemented: %s", expr.opToken.Val), expr.opToken) } } else { return v1, nil @@ -164,39 +163,35 @@ func (expr *relationalExpression) Evaluate(ctx *ExecutionContext) (*Value, *Erro if err != nil { return nil, err } - switch expr.op_token.Val { + switch expr.opToken.Val { case "<=": if v1.IsFloat() || v2.IsFloat() { return AsValue(v1.Float() <= v2.Float()), nil - } else { - return AsValue(v1.Integer() <= v2.Integer()), nil } + return AsValue(v1.Integer() <= v2.Integer()), nil case ">=": if v1.IsFloat() || v2.IsFloat() { return AsValue(v1.Float() >= v2.Float()), nil - } else { - return AsValue(v1.Integer() >= v2.Integer()), nil } + return AsValue(v1.Integer() >= v2.Integer()), nil case "==": return AsValue(v1.EqualValueTo(v2)), nil case ">": if v1.IsFloat() || v2.IsFloat() { return AsValue(v1.Float() > v2.Float()), nil - } else { - return AsValue(v1.Integer() > v2.Integer()), nil } + return AsValue(v1.Integer() > v2.Integer()), nil case "<": if v1.IsFloat() || v2.IsFloat() { return AsValue(v1.Float() < v2.Float()), nil - } else { - return AsValue(v1.Integer() < v2.Integer()), nil } + return AsValue(v1.Integer() < v2.Integer()), nil case "!=", "<>": return AsValue(!v1.EqualValueTo(v2)), nil case "in": return AsValue(v2.Contains(v1)), nil default: - panic(fmt.Sprintf("unimplemented: %s", expr.op_token.Val)) + return nil, ctx.Error(fmt.Sprintf("unimplemented: %s", expr.opToken.Val), expr.opToken) } } else { return v1, nil @@ -214,7 +209,7 @@ func (expr *simpleExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) { result = result.Negate() } - if expr.negative_sign { + if expr.negativeSign { if result.IsNumber() { switch { case result.IsFloat(): @@ -222,7 +217,7 @@ func (expr *simpleExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) { case result.IsInteger(): result = AsValue(-1 * result.Integer()) default: - panic("not possible") + return nil, ctx.Error("Operation between a number and a non-(float/integer) is not possible", nil) } } else { return nil, ctx.Error("Negative sign on a non-number expression", expr.GetPositionToken()) @@ -234,42 +229,40 @@ func (expr *simpleExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) { if err != nil { return nil, err } - switch expr.op_token.Val { + switch expr.opToken.Val { case "+": if result.IsFloat() || t2.IsFloat() { // Result will be a float return AsValue(result.Float() + t2.Float()), nil - } else { - // Result will be an integer - return AsValue(result.Integer() + t2.Integer()), nil } + // Result will be an integer + return AsValue(result.Integer() + t2.Integer()), nil case "-": if result.IsFloat() || t2.IsFloat() { // Result will be a float return AsValue(result.Float() - t2.Float()), nil - } else { - // Result will be an integer - return AsValue(result.Integer() - t2.Integer()), nil } + // Result will be an integer + return AsValue(result.Integer() - t2.Integer()), nil default: - panic("unimplemented") + return nil, ctx.Error("Unimplemented", expr.GetPositionToken()) } } return result, nil } -func (t *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) { - f1, err := t.factor1.Evaluate(ctx) +func (expr *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) { + f1, err := expr.factor1.Evaluate(ctx) if err != nil { return nil, err } - if t.factor2 != nil { - f2, err := t.factor2.Evaluate(ctx) + if expr.factor2 != nil { + f2, err := expr.factor2.Evaluate(ctx) if err != nil { return nil, err } - switch t.op_token.Val { + switch expr.opToken.Val { case "*": if f1.IsFloat() || f2.IsFloat() { // Result will be float @@ -288,27 +281,26 @@ func (t *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) { // Result will be int return AsValue(f1.Integer() % f2.Integer()), nil default: - panic("unimplemented") + return nil, ctx.Error("unimplemented", expr.opToken) } } else { return f1, nil } } -func (pw *power) Evaluate(ctx *ExecutionContext) (*Value, *Error) { - p1, err := pw.power1.Evaluate(ctx) +func (expr *power) Evaluate(ctx *ExecutionContext) (*Value, *Error) { + p1, err := expr.power1.Evaluate(ctx) if err != nil { return nil, err } - if pw.power2 != nil { - p2, err := pw.power2.Evaluate(ctx) + if expr.power2 != nil { + p2, err := expr.power2.Evaluate(ctx) if err != nil { return nil, err } return AsValue(math.Pow(p1.Float(), p2.Float())), nil - } else { - return p1, nil } + return p1, nil } func (p *Parser) parseFactor() (IEvaluator, *Error) { @@ -352,19 +344,19 @@ func (p *Parser) parsePower() (IEvaluator, *Error) { } func (p *Parser) parseTerm() (IEvaluator, *Error) { - return_term := new(term) + returnTerm := new(term) factor1, err := p.parsePower() if err != nil { return nil, err } - return_term.factor1 = factor1 + returnTerm.factor1 = factor1 for p.PeekOne(TokenSymbol, "*", "/", "%") != nil { - if return_term.op_token != nil { + if returnTerm.opToken != nil { // Create new sub-term - return_term = &term{ - factor1: return_term, + returnTerm = &term{ + factor1: returnTerm, } } @@ -376,16 +368,16 @@ func (p *Parser) parseTerm() (IEvaluator, *Error) { return nil, err } - return_term.op_token = op - return_term.factor2 = factor2 + returnTerm.opToken = op + returnTerm.factor2 = factor2 } - if return_term.op_token == nil { + if returnTerm.opToken == nil { // Shortcut for faster evaluation - return return_term.factor1, nil + return returnTerm.factor1, nil } - return return_term, nil + return returnTerm, nil } func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) { @@ -393,7 +385,7 @@ func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) { if sign := p.MatchOne(TokenSymbol, "+", "-"); sign != nil { if sign.Val == "-" { - expr.negative_sign = true + expr.negativeSign = true } } @@ -408,7 +400,7 @@ func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) { expr.term1 = term1 for p.PeekOne(TokenSymbol, "+", "-") != nil { - if expr.op_token != nil { + if expr.opToken != nil { // New sub expr expr = &simpleExpression{ term1: expr, @@ -424,10 +416,10 @@ func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) { } expr.term2 = term2 - expr.op_token = op + expr.opToken = op } - if expr.negate == false && expr.negative_sign == false && expr.term2 == nil { + if expr.negate == false && expr.negativeSign == false && expr.term2 == nil { // Shortcut for faster evaluation return expr.term1, nil } @@ -450,14 +442,14 @@ func (p *Parser) parseRelationalExpression() (IEvaluator, *Error) { if err != nil { return nil, err } - expr.op_token = t + expr.opToken = t expr.expr2 = expr2 } else if t := p.MatchOne(TokenKeyword, "in"); t != nil { expr2, err := p.parseSimpleExpression() if err != nil { return nil, err } - expr.op_token = t + expr.opToken = t expr.expr2 = expr2 } @@ -487,7 +479,7 @@ func (p *Parser) ParseExpression() (IEvaluator, *Error) { return nil, err } exp.expr2 = expr2 - exp.op_token = op + exp.opToken = op } if exp.expr2 == nil { diff --git a/vendor/github.com/flosch/pongo2/pongo2.go b/vendor/github.com/flosch/pongo2/pongo2.go index e61faa4..eda3aa0 100644 --- a/vendor/github.com/flosch/pongo2/pongo2.go +++ b/vendor/github.com/flosch/pongo2/pongo2.go @@ -1,10 +1,10 @@ package pongo2 // Version string -const Version = "v3" +const Version = "dev" -// Helper function which panics, if a Template couldn't -// successfully parsed. This is how you would use it: +// Must panics, if a Template couldn't successfully parsed. This is how you +// would use it: // var baseTemplate = pongo2.Must(pongo2.FromFile("templates/base.html")) func Must(tpl *Template, err error) *Template { if err != nil { diff --git a/vendor/github.com/flosch/pongo2/tags.go b/vendor/github.com/flosch/pongo2/tags.go index 292c30d..3668b06 100644 --- a/vendor/github.com/flosch/pongo2/tags.go +++ b/vendor/github.com/flosch/pongo2/tags.go @@ -21,6 +21,8 @@ package pongo2 import ( "fmt" + + "github.com/juju/errors" ) type INodeTag interface { @@ -53,80 +55,81 @@ func init() { tags = make(map[string]*tag) } -// Registers a new tag. If there's already a tag with the same -// name, RegisterTag will panic. You usually want to call this +// Registers a new tag. You usually want to call this // function in the tag's init() function: // http://golang.org/doc/effective_go.html#init // // See http://www.florian-schlachter.de/post/pongo2/ for more about // writing filters and tags. -func RegisterTag(name string, parserFn TagParser) { +func RegisterTag(name string, parserFn TagParser) error { _, existing := tags[name] if existing { - panic(fmt.Sprintf("Tag with name '%s' is already registered.", name)) + return errors.Errorf("tag with name '%s' is already registered", name) } tags[name] = &tag{ name: name, parser: parserFn, } + return nil } // Replaces an already registered tag with a new implementation. Use this // function with caution since it allows you to change existing tag behaviour. -func ReplaceTag(name string, parserFn TagParser) { +func ReplaceTag(name string, parserFn TagParser) error { _, existing := tags[name] if !existing { - panic(fmt.Sprintf("Tag with name '%s' does not exist (therefore cannot be overridden).", name)) + return errors.Errorf("tag with name '%s' does not exist (therefore cannot be overridden)", name) } tags[name] = &tag{ name: name, parser: parserFn, } + return nil } // Tag = "{%" IDENT ARGS "%}" func (p *Parser) parseTagElement() (INodeTag, *Error) { p.Consume() // consume "{%" - token_name := p.MatchType(TokenIdentifier) + tokenName := p.MatchType(TokenIdentifier) // Check for identifier - if token_name == nil { + if tokenName == nil { return nil, p.Error("Tag name must be an identifier.", nil) } // Check for the existing tag - tag, exists := tags[token_name.Val] + tag, exists := tags[tokenName.Val] if !exists { // Does not exists - return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", token_name.Val), token_name) + return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", tokenName.Val), tokenName) } // Check sandbox tag restriction - if _, is_banned := p.template.set.bannedTags[token_name.Val]; is_banned { - return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", token_name.Val), token_name) + if _, isBanned := p.template.set.bannedTags[tokenName.Val]; isBanned { + return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", tokenName.Val), tokenName) } - args_token := make([]*Token, 0) + var argsToken []*Token for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 { // Add token to args - args_token = append(args_token, p.Current()) + argsToken = append(argsToken, p.Current()) p.Consume() // next token } // EOF? if p.Remaining() == 0 { - return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.last_token) + return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.lastToken) } p.Match(TokenSymbol, "%}") - arg_parser := newParser(p.name, args_token, p.template) - if len(args_token) == 0 { + argParser := newParser(p.name, argsToken, p.template) + if len(argsToken) == 0 { // This is done to have nice EOF error messages - arg_parser.last_token = token_name + argParser.lastToken = tokenName } p.template.level++ defer func() { p.template.level-- }() - return tag.parser(p, token_name, arg_parser) + return tag.parser(p, tokenName, argParser) } diff --git a/vendor/github.com/flosch/pongo2/tags_autoescape.go b/vendor/github.com/flosch/pongo2/tags_autoescape.go index ec30438..590a1db 100644 --- a/vendor/github.com/flosch/pongo2/tags_autoescape.go +++ b/vendor/github.com/flosch/pongo2/tags_autoescape.go @@ -1,19 +1,15 @@ package pongo2 -import ( - "bytes" -) - type tagAutoescapeNode struct { wrapper *NodeWrapper autoescape bool } -func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { old := ctx.Autoescape ctx.Autoescape = node.autoescape - err := node.wrapper.Execute(ctx, buffer) + err := node.wrapper.Execute(ctx, writer) if err != nil { return err } @@ -24,22 +20,22 @@ func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, buffer *bytes.Buff } func tagAutoescapeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - autoescape_node := &tagAutoescapeNode{} + autoescapeNode := &tagAutoescapeNode{} wrapper, _, err := doc.WrapUntilTag("endautoescape") if err != nil { return nil, err } - autoescape_node.wrapper = wrapper + autoescapeNode.wrapper = wrapper - mode_token := arguments.MatchType(TokenIdentifier) - if mode_token == nil { + modeToken := arguments.MatchType(TokenIdentifier) + if modeToken == nil { return nil, arguments.Error("A mode is required for autoescape-tag.", nil) } - if mode_token.Val == "on" { - autoescape_node.autoescape = true - } else if mode_token.Val == "off" { - autoescape_node.autoescape = false + if modeToken.Val == "on" { + autoescapeNode.autoescape = true + } else if modeToken.Val == "off" { + autoescapeNode.autoescape = false } else { return nil, arguments.Error("Only 'on' or 'off' is valid as an autoescape-mode.", nil) } @@ -48,7 +44,7 @@ func tagAutoescapeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag return nil, arguments.Error("Malformed autoescape-tag arguments.", nil) } - return autoescape_node, nil + return autoescapeNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_block.go b/vendor/github.com/flosch/pongo2/tags_block.go index 30e205a..86145f3 100644 --- a/vendor/github.com/flosch/pongo2/tags_block.go +++ b/vendor/github.com/flosch/pongo2/tags_block.go @@ -9,47 +9,82 @@ type tagBlockNode struct { name string } -func (node *tagBlockNode) getBlockWrapperByName(tpl *Template) *NodeWrapper { +func (node *tagBlockNode) getBlockWrappers(tpl *Template) []*NodeWrapper { + nodeWrappers := make([]*NodeWrapper, 0) var t *NodeWrapper - if tpl.child != nil { - // First ask the child for the block - t = node.getBlockWrapperByName(tpl.child) - } - if t == nil { - // Child has no block, lets look up here at parent + + for tpl != nil { t = tpl.blocks[node.name] + if t != nil { + nodeWrappers = append(nodeWrappers, t) + } + tpl = tpl.child } - return t + + return nodeWrappers } -func (node *tagBlockNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagBlockNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { tpl := ctx.template if tpl == nil { panic("internal error: tpl == nil") } + // Determine the block to execute - block_wrapper := node.getBlockWrapperByName(tpl) - if block_wrapper == nil { - // fmt.Printf("could not find: %s\n", node.name) - return ctx.Error("internal error: block_wrapper == nil in tagBlockNode.Execute()", nil) + blockWrappers := node.getBlockWrappers(tpl) + lenBlockWrappers := len(blockWrappers) + + if lenBlockWrappers == 0 { + return ctx.Error("internal error: len(block_wrappers) == 0 in tagBlockNode.Execute()", nil) } - err := block_wrapper.Execute(ctx, buffer) + + blockWrapper := blockWrappers[lenBlockWrappers-1] + ctx.Private["block"] = tagBlockInformation{ + ctx: ctx, + wrappers: blockWrappers[0 : lenBlockWrappers-1], + } + err := blockWrapper.Execute(ctx, writer) if err != nil { return err } - // TODO: Add support for {{ block.super }} - return nil } +type tagBlockInformation struct { + ctx *ExecutionContext + wrappers []*NodeWrapper +} + +func (t tagBlockInformation) Super() string { + lenWrappers := len(t.wrappers) + + if lenWrappers == 0 { + return "" + } + + superCtx := NewChildExecutionContext(t.ctx) + superCtx.Private["block"] = tagBlockInformation{ + ctx: t.ctx, + wrappers: t.wrappers[0 : lenWrappers-1], + } + + blockWrapper := t.wrappers[lenWrappers-1] + buf := bytes.NewBufferString("") + err := blockWrapper.Execute(superCtx, &templateWriter{buf}) + if err != nil { + return "" + } + return buf.String() +} + func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { if arguments.Count() == 0 { return nil, arguments.Error("Tag 'block' requires an identifier.", nil) } - name_token := arguments.MatchType(TokenIdentifier) - if name_token == nil { + nameToken := arguments.MatchType(TokenIdentifier) + if nameToken == nil { return nil, arguments.Error("First argument for tag 'block' must be an identifier.", nil) } @@ -62,15 +97,15 @@ func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er return nil, err } if endtagargs.Remaining() > 0 { - endtagname_token := endtagargs.MatchType(TokenIdentifier) - if endtagname_token != nil { - if endtagname_token.Val != name_token.Val { + endtagnameToken := endtagargs.MatchType(TokenIdentifier) + if endtagnameToken != nil { + if endtagnameToken.Val != nameToken.Val { return nil, endtagargs.Error(fmt.Sprintf("Name for 'endblock' must equal to 'block'-tag's name ('%s' != '%s').", - name_token.Val, endtagname_token.Val), nil) + nameToken.Val, endtagnameToken.Val), nil) } } - if endtagname_token == nil || endtagargs.Remaining() > 0 { + if endtagnameToken == nil || endtagargs.Remaining() > 0 { return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endblock'.", nil) } } @@ -79,14 +114,14 @@ func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er if tpl == nil { panic("internal error: tpl == nil") } - _, has_block := tpl.blocks[name_token.Val] - if !has_block { - tpl.blocks[name_token.Val] = wrapper + _, hasBlock := tpl.blocks[nameToken.Val] + if !hasBlock { + tpl.blocks[nameToken.Val] = wrapper } else { - return nil, arguments.Error(fmt.Sprintf("Block named '%s' already defined", name_token.Val), nil) + return nil, arguments.Error(fmt.Sprintf("Block named '%s' already defined", nameToken.Val), nil) } - return &tagBlockNode{name: name_token.Val}, nil + return &tagBlockNode{name: nameToken.Val}, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_comment.go b/vendor/github.com/flosch/pongo2/tags_comment.go index 8c22496..56a02ed 100644 --- a/vendor/github.com/flosch/pongo2/tags_comment.go +++ b/vendor/github.com/flosch/pongo2/tags_comment.go @@ -1,20 +1,16 @@ package pongo2 -import ( - "bytes" -) - type tagCommentNode struct{} -func (node *tagCommentNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagCommentNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { return nil } func tagCommentParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - comment_node := &tagCommentNode{} + commentNode := &tagCommentNode{} // TODO: Process the endtag's arguments (see django 'comment'-tag documentation) - _, _, err := doc.WrapUntilTag("endcomment") + err := doc.SkipUntilTag("endcomment") if err != nil { return nil, err } @@ -23,7 +19,7 @@ func tagCommentParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, * return nil, arguments.Error("Tag 'comment' does not take any argument.", nil) } - return comment_node, nil + return commentNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_cycle.go b/vendor/github.com/flosch/pongo2/tags_cycle.go index 6a6830e..ffbd254 100644 --- a/vendor/github.com/flosch/pongo2/tags_cycle.go +++ b/vendor/github.com/flosch/pongo2/tags_cycle.go @@ -1,9 +1,5 @@ package pongo2 -import ( - "bytes" -) - type tagCycleValue struct { node *tagCycleNode value *Value @@ -13,7 +9,7 @@ type tagCycleNode struct { position *Token args []IEvaluator idx int - as_name string + asName string silent bool } @@ -21,7 +17,7 @@ func (cv *tagCycleValue) String() string { return cv.value.String() } -func (node *tagCycleNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagCycleNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { item := node.args[node.idx%len(node.args)] node.idx++ @@ -46,30 +42,30 @@ func (node *tagCycleNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) * t.value = val if !t.node.silent { - buffer.WriteString(val.String()) + writer.WriteString(val.String()) } } else { // Regular call - cycle_value := &tagCycleValue{ + cycleValue := &tagCycleValue{ node: node, value: val, } - if node.as_name != "" { - ctx.Private[node.as_name] = cycle_value + if node.asName != "" { + ctx.Private[node.asName] = cycleValue } if !node.silent { - buffer.WriteString(val.String()) + writer.WriteString(val.String()) } } return nil } -// HINT: We're not supporting the old comma-seperated list of expresions argument-style +// HINT: We're not supporting the old comma-separated list of expressions argument-style func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - cycle_node := &tagCycleNode{ + cycleNode := &tagCycleNode{ position: start, } @@ -78,19 +74,19 @@ func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er if err != nil { return nil, err } - cycle_node.args = append(cycle_node.args, node) + cycleNode.args = append(cycleNode.args, node) if arguments.MatchOne(TokenKeyword, "as") != nil { // as - name_token := arguments.MatchType(TokenIdentifier) - if name_token == nil { + nameToken := arguments.MatchType(TokenIdentifier) + if nameToken == nil { return nil, arguments.Error("Name (identifier) expected after 'as'.", nil) } - cycle_node.as_name = name_token.Val + cycleNode.asName = nameToken.Val if arguments.MatchOne(TokenIdentifier, "silent") != nil { - cycle_node.silent = true + cycleNode.silent = true } // Now we're finished @@ -102,7 +98,7 @@ func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er return nil, arguments.Error("Malformed cycle-tag.", nil) } - return cycle_node, nil + return cycleNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_extends.go b/vendor/github.com/flosch/pongo2/tags_extends.go index 6abbb6b..5771020 100644 --- a/vendor/github.com/flosch/pongo2/tags_extends.go +++ b/vendor/github.com/flosch/pongo2/tags_extends.go @@ -1,19 +1,15 @@ package pongo2 -import ( - "bytes" -) - type tagExtendsNode struct { filename string } -func (node *tagExtendsNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagExtendsNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { return nil } func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - extends_node := &tagExtendsNode{} + extendsNode := &tagExtendsNode{} if doc.template.level > 1 { return nil, arguments.Error("The 'extends' tag can only defined on root level.", start) @@ -24,22 +20,22 @@ func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, * return nil, arguments.Error("This template has already one parent.", start) } - if filename_token := arguments.MatchType(TokenString); filename_token != nil { + if filenameToken := arguments.MatchType(TokenString); filenameToken != nil { // prepared, static template // Get parent's filename - parent_filename := doc.template.set.resolveFilename(doc.template, filename_token.Val) + parentFilename := doc.template.set.resolveFilename(doc.template, filenameToken.Val) // Parse the parent - parent_template, err := doc.template.set.FromFile(parent_filename) + parentTemplate, err := doc.template.set.FromFile(parentFilename) if err != nil { return nil, err.(*Error) } // Keep track of things - parent_template.child = doc.template - doc.template.parent = parent_template - extends_node.filename = parent_filename + parentTemplate.child = doc.template + doc.template.parent = parentTemplate + extendsNode.filename = parentFilename } else { return nil, arguments.Error("Tag 'extends' requires a template filename as string.", nil) } @@ -48,7 +44,7 @@ func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, * return nil, arguments.Error("Tag 'extends' does only take 1 argument.", nil) } - return extends_node, nil + return extendsNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_filter.go b/vendor/github.com/flosch/pongo2/tags_filter.go index f421e5a..b38fd92 100644 --- a/vendor/github.com/flosch/pongo2/tags_filter.go +++ b/vendor/github.com/flosch/pongo2/tags_filter.go @@ -5,8 +5,8 @@ import ( ) type nodeFilterCall struct { - name string - param_expr IEvaluator + name string + paramExpr IEvaluator } type tagFilterNode struct { @@ -15,7 +15,7 @@ type tagFilterNode struct { filterChain []*nodeFilterCall } -func (node *tagFilterNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagFilterNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { temp := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB size err := node.bodyWrapper.Execute(ctx, temp) @@ -27,8 +27,8 @@ func (node *tagFilterNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) for _, call := range node.filterChain { var param *Value - if call.param_expr != nil { - param, err = call.param_expr.Evaluate(ctx) + if call.paramExpr != nil { + param, err = call.paramExpr.Evaluate(ctx) if err != nil { return err } @@ -41,13 +41,13 @@ func (node *tagFilterNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) } } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - filter_node := &tagFilterNode{ + filterNode := &tagFilterNode{ position: start, } @@ -55,16 +55,16 @@ func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *E if err != nil { return nil, err } - filter_node.bodyWrapper = wrapper + filterNode.bodyWrapper = wrapper for arguments.Remaining() > 0 { filterCall := &nodeFilterCall{} - name_token := arguments.MatchType(TokenIdentifier) - if name_token == nil { + nameToken := arguments.MatchType(TokenIdentifier) + if nameToken == nil { return nil, arguments.Error("Expected a filter name (identifier).", nil) } - filterCall.name = name_token.Val + filterCall.name = nameToken.Val if arguments.MatchOne(TokenSymbol, ":") != nil { // Filter parameter @@ -73,10 +73,10 @@ func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *E if err != nil { return nil, err } - filterCall.param_expr = expr + filterCall.paramExpr = expr } - filter_node.filterChain = append(filter_node.filterChain, filterCall) + filterNode.filterChain = append(filterNode.filterChain, filterCall) if arguments.MatchOne(TokenSymbol, "|") == nil { break @@ -87,7 +87,7 @@ func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *E return nil, arguments.Error("Malformed filter-tag arguments.", nil) } - return filter_node, nil + return filterNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_firstof.go b/vendor/github.com/flosch/pongo2/tags_firstof.go index b677979..5b2888e 100644 --- a/vendor/github.com/flosch/pongo2/tags_firstof.go +++ b/vendor/github.com/flosch/pongo2/tags_firstof.go @@ -1,15 +1,11 @@ package pongo2 -import ( - "bytes" -) - type tagFirstofNode struct { position *Token args []IEvaluator } -func (node *tagFirstofNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagFirstofNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { for _, arg := range node.args { val, err := arg.Evaluate(ctx) if err != nil { @@ -24,7 +20,7 @@ func (node *tagFirstofNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) } } - buffer.WriteString(val.String()) + writer.WriteString(val.String()) return nil } } @@ -33,7 +29,7 @@ func (node *tagFirstofNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) } func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - firstof_node := &tagFirstofNode{ + firstofNode := &tagFirstofNode{ position: start, } @@ -42,10 +38,10 @@ func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, * if err != nil { return nil, err } - firstof_node.args = append(firstof_node.args, node) + firstofNode.args = append(firstofNode.args, node) } - return firstof_node, nil + return firstofNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_for.go b/vendor/github.com/flosch/pongo2/tags_for.go index de56699..5b0b555 100644 --- a/vendor/github.com/flosch/pongo2/tags_for.go +++ b/vendor/github.com/flosch/pongo2/tags_for.go @@ -1,14 +1,11 @@ package pongo2 -import ( - "bytes" -) - type tagForNode struct { - key string - value string // only for maps: for key, value in map - object_evaluator IEvaluator - reversed bool + key string + value string // only for maps: for key, value in map + objectEvaluator IEvaluator + reversed bool + sorted bool bodyWrapper *NodeWrapper emptyWrapper *NodeWrapper @@ -24,7 +21,7 @@ type tagForLoopInformation struct { Parentloop *tagForLoopInformation } -func (node *tagForNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) (forError *Error) { +func (node *tagForNode) Execute(ctx *ExecutionContext, writer TemplateWriter) (forError *Error) { // Backup forloop (as parentloop in public context), key-name and value-name forCtx := NewChildExecutionContext(ctx) parentloop := forCtx.Private["forloop"] @@ -42,7 +39,7 @@ func (node *tagForNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) (fo // Register loopInfo in public context forCtx.Private["forloop"] = loopInfo - obj, err := node.object_evaluator.Evaluate(forCtx) + obj, err := node.objectEvaluator.Evaluate(forCtx) if err != nil { return err } @@ -67,7 +64,7 @@ func (node *tagForNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) (fo loopInfo.Revcounter0 = count - (idx + 1) // TODO: Not sure about this, have to look it up // Render elements with updated context - err := node.bodyWrapper.Execute(forCtx, buffer) + err := node.bodyWrapper.Execute(forCtx, writer) if err != nil { forError = err return false @@ -76,30 +73,30 @@ func (node *tagForNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) (fo }, func() { // Nothing to iterate over (maybe wrong type or no items) if node.emptyWrapper != nil { - err := node.emptyWrapper.Execute(forCtx, buffer) + err := node.emptyWrapper.Execute(forCtx, writer) if err != nil { forError = err } } - }, node.reversed) + }, node.reversed, node.sorted) - return nil + return forError } func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - for_node := &tagForNode{} + forNode := &tagForNode{} // Arguments parsing - var value_token *Token - key_token := arguments.MatchType(TokenIdentifier) - if key_token == nil { + var valueToken *Token + keyToken := arguments.MatchType(TokenIdentifier) + if keyToken == nil { return nil, arguments.Error("Expected an key identifier as first argument for 'for'-tag", nil) } if arguments.Match(TokenSymbol, ",") != nil { // Value name is provided - value_token = arguments.MatchType(TokenIdentifier) - if value_token == nil { + valueToken = arguments.MatchType(TokenIdentifier) + if valueToken == nil { return nil, arguments.Error("Value name must be an identifier.", nil) } } @@ -108,18 +105,22 @@ func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Erro return nil, arguments.Error("Expected keyword 'in'.", nil) } - object_evaluator, err := arguments.ParseExpression() + objectEvaluator, err := arguments.ParseExpression() if err != nil { return nil, err } - for_node.object_evaluator = object_evaluator - for_node.key = key_token.Val - if value_token != nil { - for_node.value = value_token.Val + forNode.objectEvaluator = objectEvaluator + forNode.key = keyToken.Val + if valueToken != nil { + forNode.value = valueToken.Val } if arguments.MatchOne(TokenIdentifier, "reversed") != nil { - for_node.reversed = true + forNode.reversed = true + } + + if arguments.MatchOne(TokenIdentifier, "sorted") != nil { + forNode.sorted = true } if arguments.Remaining() > 0 { @@ -131,7 +132,7 @@ func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Erro if err != nil { return nil, err } - for_node.bodyWrapper = wrapper + forNode.bodyWrapper = wrapper if endargs.Count() > 0 { return nil, endargs.Error("Arguments not allowed here.", nil) @@ -143,14 +144,14 @@ func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Erro if err != nil { return nil, err } - for_node.emptyWrapper = wrapper + forNode.emptyWrapper = wrapper if endargs.Count() > 0 { return nil, endargs.Error("Arguments not allowed here.", nil) } } - return for_node, nil + return forNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_if.go b/vendor/github.com/flosch/pongo2/tags_if.go index 2515c44..3eeaf3b 100644 --- a/vendor/github.com/flosch/pongo2/tags_if.go +++ b/vendor/github.com/flosch/pongo2/tags_if.go @@ -1,15 +1,11 @@ package pongo2 -import ( - "bytes" -) - type tagIfNode struct { conditions []IEvaluator wrappers []*NodeWrapper } -func (node *tagIfNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagIfNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { for i, condition := range node.conditions { result, err := condition.Evaluate(ctx) if err != nil { @@ -17,26 +13,25 @@ func (node *tagIfNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Err } if result.IsTrue() { - return node.wrappers[i].Execute(ctx, buffer) - } else { - // Last condition? - if len(node.conditions) == i+1 && len(node.wrappers) > i+1 { - return node.wrappers[i+1].Execute(ctx, buffer) - } + return node.wrappers[i].Execute(ctx, writer) + } + // Last condition? + if len(node.conditions) == i+1 && len(node.wrappers) > i+1 { + return node.wrappers[i+1].Execute(ctx, writer) } } return nil } func tagIfParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - if_node := &tagIfNode{} + ifNode := &tagIfNode{} // Parse first and main IF condition condition, err := arguments.ParseExpression() if err != nil { return nil, err } - if_node.conditions = append(if_node.conditions, condition) + ifNode.conditions = append(ifNode.conditions, condition) if arguments.Remaining() > 0 { return nil, arguments.Error("If-condition is malformed.", nil) @@ -44,27 +39,27 @@ func tagIfParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error // Check the rest for { - wrapper, tag_args, err := doc.WrapUntilTag("elif", "else", "endif") + wrapper, tagArgs, err := doc.WrapUntilTag("elif", "else", "endif") if err != nil { return nil, err } - if_node.wrappers = append(if_node.wrappers, wrapper) + ifNode.wrappers = append(ifNode.wrappers, wrapper) if wrapper.Endtag == "elif" { // elif can take a condition - condition, err := tag_args.ParseExpression() + condition, err = tagArgs.ParseExpression() if err != nil { return nil, err } - if_node.conditions = append(if_node.conditions, condition) + ifNode.conditions = append(ifNode.conditions, condition) - if tag_args.Remaining() > 0 { - return nil, tag_args.Error("Elif-condition is malformed.", nil) + if tagArgs.Remaining() > 0 { + return nil, tagArgs.Error("Elif-condition is malformed.", nil) } } else { - if tag_args.Count() > 0 { + if tagArgs.Count() > 0 { // else/endif can't take any conditions - return nil, tag_args.Error("Arguments not allowed here.", nil) + return nil, tagArgs.Error("Arguments not allowed here.", nil) } } @@ -73,7 +68,7 @@ func tagIfParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error } } - return if_node, nil + return ifNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_ifchanged.go b/vendor/github.com/flosch/pongo2/tags_ifchanged.go index 4412ace..45296a0 100644 --- a/vendor/github.com/flosch/pongo2/tags_ifchanged.go +++ b/vendor/github.com/flosch/pongo2/tags_ifchanged.go @@ -5,16 +5,15 @@ import ( ) type tagIfchangedNode struct { - watched_expr []IEvaluator - last_values []*Value - last_content []byte - thenWrapper *NodeWrapper - elseWrapper *NodeWrapper + watchedExpr []IEvaluator + lastValues []*Value + lastContent []byte + thenWrapper *NodeWrapper + elseWrapper *NodeWrapper } -func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { - - if len(node.watched_expr) == 0 { +func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + if len(node.watchedExpr) == 0 { // Check against own rendered body buf := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB @@ -23,43 +22,43 @@ func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffe return err } - buf_bytes := buf.Bytes() - if !bytes.Equal(node.last_content, buf_bytes) { + bufBytes := buf.Bytes() + if !bytes.Equal(node.lastContent, bufBytes) { // Rendered content changed, output it - buffer.Write(buf_bytes) - node.last_content = buf_bytes + writer.Write(bufBytes) + node.lastContent = bufBytes } } else { - now_values := make([]*Value, 0, len(node.watched_expr)) - for _, expr := range node.watched_expr { + nowValues := make([]*Value, 0, len(node.watchedExpr)) + for _, expr := range node.watchedExpr { val, err := expr.Evaluate(ctx) if err != nil { return err } - now_values = append(now_values, val) + nowValues = append(nowValues, val) } // Compare old to new values now - changed := len(node.last_values) == 0 + changed := len(node.lastValues) == 0 - for idx, old_val := range node.last_values { - if !old_val.EqualValueTo(now_values[idx]) { + for idx, oldVal := range node.lastValues { + if !oldVal.EqualValueTo(nowValues[idx]) { changed = true break // we can stop here because ONE value changed } } - node.last_values = now_values + node.lastValues = nowValues if changed { // Render thenWrapper - err := node.thenWrapper.Execute(ctx, buffer) + err := node.thenWrapper.Execute(ctx, writer) if err != nil { return err } } else { // Render elseWrapper - err := node.elseWrapper.Execute(ctx, buffer) + err := node.elseWrapper.Execute(ctx, writer) if err != nil { return err } @@ -70,7 +69,7 @@ func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffe } func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - ifchanged_node := &tagIfchangedNode{} + ifchangedNode := &tagIfchangedNode{} for arguments.Remaining() > 0 { // Parse condition @@ -78,7 +77,7 @@ func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, if err != nil { return nil, err } - ifchanged_node.watched_expr = append(ifchanged_node.watched_expr, expr) + ifchangedNode.watchedExpr = append(ifchangedNode.watchedExpr, expr) } if arguments.Remaining() > 0 { @@ -90,7 +89,7 @@ func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, if err != nil { return nil, err } - ifchanged_node.thenWrapper = wrapper + ifchangedNode.thenWrapper = wrapper if endargs.Count() > 0 { return nil, endargs.Error("Arguments not allowed here.", nil) @@ -102,14 +101,14 @@ func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, if err != nil { return nil, err } - ifchanged_node.elseWrapper = wrapper + ifchangedNode.elseWrapper = wrapper if endargs.Count() > 0 { return nil, endargs.Error("Arguments not allowed here.", nil) } } - return ifchanged_node, nil + return ifchangedNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_ifequal.go b/vendor/github.com/flosch/pongo2/tags_ifequal.go index 035b8fd..103f1c7 100644 --- a/vendor/github.com/flosch/pongo2/tags_ifequal.go +++ b/vendor/github.com/flosch/pongo2/tags_ifequal.go @@ -1,16 +1,12 @@ package pongo2 -import ( - "bytes" -) - type tagIfEqualNode struct { var1, var2 IEvaluator thenWrapper *NodeWrapper elseWrapper *NodeWrapper } -func (node *tagIfEqualNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagIfEqualNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { r1, err := node.var1.Evaluate(ctx) if err != nil { return err @@ -23,17 +19,16 @@ func (node *tagIfEqualNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) result := r1.EqualValueTo(r2) if result { - return node.thenWrapper.Execute(ctx, buffer) - } else { - if node.elseWrapper != nil { - return node.elseWrapper.Execute(ctx, buffer) - } + return node.thenWrapper.Execute(ctx, writer) + } + if node.elseWrapper != nil { + return node.elseWrapper.Execute(ctx, writer) } return nil } func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - ifequal_node := &tagIfEqualNode{} + ifequalNode := &tagIfEqualNode{} // Parse two expressions var1, err := arguments.ParseExpression() @@ -44,8 +39,8 @@ func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, * if err != nil { return nil, err } - ifequal_node.var1 = var1 - ifequal_node.var2 = var2 + ifequalNode.var1 = var1 + ifequalNode.var2 = var2 if arguments.Remaining() > 0 { return nil, arguments.Error("ifequal only takes 2 arguments.", nil) @@ -56,7 +51,7 @@ func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, * if err != nil { return nil, err } - ifequal_node.thenWrapper = wrapper + ifequalNode.thenWrapper = wrapper if endargs.Count() > 0 { return nil, endargs.Error("Arguments not allowed here.", nil) @@ -68,14 +63,14 @@ func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, * if err != nil { return nil, err } - ifequal_node.elseWrapper = wrapper + ifequalNode.elseWrapper = wrapper if endargs.Count() > 0 { return nil, endargs.Error("Arguments not allowed here.", nil) } } - return ifequal_node, nil + return ifequalNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_ifnotequal.go b/vendor/github.com/flosch/pongo2/tags_ifnotequal.go index 1c1ba53..0d287d3 100644 --- a/vendor/github.com/flosch/pongo2/tags_ifnotequal.go +++ b/vendor/github.com/flosch/pongo2/tags_ifnotequal.go @@ -1,16 +1,12 @@ package pongo2 -import ( - "bytes" -) - type tagIfNotEqualNode struct { var1, var2 IEvaluator thenWrapper *NodeWrapper elseWrapper *NodeWrapper } -func (node *tagIfNotEqualNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagIfNotEqualNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { r1, err := node.var1.Evaluate(ctx) if err != nil { return err @@ -23,17 +19,16 @@ func (node *tagIfNotEqualNode) Execute(ctx *ExecutionContext, buffer *bytes.Buff result := !r1.EqualValueTo(r2) if result { - return node.thenWrapper.Execute(ctx, buffer) - } else { - if node.elseWrapper != nil { - return node.elseWrapper.Execute(ctx, buffer) - } + return node.thenWrapper.Execute(ctx, writer) + } + if node.elseWrapper != nil { + return node.elseWrapper.Execute(ctx, writer) } return nil } func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - ifnotequal_node := &tagIfNotEqualNode{} + ifnotequalNode := &tagIfNotEqualNode{} // Parse two expressions var1, err := arguments.ParseExpression() @@ -44,19 +39,19 @@ func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag if err != nil { return nil, err } - ifnotequal_node.var1 = var1 - ifnotequal_node.var2 = var2 + ifnotequalNode.var1 = var1 + ifnotequalNode.var2 = var2 if arguments.Remaining() > 0 { return nil, arguments.Error("ifequal only takes 2 arguments.", nil) } // Wrap then/else-blocks - wrapper, endargs, err := doc.WrapUntilTag("else", "endifequal") + wrapper, endargs, err := doc.WrapUntilTag("else", "endifnotequal") if err != nil { return nil, err } - ifnotequal_node.thenWrapper = wrapper + ifnotequalNode.thenWrapper = wrapper if endargs.Count() > 0 { return nil, endargs.Error("Arguments not allowed here.", nil) @@ -64,18 +59,18 @@ func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag if wrapper.Endtag == "else" { // if there's an else in the if-statement, we need the else-Block as well - wrapper, endargs, err = doc.WrapUntilTag("endifequal") + wrapper, endargs, err = doc.WrapUntilTag("endifnotequal") if err != nil { return nil, err } - ifnotequal_node.elseWrapper = wrapper + ifnotequalNode.elseWrapper = wrapper if endargs.Count() > 0 { return nil, endargs.Error("Arguments not allowed here.", nil) } } - return ifnotequal_node, nil + return ifnotequalNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_import.go b/vendor/github.com/flosch/pongo2/tags_import.go index 2abeccd..7e0d6a0 100644 --- a/vendor/github.com/flosch/pongo2/tags_import.go +++ b/vendor/github.com/flosch/pongo2/tags_import.go @@ -1,18 +1,16 @@ package pongo2 import ( - "bytes" "fmt" ) type tagImportNode struct { position *Token filename string - template *Template macros map[string]*tagMacroNode // alias/name -> macro instance } -func (node *tagImportNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagImportNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { for name, macro := range node.macros { func(name string, macro *tagMacroNode) { ctx.Private[name] = func(args ...*Value) *Value { @@ -24,50 +22,50 @@ func (node *tagImportNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) } func tagImportParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - import_node := &tagImportNode{ + importNode := &tagImportNode{ position: start, macros: make(map[string]*tagMacroNode), } - filename_token := arguments.MatchType(TokenString) - if filename_token == nil { + filenameToken := arguments.MatchType(TokenString) + if filenameToken == nil { return nil, arguments.Error("Import-tag needs a filename as string.", nil) } - import_node.filename = doc.template.set.resolveFilename(doc.template, filename_token.Val) + importNode.filename = doc.template.set.resolveFilename(doc.template, filenameToken.Val) if arguments.Remaining() == 0 { return nil, arguments.Error("You must at least specify one macro to import.", nil) } // Compile the given template - tpl, err := doc.template.set.FromFile(import_node.filename) + tpl, err := doc.template.set.FromFile(importNode.filename) if err != nil { return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, start) } for arguments.Remaining() > 0 { - macro_name_token := arguments.MatchType(TokenIdentifier) - if macro_name_token == nil { + macroNameToken := arguments.MatchType(TokenIdentifier) + if macroNameToken == nil { return nil, arguments.Error("Expected macro name (identifier).", nil) } - as_name := macro_name_token.Val + asName := macroNameToken.Val if arguments.Match(TokenKeyword, "as") != nil { - alias_token := arguments.MatchType(TokenIdentifier) - if alias_token == nil { + aliasToken := arguments.MatchType(TokenIdentifier) + if aliasToken == nil { return nil, arguments.Error("Expected macro alias name (identifier).", nil) } - as_name = alias_token.Val + asName = aliasToken.Val } - macro_instance, has := tpl.exported_macros[macro_name_token.Val] + macroInstance, has := tpl.exportedMacros[macroNameToken.Val] if !has { - return nil, arguments.Error(fmt.Sprintf("Macro '%s' not found (or not exported) in '%s'.", macro_name_token.Val, - import_node.filename), macro_name_token) + return nil, arguments.Error(fmt.Sprintf("Macro '%s' not found (or not exported) in '%s'.", macroNameToken.Val, + importNode.filename), macroNameToken) } - import_node.macros[as_name] = macro_instance + importNode.macros[asName] = macroInstance if arguments.Remaining() == 0 { break @@ -78,7 +76,7 @@ func tagImportParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *E } } - return import_node, nil + return importNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_include.go b/vendor/github.com/flosch/pongo2/tags_include.go index 7a7cce2..6d619fd 100644 --- a/vendor/github.com/flosch/pongo2/tags_include.go +++ b/vendor/github.com/flosch/pongo2/tags_include.go @@ -1,41 +1,38 @@ package pongo2 -import ( - "bytes" -) - type tagIncludeNode struct { - tpl *Template - filename_evaluator IEvaluator - lazy bool - only bool - filename string - with_pairs map[string]IEvaluator + tpl *Template + filenameEvaluator IEvaluator + lazy bool + only bool + filename string + withPairs map[string]IEvaluator + ifExists bool } -func (node *tagIncludeNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagIncludeNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { // Building the context for the template - include_ctx := make(Context) + includeCtx := make(Context) // Fill the context with all data from the parent if !node.only { - include_ctx.Update(ctx.Public) - include_ctx.Update(ctx.Private) + includeCtx.Update(ctx.Public) + includeCtx.Update(ctx.Private) } // Put all custom with-pairs into the context - for key, value := range node.with_pairs { + for key, value := range node.withPairs { val, err := value.Evaluate(ctx) if err != nil { return err } - include_ctx[key] = val + includeCtx[key] = val } // Execute the template if node.lazy { // Evaluate the filename - filename, err := node.filename_evaluator.Evaluate(ctx) + filename, err := node.filenameEvaluator.Evaluate(ctx) if err != nil { return err } @@ -45,76 +42,93 @@ func (node *tagIncludeNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) } // Get include-filename - included_filename := ctx.template.set.resolveFilename(ctx.template, filename.String()) + includedFilename := ctx.template.set.resolveFilename(ctx.template, filename.String()) - included_tpl, err2 := ctx.template.set.FromFile(included_filename) + includedTpl, err2 := ctx.template.set.FromFile(includedFilename) if err2 != nil { + // if this is ReadFile error, and "if_exists" flag is enabled + if node.ifExists && err2.(*Error).Sender == "fromfile" { + return nil + } return err2.(*Error) } - err2 = included_tpl.ExecuteWriter(include_ctx, buffer) + err2 = includedTpl.ExecuteWriter(includeCtx, writer) if err2 != nil { return err2.(*Error) } return nil - } else { - // Template is already parsed with static filename - err := node.tpl.ExecuteWriter(include_ctx, buffer) - if err != nil { - return err.(*Error) - } - return nil } + // Template is already parsed with static filename + err := node.tpl.ExecuteWriter(includeCtx, writer) + if err != nil { + return err.(*Error) + } + return nil +} + +type tagIncludeEmptyNode struct{} + +func (node *tagIncludeEmptyNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + return nil } func tagIncludeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - include_node := &tagIncludeNode{ - with_pairs: make(map[string]IEvaluator), + includeNode := &tagIncludeNode{ + withPairs: make(map[string]IEvaluator), } - if filename_token := arguments.MatchType(TokenString); filename_token != nil { + if filenameToken := arguments.MatchType(TokenString); filenameToken != nil { // prepared, static template + // "if_exists" flag + ifExists := arguments.Match(TokenIdentifier, "if_exists") != nil + // Get include-filename - included_filename := doc.template.set.resolveFilename(doc.template, filename_token.Val) + includedFilename := doc.template.set.resolveFilename(doc.template, filenameToken.Val) // Parse the parent - include_node.filename = included_filename - included_tpl, err := doc.template.set.FromFile(included_filename) + includeNode.filename = includedFilename + includedTpl, err := doc.template.set.FromFile(includedFilename) if err != nil { - return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, filename_token) + // if this is ReadFile error, and "if_exists" token presents we should create and empty node + if err.(*Error).Sender == "fromfile" && ifExists { + return &tagIncludeEmptyNode{}, nil + } + return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, filenameToken) } - include_node.tpl = included_tpl + includeNode.tpl = includedTpl } else { // No String, then the user wants to use lazy-evaluation (slower, but possible) - filename_evaluator, err := arguments.ParseExpression() + filenameEvaluator, err := arguments.ParseExpression() if err != nil { - return nil, err.updateFromTokenIfNeeded(doc.template, filename_token) + return nil, err.updateFromTokenIfNeeded(doc.template, filenameToken) } - include_node.filename_evaluator = filename_evaluator - include_node.lazy = true + includeNode.filenameEvaluator = filenameEvaluator + includeNode.lazy = true + includeNode.ifExists = arguments.Match(TokenIdentifier, "if_exists") != nil // "if_exists" flag } // After having parsed the filename we're gonna parse the with+only options if arguments.Match(TokenIdentifier, "with") != nil { for arguments.Remaining() > 0 { // We have at least one key=expr pair (because of starting "with") - key_token := arguments.MatchType(TokenIdentifier) - if key_token == nil { + keyToken := arguments.MatchType(TokenIdentifier) + if keyToken == nil { return nil, arguments.Error("Expected an identifier", nil) } if arguments.Match(TokenSymbol, "=") == nil { return nil, arguments.Error("Expected '='.", nil) } - value_expr, err := arguments.ParseExpression() + valueExpr, err := arguments.ParseExpression() if err != nil { - return nil, err.updateFromTokenIfNeeded(doc.template, key_token) + return nil, err.updateFromTokenIfNeeded(doc.template, keyToken) } - include_node.with_pairs[key_token.Val] = value_expr + includeNode.withPairs[keyToken.Val] = valueExpr // Only? if arguments.Match(TokenIdentifier, "only") != nil { - include_node.only = true + includeNode.only = true break // stop parsing arguments because it's the last option } } @@ -124,7 +138,7 @@ func tagIncludeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, * return nil, arguments.Error("Malformed 'include'-tag arguments.", nil) } - return include_node, nil + return includeNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_lorem.go b/vendor/github.com/flosch/pongo2/tags_lorem.go index 16b018c..1d353f2 100644 --- a/vendor/github.com/flosch/pongo2/tags_lorem.go +++ b/vendor/github.com/flosch/pongo2/tags_lorem.go @@ -1,10 +1,11 @@ package pongo2 import ( - "bytes" "math/rand" "strings" "time" + + "github.com/juju/errors" ) var ( @@ -19,102 +20,102 @@ type tagLoremNode struct { random bool // does not use the default paragraph "Lorem ipsum dolor sit amet, ..." } -func (node *tagLoremNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagLoremNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { switch node.method { case "b": if node.random { for i := 0; i < node.count; i++ { if i > 0 { - buffer.WriteString("\n") + writer.WriteString("\n") } par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))] - buffer.WriteString(par) + writer.WriteString(par) } } else { for i := 0; i < node.count; i++ { if i > 0 { - buffer.WriteString("\n") + writer.WriteString("\n") } par := tagLoremParagraphs[i%len(tagLoremParagraphs)] - buffer.WriteString(par) + writer.WriteString(par) } } case "w": if node.random { for i := 0; i < node.count; i++ { if i > 0 { - buffer.WriteString(" ") + writer.WriteString(" ") } word := tagLoremWords[rand.Intn(len(tagLoremWords))] - buffer.WriteString(word) + writer.WriteString(word) } } else { for i := 0; i < node.count; i++ { if i > 0 { - buffer.WriteString(" ") + writer.WriteString(" ") } word := tagLoremWords[i%len(tagLoremWords)] - buffer.WriteString(word) + writer.WriteString(word) } } case "p": if node.random { for i := 0; i < node.count; i++ { if i > 0 { - buffer.WriteString("\n") + writer.WriteString("\n") } - buffer.WriteString("

") + writer.WriteString("

") par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))] - buffer.WriteString(par) - buffer.WriteString("

") + writer.WriteString(par) + writer.WriteString("

") } } else { for i := 0; i < node.count; i++ { if i > 0 { - buffer.WriteString("\n") + writer.WriteString("\n") } - buffer.WriteString("

") + writer.WriteString("

") par := tagLoremParagraphs[i%len(tagLoremParagraphs)] - buffer.WriteString(par) - buffer.WriteString("

") + writer.WriteString(par) + writer.WriteString("

") } } default: - panic("unsupported method") + return ctx.OrigError(errors.Errorf("unsupported method: %s", node.method), nil) } return nil } func tagLoremParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - lorem_node := &tagLoremNode{ + loremNode := &tagLoremNode{ position: start, count: 1, method: "b", } - if count_token := arguments.MatchType(TokenNumber); count_token != nil { - lorem_node.count = AsValue(count_token.Val).Integer() + if countToken := arguments.MatchType(TokenNumber); countToken != nil { + loremNode.count = AsValue(countToken.Val).Integer() } - if method_token := arguments.MatchType(TokenIdentifier); method_token != nil { - if method_token.Val != "w" && method_token.Val != "p" && method_token.Val != "b" { + if methodToken := arguments.MatchType(TokenIdentifier); methodToken != nil { + if methodToken.Val != "w" && methodToken.Val != "p" && methodToken.Val != "b" { return nil, arguments.Error("lorem-method must be either 'w', 'p' or 'b'.", nil) } - lorem_node.method = method_token.Val + loremNode.method = methodToken.Val } if arguments.MatchOne(TokenIdentifier, "random") != nil { - lorem_node.random = true + loremNode.random = true } if arguments.Remaining() > 0 { return nil, arguments.Error("Malformed lorem-tag arguments.", nil) } - return lorem_node, nil + return loremNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_macro.go b/vendor/github.com/flosch/pongo2/tags_macro.go index 41cba99..dd3e0bf 100644 --- a/vendor/github.com/flosch/pongo2/tags_macro.go +++ b/vendor/github.com/flosch/pongo2/tags_macro.go @@ -6,16 +6,16 @@ import ( ) type tagMacroNode struct { - position *Token - name string - args_order []string - args map[string]IEvaluator - exported bool + position *Token + name string + argsOrder []string + args map[string]IEvaluator + exported bool wrapper *NodeWrapper } -func (node *tagMacroNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagMacroNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { ctx.Private[node.name] = func(args ...*Value) *Value { return node.call(ctx, args...) } @@ -24,28 +24,28 @@ func (node *tagMacroNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) * } func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value { - args_ctx := make(Context) + argsCtx := make(Context) for k, v := range node.args { if v == nil { // User did not provided a default value - args_ctx[k] = nil + argsCtx[k] = nil } else { // Evaluate the default value - value_expr, err := v.Evaluate(ctx) + valueExpr, err := v.Evaluate(ctx) if err != nil { ctx.Logf(err.Error()) return AsSafeValue(err.Error()) } - args_ctx[k] = value_expr + argsCtx[k] = valueExpr } } - if len(args) > len(node.args_order) { + if len(args) > len(node.argsOrder) { // Too many arguments, we're ignoring them and just logging into debug mode. err := ctx.Error(fmt.Sprintf("Macro '%s' called with too many arguments (%d instead of %d).", - node.name, len(args), len(node.args_order)), nil).updateFromTokenIfNeeded(ctx.template, node.position) + node.name, len(args), len(node.argsOrder)), nil).updateFromTokenIfNeeded(ctx.template, node.position) ctx.Logf(err.Error()) // TODO: This is a workaround, because the error is not returned yet to the Execution()-methods return AsSafeValue(err.Error()) @@ -55,10 +55,10 @@ func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value { macroCtx := NewChildExecutionContext(ctx) // Register all arguments in the private context - macroCtx.Private.Update(args_ctx) + macroCtx.Private.Update(argsCtx) - for idx, arg_value := range args { - macroCtx.Private[node.args_order[idx]] = arg_value.Interface() + for idx, argValue := range args { + macroCtx.Private[node.argsOrder[idx]] = argValue.Interface() } var b bytes.Buffer @@ -71,38 +71,38 @@ func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value { } func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - macro_node := &tagMacroNode{ + macroNode := &tagMacroNode{ position: start, args: make(map[string]IEvaluator), } - name_token := arguments.MatchType(TokenIdentifier) - if name_token == nil { + nameToken := arguments.MatchType(TokenIdentifier) + if nameToken == nil { return nil, arguments.Error("Macro-tag needs at least an identifier as name.", nil) } - macro_node.name = name_token.Val + macroNode.name = nameToken.Val if arguments.MatchOne(TokenSymbol, "(") == nil { return nil, arguments.Error("Expected '('.", nil) } for arguments.Match(TokenSymbol, ")") == nil { - arg_name_token := arguments.MatchType(TokenIdentifier) - if arg_name_token == nil { + argNameToken := arguments.MatchType(TokenIdentifier) + if argNameToken == nil { return nil, arguments.Error("Expected argument name as identifier.", nil) } - macro_node.args_order = append(macro_node.args_order, arg_name_token.Val) + macroNode.argsOrder = append(macroNode.argsOrder, argNameToken.Val) if arguments.Match(TokenSymbol, "=") != nil { // Default expression follows - arg_default_expr, err := arguments.ParseExpression() + argDefaultExpr, err := arguments.ParseExpression() if err != nil { return nil, err } - macro_node.args[arg_name_token.Val] = arg_default_expr + macroNode.args[argNameToken.Val] = argDefaultExpr } else { // No default expression - macro_node.args[arg_name_token.Val] = nil + macroNode.args[argNameToken.Val] = nil } if arguments.Match(TokenSymbol, ")") != nil { @@ -114,7 +114,7 @@ func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er } if arguments.Match(TokenKeyword, "export") != nil { - macro_node.exported = true + macroNode.exported = true } if arguments.Remaining() > 0 { @@ -126,22 +126,22 @@ func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er if err != nil { return nil, err } - macro_node.wrapper = wrapper + macroNode.wrapper = wrapper if endargs.Count() > 0 { return nil, endargs.Error("Arguments not allowed here.", nil) } - if macro_node.exported { + if macroNode.exported { // Now register the macro if it wants to be exported - _, has := doc.template.exported_macros[macro_node.name] + _, has := doc.template.exportedMacros[macroNode.name] if has { - return nil, doc.Error(fmt.Sprintf("Another macro with name '%s' already exported.", macro_node.name), start) + return nil, doc.Error(fmt.Sprintf("another macro with name '%s' already exported", macroNode.name), start) } - doc.template.exported_macros[macro_node.name] = macro_node + doc.template.exportedMacros[macroNode.name] = macroNode } - return macro_node, nil + return macroNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_now.go b/vendor/github.com/flosch/pongo2/tags_now.go index 0f4320f..d9fa4a3 100644 --- a/vendor/github.com/flosch/pongo2/tags_now.go +++ b/vendor/github.com/flosch/pongo2/tags_now.go @@ -1,7 +1,6 @@ package pongo2 import ( - "bytes" "time" ) @@ -11,7 +10,7 @@ type tagNowNode struct { fake bool } -func (node *tagNowNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagNowNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { var t time.Time if node.fake { t = time.Date(2014, time.February, 05, 18, 31, 45, 00, time.UTC) @@ -19,31 +18,31 @@ func (node *tagNowNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Er t = time.Now() } - buffer.WriteString(t.Format(node.format)) + writer.WriteString(t.Format(node.format)) return nil } func tagNowParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - now_node := &tagNowNode{ + nowNode := &tagNowNode{ position: start, } - format_token := arguments.MatchType(TokenString) - if format_token == nil { + formatToken := arguments.MatchType(TokenString) + if formatToken == nil { return nil, arguments.Error("Expected a format string.", nil) } - now_node.format = format_token.Val + nowNode.format = formatToken.Val if arguments.MatchOne(TokenIdentifier, "fake") != nil { - now_node.fake = true + nowNode.fake = true } if arguments.Remaining() > 0 { return nil, arguments.Error("Malformed now-tag arguments.", nil) } - return now_node, nil + return nowNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_set.go b/vendor/github.com/flosch/pongo2/tags_set.go index 2729f44..be121c1 100644 --- a/vendor/github.com/flosch/pongo2/tags_set.go +++ b/vendor/github.com/flosch/pongo2/tags_set.go @@ -1,13 +1,11 @@ package pongo2 -import "bytes" - type tagSetNode struct { name string expression IEvaluator } -func (node *tagSetNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagSetNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { // Evaluate expression value, err := node.expression.Evaluate(ctx) if err != nil { diff --git a/vendor/github.com/flosch/pongo2/tags_spaceless.go b/vendor/github.com/flosch/pongo2/tags_spaceless.go index a4b3003..4fa851b 100644 --- a/vendor/github.com/flosch/pongo2/tags_spaceless.go +++ b/vendor/github.com/flosch/pongo2/tags_spaceless.go @@ -11,7 +11,7 @@ type tagSpacelessNode struct { var tagSpacelessRegexp = regexp.MustCompile(`(?U:(<.*>))([\t\n\v\f\r ]+)(?U:(<.*>))`) -func (node *tagSpacelessNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagSpacelessNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { b := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB err := node.wrapper.Execute(ctx, b) @@ -28,25 +28,25 @@ func (node *tagSpacelessNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffe s = s2 } - buffer.WriteString(s) + writer.WriteString(s) return nil } func tagSpacelessParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - spaceless_node := &tagSpacelessNode{} + spacelessNode := &tagSpacelessNode{} wrapper, _, err := doc.WrapUntilTag("endspaceless") if err != nil { return nil, err } - spaceless_node.wrapper = wrapper + spacelessNode.wrapper = wrapper if arguments.Remaining() > 0 { return nil, arguments.Error("Malformed spaceless-tag arguments.", nil) } - return spaceless_node, nil + return spacelessNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_ssi.go b/vendor/github.com/flosch/pongo2/tags_ssi.go index 3c3894f..c33858d 100644 --- a/vendor/github.com/flosch/pongo2/tags_ssi.go +++ b/vendor/github.com/flosch/pongo2/tags_ssi.go @@ -1,7 +1,6 @@ package pongo2 import ( - "bytes" "io/ioutil" ) @@ -11,47 +10,47 @@ type tagSSINode struct { template *Template } -func (node *tagSSINode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagSSINode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { if node.template != nil { // Execute the template within the current context includeCtx := make(Context) includeCtx.Update(ctx.Public) includeCtx.Update(ctx.Private) - err := node.template.ExecuteWriter(includeCtx, buffer) + err := node.template.execute(includeCtx, writer) if err != nil { return err.(*Error) } } else { // Just print out the content - buffer.WriteString(node.content) + writer.WriteString(node.content) } return nil } func tagSSIParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - ssi_node := &tagSSINode{} + SSINode := &tagSSINode{} - if file_token := arguments.MatchType(TokenString); file_token != nil { - ssi_node.filename = file_token.Val + if fileToken := arguments.MatchType(TokenString); fileToken != nil { + SSINode.filename = fileToken.Val if arguments.Match(TokenIdentifier, "parsed") != nil { // parsed - temporary_tpl, err := doc.template.set.FromFile(doc.template.set.resolveFilename(doc.template, file_token.Val)) + temporaryTpl, err := doc.template.set.FromFile(doc.template.set.resolveFilename(doc.template, fileToken.Val)) if err != nil { - return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, file_token) + return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, fileToken) } - ssi_node.template = temporary_tpl + SSINode.template = temporaryTpl } else { // plaintext - buf, err := ioutil.ReadFile(doc.template.set.resolveFilename(doc.template, file_token.Val)) + buf, err := ioutil.ReadFile(doc.template.set.resolveFilename(doc.template, fileToken.Val)) if err != nil { return nil, (&Error{ - Sender: "tag:ssi", - ErrorMsg: err.Error(), - }).updateFromTokenIfNeeded(doc.template, file_token) + Sender: "tag:ssi", + OrigError: err, + }).updateFromTokenIfNeeded(doc.template, fileToken) } - ssi_node.content = string(buf) + SSINode.content = string(buf) } } else { return nil, arguments.Error("First argument must be a string.", nil) @@ -61,7 +60,7 @@ func tagSSIParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Erro return nil, arguments.Error("Malformed SSI-tag argument.", nil) } - return ssi_node, nil + return SSINode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_templatetag.go b/vendor/github.com/flosch/pongo2/tags_templatetag.go index ffd3d9d..164b4dc 100644 --- a/vendor/github.com/flosch/pongo2/tags_templatetag.go +++ b/vendor/github.com/flosch/pongo2/tags_templatetag.go @@ -1,9 +1,5 @@ package pongo2 -import ( - "bytes" -) - type tagTemplateTagNode struct { content string } @@ -19,20 +15,20 @@ var templateTagMapping = map[string]string{ "closecomment": "#}", } -func (node *tagTemplateTagNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { - buffer.WriteString(node.content) +func (node *tagTemplateTagNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + writer.WriteString(node.content) return nil } func tagTemplateTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - tt_node := &tagTemplateTagNode{} + ttNode := &tagTemplateTagNode{} - if arg_token := arguments.MatchType(TokenIdentifier); arg_token != nil { - output, found := templateTagMapping[arg_token.Val] + if argToken := arguments.MatchType(TokenIdentifier); argToken != nil { + output, found := templateTagMapping[argToken.Val] if !found { - return nil, arguments.Error("Argument not found", arg_token) + return nil, arguments.Error("Argument not found", argToken) } - tt_node.content = output + ttNode.content = output } else { return nil, arguments.Error("Identifier expected.", nil) } @@ -41,7 +37,7 @@ func tagTemplateTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTa return nil, arguments.Error("Malformed templatetag-tag argument.", nil) } - return tt_node, nil + return ttNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_widthratio.go b/vendor/github.com/flosch/pongo2/tags_widthratio.go index d7d7141..70c9c3e 100644 --- a/vendor/github.com/flosch/pongo2/tags_widthratio.go +++ b/vendor/github.com/flosch/pongo2/tags_widthratio.go @@ -1,7 +1,6 @@ package pongo2 import ( - "bytes" "fmt" "math" ) @@ -10,10 +9,10 @@ type tagWidthratioNode struct { position *Token current, max IEvaluator width IEvaluator - ctx_name string + ctxName string } -func (node *tagWidthratioNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagWidthratioNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { current, err := node.current.Evaluate(ctx) if err != nil { return err @@ -31,17 +30,17 @@ func (node *tagWidthratioNode) Execute(ctx *ExecutionContext, buffer *bytes.Buff value := int(math.Ceil(current.Float()/max.Float()*width.Float() + 0.5)) - if node.ctx_name == "" { - buffer.WriteString(fmt.Sprintf("%d", value)) + if node.ctxName == "" { + writer.WriteString(fmt.Sprintf("%d", value)) } else { - ctx.Private[node.ctx_name] = value + ctx.Private[node.ctxName] = value } return nil } func tagWidthratioParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - widthratio_node := &tagWidthratioNode{ + widthratioNode := &tagWidthratioNode{ position: start, } @@ -49,34 +48,34 @@ func tagWidthratioParser(doc *Parser, start *Token, arguments *Parser) (INodeTag if err != nil { return nil, err } - widthratio_node.current = current + widthratioNode.current = current max, err := arguments.ParseExpression() if err != nil { return nil, err } - widthratio_node.max = max + widthratioNode.max = max width, err := arguments.ParseExpression() if err != nil { return nil, err } - widthratio_node.width = width + widthratioNode.width = width if arguments.MatchOne(TokenKeyword, "as") != nil { // Name follows - name_token := arguments.MatchType(TokenIdentifier) - if name_token == nil { + nameToken := arguments.MatchType(TokenIdentifier) + if nameToken == nil { return nil, arguments.Error("Expected name (identifier).", nil) } - widthratio_node.ctx_name = name_token.Val + widthratioNode.ctxName = nameToken.Val } if arguments.Remaining() > 0 { return nil, arguments.Error("Malformed widthratio-tag arguments.", nil) } - return widthratio_node, nil + return widthratioNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/tags_with.go b/vendor/github.com/flosch/pongo2/tags_with.go index 5bf4af0..32b3c1c 100644 --- a/vendor/github.com/flosch/pongo2/tags_with.go +++ b/vendor/github.com/flosch/pongo2/tags_with.go @@ -1,20 +1,16 @@ package pongo2 -import ( - "bytes" -) - type tagWithNode struct { - with_pairs map[string]IEvaluator - wrapper *NodeWrapper + withPairs map[string]IEvaluator + wrapper *NodeWrapper } -func (node *tagWithNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (node *tagWithNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { //new context for block withctx := NewChildExecutionContext(ctx) // Put all custom with-pairs into the context - for key, value := range node.with_pairs { + for key, value := range node.withPairs { val, err := value.Evaluate(ctx) if err != nil { return err @@ -22,12 +18,12 @@ func (node *tagWithNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *E withctx.Private[key] = val } - return node.wrapper.Execute(withctx, buffer) + return node.wrapper.Execute(withctx, writer) } func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { - with_node := &tagWithNode{ - with_pairs: make(map[string]IEvaluator), + withNode := &tagWithNode{ + withPairs: make(map[string]IEvaluator), } if arguments.Count() == 0 { @@ -38,7 +34,7 @@ func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Err if err != nil { return nil, err } - with_node.wrapper = wrapper + withNode.wrapper = wrapper if endargs.Count() > 0 { return nil, endargs.Error("Arguments not allowed here.", nil) @@ -46,45 +42,45 @@ func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Err // Scan through all arguments to see which style the user uses (old or new style). // If we find any "as" keyword we will enforce old style; otherwise we will use new style. - old_style := false // by default we're using the new_style + oldStyle := false // by default we're using the new_style for i := 0; i < arguments.Count(); i++ { if arguments.PeekN(i, TokenKeyword, "as") != nil { - old_style = true + oldStyle = true break } } for arguments.Remaining() > 0 { - if old_style { - value_expr, err := arguments.ParseExpression() + if oldStyle { + valueExpr, err := arguments.ParseExpression() if err != nil { return nil, err } if arguments.Match(TokenKeyword, "as") == nil { return nil, arguments.Error("Expected 'as' keyword.", nil) } - key_token := arguments.MatchType(TokenIdentifier) - if key_token == nil { + keyToken := arguments.MatchType(TokenIdentifier) + if keyToken == nil { return nil, arguments.Error("Expected an identifier", nil) } - with_node.with_pairs[key_token.Val] = value_expr + withNode.withPairs[keyToken.Val] = valueExpr } else { - key_token := arguments.MatchType(TokenIdentifier) - if key_token == nil { + keyToken := arguments.MatchType(TokenIdentifier) + if keyToken == nil { return nil, arguments.Error("Expected an identifier", nil) } if arguments.Match(TokenSymbol, "=") == nil { return nil, arguments.Error("Expected '='.", nil) } - value_expr, err := arguments.ParseExpression() + valueExpr, err := arguments.ParseExpression() if err != nil { return nil, err } - with_node.with_pairs[key_token.Val] = value_expr + withNode.withPairs[keyToken.Val] = valueExpr } } - return with_node, nil + return withNode, nil } func init() { diff --git a/vendor/github.com/flosch/pongo2/template.go b/vendor/github.com/flosch/pongo2/template.go index c7fe98b..869adce 100644 --- a/vendor/github.com/flosch/pongo2/template.go +++ b/vendor/github.com/flosch/pongo2/template.go @@ -2,52 +2,72 @@ package pongo2 import ( "bytes" - "fmt" "io" + + "github.com/juju/errors" ) +type TemplateWriter interface { + io.Writer + WriteString(string) (int, error) +} + +type templateWriter struct { + w io.Writer +} + +func (tw *templateWriter) WriteString(s string) (int, error) { + return tw.w.Write([]byte(s)) +} + +func (tw *templateWriter) Write(b []byte) (int, error) { + return tw.w.Write(b) +} + type Template struct { set *TemplateSet // Input - is_tpl_string bool - name string - tpl string - size int + isTplString bool + name string + tpl string + size int // Calculation tokens []*Token parser *Parser // first come, first serve (it's important to not override existing entries in here) - level int - parent *Template - child *Template - blocks map[string]*NodeWrapper - exported_macros map[string]*tagMacroNode + level int + parent *Template + child *Template + blocks map[string]*NodeWrapper + exportedMacros map[string]*tagMacroNode // Output root *nodeDocument } -func newTemplateString(set *TemplateSet, tpl string) (*Template, error) { +func newTemplateString(set *TemplateSet, tpl []byte) (*Template, error) { return newTemplate(set, "", true, tpl) } -func newTemplate(set *TemplateSet, name string, is_tpl_string bool, tpl string) (*Template, error) { +func newTemplate(set *TemplateSet, name string, isTplString bool, tpl []byte) (*Template, error) { + strTpl := string(tpl) + // Create the template t := &Template{ - set: set, - is_tpl_string: is_tpl_string, - name: name, - tpl: tpl, - size: len(tpl), - blocks: make(map[string]*NodeWrapper), - exported_macros: make(map[string]*tagMacroNode), + set: set, + isTplString: isTplString, + name: name, + tpl: strTpl, + size: len(strTpl), + blocks: make(map[string]*NodeWrapper), + exportedMacros: make(map[string]*tagMacroNode), } // Tokenize it - tokens, err := lex(name, tpl) + tokens, err := lex(name, strTpl) if err != nil { return nil, err } @@ -67,11 +87,7 @@ func newTemplate(set *TemplateSet, name string, is_tpl_string bool, tpl string) return t, nil } -func (tpl *Template) execute(context Context) (*bytes.Buffer, error) { - // Create output buffer - // We assume that the rendered template will be 30% larger - buffer := bytes.NewBuffer(make([]byte, 0, int(float64(tpl.size)*1.3))) - +func (tpl *Template) execute(context Context, writer TemplateWriter) error { // Determine the parent to be executed (for template inheritance) parent := tpl for parent.parent != nil { @@ -89,17 +105,17 @@ func (tpl *Template) execute(context Context) (*bytes.Buffer, error) { // Check for context name syntax err := newContext.checkForValidIdentifiers() if err != nil { - return nil, err + return err } // Check for clashes with macro names - for k, _ := range newContext { - _, has := tpl.exported_macros[k] + for k := range newContext { + _, has := tpl.exportedMacros[k] if has { - return nil, &Error{ - Filename: tpl.name, - Sender: "execution", - ErrorMsg: fmt.Sprintf("Context key name '%s' clashes with macro '%s'.", k, k), + return &Error{ + Filename: tpl.name, + Sender: "execution", + OrigError: errors.Errorf("context key name '%s' clashes with macro '%s'", k, k), } } } @@ -110,8 +126,22 @@ func (tpl *Template) execute(context Context) (*bytes.Buffer, error) { ctx := newExecutionContext(parent, newContext) // Run the selected document - err := parent.root.Execute(ctx, buffer) - if err != nil { + if err := parent.root.Execute(ctx, writer); err != nil { + return err + } + + return nil +} + +func (tpl *Template) newTemplateWriterAndExecute(context Context, writer io.Writer) error { + return tpl.execute(context, &templateWriter{w: writer}) +} + +func (tpl *Template) newBufferAndExecute(context Context) (*bytes.Buffer, error) { + // Create output buffer + // We assume that the rendered template will be 30% larger + buffer := bytes.NewBuffer(make([]byte, 0, int(float64(tpl.size)*1.3))) + if err := tpl.execute(context, buffer); err != nil { return nil, err } return buffer, nil @@ -121,30 +151,30 @@ func (tpl *Template) execute(context Context) (*bytes.Buffer, error) { // on success. Context can be nil. Nothing is written on error; instead the error // is being returned. func (tpl *Template) ExecuteWriter(context Context, writer io.Writer) error { - buffer, err := tpl.execute(context) + buf, err := tpl.newBufferAndExecute(context) if err != nil { return err } - - l := buffer.Len() - n, werr := buffer.WriteTo(writer) - if int(n) != l { - panic(fmt.Sprintf("error on writing template: n(%d) != buffer.Len(%d)", n, l)) - } - if werr != nil { - return &Error{ - Filename: tpl.name, - Sender: "execution", - ErrorMsg: werr.Error(), - } + _, err = buf.WriteTo(writer) + if err != nil { + return err } return nil } +// Same as ExecuteWriter. The only difference between both functions is that +// this function might already have written parts of the generated template in the +// case of an execution error because there's no intermediate buffer involved for +// performance reasons. This is handy if you need high performance template +// generation or if you want to manage your own pool of buffers. +func (tpl *Template) ExecuteWriterUnbuffered(context Context, writer io.Writer) error { + return tpl.newTemplateWriterAndExecute(context, writer) +} + // Executes the template and returns the rendered template as a []byte func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) { // Execute template - buffer, err := tpl.execute(context) + buffer, err := tpl.newBufferAndExecute(context) if err != nil { return nil, err } @@ -154,7 +184,7 @@ func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) { // Executes the template and returns the rendered template as a string func (tpl *Template) Execute(context Context) (string, error) { // Execute template - buffer, err := tpl.execute(context) + buffer, err := tpl.newBufferAndExecute(context) if err != nil { return "", err } diff --git a/vendor/github.com/flosch/pongo2/template_loader.go b/vendor/github.com/flosch/pongo2/template_loader.go new file mode 100644 index 0000000..bc80f4a --- /dev/null +++ b/vendor/github.com/flosch/pongo2/template_loader.go @@ -0,0 +1,157 @@ +package pongo2 + +import ( + "bytes" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + + "github.com/juju/errors" +) + +// LocalFilesystemLoader represents a local filesystem loader with basic +// BaseDirectory capabilities. The access to the local filesystem is unrestricted. +type LocalFilesystemLoader struct { + baseDir string +} + +// MustNewLocalFileSystemLoader creates a new LocalFilesystemLoader instance +// and panics if there's any error during instantiation. The parameters +// are the same like NewLocalFileSystemLoader. +func MustNewLocalFileSystemLoader(baseDir string) *LocalFilesystemLoader { + fs, err := NewLocalFileSystemLoader(baseDir) + if err != nil { + log.Panic(err) + } + return fs +} + +// NewLocalFileSystemLoader creates a new LocalFilesystemLoader and allows +// templatesto be loaded from disk (unrestricted). If any base directory +// is given (or being set using SetBaseDir), this base directory is being used +// for path calculation in template inclusions/imports. Otherwise the path +// is calculated based relatively to the including template's path. +func NewLocalFileSystemLoader(baseDir string) (*LocalFilesystemLoader, error) { + fs := &LocalFilesystemLoader{} + if baseDir != "" { + if err := fs.SetBaseDir(baseDir); err != nil { + return nil, err + } + } + return fs, nil +} + +// SetBaseDir sets the template's base directory. This directory will +// be used for any relative path in filters, tags and From*-functions to determine +// your template. See the comment for NewLocalFileSystemLoader as well. +func (fs *LocalFilesystemLoader) SetBaseDir(path string) error { + // Make the path absolute + if !filepath.IsAbs(path) { + abs, err := filepath.Abs(path) + if err != nil { + return err + } + path = abs + } + + // Check for existence + fi, err := os.Stat(path) + if err != nil { + return err + } + if !fi.IsDir() { + return errors.Errorf("The given path '%s' is not a directory.", path) + } + + fs.baseDir = path + return nil +} + +// Get reads the path's content from your local filesystem. +func (fs *LocalFilesystemLoader) Get(path string) (io.Reader, error) { + buf, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + return bytes.NewReader(buf), nil +} + +// Abs resolves a filename relative to the base directory. Absolute paths are allowed. +// When there's no base dir set, the absolute path to the filename +// will be calculated based on either the provided base directory (which +// might be a path of a template which includes another template) or +// the current working directory. +func (fs *LocalFilesystemLoader) Abs(base, name string) string { + if filepath.IsAbs(name) { + return name + } + + // Our own base dir has always priority; if there's none + // we use the path provided in base. + var err error + if fs.baseDir == "" { + if base == "" { + base, err = os.Getwd() + if err != nil { + panic(err) + } + return filepath.Join(base, name) + } + + return filepath.Join(filepath.Dir(base), name) + } + + return filepath.Join(fs.baseDir, name) +} + +// SandboxedFilesystemLoader is still WIP. +type SandboxedFilesystemLoader struct { + *LocalFilesystemLoader +} + +// NewSandboxedFilesystemLoader creates a new sandboxed local file system instance. +func NewSandboxedFilesystemLoader(baseDir string) (*SandboxedFilesystemLoader, error) { + fs, err := NewLocalFileSystemLoader(baseDir) + if err != nil { + return nil, err + } + return &SandboxedFilesystemLoader{ + LocalFilesystemLoader: fs, + }, nil +} + +// Move sandbox to a virtual fs + +/* +if len(set.SandboxDirectories) > 0 { + defer func() { + // Remove any ".." or other crap + resolvedPath = filepath.Clean(resolvedPath) + + // Make the path absolute + absPath, err := filepath.Abs(resolvedPath) + if err != nil { + panic(err) + } + resolvedPath = absPath + + // Check against the sandbox directories (once one pattern matches, we're done and can allow it) + for _, pattern := range set.SandboxDirectories { + matched, err := filepath.Match(pattern, resolvedPath) + if err != nil { + panic("Wrong sandbox directory match pattern (see http://golang.org/pkg/path/filepath/#Match).") + } + if matched { + // OK! + return + } + } + + // No pattern matched, we have to log+deny the request + set.logf("Access attempt outside of the sandbox directories (blocked): '%s'", resolvedPath) + resolvedPath = "" + }() +} +*/ diff --git a/vendor/github.com/flosch/pongo2/template_sets.go b/vendor/github.com/flosch/pongo2/template_sets.go index c582c5d..6b4533c 100644 --- a/vendor/github.com/flosch/pongo2/template_sets.go +++ b/vendor/github.com/flosch/pongo2/template_sets.go @@ -2,48 +2,49 @@ package pongo2 import ( "fmt" + "io" "io/ioutil" "log" "os" - "path/filepath" "sync" + + "github.com/juju/errors" ) -// A template set allows you to create your own group of templates with their own global context (which is shared -// among all members of the set), their own configuration (like a specific base directory) and their own sandbox. -// It's useful for a separation of different kind of templates (e. g. web templates vs. mail templates). +// TemplateLoader allows to implement a virtual file system. +type TemplateLoader interface { + // Abs calculates the path to a given template. Whenever a path must be resolved + // due to an import from another template, the base equals the parent template's path. + Abs(base, name string) string + + // Get returns an io.Reader where the template's content can be read from. + Get(path string) (io.Reader, error) +} + +// TemplateSet allows you to create your own group of templates with their own +// global context (which is shared among all members of the set) and their own +// configuration. +// It's useful for a separation of different kind of templates +// (e. g. web templates vs. mail templates). type TemplateSet struct { - name string + name string + loader TemplateLoader // Globals will be provided to all templates created within this template set Globals Context - // If debug is true (default false), ExecutionContext.Logf() will work and output to STDOUT. Furthermore, - // FromCache() won't cache the templates. Make sure to synchronize the access to it in case you're changing this + // If debug is true (default false), ExecutionContext.Logf() will work and output + // to STDOUT. Furthermore, FromCache() won't cache the templates. + // Make sure to synchronize the access to it in case you're changing this // variable during program execution (and template compilation/execution). Debug bool - // Base directory: If you set the base directory (string is non-empty), all filename lookups in tags/filters are - // relative to this directory. If it's empty, all lookups are relative to the current filename which is importing. - baseDirectory string - // Sandbox features - // - Limit access to directories (using SandboxDirectories) // - Disallow access to specific tags and/or filters (using BanTag() and BanFilter()) // - // You can limit file accesses (for all tags/filters which are using pongo2's file resolver technique) - // to these sandbox directories. All default pongo2 filters/tags are respecting these restrictions. - // For example, if you only have your base directory in the list, a {% ssi "/etc/passwd" %} will not work. - // No items in SandboxDirectories means no restrictions at all. - // - // For efficiency reasons you can ban tags/filters only *before* you have added your first - // template to the set (restrictions are statically checked). After you added one, it's not possible anymore - // (for your personal security). - // - // SandboxDirectories can be changed at runtime. Please synchronize the access to it if you need to change it - // after you've added your first template to the set. You *must* use this match pattern for your directories: - // http://golang.org/pkg/path/filepath/#Match - SandboxDirectories []string + // For efficiency reasons you can ban tags/filters only *before* you have + // added your first template to the set (restrictions are statically checked). + // After you added one, it's not possible anymore (for your personal security). firstTemplateCreated bool bannedTags map[string]bool bannedFilters map[string]bool @@ -53,11 +54,13 @@ type TemplateSet struct { templateCacheMutex sync.Mutex } -// Create your own template sets to separate different kind of templates (e. g. web from mail templates) with -// different globals or other configurations (like base directories). -func NewSet(name string) *TemplateSet { +// NewSet can be used to create sets with different kind of templates +// (e. g. web from mail templates), with different globals or +// other configurations. +func NewSet(name string, loader TemplateLoader) *TemplateSet { return &TemplateSet{ name: name, + loader: loader, Globals: make(Context), bannedTags: make(map[string]bool), bannedFilters: make(map[string]bool), @@ -65,151 +68,157 @@ func NewSet(name string) *TemplateSet { } } -// Use this function to set your template set's base directory. This directory will be used for any relative -// path in filters, tags and From*-functions to determine your template. -func (set *TemplateSet) SetBaseDirectory(name string) error { - // Make the path absolute - if !filepath.IsAbs(name) { - abs, err := filepath.Abs(name) - if err != nil { - return err - } - name = abs +func (set *TemplateSet) resolveFilename(tpl *Template, path string) string { + name := "" + if tpl != nil && tpl.isTplString { + return path } - - // Check for existence - fi, err := os.Stat(name) - if err != nil { - return err + if tpl != nil { + name = tpl.name } - if !fi.IsDir() { - return fmt.Errorf("The given path '%s' is not a directory.") - } - - set.baseDirectory = name - return nil + return set.loader.Abs(name, path) } -func (set *TemplateSet) BaseDirectory() string { - return set.baseDirectory -} - -// Ban a specific tag for this template set. See more in the documentation for TemplateSet. -func (set *TemplateSet) BanTag(name string) { +// BanTag bans a specific tag for this template set. See more in the documentation for TemplateSet. +func (set *TemplateSet) BanTag(name string) error { _, has := tags[name] if !has { - panic(fmt.Sprintf("Tag '%s' not found.", name)) + return errors.Errorf("tag '%s' not found", name) } if set.firstTemplateCreated { - panic("You cannot ban any tags after you've added your first template to your template set.") + return errors.New("you cannot ban any tags after you've added your first template to your template set") } _, has = set.bannedTags[name] if has { - panic(fmt.Sprintf("Tag '%s' is already banned.", name)) + return errors.Errorf("tag '%s' is already banned", name) } set.bannedTags[name] = true + + return nil } -// Ban a specific filter for this template set. See more in the documentation for TemplateSet. -func (set *TemplateSet) BanFilter(name string) { +// BanFilter bans a specific filter for this template set. See more in the documentation for TemplateSet. +func (set *TemplateSet) BanFilter(name string) error { _, has := filters[name] if !has { - panic(fmt.Sprintf("Filter '%s' not found.", name)) + return errors.Errorf("filter '%s' not found", name) } if set.firstTemplateCreated { - panic("You cannot ban any filters after you've added your first template to your template set.") + return errors.New("you cannot ban any filters after you've added your first template to your template set") } _, has = set.bannedFilters[name] if has { - panic(fmt.Sprintf("Filter '%s' is already banned.", name)) + return errors.Errorf("filter '%s' is already banned", name) } set.bannedFilters[name] = true + + return nil } -// FromCache() is a convenient method to cache templates. It is thread-safe +// FromCache is a convenient method to cache templates. It is thread-safe // and will only compile the template associated with a filename once. // If TemplateSet.Debug is true (for example during development phase), // FromCache() will not cache the template and instead recompile it on any // call (to make changes to a template live instantaneously). -// Like FromFile(), FromCache() takes a relative path to a set base directory. -// Sandbox restrictions apply (if given). func (set *TemplateSet) FromCache(filename string) (*Template, error) { if set.Debug { // Recompile on any request return set.FromFile(filename) - } else { - // Cache the template - cleaned_filename := set.resolveFilename(nil, filename) + } + // Cache the template + cleanedFilename := set.resolveFilename(nil, filename) - set.templateCacheMutex.Lock() - defer set.templateCacheMutex.Unlock() + set.templateCacheMutex.Lock() + defer set.templateCacheMutex.Unlock() - tpl, has := set.templateCache[cleaned_filename] + tpl, has := set.templateCache[cleanedFilename] - // Cache miss - if !has { - tpl, err := set.FromFile(cleaned_filename) - if err != nil { - return nil, err - } - set.templateCache[cleaned_filename] = tpl - return tpl, nil + // Cache miss + if !has { + tpl, err := set.FromFile(cleanedFilename) + if err != nil { + return nil, err } - - // Cache hit + set.templateCache[cleanedFilename] = tpl return tpl, nil } + + // Cache hit + return tpl, nil } -// Loads a template from string and returns a Template instance. +// FromString loads a template from string and returns a Template instance. func (set *TemplateSet) FromString(tpl string) (*Template, error) { set.firstTemplateCreated = true + return newTemplateString(set, []byte(tpl)) +} + +// FromBytes loads a template from bytes and returns a Template instance. +func (set *TemplateSet) FromBytes(tpl []byte) (*Template, error) { + set.firstTemplateCreated = true + return newTemplateString(set, tpl) } -// Loads a template from a filename and returns a Template instance. -// If a base directory is set, the filename must be either relative to it -// or be an absolute path. Sandbox restrictions (SandboxDirectories) apply -// if given. +// FromFile loads a template from a filename and returns a Template instance. func (set *TemplateSet) FromFile(filename string) (*Template, error) { set.firstTemplateCreated = true - buf, err := ioutil.ReadFile(set.resolveFilename(nil, filename)) + fd, err := set.loader.Get(set.resolveFilename(nil, filename)) if err != nil { return nil, &Error{ - Filename: filename, - Sender: "fromfile", - ErrorMsg: err.Error(), + Filename: filename, + Sender: "fromfile", + OrigError: err, } } - return newTemplate(set, filename, false, string(buf)) + buf, err := ioutil.ReadAll(fd) + if err != nil { + return nil, &Error{ + Filename: filename, + Sender: "fromfile", + OrigError: err, + } + } + + return newTemplate(set, filename, false, buf) } -// Shortcut; renders a template string directly. Panics when providing a -// malformed template or an error occurs during execution. -func (set *TemplateSet) RenderTemplateString(s string, ctx Context) string { +// RenderTemplateString is a shortcut and renders a template string directly. +func (set *TemplateSet) RenderTemplateString(s string, ctx Context) (string, error) { set.firstTemplateCreated = true tpl := Must(set.FromString(s)) result, err := tpl.Execute(ctx) if err != nil { - panic(err) + return "", err } - return result + return result, nil } -// Shortcut; renders a template file directly. Panics when providing a -// malformed template or an error occurs during execution. -func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) string { +// RenderTemplateBytes is a shortcut and renders template bytes directly. +func (set *TemplateSet) RenderTemplateBytes(b []byte, ctx Context) (string, error) { + set.firstTemplateCreated = true + + tpl := Must(set.FromBytes(b)) + result, err := tpl.Execute(ctx) + if err != nil { + return "", err + } + return result, nil +} + +// RenderTemplateFile is a shortcut and renders a template file directly. +func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) (string, error) { set.firstTemplateCreated = true tpl := Must(set.FromFile(fn)) result, err := tpl.Execute(ctx) if err != nil { - panic(err) + return "", err } - return result + return result, nil } func (set *TemplateSet) logf(format string, args ...interface{}) { @@ -218,58 +227,6 @@ func (set *TemplateSet) logf(format string, args ...interface{}) { } } -// Resolves a filename relative to the base directory. Absolute paths are allowed. -// If sandbox restrictions are given (SandboxDirectories), they will be respected and checked. -// On sandbox restriction violation, resolveFilename() panics. -func (set *TemplateSet) resolveFilename(tpl *Template, filename string) (resolved_path string) { - if len(set.SandboxDirectories) > 0 { - defer func() { - // Remove any ".." or other crap - resolved_path = filepath.Clean(resolved_path) - - // Make the path absolute - abs_path, err := filepath.Abs(resolved_path) - if err != nil { - panic(err) - } - resolved_path = abs_path - - // Check against the sandbox directories (once one pattern matches, we're done and can allow it) - for _, pattern := range set.SandboxDirectories { - matched, err := filepath.Match(pattern, resolved_path) - if err != nil { - panic("Wrong sandbox directory match pattern (see http://golang.org/pkg/path/filepath/#Match).") - } - if matched { - // OK! - return - } - } - - // No pattern matched, we have to log+deny the request - set.logf("Access attempt outside of the sandbox directories (blocked): '%s'", resolved_path) - resolved_path = "" - }() - } - - if filepath.IsAbs(filename) { - return filename - } - - if set.baseDirectory == "" { - if tpl != nil { - if tpl.is_tpl_string { - return filename - } - base := filepath.Dir(tpl.name) - return filepath.Join(base, filename) - } - return filename - } else { - return filepath.Join(set.baseDirectory, filename) - } -} - // Logging function (internally used) func logf(format string, items ...interface{}) { if debug { @@ -279,13 +236,18 @@ func logf(format string, items ...interface{}) { var ( debug bool // internal debugging - logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags) + logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags|log.Lshortfile) - // Creating a default set - DefaultSet = NewSet("default") + // DefaultLoader allows the default un-sandboxed access to the local file + // system and is being used by the DefaultSet. + DefaultLoader = MustNewLocalFileSystemLoader("") + + // DefaultSet is a set created for you for convinience reasons. + DefaultSet = NewSet("default", DefaultLoader) // Methods on the default set FromString = DefaultSet.FromString + FromBytes = DefaultSet.FromBytes FromFile = DefaultSet.FromFile FromCache = DefaultSet.FromCache RenderTemplateString = DefaultSet.RenderTemplateString diff --git a/vendor/github.com/flosch/pongo2/value.go b/vendor/github.com/flosch/pongo2/value.go index 8cf8552..df70bbc 100644 --- a/vendor/github.com/flosch/pongo2/value.go +++ b/vendor/github.com/flosch/pongo2/value.go @@ -3,6 +3,7 @@ package pongo2 import ( "fmt" "reflect" + "sort" "strconv" "strings" ) @@ -12,7 +13,7 @@ type Value struct { safe bool // used to indicate whether a Value needs explicit escaping in the template } -// Converts any given value to a pongo2.Value +// AsValue converts any given value to a pongo2.Value // Usually being used within own functions passed to a template // through a Context or within filter functions. // @@ -24,7 +25,7 @@ func AsValue(i interface{}) *Value { } } -// Like AsValue, but does not apply the 'escape' filter. +// AsSafeValue works like AsValue, but does not apply the 'escape' filter. func AsSafeValue(i interface{}) *Value { return &Value{ val: reflect.ValueOf(i), @@ -39,23 +40,23 @@ func (v *Value) getResolvedValue() reflect.Value { return v.val } -// Checks whether the underlying value is a string +// IsString checks whether the underlying value is a string func (v *Value) IsString() bool { return v.getResolvedValue().Kind() == reflect.String } -// Checks whether the underlying value is a bool +// IsBool checks whether the underlying value is a bool func (v *Value) IsBool() bool { return v.getResolvedValue().Kind() == reflect.Bool } -// Checks whether the underlying value is a float +// IsFloat checks whether the underlying value is a float func (v *Value) IsFloat() bool { return v.getResolvedValue().Kind() == reflect.Float32 || v.getResolvedValue().Kind() == reflect.Float64 } -// Checks whether the underlying value is an integer +// IsInteger checks whether the underlying value is an integer func (v *Value) IsInteger() bool { return v.getResolvedValue().Kind() == reflect.Int || v.getResolvedValue().Kind() == reflect.Int8 || @@ -69,19 +70,19 @@ func (v *Value) IsInteger() bool { v.getResolvedValue().Kind() == reflect.Uint64 } -// Checks whether the underlying value is either an integer +// IsNumber checks whether the underlying value is either an integer // or a float. func (v *Value) IsNumber() bool { return v.IsInteger() || v.IsFloat() } -// Checks whether the underlying value is NIL +// IsNil checks whether the underlying value is NIL func (v *Value) IsNil() bool { //fmt.Printf("%+v\n", v.getResolvedValue().Type().String()) return !v.getResolvedValue().IsValid() } -// Returns a string for the underlying value. If this value is not +// String returns a string for the underlying value. If this value is not // of type string, pongo2 tries to convert it. Currently the following // types for underlying values are supported: // @@ -111,9 +112,8 @@ func (v *Value) String() string { case reflect.Bool: if v.Bool() { return "True" - } else { - return "False" } + return "False" case reflect.Struct: if t, ok := v.Interface().(fmt.Stringer); ok { return t.String() @@ -124,7 +124,7 @@ func (v *Value) String() string { return v.getResolvedValue().String() } -// Returns the underlying value as an integer (converts the underlying +// Integer returns the underlying value as an integer (converts the underlying // value, if necessary). If it's not possible to convert the underlying value, // it will return 0. func (v *Value) Integer() int { @@ -148,7 +148,7 @@ func (v *Value) Integer() int { } } -// Returns the underlying value as a float (converts the underlying +// Float returns the underlying value as a float (converts the underlying // value, if necessary). If it's not possible to convert the underlying value, // it will return 0.0. func (v *Value) Float() float64 { @@ -172,7 +172,7 @@ func (v *Value) Float() float64 { } } -// Returns the underlying value as bool. If the value is not bool, false +// Bool returns the underlying value as bool. If the value is not bool, false // will always be returned. If you're looking for true/false-evaluation of the // underlying value, have a look on the IsTrue()-function. func (v *Value) Bool() bool { @@ -185,7 +185,7 @@ func (v *Value) Bool() bool { } } -// Tries to evaluate the underlying value the Pythonic-way: +// IsTrue tries to evaluate the underlying value the Pythonic-way: // // Returns TRUE in one the following cases: // @@ -217,7 +217,7 @@ func (v *Value) IsTrue() bool { } } -// Tries to negate the underlying value. It's mainly used for +// Negate tries to negate the underlying value. It's mainly used for // the NOT-operator and in conjunction with a call to // return_value.IsTrue() afterwards. // @@ -229,26 +229,26 @@ func (v *Value) Negate() *Value { reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if v.Integer() != 0 { return AsValue(0) - } else { - return AsValue(1) } + return AsValue(1) case reflect.Float32, reflect.Float64: if v.Float() != 0.0 { return AsValue(float64(0.0)) - } else { - return AsValue(float64(1.1)) } + return AsValue(float64(1.1)) case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: return AsValue(v.getResolvedValue().Len() == 0) case reflect.Bool: return AsValue(!v.getResolvedValue().Bool()) + case reflect.Struct: + return AsValue(false) default: logf("Value.IsTrue() not available for type: %s\n", v.getResolvedValue().Kind().String()) return AsValue(true) } } -// Returns the length for an array, chan, map, slice or string. +// Len returns the length for an array, chan, map, slice or string. // Otherwise it will return 0. func (v *Value) Len() int { switch v.getResolvedValue().Kind() { @@ -263,7 +263,7 @@ func (v *Value) Len() int { } } -// Slices an array, slice or string. Otherwise it will +// Slice slices an array, slice or string. Otherwise it will // return an empty []int. func (v *Value) Slice(i, j int) *Value { switch v.getResolvedValue().Kind() { @@ -278,7 +278,7 @@ func (v *Value) Slice(i, j int) *Value { } } -// Get the i-th item of an array, slice or string. Otherwise +// Index gets the i-th item of an array, slice or string. Otherwise // it will return NIL. func (v *Value) Index(i int) *Value { switch v.getResolvedValue().Kind() { @@ -301,7 +301,7 @@ func (v *Value) Index(i int) *Value { } } -// Checks whether the underlying value (which must be of type struct, map, +// Contains checks whether the underlying value (which must be of type struct, map, // string, array or slice) contains of another Value (e. g. used to check // whether a struct contains of a specific field or a map contains a specific key). // @@ -310,25 +310,32 @@ func (v *Value) Index(i int) *Value { func (v *Value) Contains(other *Value) bool { switch v.getResolvedValue().Kind() { case reflect.Struct: - field_value := v.getResolvedValue().FieldByName(other.String()) - return field_value.IsValid() + fieldValue := v.getResolvedValue().FieldByName(other.String()) + return fieldValue.IsValid() case reflect.Map: - var map_value reflect.Value + var mapValue reflect.Value switch other.Interface().(type) { case int: - map_value = v.getResolvedValue().MapIndex(other.getResolvedValue()) + mapValue = v.getResolvedValue().MapIndex(other.getResolvedValue()) case string: - map_value = v.getResolvedValue().MapIndex(other.getResolvedValue()) + mapValue = v.getResolvedValue().MapIndex(other.getResolvedValue()) default: logf("Value.Contains() does not support lookup type '%s'\n", other.getResolvedValue().Kind().String()) return false } - return map_value.IsValid() + return mapValue.IsValid() case reflect.String: return strings.Contains(v.getResolvedValue().String(), other.String()) - // TODO: reflect.Array, reflect.Slice + case reflect.Slice, reflect.Array: + for i := 0; i < v.getResolvedValue().Len(); i++ { + item := v.getResolvedValue().Index(i) + if other.Interface() == item.Interface() { + return true + } + } + return false default: logf("Value.Contains() not available for type: %s\n", v.getResolvedValue().Kind().String()) @@ -336,7 +343,7 @@ func (v *Value) Contains(other *Value) bool { } } -// Checks whether the underlying value is of type array, slice or string. +// CanSlice checks whether the underlying value is of type array, slice or string. // You normally would use CanSlice() before using the Slice() operation. func (v *Value) CanSlice() bool { switch v.getResolvedValue().Kind() { @@ -346,7 +353,7 @@ func (v *Value) CanSlice() bool { return false } -// Iterates over a map, array, slice or a string. It calls the +// Iterate iterates over a map, array, slice or a string. It calls the // function's first argument for every value with the following arguments: // // idx current 0-index @@ -357,16 +364,23 @@ func (v *Value) CanSlice() bool { // If the underlying value has no items or is not one of the types above, // the empty function (function's second argument) will be called. func (v *Value) Iterate(fn func(idx, count int, key, value *Value) bool, empty func()) { - v.IterateOrder(fn, empty, false) + v.IterateOrder(fn, empty, false, false) } -// Like Value.Iterate, but can iterate through an array/slice/string in reverse. Does +// IterateOrder behaves like Value.Iterate, but can iterate through an array/slice/string in reverse. Does // not affect the iteration through a map because maps don't have any particular order. -func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, empty func(), reverse bool) { +// However, you can force an order using the `sorted` keyword (and even use `reversed sorted`). +func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, empty func(), reverse bool, sorted bool) { switch v.getResolvedValue().Kind() { case reflect.Map: - // Reverse not needed for maps, since they are not ordered - keys := v.getResolvedValue().MapKeys() + keys := sortedKeys(v.getResolvedValue().MapKeys()) + if sorted { + if reverse { + sort.Sort(sort.Reverse(keys)) + } else { + sort.Sort(keys) + } + } keyLen := len(keys) for idx, key := range keys { value := v.getResolvedValue().MapIndex(key) @@ -379,19 +393,31 @@ func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, em } return // done case reflect.Array, reflect.Slice: + var items valuesList + itemCount := v.getResolvedValue().Len() - if itemCount > 0 { + for i := 0; i < itemCount; i++ { + items = append(items, &Value{val: v.getResolvedValue().Index(i)}) + } + + if sorted { if reverse { - for i := itemCount - 1; i >= 0; i-- { - if !fn(i, itemCount, &Value{val: v.getResolvedValue().Index(i)}, nil) { - return - } - } + sort.Sort(sort.Reverse(items)) } else { - for i := 0; i < itemCount; i++ { - if !fn(i, itemCount, &Value{val: v.getResolvedValue().Index(i)}, nil) { - return - } + sort.Sort(items) + } + } else { + if reverse { + for i := 0; i < itemCount/2; i++ { + items[i], items[itemCount-1-i] = items[itemCount-1-i], items[i] + } + } + } + + if len(items) > 0 { + for idx, item := range items { + if !fn(idx, itemCount, item, nil) { + return } } } else { @@ -399,7 +425,12 @@ func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, em } return // done case reflect.String: - // TODO: Not utf8-compatible (utf8-decoding neccessary) + if sorted { + // TODO(flosch): Handle sorted + panic("TODO: handle sort for type string") + } + + // TODO(flosch): Not utf8-compatible (utf8-decoding necessary) charCount := v.getResolvedValue().Len() if charCount > 0 { if reverse { @@ -425,7 +456,7 @@ func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, em empty() } -// Gives you access to the underlying value. +// Interface gives you access to the underlying value. func (v *Value) Interface() interface{} { if v.val.IsValid() { return v.val.Interface() @@ -433,7 +464,57 @@ func (v *Value) Interface() interface{} { return nil } -// Checks whether two values are containing the same value or object. +// EqualValueTo checks whether two values are containing the same value or object. func (v *Value) EqualValueTo(other *Value) bool { + // comparison of uint with int fails using .Interface()-comparison (see issue #64) + if v.IsInteger() && other.IsInteger() { + return v.Integer() == other.Integer() + } return v.Interface() == other.Interface() } + +type sortedKeys []reflect.Value + +func (sk sortedKeys) Len() int { + return len(sk) +} + +func (sk sortedKeys) Less(i, j int) bool { + vi := &Value{val: sk[i]} + vj := &Value{val: sk[j]} + switch { + case vi.IsInteger() && vj.IsInteger(): + return vi.Integer() < vj.Integer() + case vi.IsFloat() && vj.IsFloat(): + return vi.Float() < vj.Float() + default: + return vi.String() < vj.String() + } +} + +func (sk sortedKeys) Swap(i, j int) { + sk[i], sk[j] = sk[j], sk[i] +} + +type valuesList []*Value + +func (vl valuesList) Len() int { + return len(vl) +} + +func (vl valuesList) Less(i, j int) bool { + vi := vl[i] + vj := vl[j] + switch { + case vi.IsInteger() && vj.IsInteger(): + return vi.Integer() < vj.Integer() + case vi.IsFloat() && vj.IsFloat(): + return vi.Float() < vj.Float() + default: + return vi.String() < vj.String() + } +} + +func (vl valuesList) Swap(i, j int) { + vl[i], vl[j] = vl[j], vl[i] +} diff --git a/vendor/github.com/flosch/pongo2/variable.go b/vendor/github.com/flosch/pongo2/variable.go index 9ec6a59..4a1ee69 100644 --- a/vendor/github.com/flosch/pongo2/variable.go +++ b/vendor/github.com/flosch/pongo2/variable.go @@ -1,11 +1,12 @@ package pongo2 import ( - "bytes" "fmt" "reflect" "strconv" "strings" + + "github.com/juju/errors" ) const ( @@ -13,13 +14,18 @@ const ( varTypeIdent ) +var ( + typeOfValuePtr = reflect.TypeOf(new(Value)) + typeOfExecCtxPtr = reflect.TypeOf(new(ExecutionContext)) +) + type variablePart struct { typ int s string i int - is_function_call bool - calling_args []functionCallArgument // needed for a function call, represents all argument nodes (INode supports nested function calls) + isFunctionCall bool + callingArgs []functionCallArgument // needed for a function call, represents all argument nodes (INode supports nested function calls) } type functionCallArgument interface { @@ -28,119 +34,121 @@ type functionCallArgument interface { // TODO: Add location tokens type stringResolver struct { - location_token *Token - val string + locationToken *Token + val string } type intResolver struct { - location_token *Token - val int + locationToken *Token + val int } type floatResolver struct { - location_token *Token - val float64 + locationToken *Token + val float64 } type boolResolver struct { - location_token *Token - val bool + locationToken *Token + val bool } type variableResolver struct { - location_token *Token + locationToken *Token parts []*variablePart } type nodeFilteredVariable struct { - location_token *Token + locationToken *Token resolver IEvaluator filterChain []*filterCall } type nodeVariable struct { - location_token *Token - expr IEvaluator + locationToken *Token + expr IEvaluator } -func (expr *nodeFilteredVariable) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { - value, err := expr.Evaluate(ctx) +type executionCtxEval struct{} + +func (v *nodeFilteredVariable) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + value, err := v.Evaluate(ctx) if err != nil { return err } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } -func (expr *variableResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { - value, err := expr.Evaluate(ctx) +func (vr *variableResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + value, err := vr.Evaluate(ctx) if err != nil { return err } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } -func (expr *stringResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { - value, err := expr.Evaluate(ctx) +func (s *stringResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + value, err := s.Evaluate(ctx) if err != nil { return err } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } -func (expr *intResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { - value, err := expr.Evaluate(ctx) +func (i *intResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + value, err := i.Evaluate(ctx) if err != nil { return err } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } -func (expr *floatResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { - value, err := expr.Evaluate(ctx) +func (f *floatResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + value, err := f.Evaluate(ctx) if err != nil { return err } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } -func (expr *boolResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { - value, err := expr.Evaluate(ctx) +func (b *boolResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + value, err := b.Evaluate(ctx) if err != nil { return err } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } func (v *nodeFilteredVariable) GetPositionToken() *Token { - return v.location_token + return v.locationToken } -func (v *variableResolver) GetPositionToken() *Token { - return v.location_token +func (vr *variableResolver) GetPositionToken() *Token { + return vr.locationToken } -func (v *stringResolver) GetPositionToken() *Token { - return v.location_token +func (s *stringResolver) GetPositionToken() *Token { + return s.locationToken } -func (v *intResolver) GetPositionToken() *Token { - return v.location_token +func (i *intResolver) GetPositionToken() *Token { + return i.locationToken } -func (v *floatResolver) GetPositionToken() *Token { - return v.location_token +func (f *floatResolver) GetPositionToken() *Token { + return f.locationToken } -func (v *boolResolver) GetPositionToken() *Token { - return v.location_token +func (b *boolResolver) GetPositionToken() *Token { + return b.locationToken } func (s *stringResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) { @@ -179,7 +187,7 @@ func (nv *nodeVariable) FilterApplied(name string) bool { return nv.expr.FilterApplied(name) } -func (nv *nodeVariable) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { +func (nv *nodeVariable) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { value, err := nv.expr.Evaluate(ctx) if err != nil { return err @@ -193,10 +201,14 @@ func (nv *nodeVariable) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Er } } - buffer.WriteString(value.String()) + writer.WriteString(value.String()) return nil } +func (executionCtxEval) Evaluate(ctx *ExecutionContext) (*Value, *Error) { + return AsValue(ctx), nil +} + func (vr *variableResolver) FilterApplied(name string) bool { return false } @@ -218,15 +230,15 @@ func (vr *variableResolver) String() string { func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) { var current reflect.Value - var is_safe bool + var isSafe bool for idx, part := range vr.parts { if idx == 0 { // We're looking up the first part of the variable. // First we're having a look in our private // context (e. g. information provided by tags, like the forloop) - val, in_private := ctx.Private[vr.parts[0].s] - if !in_private { + val, inPrivate := ctx.Private[vr.parts[0].s] + if !inPrivate { // Nothing found? Then have a final lookup in the public context val = ctx.Public[vr.parts[0].s] } @@ -236,16 +248,16 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) { // Before resolving the pointer, let's see if we have a method to call // Problem with resolving the pointer is we're changing the receiver - is_func := false + isFunc := false if part.typ == varTypeIdent { - func_value := current.MethodByName(part.s) - if func_value.IsValid() { - current = func_value - is_func = true + funcValue := current.MethodByName(part.s) + if funcValue.IsValid() { + current = funcValue + isFunc = true } } - if !is_func { + if !isFunc { // If current a pointer, resolve it if current.Kind() == reflect.Ptr { current = current.Elem() @@ -262,9 +274,14 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) { // * slices/arrays/strings switch current.Kind() { case reflect.String, reflect.Array, reflect.Slice: - current = current.Index(part.i) + if part.i >= 0 && current.Len() > part.i { + current = current.Index(part.i) + } else { + // In Django, exceeding the length of a list is just empty. + return AsValue(nil), nil + } default: - return nil, fmt.Errorf("Can't access an index on type %s (variable %s)", + return nil, errors.Errorf("Can't access an index on type %s (variable %s)", current.Kind().String(), vr.String()) } case varTypeIdent: @@ -278,7 +295,7 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) { case reflect.Map: current = current.MapIndex(reflect.ValueOf(part.s)) default: - return nil, fmt.Errorf("Can't access a field by name on type %s (variable %s)", + return nil, errors.Errorf("Can't access a field by name on type %s (variable %s)", current.Kind().String(), vr.String()) } default: @@ -295,10 +312,10 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) { // If current is a reflect.ValueOf(pongo2.Value), then unpack it // Happens in function calls (as a return value) or by injecting // into the execution context (e.g. in a for-loop) - if current.Type() == reflect.TypeOf(&Value{}) { - tmp_value := current.Interface().(*Value) - current = tmp_value.val - is_safe = tmp_value.safe + if current.Type() == typeOfValuePtr { + tmpValue := current.Interface().(*Value) + current = tmpValue.val + isSafe = tmpValue.safe } // Check whether this is an interface and resolve it where required @@ -307,69 +324,73 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) { } // Check if the part is a function call - if part.is_function_call || current.Kind() == reflect.Func { + if part.isFunctionCall || current.Kind() == reflect.Func { // Check for callable if current.Kind() != reflect.Func { - return nil, fmt.Errorf("'%s' is not a function (it is %s).", vr.String(), current.Kind().String()) + return nil, errors.Errorf("'%s' is not a function (it is %s)", vr.String(), current.Kind().String()) } // Check for correct function syntax and types // func(*Value, ...) *Value t := current.Type() + currArgs := part.callingArgs + + // If an implicit ExecCtx is needed + if t.NumIn() > 0 && t.In(0) == typeOfExecCtxPtr { + currArgs = append([]functionCallArgument{executionCtxEval{}}, currArgs...) + } // Input arguments - if len(part.calling_args) != t.NumIn() && !(len(part.calling_args) >= t.NumIn()-1 && t.IsVariadic()) { + if len(currArgs) != t.NumIn() && !(len(currArgs) >= t.NumIn()-1 && t.IsVariadic()) { return nil, - fmt.Errorf("Function input argument count (%d) of '%s' must be equal to the calling argument count (%d).", - t.NumIn(), vr.String(), len(part.calling_args)) + errors.Errorf("Function input argument count (%d) of '%s' must be equal to the calling argument count (%d).", + t.NumIn(), vr.String(), len(currArgs)) } // Output arguments if t.NumOut() != 1 { - return nil, fmt.Errorf("'%s' must have exactly 1 output argument.", vr.String()) + return nil, errors.Errorf("'%s' must have exactly 1 output argument", vr.String()) } // Evaluate all parameters - parameters := make([]reflect.Value, 0) + var parameters []reflect.Value - num_args := t.NumIn() - is_variadic := t.IsVariadic() - var fn_arg reflect.Type + numArgs := t.NumIn() + isVariadic := t.IsVariadic() + var fnArg reflect.Type - for idx, arg := range part.calling_args { + for idx, arg := range currArgs { pv, err := arg.Evaluate(ctx) if err != nil { return nil, err } - if is_variadic { + if isVariadic { if idx >= t.NumIn()-1 { - fn_arg = t.In(num_args - 1).Elem() + fnArg = t.In(numArgs - 1).Elem() } else { - fn_arg = t.In(idx) + fnArg = t.In(idx) } } else { - fn_arg = t.In(idx) + fnArg = t.In(idx) } - if fn_arg != reflect.TypeOf(new(Value)) { + if fnArg != typeOfValuePtr { // Function's argument is not a *pongo2.Value, then we have to check whether input argument is of the same type as the function's argument - if !is_variadic { - if fn_arg != reflect.TypeOf(pv.Interface()) && fn_arg.Kind() != reflect.Interface { - return nil, fmt.Errorf("Function input argument %d of '%s' must be of type %s or *pongo2.Value (not %T).", - idx, vr.String(), fn_arg.String(), pv.Interface()) - } else { - // Function's argument has another type, using the interface-value - parameters = append(parameters, reflect.ValueOf(pv.Interface())) + if !isVariadic { + if fnArg != reflect.TypeOf(pv.Interface()) && fnArg.Kind() != reflect.Interface { + return nil, errors.Errorf("Function input argument %d of '%s' must be of type %s or *pongo2.Value (not %T).", + idx, vr.String(), fnArg.String(), pv.Interface()) } + // Function's argument has another type, using the interface-value + parameters = append(parameters, reflect.ValueOf(pv.Interface())) } else { - if fn_arg != reflect.TypeOf(pv.Interface()) && fn_arg.Kind() != reflect.Interface { - return nil, fmt.Errorf("Function variadic input argument of '%s' must be of type %s or *pongo2.Value (not %T).", - vr.String(), fn_arg.String(), pv.Interface()) - } else { - // Function's argument has another type, using the interface-value - parameters = append(parameters, reflect.ValueOf(pv.Interface())) + if fnArg != reflect.TypeOf(pv.Interface()) && fnArg.Kind() != reflect.Interface { + return nil, errors.Errorf("Function variadic input argument of '%s' must be of type %s or *pongo2.Value (not %T).", + vr.String(), fnArg.String(), pv.Interface()) } + // Function's argument has another type, using the interface-value + parameters = append(parameters, reflect.ValueOf(pv.Interface())) } } else { // Function's argument is a *pongo2.Value @@ -377,31 +398,38 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) { } } + // Check if any of the values are invalid + for _, p := range parameters { + if p.Kind() == reflect.Invalid { + return nil, errors.Errorf("Calling a function using an invalid parameter") + } + } + // Call it and get first return parameter back rv := current.Call(parameters)[0] - if rv.Type() != reflect.TypeOf(new(Value)) { + if rv.Type() != typeOfValuePtr { current = reflect.ValueOf(rv.Interface()) } else { // Return the function call value current = rv.Interface().(*Value).val - is_safe = rv.Interface().(*Value).safe + isSafe = rv.Interface().(*Value).safe } } + + if !current.IsValid() { + // Value is not valid (e. g. NIL value) + return AsValue(nil), nil + } } - if !current.IsValid() { - // Value is not valid (e. g. NIL value) - return AsValue(nil), nil - } - - return &Value{val: current, safe: is_safe}, nil + return &Value{val: current, safe: isSafe}, nil } func (vr *variableResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) { value, err := vr.resolve(ctx) if err != nil { - return AsValue(nil), ctx.Error(err.Error(), vr.location_token) + return AsValue(nil), ctx.Error(err.Error(), vr.locationToken) } return value, nil } @@ -436,7 +464,7 @@ func (p *Parser) parseVariableOrLiteral() (IEvaluator, *Error) { t := p.Current() if t == nil { - return nil, p.Error("Unexpected EOF, expected a number, string, keyword or identifier.", p.last_token) + return nil, p.Error("Unexpected EOF, expected a number, string, keyword or identifier.", p.lastToken) } // Is first part a number or a string, there's nothing to resolve (because there's only to return the value then) @@ -460,26 +488,26 @@ func (p *Parser) parseVariableOrLiteral() (IEvaluator, *Error) { return nil, p.Error(err.Error(), t) } fr := &floatResolver{ - location_token: t, - val: f, + locationToken: t, + val: f, } return fr, nil - } else { - i, err := strconv.Atoi(t.Val) - if err != nil { - return nil, p.Error(err.Error(), t) - } - nr := &intResolver{ - location_token: t, - val: i, - } - return nr, nil } + i, err := strconv.Atoi(t.Val) + if err != nil { + return nil, p.Error(err.Error(), t) + } + nr := &intResolver{ + locationToken: t, + val: i, + } + return nr, nil + case TokenString: p.Consume() sr := &stringResolver{ - location_token: t, - val: t.Val, + locationToken: t, + val: t.Val, } return sr, nil case TokenKeyword: @@ -487,14 +515,14 @@ func (p *Parser) parseVariableOrLiteral() (IEvaluator, *Error) { switch t.Val { case "true": br := &boolResolver{ - location_token: t, - val: true, + locationToken: t, + val: true, } return br, nil case "false": br := &boolResolver{ - location_token: t, - val: false, + locationToken: t, + val: false, } return br, nil default: @@ -503,7 +531,7 @@ func (p *Parser) parseVariableOrLiteral() (IEvaluator, *Error) { } resolver := &variableResolver{ - location_token: t, + locationToken: t, } // First part of a variable MUST be an identifier @@ -551,26 +579,26 @@ variableLoop: } else { // EOF return nil, p.Error("Unexpected EOF, expected either IDENTIFIER or NUMBER after DOT.", - p.last_token) + p.lastToken) } } else if p.Match(TokenSymbol, "(") != nil { // Function call // FunctionName '(' Comma-separated list of expressions ')' part := resolver.parts[len(resolver.parts)-1] - part.is_function_call = true + part.isFunctionCall = true argumentLoop: for { if p.Remaining() == 0 { - return nil, p.Error("Unexpected EOF, expected function call argument list.", p.last_token) + return nil, p.Error("Unexpected EOF, expected function call argument list.", p.lastToken) } if p.Peek(TokenSymbol, ")") == nil { // No closing bracket, so we're parsing an expression - expr_arg, err := p.ParseExpression() + exprArg, err := p.ParseExpression() if err != nil { return nil, err } - part.calling_args = append(part.calling_args, expr_arg) + part.callingArgs = append(part.callingArgs, exprArg) if p.Match(TokenSymbol, ")") != nil { // If there's a closing bracket after an expression, we will stop parsing the arguments @@ -601,7 +629,7 @@ variableLoop: func (p *Parser) parseVariableOrLiteralWithFilter() (*nodeFilteredVariable, *Error) { v := &nodeFilteredVariable{ - location_token: p.Current(), + locationToken: p.Current(), } // Parse the variable name @@ -621,15 +649,13 @@ filterLoop: } // Check sandbox filter restriction - if _, is_banned := p.template.set.bannedFilters[filter.name]; is_banned { + if _, isBanned := p.template.set.bannedFilters[filter.name]; isBanned { return nil, p.Error(fmt.Sprintf("Usage of filter '%s' is not allowed (sandbox restriction active).", filter.name), nil) } v.filterChain = append(v.filterChain, filter) continue filterLoop - - return nil, p.Error("This token is not allowed within a variable.", nil) } return v, nil @@ -637,7 +663,7 @@ filterLoop: func (p *Parser) parseVariableElement() (INode, *Error) { node := &nodeVariable{ - location_token: p.Current(), + locationToken: p.Current(), } p.Consume() // consume '{{' diff --git a/vendor/github.com/gorilla/context/.travis.yml b/vendor/github.com/gorilla/context/.travis.yml deleted file mode 100644 index 6f440f1..0000000 --- a/vendor/github.com/gorilla/context/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: go -sudo: false - -matrix: - include: - - go: 1.3 - - go: 1.4 - - go: 1.5 - - go: 1.6 - - go: 1.7 - - go: tip - allow_failures: - - go: tip - -script: - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d .) - - go vet $(go list ./... | grep -v /vendor/) - - go test -v -race ./... diff --git a/vendor/github.com/gorilla/context/LICENSE b/vendor/github.com/gorilla/context/LICENSE deleted file mode 100644 index 0e5fb87..0000000 --- a/vendor/github.com/gorilla/context/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 Rodrigo Moraes. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gorilla/context/README.md b/vendor/github.com/gorilla/context/README.md deleted file mode 100644 index 08f8669..0000000 --- a/vendor/github.com/gorilla/context/README.md +++ /dev/null @@ -1,10 +0,0 @@ -context -======= -[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context) - -gorilla/context is a general purpose registry for global request variables. - -> Note: gorilla/context, having been born well before `context.Context` existed, does not play well -> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`. - -Read the full documentation here: http://www.gorillatoolkit.org/pkg/context diff --git a/vendor/github.com/gorilla/context/context.go b/vendor/github.com/gorilla/context/context.go deleted file mode 100644 index 81cb128..0000000 --- a/vendor/github.com/gorilla/context/context.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package context - -import ( - "net/http" - "sync" - "time" -) - -var ( - mutex sync.RWMutex - data = make(map[*http.Request]map[interface{}]interface{}) - datat = make(map[*http.Request]int64) -) - -// Set stores a value for a given key in a given request. -func Set(r *http.Request, key, val interface{}) { - mutex.Lock() - if data[r] == nil { - data[r] = make(map[interface{}]interface{}) - datat[r] = time.Now().Unix() - } - data[r][key] = val - mutex.Unlock() -} - -// Get returns a value stored for a given key in a given request. -func Get(r *http.Request, key interface{}) interface{} { - mutex.RLock() - if ctx := data[r]; ctx != nil { - value := ctx[key] - mutex.RUnlock() - return value - } - mutex.RUnlock() - return nil -} - -// GetOk returns stored value and presence state like multi-value return of map access. -func GetOk(r *http.Request, key interface{}) (interface{}, bool) { - mutex.RLock() - if _, ok := data[r]; ok { - value, ok := data[r][key] - mutex.RUnlock() - return value, ok - } - mutex.RUnlock() - return nil, false -} - -// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. -func GetAll(r *http.Request) map[interface{}]interface{} { - mutex.RLock() - if context, ok := data[r]; ok { - result := make(map[interface{}]interface{}, len(context)) - for k, v := range context { - result[k] = v - } - mutex.RUnlock() - return result - } - mutex.RUnlock() - return nil -} - -// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if -// the request was registered. -func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { - mutex.RLock() - context, ok := data[r] - result := make(map[interface{}]interface{}, len(context)) - for k, v := range context { - result[k] = v - } - mutex.RUnlock() - return result, ok -} - -// Delete removes a value stored for a given key in a given request. -func Delete(r *http.Request, key interface{}) { - mutex.Lock() - if data[r] != nil { - delete(data[r], key) - } - mutex.Unlock() -} - -// Clear removes all values stored for a given request. -// -// This is usually called by a handler wrapper to clean up request -// variables at the end of a request lifetime. See ClearHandler(). -func Clear(r *http.Request) { - mutex.Lock() - clear(r) - mutex.Unlock() -} - -// clear is Clear without the lock. -func clear(r *http.Request) { - delete(data, r) - delete(datat, r) -} - -// Purge removes request data stored for longer than maxAge, in seconds. -// It returns the amount of requests removed. -// -// If maxAge <= 0, all request data is removed. -// -// This is only used for sanity check: in case context cleaning was not -// properly set some request data can be kept forever, consuming an increasing -// amount of memory. In case this is detected, Purge() must be called -// periodically until the problem is fixed. -func Purge(maxAge int) int { - mutex.Lock() - count := 0 - if maxAge <= 0 { - count = len(data) - data = make(map[*http.Request]map[interface{}]interface{}) - datat = make(map[*http.Request]int64) - } else { - min := time.Now().Unix() - int64(maxAge) - for r := range data { - if datat[r] < min { - clear(r) - count++ - } - } - } - mutex.Unlock() - return count -} - -// ClearHandler wraps an http.Handler and clears request values at the end -// of a request lifetime. -func ClearHandler(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer Clear(r) - h.ServeHTTP(w, r) - }) -} diff --git a/vendor/github.com/gorilla/context/doc.go b/vendor/github.com/gorilla/context/doc.go deleted file mode 100644 index 448d1bf..0000000 --- a/vendor/github.com/gorilla/context/doc.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package context stores values shared during a request lifetime. - -Note: gorilla/context, having been born well before `context.Context` existed, -does not play well > with the shallow copying of the request that -[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) -(added to net/http Go 1.7 onwards) performs. You should either use *just* -gorilla/context, or moving forward, the new `http.Request.Context()`. - -For example, a router can set variables extracted from the URL and later -application handlers can access those values, or it can be used to store -sessions values to be saved at the end of a request. There are several -others common uses. - -The idea was posted by Brad Fitzpatrick to the go-nuts mailing list: - - http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53 - -Here's the basic usage: first define the keys that you will need. The key -type is interface{} so a key can be of any type that supports equality. -Here we define a key using a custom int type to avoid name collisions: - - package foo - - import ( - "github.com/gorilla/context" - ) - - type key int - - const MyKey key = 0 - -Then set a variable. Variables are bound to an http.Request object, so you -need a request instance to set a value: - - context.Set(r, MyKey, "bar") - -The application can later access the variable using the same key you provided: - - func MyHandler(w http.ResponseWriter, r *http.Request) { - // val is "bar". - val := context.Get(r, foo.MyKey) - - // returns ("bar", true) - val, ok := context.GetOk(r, foo.MyKey) - // ... - } - -And that's all about the basic usage. We discuss some other ideas below. - -Any type can be stored in the context. To enforce a given type, make the key -private and wrap Get() and Set() to accept and return values of a specific -type: - - type key int - - const mykey key = 0 - - // GetMyKey returns a value for this package from the request values. - func GetMyKey(r *http.Request) SomeType { - if rv := context.Get(r, mykey); rv != nil { - return rv.(SomeType) - } - return nil - } - - // SetMyKey sets a value for this package in the request values. - func SetMyKey(r *http.Request, val SomeType) { - context.Set(r, mykey, val) - } - -Variables must be cleared at the end of a request, to remove all values -that were stored. This can be done in an http.Handler, after a request was -served. Just call Clear() passing the request: - - context.Clear(r) - -...or use ClearHandler(), which conveniently wraps an http.Handler to clear -variables at the end of a request lifetime. - -The Routers from the packages gorilla/mux and gorilla/pat call Clear() -so if you are using either of them you don't need to clear the context manually. -*/ -package context diff --git a/vendor/github.com/gorilla/mux/.travis.yml b/vendor/github.com/gorilla/mux/.travis.yml deleted file mode 100644 index ad0935d..0000000 --- a/vendor/github.com/gorilla/mux/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: go -sudo: false - -matrix: - include: - - go: 1.5.x - - go: 1.6.x - - go: 1.7.x - - go: 1.8.x - - go: 1.9.x - - go: 1.10.x - - go: tip - allow_failures: - - go: tip - -install: - - # Skip - -script: - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d .) - - go tool vet . - - go test -v -race ./... diff --git a/vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md b/vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md deleted file mode 100644 index 232be82..0000000 --- a/vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,11 +0,0 @@ -**What version of Go are you running?** (Paste the output of `go version`) - - -**What version of gorilla/mux are you at?** (Paste the output of `git rev-parse HEAD` inside `$GOPATH/src/github.com/gorilla/mux`) - - -**Describe your problem** (and what you have tried so far) - - -**Paste a minimal, runnable, reproduction of your issue below** (use backticks to format it) - diff --git a/vendor/github.com/gorilla/mux/LICENSE b/vendor/github.com/gorilla/mux/LICENSE deleted file mode 100644 index 0e5fb87..0000000 --- a/vendor/github.com/gorilla/mux/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 Rodrigo Moraes. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gorilla/mux/README.md b/vendor/github.com/gorilla/mux/README.md deleted file mode 100644 index e424397..0000000 --- a/vendor/github.com/gorilla/mux/README.md +++ /dev/null @@ -1,649 +0,0 @@ -# gorilla/mux - -[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) -[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux) -[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge) - -![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png) - -http://www.gorillatoolkit.org/pkg/mux - -Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to -their respective handler. - -The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are: - -* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`. -* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers. -* URL hosts, paths and query values can have variables with an optional regular expression. -* Registered URLs can be built, or "reversed", which helps maintaining references to resources. -* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching. - ---- - -* [Install](#install) -* [Examples](#examples) -* [Matching Routes](#matching-routes) -* [Static Files](#static-files) -* [Registered URLs](#registered-urls) -* [Walking Routes](#walking-routes) -* [Graceful Shutdown](#graceful-shutdown) -* [Middleware](#middleware) -* [Testing Handlers](#testing-handlers) -* [Full Example](#full-example) - ---- - -## Install - -With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain: - -```sh -go get -u github.com/gorilla/mux -``` - -## Examples - -Let's start registering a couple of URL paths and handlers: - -```go -func main() { - r := mux.NewRouter() - r.HandleFunc("/", HomeHandler) - r.HandleFunc("/products", ProductsHandler) - r.HandleFunc("/articles", ArticlesHandler) - http.Handle("/", r) -} -``` - -Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters. - -Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example: - -```go -r := mux.NewRouter() -r.HandleFunc("/products/{key}", ProductHandler) -r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) -r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) -``` - -The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`: - -```go -func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "Category: %v\n", vars["category"]) -} -``` - -And this is all you need to know about the basic usage. More advanced options are explained below. - -### Matching Routes - -Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables: - -```go -r := mux.NewRouter() -// Only matches if domain is "www.example.com". -r.Host("www.example.com") -// Matches a dynamic subdomain. -r.Host("{subdomain:[a-z]+}.domain.com") -``` - -There are several other matchers that can be added. To match path prefixes: - -```go -r.PathPrefix("/products/") -``` - -...or HTTP methods: - -```go -r.Methods("GET", "POST") -``` - -...or URL schemes: - -```go -r.Schemes("https") -``` - -...or header values: - -```go -r.Headers("X-Requested-With", "XMLHttpRequest") -``` - -...or query values: - -```go -r.Queries("key", "value") -``` - -...or to use a custom matcher function: - -```go -r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { - return r.ProtoMajor == 0 -}) -``` - -...and finally, it is possible to combine several matchers in a single route: - -```go -r.HandleFunc("/products", ProductsHandler). - Host("www.example.com"). - Methods("GET"). - Schemes("http") -``` - -Routes are tested in the order they were added to the router. If two routes match, the first one wins: - -```go -r := mux.NewRouter() -r.HandleFunc("/specific", specificHandler) -r.PathPrefix("/").Handler(catchAllHandler) -``` - -Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting". - -For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it: - -```go -r := mux.NewRouter() -s := r.Host("www.example.com").Subrouter() -``` - -Then register routes in the subrouter: - -```go -s.HandleFunc("/products/", ProductsHandler) -s.HandleFunc("/products/{key}", ProductHandler) -s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) -``` - -The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route. - -Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter. - -There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths: - -```go -r := mux.NewRouter() -s := r.PathPrefix("/products").Subrouter() -// "/products/" -s.HandleFunc("/", ProductsHandler) -// "/products/{key}/" -s.HandleFunc("/{key}/", ProductHandler) -// "/products/{key}/details" -s.HandleFunc("/{key}/details", ProductDetailsHandler) -``` - - -### Static Files - -Note that the path provided to `PathPrefix()` represents a "wildcard": calling -`PathPrefix("/static/").Handler(...)` means that the handler will be passed any -request that matches "/static/\*". This makes it easy to serve static files with mux: - -```go -func main() { - var dir string - - flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") - flag.Parse() - r := mux.NewRouter() - - // This will serve files under http://localhost:8000/static/ - r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) - - srv := &http.Server{ - Handler: r, - Addr: "127.0.0.1:8000", - // Good practice: enforce timeouts for servers you create! - WriteTimeout: 15 * time.Second, - ReadTimeout: 15 * time.Second, - } - - log.Fatal(srv.ListenAndServe()) -} -``` - -### Registered URLs - -Now let's see how to build registered URLs. - -Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example: - -```go -r := mux.NewRouter() -r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). - Name("article") -``` - -To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do: - -```go -url, err := r.Get("article").URL("category", "technology", "id", "42") -``` - -...and the result will be a `url.URL` with the following path: - -``` -"/articles/technology/42" -``` - -This also works for host and query value variables: - -```go -r := mux.NewRouter() -r.Host("{subdomain}.domain.com"). - Path("/articles/{category}/{id:[0-9]+}"). - Queries("filter", "{filter}"). - HandlerFunc(ArticleHandler). - Name("article") - -// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla" -url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42", - "filter", "gorilla") -``` - -All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match. - -Regex support also exists for matching Headers within a route. For example, we could do: - -```go -r.HeadersRegexp("Content-Type", "application/(text|json)") -``` - -...and the route will match both requests with a Content-Type of `application/json` as well as `application/text` - -There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do: - -```go -// "http://news.domain.com/" -host, err := r.Get("article").URLHost("subdomain", "news") - -// "/articles/technology/42" -path, err := r.Get("article").URLPath("category", "technology", "id", "42") -``` - -And if you use subrouters, host and path defined separately can be built as well: - -```go -r := mux.NewRouter() -s := r.Host("{subdomain}.domain.com").Subrouter() -s.Path("/articles/{category}/{id:[0-9]+}"). - HandlerFunc(ArticleHandler). - Name("article") - -// "http://news.domain.com/articles/technology/42" -url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42") -``` - -### Walking Routes - -The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example, -the following prints all of the registered routes: - -```go -package main - -import ( - "fmt" - "net/http" - "strings" - - "github.com/gorilla/mux" -) - -func handler(w http.ResponseWriter, r *http.Request) { - return -} - -func main() { - r := mux.NewRouter() - r.HandleFunc("/", handler) - r.HandleFunc("/products", handler).Methods("POST") - r.HandleFunc("/articles", handler).Methods("GET") - r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT") - r.HandleFunc("/authors", handler).Queries("surname", "{surname}") - err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { - pathTemplate, err := route.GetPathTemplate() - if err == nil { - fmt.Println("ROUTE:", pathTemplate) - } - pathRegexp, err := route.GetPathRegexp() - if err == nil { - fmt.Println("Path regexp:", pathRegexp) - } - queriesTemplates, err := route.GetQueriesTemplates() - if err == nil { - fmt.Println("Queries templates:", strings.Join(queriesTemplates, ",")) - } - queriesRegexps, err := route.GetQueriesRegexp() - if err == nil { - fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ",")) - } - methods, err := route.GetMethods() - if err == nil { - fmt.Println("Methods:", strings.Join(methods, ",")) - } - fmt.Println() - return nil - }) - - if err != nil { - fmt.Println(err) - } - - http.Handle("/", r) -} -``` - -### Graceful Shutdown - -Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`: - -```go -package main - -import ( - "context" - "flag" - "log" - "net/http" - "os" - "os/signal" - "time" - - "github.com/gorilla/mux" -) - -func main() { - var wait time.Duration - flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m") - flag.Parse() - - r := mux.NewRouter() - // Add your routes as needed - - srv := &http.Server{ - Addr: "0.0.0.0:8080", - // Good practice to set timeouts to avoid Slowloris attacks. - WriteTimeout: time.Second * 15, - ReadTimeout: time.Second * 15, - IdleTimeout: time.Second * 60, - Handler: r, // Pass our instance of gorilla/mux in. - } - - // Run our server in a goroutine so that it doesn't block. - go func() { - if err := srv.ListenAndServe(); err != nil { - log.Println(err) - } - }() - - c := make(chan os.Signal, 1) - // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) - // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. - signal.Notify(c, os.Interrupt) - - // Block until we receive our signal. - <-c - - // Create a deadline to wait for. - ctx, cancel := context.WithTimeout(context.Background(), wait) - defer cancel() - // Doesn't block if no connections, but will otherwise wait - // until the timeout deadline. - srv.Shutdown(ctx) - // Optionally, you could run srv.Shutdown in a goroutine and block on - // <-ctx.Done() if your application should wait for other services - // to finalize based on context cancellation. - log.Println("shutting down") - os.Exit(0) -} -``` - -### Middleware - -Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters. -Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking. - -Mux middlewares are defined using the de facto standard type: - -```go -type MiddlewareFunc func(http.Handler) http.Handler -``` - -Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers. - -A very basic middleware which logs the URI of the request being handled could be written as: - -```go -func loggingMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Do stuff here - log.Println(r.RequestURI) - // Call the next handler, which can be another middleware in the chain, or the final handler. - next.ServeHTTP(w, r) - }) -} -``` - -Middlewares can be added to a router using `Router.Use()`: - -```go -r := mux.NewRouter() -r.HandleFunc("/", handler) -r.Use(loggingMiddleware) -``` - -A more complex authentication middleware, which maps session token to users, could be written as: - -```go -// Define our struct -type authenticationMiddleware struct { - tokenUsers map[string]string -} - -// Initialize it somewhere -func (amw *authenticationMiddleware) Populate() { - amw.tokenUsers["00000000"] = "user0" - amw.tokenUsers["aaaaaaaa"] = "userA" - amw.tokenUsers["05f717e5"] = "randomUser" - amw.tokenUsers["deadbeef"] = "user0" -} - -// Middleware function, which will be called for each request -func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - token := r.Header.Get("X-Session-Token") - - if user, found := amw.tokenUsers[token]; found { - // We found the token in our map - log.Printf("Authenticated user %s\n", user) - // Pass down the request to the next middleware (or final handler) - next.ServeHTTP(w, r) - } else { - // Write an error and stop the handler chain - http.Error(w, "Forbidden", http.StatusForbidden) - } - }) -} -``` - -```go -r := mux.NewRouter() -r.HandleFunc("/", handler) - -amw := authenticationMiddleware{} -amw.Populate() - -r.Use(amw.Middleware) -``` - -Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it. - -### Testing Handlers - -Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_. - -First, our simple HTTP handler: - -```go -// endpoints.go -package main - -func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { - // A very simple health check. - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "application/json") - - // In the future we could report back on the status of our DB, or our cache - // (e.g. Redis) by performing a simple PING, and include them in the response. - io.WriteString(w, `{"alive": true}`) -} - -func main() { - r := mux.NewRouter() - r.HandleFunc("/health", HealthCheckHandler) - - log.Fatal(http.ListenAndServe("localhost:8080", r)) -} -``` - -Our test code: - -```go -// endpoints_test.go -package main - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -func TestHealthCheckHandler(t *testing.T) { - // Create a request to pass to our handler. We don't have any query parameters for now, so we'll - // pass 'nil' as the third parameter. - req, err := http.NewRequest("GET", "/health", nil) - if err != nil { - t.Fatal(err) - } - - // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response. - rr := httptest.NewRecorder() - handler := http.HandlerFunc(HealthCheckHandler) - - // Our handlers satisfy http.Handler, so we can call their ServeHTTP method - // directly and pass in our Request and ResponseRecorder. - handler.ServeHTTP(rr, req) - - // Check the status code is what we expect. - if status := rr.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", - status, http.StatusOK) - } - - // Check the response body is what we expect. - expected := `{"alive": true}` - if rr.Body.String() != expected { - t.Errorf("handler returned unexpected body: got %v want %v", - rr.Body.String(), expected) - } -} -``` - -In the case that our routes have [variables](#examples), we can pass those in the request. We could write -[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple -possible route variables as needed. - -```go -// endpoints.go -func main() { - r := mux.NewRouter() - // A route with a route variable: - r.HandleFunc("/metrics/{type}", MetricsHandler) - - log.Fatal(http.ListenAndServe("localhost:8080", r)) -} -``` - -Our test file, with a table-driven test of `routeVariables`: - -```go -// endpoints_test.go -func TestMetricsHandler(t *testing.T) { - tt := []struct{ - routeVariable string - shouldPass bool - }{ - {"goroutines", true}, - {"heap", true}, - {"counters", true}, - {"queries", true}, - {"adhadaeqm3k", false}, - } - - for _, tc := range tt { - path := fmt.Sprintf("/metrics/%s", tc.routeVariable) - req, err := http.NewRequest("GET", path, nil) - if err != nil { - t.Fatal(err) - } - - rr := httptest.NewRecorder() - - // Need to create a router that we can pass the request through so that the vars will be added to the context - router := mux.NewRouter() - router.HandleFunc("/metrics/{type}", MetricsHandler) - router.ServeHTTP(rr, req) - - // In this case, our MetricsHandler returns a non-200 response - // for a route variable it doesn't know about. - if rr.Code == http.StatusOK && !tc.shouldPass { - t.Errorf("handler should have failed on routeVariable %s: got %v want %v", - tc.routeVariable, rr.Code, http.StatusOK) - } - } -} -``` - -## Full Example - -Here's a complete, runnable example of a small `mux` based server: - -```go -package main - -import ( - "net/http" - "log" - "github.com/gorilla/mux" -) - -func YourHandler(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Gorilla!\n")) -} - -func main() { - r := mux.NewRouter() - // Routes consist of a path and a handler function. - r.HandleFunc("/", YourHandler) - - // Bind to a port and pass our router in - log.Fatal(http.ListenAndServe(":8000", r)) -} -``` - -## License - -BSD licensed. See the LICENSE file for details. diff --git a/vendor/github.com/gorilla/mux/context_gorilla.go b/vendor/github.com/gorilla/mux/context_gorilla.go deleted file mode 100644 index d7adaa8..0000000 --- a/vendor/github.com/gorilla/mux/context_gorilla.go +++ /dev/null @@ -1,26 +0,0 @@ -// +build !go1.7 - -package mux - -import ( - "net/http" - - "github.com/gorilla/context" -) - -func contextGet(r *http.Request, key interface{}) interface{} { - return context.Get(r, key) -} - -func contextSet(r *http.Request, key, val interface{}) *http.Request { - if val == nil { - return r - } - - context.Set(r, key, val) - return r -} - -func contextClear(r *http.Request) { - context.Clear(r) -} diff --git a/vendor/github.com/gorilla/mux/context_native.go b/vendor/github.com/gorilla/mux/context_native.go deleted file mode 100644 index 209cbea..0000000 --- a/vendor/github.com/gorilla/mux/context_native.go +++ /dev/null @@ -1,24 +0,0 @@ -// +build go1.7 - -package mux - -import ( - "context" - "net/http" -) - -func contextGet(r *http.Request, key interface{}) interface{} { - return r.Context().Value(key) -} - -func contextSet(r *http.Request, key, val interface{}) *http.Request { - if val == nil { - return r - } - - return r.WithContext(context.WithValue(r.Context(), key, val)) -} - -func contextClear(r *http.Request) { - return -} diff --git a/vendor/github.com/gorilla/mux/doc.go b/vendor/github.com/gorilla/mux/doc.go deleted file mode 100644 index 38957de..0000000 --- a/vendor/github.com/gorilla/mux/doc.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package mux implements a request router and dispatcher. - -The name mux stands for "HTTP request multiplexer". Like the standard -http.ServeMux, mux.Router matches incoming requests against a list of -registered routes and calls a handler for the route that matches the URL -or other conditions. The main features are: - - * Requests can be matched based on URL host, path, path prefix, schemes, - header and query values, HTTP methods or using custom matchers. - * URL hosts, paths and query values can have variables with an optional - regular expression. - * Registered URLs can be built, or "reversed", which helps maintaining - references to resources. - * Routes can be used as subrouters: nested routes are only tested if the - parent route matches. This is useful to define groups of routes that - share common conditions like a host, a path prefix or other repeated - attributes. As a bonus, this optimizes request matching. - * It implements the http.Handler interface so it is compatible with the - standard http.ServeMux. - -Let's start registering a couple of URL paths and handlers: - - func main() { - r := mux.NewRouter() - r.HandleFunc("/", HomeHandler) - r.HandleFunc("/products", ProductsHandler) - r.HandleFunc("/articles", ArticlesHandler) - http.Handle("/", r) - } - -Here we register three routes mapping URL paths to handlers. This is -equivalent to how http.HandleFunc() works: if an incoming request URL matches -one of the paths, the corresponding handler is called passing -(http.ResponseWriter, *http.Request) as parameters. - -Paths can have variables. They are defined using the format {name} or -{name:pattern}. If a regular expression pattern is not defined, the matched -variable will be anything until the next slash. For example: - - r := mux.NewRouter() - r.HandleFunc("/products/{key}", ProductHandler) - r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) - r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) - -Groups can be used inside patterns, as long as they are non-capturing (?:re). For example: - - r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler) - -The names are used to create a map of route variables which can be retrieved -calling mux.Vars(): - - vars := mux.Vars(request) - category := vars["category"] - -Note that if any capturing groups are present, mux will panic() during parsing. To prevent -this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to -"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably -when capturing groups were present. - -And this is all you need to know about the basic usage. More advanced options -are explained below. - -Routes can also be restricted to a domain or subdomain. Just define a host -pattern to be matched. They can also have variables: - - r := mux.NewRouter() - // Only matches if domain is "www.example.com". - r.Host("www.example.com") - // Matches a dynamic subdomain. - r.Host("{subdomain:[a-z]+}.domain.com") - -There are several other matchers that can be added. To match path prefixes: - - r.PathPrefix("/products/") - -...or HTTP methods: - - r.Methods("GET", "POST") - -...or URL schemes: - - r.Schemes("https") - -...or header values: - - r.Headers("X-Requested-With", "XMLHttpRequest") - -...or query values: - - r.Queries("key", "value") - -...or to use a custom matcher function: - - r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { - return r.ProtoMajor == 0 - }) - -...and finally, it is possible to combine several matchers in a single route: - - r.HandleFunc("/products", ProductsHandler). - Host("www.example.com"). - Methods("GET"). - Schemes("http") - -Setting the same matching conditions again and again can be boring, so we have -a way to group several routes that share the same requirements. -We call it "subrouting". - -For example, let's say we have several URLs that should only match when the -host is "www.example.com". Create a route for that host and get a "subrouter" -from it: - - r := mux.NewRouter() - s := r.Host("www.example.com").Subrouter() - -Then register routes in the subrouter: - - s.HandleFunc("/products/", ProductsHandler) - s.HandleFunc("/products/{key}", ProductHandler) - s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) - -The three URL paths we registered above will only be tested if the domain is -"www.example.com", because the subrouter is tested first. This is not -only convenient, but also optimizes request matching. You can create -subrouters combining any attribute matchers accepted by a route. - -Subrouters can be used to create domain or path "namespaces": you define -subrouters in a central place and then parts of the app can register its -paths relatively to a given subrouter. - -There's one more thing about subroutes. When a subrouter has a path prefix, -the inner routes use it as base for their paths: - - r := mux.NewRouter() - s := r.PathPrefix("/products").Subrouter() - // "/products/" - s.HandleFunc("/", ProductsHandler) - // "/products/{key}/" - s.HandleFunc("/{key}/", ProductHandler) - // "/products/{key}/details" - s.HandleFunc("/{key}/details", ProductDetailsHandler) - -Note that the path provided to PathPrefix() represents a "wildcard": calling -PathPrefix("/static/").Handler(...) means that the handler will be passed any -request that matches "/static/*". This makes it easy to serve static files with mux: - - func main() { - var dir string - - flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir") - flag.Parse() - r := mux.NewRouter() - - // This will serve files under http://localhost:8000/static/ - r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir)))) - - srv := &http.Server{ - Handler: r, - Addr: "127.0.0.1:8000", - // Good practice: enforce timeouts for servers you create! - WriteTimeout: 15 * time.Second, - ReadTimeout: 15 * time.Second, - } - - log.Fatal(srv.ListenAndServe()) - } - -Now let's see how to build registered URLs. - -Routes can be named. All routes that define a name can have their URLs built, -or "reversed". We define a name calling Name() on a route. For example: - - r := mux.NewRouter() - r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). - Name("article") - -To build a URL, get the route and call the URL() method, passing a sequence of -key/value pairs for the route variables. For the previous route, we would do: - - url, err := r.Get("article").URL("category", "technology", "id", "42") - -...and the result will be a url.URL with the following path: - - "/articles/technology/42" - -This also works for host and query value variables: - - r := mux.NewRouter() - r.Host("{subdomain}.domain.com"). - Path("/articles/{category}/{id:[0-9]+}"). - Queries("filter", "{filter}"). - HandlerFunc(ArticleHandler). - Name("article") - - // url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla" - url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42", - "filter", "gorilla") - -All variables defined in the route are required, and their values must -conform to the corresponding patterns. These requirements guarantee that a -generated URL will always match a registered route -- the only exception is -for explicitly defined "build-only" routes which never match. - -Regex support also exists for matching Headers within a route. For example, we could do: - - r.HeadersRegexp("Content-Type", "application/(text|json)") - -...and the route will match both requests with a Content-Type of `application/json` as well as -`application/text` - -There's also a way to build only the URL host or path for a route: -use the methods URLHost() or URLPath() instead. For the previous route, -we would do: - - // "http://news.domain.com/" - host, err := r.Get("article").URLHost("subdomain", "news") - - // "/articles/technology/42" - path, err := r.Get("article").URLPath("category", "technology", "id", "42") - -And if you use subrouters, host and path defined separately can be built -as well: - - r := mux.NewRouter() - s := r.Host("{subdomain}.domain.com").Subrouter() - s.Path("/articles/{category}/{id:[0-9]+}"). - HandlerFunc(ArticleHandler). - Name("article") - - // "http://news.domain.com/articles/technology/42" - url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42") - -Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking. - - type MiddlewareFunc func(http.Handler) http.Handler - -Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created). - -A very basic middleware which logs the URI of the request being handled could be written as: - - func simpleMw(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Do stuff here - log.Println(r.RequestURI) - // Call the next handler, which can be another middleware in the chain, or the final handler. - next.ServeHTTP(w, r) - }) - } - -Middlewares can be added to a router using `Router.Use()`: - - r := mux.NewRouter() - r.HandleFunc("/", handler) - r.Use(simpleMw) - -A more complex authentication middleware, which maps session token to users, could be written as: - - // Define our struct - type authenticationMiddleware struct { - tokenUsers map[string]string - } - - // Initialize it somewhere - func (amw *authenticationMiddleware) Populate() { - amw.tokenUsers["00000000"] = "user0" - amw.tokenUsers["aaaaaaaa"] = "userA" - amw.tokenUsers["05f717e5"] = "randomUser" - amw.tokenUsers["deadbeef"] = "user0" - } - - // Middleware function, which will be called for each request - func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - token := r.Header.Get("X-Session-Token") - - if user, found := amw.tokenUsers[token]; found { - // We found the token in our map - log.Printf("Authenticated user %s\n", user) - next.ServeHTTP(w, r) - } else { - http.Error(w, "Forbidden", http.StatusForbidden) - } - }) - } - - r := mux.NewRouter() - r.HandleFunc("/", handler) - - amw := authenticationMiddleware{} - amw.Populate() - - r.Use(amw.Middleware) - -Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. - -*/ -package mux diff --git a/vendor/github.com/gorilla/mux/middleware.go b/vendor/github.com/gorilla/mux/middleware.go deleted file mode 100644 index ceb812c..0000000 --- a/vendor/github.com/gorilla/mux/middleware.go +++ /dev/null @@ -1,72 +0,0 @@ -package mux - -import ( - "net/http" - "strings" -) - -// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler. -// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed -// to it, and then calls the handler passed as parameter to the MiddlewareFunc. -type MiddlewareFunc func(http.Handler) http.Handler - -// middleware interface is anything which implements a MiddlewareFunc named Middleware. -type middleware interface { - Middleware(handler http.Handler) http.Handler -} - -// Middleware allows MiddlewareFunc to implement the middleware interface. -func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler { - return mw(handler) -} - -// Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. -func (r *Router) Use(mwf ...MiddlewareFunc) { - for _, fn := range mwf { - r.middlewares = append(r.middlewares, fn) - } -} - -// useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router. -func (r *Router) useInterface(mw middleware) { - r.middlewares = append(r.middlewares, mw) -} - -// CORSMethodMiddleware sets the Access-Control-Allow-Methods response header -// on a request, by matching routes based only on paths. It also handles -// OPTIONS requests, by settings Access-Control-Allow-Methods, and then -// returning without calling the next http handler. -func CORSMethodMiddleware(r *Router) MiddlewareFunc { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - var allMethods []string - - err := r.Walk(func(route *Route, _ *Router, _ []*Route) error { - for _, m := range route.matchers { - if _, ok := m.(*routeRegexp); ok { - if m.Match(req, &RouteMatch{}) { - methods, err := route.GetMethods() - if err != nil { - return err - } - - allMethods = append(allMethods, methods...) - } - break - } - } - return nil - }) - - if err == nil { - w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allMethods, "OPTIONS"), ",")) - - if req.Method == "OPTIONS" { - return - } - } - - next.ServeHTTP(w, req) - }) - } -} diff --git a/vendor/github.com/gorilla/mux/mux.go b/vendor/github.com/gorilla/mux/mux.go deleted file mode 100644 index 4bbafa5..0000000 --- a/vendor/github.com/gorilla/mux/mux.go +++ /dev/null @@ -1,588 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mux - -import ( - "errors" - "fmt" - "net/http" - "path" - "regexp" -) - -var ( - // ErrMethodMismatch is returned when the method in the request does not match - // the method defined against the route. - ErrMethodMismatch = errors.New("method is not allowed") - // ErrNotFound is returned when no route match is found. - ErrNotFound = errors.New("no matching route was found") -) - -// NewRouter returns a new router instance. -func NewRouter() *Router { - return &Router{namedRoutes: make(map[string]*Route), KeepContext: false} -} - -// Router registers routes to be matched and dispatches a handler. -// -// It implements the http.Handler interface, so it can be registered to serve -// requests: -// -// var router = mux.NewRouter() -// -// func main() { -// http.Handle("/", router) -// } -// -// Or, for Google App Engine, register it in a init() function: -// -// func init() { -// http.Handle("/", router) -// } -// -// This will send all incoming requests to the router. -type Router struct { - // Configurable Handler to be used when no route matches. - NotFoundHandler http.Handler - - // Configurable Handler to be used when the request method does not match the route. - MethodNotAllowedHandler http.Handler - - // Parent route, if this is a subrouter. - parent parentRoute - // Routes to be matched, in order. - routes []*Route - // Routes by name for URL building. - namedRoutes map[string]*Route - // See Router.StrictSlash(). This defines the flag for new routes. - strictSlash bool - // See Router.SkipClean(). This defines the flag for new routes. - skipClean bool - // If true, do not clear the request context after handling the request. - // This has no effect when go1.7+ is used, since the context is stored - // on the request itself. - KeepContext bool - // see Router.UseEncodedPath(). This defines a flag for all routes. - useEncodedPath bool - // Slice of middlewares to be called after a match is found - middlewares []middleware -} - -// Match attempts to match the given request against the router's registered routes. -// -// If the request matches a route of this router or one of its subrouters the Route, -// Handler, and Vars fields of the the match argument are filled and this function -// returns true. -// -// If the request does not match any of this router's or its subrouters' routes -// then this function returns false. If available, a reason for the match failure -// will be filled in the match argument's MatchErr field. If the match failure type -// (eg: not found) has a registered handler, the handler is assigned to the Handler -// field of the match argument. -func (r *Router) Match(req *http.Request, match *RouteMatch) bool { - for _, route := range r.routes { - if route.Match(req, match) { - // Build middleware chain if no error was found - if match.MatchErr == nil { - for i := len(r.middlewares) - 1; i >= 0; i-- { - match.Handler = r.middlewares[i].Middleware(match.Handler) - } - } - return true - } - } - - if match.MatchErr == ErrMethodMismatch { - if r.MethodNotAllowedHandler != nil { - match.Handler = r.MethodNotAllowedHandler - return true - } - - return false - } - - // Closest match for a router (includes sub-routers) - if r.NotFoundHandler != nil { - match.Handler = r.NotFoundHandler - match.MatchErr = ErrNotFound - return true - } - - match.MatchErr = ErrNotFound - return false -} - -// ServeHTTP dispatches the handler registered in the matched route. -// -// When there is a match, the route variables can be retrieved calling -// mux.Vars(request). -func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { - if !r.skipClean { - path := req.URL.Path - if r.useEncodedPath { - path = req.URL.EscapedPath() - } - // Clean path to canonical form and redirect. - if p := cleanPath(path); p != path { - - // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query. - // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: - // http://code.google.com/p/go/issues/detail?id=5252 - url := *req.URL - url.Path = p - p = url.String() - - w.Header().Set("Location", p) - w.WriteHeader(http.StatusMovedPermanently) - return - } - } - var match RouteMatch - var handler http.Handler - if r.Match(req, &match) { - handler = match.Handler - req = setVars(req, match.Vars) - req = setCurrentRoute(req, match.Route) - } - - if handler == nil && match.MatchErr == ErrMethodMismatch { - handler = methodNotAllowedHandler() - } - - if handler == nil { - handler = http.NotFoundHandler() - } - - if !r.KeepContext { - defer contextClear(req) - } - - handler.ServeHTTP(w, req) -} - -// Get returns a route registered with the given name. -func (r *Router) Get(name string) *Route { - return r.getNamedRoutes()[name] -} - -// GetRoute returns a route registered with the given name. This method -// was renamed to Get() and remains here for backwards compatibility. -func (r *Router) GetRoute(name string) *Route { - return r.getNamedRoutes()[name] -} - -// StrictSlash defines the trailing slash behavior for new routes. The initial -// value is false. -// -// When true, if the route path is "/path/", accessing "/path" will perform a redirect -// to the former and vice versa. In other words, your application will always -// see the path as specified in the route. -// -// When false, if the route path is "/path", accessing "/path/" will not match -// this route and vice versa. -// -// The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for -// routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed -// request will be made as a GET by most clients. Use middleware or client settings -// to modify this behaviour as needed. -// -// Special case: when a route sets a path prefix using the PathPrefix() method, -// strict slash is ignored for that route because the redirect behavior can't -// be determined from a prefix alone. However, any subrouters created from that -// route inherit the original StrictSlash setting. -func (r *Router) StrictSlash(value bool) *Router { - r.strictSlash = value - return r -} - -// SkipClean defines the path cleaning behaviour for new routes. The initial -// value is false. Users should be careful about which routes are not cleaned -// -// When true, if the route path is "/path//to", it will remain with the double -// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/ -// -// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will -// become /fetch/http/xkcd.com/534 -func (r *Router) SkipClean(value bool) *Router { - r.skipClean = value - return r -} - -// UseEncodedPath tells the router to match the encoded original path -// to the routes. -// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to". -// -// If not called, the router will match the unencoded path to the routes. -// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to" -func (r *Router) UseEncodedPath() *Router { - r.useEncodedPath = true - return r -} - -// ---------------------------------------------------------------------------- -// parentRoute -// ---------------------------------------------------------------------------- - -func (r *Router) getBuildScheme() string { - if r.parent != nil { - return r.parent.getBuildScheme() - } - return "" -} - -// getNamedRoutes returns the map where named routes are registered. -func (r *Router) getNamedRoutes() map[string]*Route { - if r.namedRoutes == nil { - if r.parent != nil { - r.namedRoutes = r.parent.getNamedRoutes() - } else { - r.namedRoutes = make(map[string]*Route) - } - } - return r.namedRoutes -} - -// getRegexpGroup returns regexp definitions from the parent route, if any. -func (r *Router) getRegexpGroup() *routeRegexpGroup { - if r.parent != nil { - return r.parent.getRegexpGroup() - } - return nil -} - -func (r *Router) buildVars(m map[string]string) map[string]string { - if r.parent != nil { - m = r.parent.buildVars(m) - } - return m -} - -// ---------------------------------------------------------------------------- -// Route factories -// ---------------------------------------------------------------------------- - -// NewRoute registers an empty route. -func (r *Router) NewRoute() *Route { - route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath} - r.routes = append(r.routes, route) - return route -} - -// Handle registers a new route with a matcher for the URL path. -// See Route.Path() and Route.Handler(). -func (r *Router) Handle(path string, handler http.Handler) *Route { - return r.NewRoute().Path(path).Handler(handler) -} - -// HandleFunc registers a new route with a matcher for the URL path. -// See Route.Path() and Route.HandlerFunc(). -func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, - *http.Request)) *Route { - return r.NewRoute().Path(path).HandlerFunc(f) -} - -// Headers registers a new route with a matcher for request header values. -// See Route.Headers(). -func (r *Router) Headers(pairs ...string) *Route { - return r.NewRoute().Headers(pairs...) -} - -// Host registers a new route with a matcher for the URL host. -// See Route.Host(). -func (r *Router) Host(tpl string) *Route { - return r.NewRoute().Host(tpl) -} - -// MatcherFunc registers a new route with a custom matcher function. -// See Route.MatcherFunc(). -func (r *Router) MatcherFunc(f MatcherFunc) *Route { - return r.NewRoute().MatcherFunc(f) -} - -// Methods registers a new route with a matcher for HTTP methods. -// See Route.Methods(). -func (r *Router) Methods(methods ...string) *Route { - return r.NewRoute().Methods(methods...) -} - -// Path registers a new route with a matcher for the URL path. -// See Route.Path(). -func (r *Router) Path(tpl string) *Route { - return r.NewRoute().Path(tpl) -} - -// PathPrefix registers a new route with a matcher for the URL path prefix. -// See Route.PathPrefix(). -func (r *Router) PathPrefix(tpl string) *Route { - return r.NewRoute().PathPrefix(tpl) -} - -// Queries registers a new route with a matcher for URL query values. -// See Route.Queries(). -func (r *Router) Queries(pairs ...string) *Route { - return r.NewRoute().Queries(pairs...) -} - -// Schemes registers a new route with a matcher for URL schemes. -// See Route.Schemes(). -func (r *Router) Schemes(schemes ...string) *Route { - return r.NewRoute().Schemes(schemes...) -} - -// BuildVarsFunc registers a new route with a custom function for modifying -// route variables before building a URL. -func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { - return r.NewRoute().BuildVarsFunc(f) -} - -// Walk walks the router and all its sub-routers, calling walkFn for each route -// in the tree. The routes are walked in the order they were added. Sub-routers -// are explored depth-first. -func (r *Router) Walk(walkFn WalkFunc) error { - return r.walk(walkFn, []*Route{}) -} - -// SkipRouter is used as a return value from WalkFuncs to indicate that the -// router that walk is about to descend down to should be skipped. -var SkipRouter = errors.New("skip this router") - -// WalkFunc is the type of the function called for each route visited by Walk. -// At every invocation, it is given the current route, and the current router, -// and a list of ancestor routes that lead to the current route. -type WalkFunc func(route *Route, router *Router, ancestors []*Route) error - -func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error { - for _, t := range r.routes { - err := walkFn(t, r, ancestors) - if err == SkipRouter { - continue - } - if err != nil { - return err - } - for _, sr := range t.matchers { - if h, ok := sr.(*Router); ok { - ancestors = append(ancestors, t) - err := h.walk(walkFn, ancestors) - if err != nil { - return err - } - ancestors = ancestors[:len(ancestors)-1] - } - } - if h, ok := t.handler.(*Router); ok { - ancestors = append(ancestors, t) - err := h.walk(walkFn, ancestors) - if err != nil { - return err - } - ancestors = ancestors[:len(ancestors)-1] - } - } - return nil -} - -// ---------------------------------------------------------------------------- -// Context -// ---------------------------------------------------------------------------- - -// RouteMatch stores information about a matched route. -type RouteMatch struct { - Route *Route - Handler http.Handler - Vars map[string]string - - // MatchErr is set to appropriate matching error - // It is set to ErrMethodMismatch if there is a mismatch in - // the request method and route method - MatchErr error -} - -type contextKey int - -const ( - varsKey contextKey = iota - routeKey -) - -// Vars returns the route variables for the current request, if any. -func Vars(r *http.Request) map[string]string { - if rv := contextGet(r, varsKey); rv != nil { - return rv.(map[string]string) - } - return nil -} - -// CurrentRoute returns the matched route for the current request, if any. -// This only works when called inside the handler of the matched route -// because the matched route is stored in the request context which is cleared -// after the handler returns, unless the KeepContext option is set on the -// Router. -func CurrentRoute(r *http.Request) *Route { - if rv := contextGet(r, routeKey); rv != nil { - return rv.(*Route) - } - return nil -} - -func setVars(r *http.Request, val interface{}) *http.Request { - return contextSet(r, varsKey, val) -} - -func setCurrentRoute(r *http.Request, val interface{}) *http.Request { - return contextSet(r, routeKey, val) -} - -// ---------------------------------------------------------------------------- -// Helpers -// ---------------------------------------------------------------------------- - -// cleanPath returns the canonical path for p, eliminating . and .. elements. -// Borrowed from the net/http package. -func cleanPath(p string) string { - if p == "" { - return "/" - } - if p[0] != '/' { - p = "/" + p - } - np := path.Clean(p) - // path.Clean removes trailing slash except for root; - // put the trailing slash back if necessary. - if p[len(p)-1] == '/' && np != "/" { - np += "/" - } - - return np -} - -// uniqueVars returns an error if two slices contain duplicated strings. -func uniqueVars(s1, s2 []string) error { - for _, v1 := range s1 { - for _, v2 := range s2 { - if v1 == v2 { - return fmt.Errorf("mux: duplicated route variable %q", v2) - } - } - } - return nil -} - -// checkPairs returns the count of strings passed in, and an error if -// the count is not an even number. -func checkPairs(pairs ...string) (int, error) { - length := len(pairs) - if length%2 != 0 { - return length, fmt.Errorf( - "mux: number of parameters must be multiple of 2, got %v", pairs) - } - return length, nil -} - -// mapFromPairsToString converts variadic string parameters to a -// string to string map. -func mapFromPairsToString(pairs ...string) (map[string]string, error) { - length, err := checkPairs(pairs...) - if err != nil { - return nil, err - } - m := make(map[string]string, length/2) - for i := 0; i < length; i += 2 { - m[pairs[i]] = pairs[i+1] - } - return m, nil -} - -// mapFromPairsToRegex converts variadic string parameters to a -// string to regex map. -func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { - length, err := checkPairs(pairs...) - if err != nil { - return nil, err - } - m := make(map[string]*regexp.Regexp, length/2) - for i := 0; i < length; i += 2 { - regex, err := regexp.Compile(pairs[i+1]) - if err != nil { - return nil, err - } - m[pairs[i]] = regex - } - return m, nil -} - -// matchInArray returns true if the given string value is in the array. -func matchInArray(arr []string, value string) bool { - for _, v := range arr { - if v == value { - return true - } - } - return false -} - -// matchMapWithString returns true if the given key/value pairs exist in a given map. -func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool { - for k, v := range toCheck { - // Check if key exists. - if canonicalKey { - k = http.CanonicalHeaderKey(k) - } - if values := toMatch[k]; values == nil { - return false - } else if v != "" { - // If value was defined as an empty string we only check that the - // key exists. Otherwise we also check for equality. - valueExists := false - for _, value := range values { - if v == value { - valueExists = true - break - } - } - if !valueExists { - return false - } - } - } - return true -} - -// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against -// the given regex -func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool { - for k, v := range toCheck { - // Check if key exists. - if canonicalKey { - k = http.CanonicalHeaderKey(k) - } - if values := toMatch[k]; values == nil { - return false - } else if v != nil { - // If value was defined as an empty string we only check that the - // key exists. Otherwise we also check for equality. - valueExists := false - for _, value := range values { - if v.MatchString(value) { - valueExists = true - break - } - } - if !valueExists { - return false - } - } - } - return true -} - -// methodNotAllowed replies to the request with an HTTP status code 405. -func methodNotAllowed(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusMethodNotAllowed) -} - -// methodNotAllowedHandler returns a simple request handler -// that replies to each request with a status code 405. -func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) } diff --git a/vendor/github.com/gorilla/mux/regexp.go b/vendor/github.com/gorilla/mux/regexp.go deleted file mode 100644 index 2b57e56..0000000 --- a/vendor/github.com/gorilla/mux/regexp.go +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mux - -import ( - "bytes" - "fmt" - "net/http" - "net/url" - "regexp" - "strconv" - "strings" -) - -type routeRegexpOptions struct { - strictSlash bool - useEncodedPath bool -} - -type regexpType int - -const ( - regexpTypePath regexpType = 0 - regexpTypeHost regexpType = 1 - regexpTypePrefix regexpType = 2 - regexpTypeQuery regexpType = 3 -) - -// newRouteRegexp parses a route template and returns a routeRegexp, -// used to match a host, a path or a query string. -// -// It will extract named variables, assemble a regexp to be matched, create -// a "reverse" template to build URLs and compile regexps to validate variable -// values used in URL building. -// -// Previously we accepted only Python-like identifiers for variable -// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that -// name and pattern can't be empty, and names can't contain a colon. -func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) { - // Check if it is well-formed. - idxs, errBraces := braceIndices(tpl) - if errBraces != nil { - return nil, errBraces - } - // Backup the original. - template := tpl - // Now let's parse it. - defaultPattern := "[^/]+" - if typ == regexpTypeQuery { - defaultPattern = ".*" - } else if typ == regexpTypeHost { - defaultPattern = "[^.]+" - } - // Only match strict slash if not matching - if typ != regexpTypePath { - options.strictSlash = false - } - // Set a flag for strictSlash. - endSlash := false - if options.strictSlash && strings.HasSuffix(tpl, "/") { - tpl = tpl[:len(tpl)-1] - endSlash = true - } - varsN := make([]string, len(idxs)/2) - varsR := make([]*regexp.Regexp, len(idxs)/2) - pattern := bytes.NewBufferString("") - pattern.WriteByte('^') - reverse := bytes.NewBufferString("") - var end int - var err error - for i := 0; i < len(idxs); i += 2 { - // Set all values we are interested in. - raw := tpl[end:idxs[i]] - end = idxs[i+1] - parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) - name := parts[0] - patt := defaultPattern - if len(parts) == 2 { - patt = parts[1] - } - // Name or pattern can't be empty. - if name == "" || patt == "" { - return nil, fmt.Errorf("mux: missing name or pattern in %q", - tpl[idxs[i]:end]) - } - // Build the regexp pattern. - fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt) - - // Build the reverse template. - fmt.Fprintf(reverse, "%s%%s", raw) - - // Append variable name and compiled pattern. - varsN[i/2] = name - varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) - if err != nil { - return nil, err - } - } - // Add the remaining. - raw := tpl[end:] - pattern.WriteString(regexp.QuoteMeta(raw)) - if options.strictSlash { - pattern.WriteString("[/]?") - } - if typ == regexpTypeQuery { - // Add the default pattern if the query value is empty - if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" { - pattern.WriteString(defaultPattern) - } - } - if typ != regexpTypePrefix { - pattern.WriteByte('$') - } - reverse.WriteString(raw) - if endSlash { - reverse.WriteByte('/') - } - // Compile full regexp. - reg, errCompile := regexp.Compile(pattern.String()) - if errCompile != nil { - return nil, errCompile - } - - // Check for capturing groups which used to work in older versions - if reg.NumSubexp() != len(idxs)/2 { - panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) + - "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)") - } - - // Done! - return &routeRegexp{ - template: template, - regexpType: typ, - options: options, - regexp: reg, - reverse: reverse.String(), - varsN: varsN, - varsR: varsR, - }, nil -} - -// routeRegexp stores a regexp to match a host or path and information to -// collect and validate route variables. -type routeRegexp struct { - // The unmodified template. - template string - // The type of match - regexpType regexpType - // Options for matching - options routeRegexpOptions - // Expanded regexp. - regexp *regexp.Regexp - // Reverse template. - reverse string - // Variable names. - varsN []string - // Variable regexps (validators). - varsR []*regexp.Regexp -} - -// Match matches the regexp against the URL host or path. -func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { - if r.regexpType != regexpTypeHost { - if r.regexpType == regexpTypeQuery { - return r.matchQueryString(req) - } - path := req.URL.Path - if r.options.useEncodedPath { - path = req.URL.EscapedPath() - } - return r.regexp.MatchString(path) - } - - return r.regexp.MatchString(getHost(req)) -} - -// url builds a URL part using the given values. -func (r *routeRegexp) url(values map[string]string) (string, error) { - urlValues := make([]interface{}, len(r.varsN)) - for k, v := range r.varsN { - value, ok := values[v] - if !ok { - return "", fmt.Errorf("mux: missing route variable %q", v) - } - if r.regexpType == regexpTypeQuery { - value = url.QueryEscape(value) - } - urlValues[k] = value - } - rv := fmt.Sprintf(r.reverse, urlValues...) - if !r.regexp.MatchString(rv) { - // The URL is checked against the full regexp, instead of checking - // individual variables. This is faster but to provide a good error - // message, we check individual regexps if the URL doesn't match. - for k, v := range r.varsN { - if !r.varsR[k].MatchString(values[v]) { - return "", fmt.Errorf( - "mux: variable %q doesn't match, expected %q", values[v], - r.varsR[k].String()) - } - } - } - return rv, nil -} - -// getURLQuery returns a single query parameter from a request URL. -// For a URL with foo=bar&baz=ding, we return only the relevant key -// value pair for the routeRegexp. -func (r *routeRegexp) getURLQuery(req *http.Request) string { - if r.regexpType != regexpTypeQuery { - return "" - } - templateKey := strings.SplitN(r.template, "=", 2)[0] - for key, vals := range req.URL.Query() { - if key == templateKey && len(vals) > 0 { - return key + "=" + vals[0] - } - } - return "" -} - -func (r *routeRegexp) matchQueryString(req *http.Request) bool { - return r.regexp.MatchString(r.getURLQuery(req)) -} - -// braceIndices returns the first level curly brace indices from a string. -// It returns an error in case of unbalanced braces. -func braceIndices(s string) ([]int, error) { - var level, idx int - var idxs []int - for i := 0; i < len(s); i++ { - switch s[i] { - case '{': - if level++; level == 1 { - idx = i - } - case '}': - if level--; level == 0 { - idxs = append(idxs, idx, i+1) - } else if level < 0 { - return nil, fmt.Errorf("mux: unbalanced braces in %q", s) - } - } - } - if level != 0 { - return nil, fmt.Errorf("mux: unbalanced braces in %q", s) - } - return idxs, nil -} - -// varGroupName builds a capturing group name for the indexed variable. -func varGroupName(idx int) string { - return "v" + strconv.Itoa(idx) -} - -// ---------------------------------------------------------------------------- -// routeRegexpGroup -// ---------------------------------------------------------------------------- - -// routeRegexpGroup groups the route matchers that carry variables. -type routeRegexpGroup struct { - host *routeRegexp - path *routeRegexp - queries []*routeRegexp -} - -// setMatch extracts the variables from the URL once a route matches. -func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { - // Store host variables. - if v.host != nil { - host := getHost(req) - matches := v.host.regexp.FindStringSubmatchIndex(host) - if len(matches) > 0 { - extractVars(host, matches, v.host.varsN, m.Vars) - } - } - path := req.URL.Path - if r.useEncodedPath { - path = req.URL.EscapedPath() - } - // Store path variables. - if v.path != nil { - matches := v.path.regexp.FindStringSubmatchIndex(path) - if len(matches) > 0 { - extractVars(path, matches, v.path.varsN, m.Vars) - // Check if we should redirect. - if v.path.options.strictSlash { - p1 := strings.HasSuffix(path, "/") - p2 := strings.HasSuffix(v.path.template, "/") - if p1 != p2 { - u, _ := url.Parse(req.URL.String()) - if p1 { - u.Path = u.Path[:len(u.Path)-1] - } else { - u.Path += "/" - } - m.Handler = http.RedirectHandler(u.String(), 301) - } - } - } - } - // Store query string variables. - for _, q := range v.queries { - queryURL := q.getURLQuery(req) - matches := q.regexp.FindStringSubmatchIndex(queryURL) - if len(matches) > 0 { - extractVars(queryURL, matches, q.varsN, m.Vars) - } - } -} - -// getHost tries its best to return the request host. -func getHost(r *http.Request) string { - if r.URL.IsAbs() { - return r.URL.Host - } - host := r.Host - // Slice off any port information. - if i := strings.Index(host, ":"); i != -1 { - host = host[:i] - } - return host - -} - -func extractVars(input string, matches []int, names []string, output map[string]string) { - for i, name := range names { - output[name] = input[matches[2*i+2]:matches[2*i+3]] - } -} diff --git a/vendor/github.com/gorilla/mux/route.go b/vendor/github.com/gorilla/mux/route.go deleted file mode 100644 index a591d73..0000000 --- a/vendor/github.com/gorilla/mux/route.go +++ /dev/null @@ -1,763 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mux - -import ( - "errors" - "fmt" - "net/http" - "net/url" - "regexp" - "strings" -) - -// Route stores information to match a request and build URLs. -type Route struct { - // Parent where the route was registered (a Router). - parent parentRoute - // Request handler for the route. - handler http.Handler - // List of matchers. - matchers []matcher - // Manager for the variables from host and path. - regexp *routeRegexpGroup - // If true, when the path pattern is "/path/", accessing "/path" will - // redirect to the former and vice versa. - strictSlash bool - // If true, when the path pattern is "/path//to", accessing "/path//to" - // will not redirect - skipClean bool - // If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to" - useEncodedPath bool - // The scheme used when building URLs. - buildScheme string - // If true, this route never matches: it is only used to build URLs. - buildOnly bool - // The name used to build URLs. - name string - // Error resulted from building a route. - err error - - buildVarsFunc BuildVarsFunc -} - -// SkipClean reports whether path cleaning is enabled for this route via -// Router.SkipClean. -func (r *Route) SkipClean() bool { - return r.skipClean -} - -// Match matches the route against the request. -func (r *Route) Match(req *http.Request, match *RouteMatch) bool { - if r.buildOnly || r.err != nil { - return false - } - - var matchErr error - - // Match everything. - for _, m := range r.matchers { - if matched := m.Match(req, match); !matched { - if _, ok := m.(methodMatcher); ok { - matchErr = ErrMethodMismatch - continue - } - matchErr = nil - return false - } - } - - if matchErr != nil { - match.MatchErr = matchErr - return false - } - - if match.MatchErr == ErrMethodMismatch { - // We found a route which matches request method, clear MatchErr - match.MatchErr = nil - // Then override the mis-matched handler - match.Handler = r.handler - } - - // Yay, we have a match. Let's collect some info about it. - if match.Route == nil { - match.Route = r - } - if match.Handler == nil { - match.Handler = r.handler - } - if match.Vars == nil { - match.Vars = make(map[string]string) - } - - // Set variables. - if r.regexp != nil { - r.regexp.setMatch(req, match, r) - } - return true -} - -// ---------------------------------------------------------------------------- -// Route attributes -// ---------------------------------------------------------------------------- - -// GetError returns an error resulted from building the route, if any. -func (r *Route) GetError() error { - return r.err -} - -// BuildOnly sets the route to never match: it is only used to build URLs. -func (r *Route) BuildOnly() *Route { - r.buildOnly = true - return r -} - -// Handler -------------------------------------------------------------------- - -// Handler sets a handler for the route. -func (r *Route) Handler(handler http.Handler) *Route { - if r.err == nil { - r.handler = handler - } - return r -} - -// HandlerFunc sets a handler function for the route. -func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route { - return r.Handler(http.HandlerFunc(f)) -} - -// GetHandler returns the handler for the route, if any. -func (r *Route) GetHandler() http.Handler { - return r.handler -} - -// Name ----------------------------------------------------------------------- - -// Name sets the name for the route, used to build URLs. -// If the name was registered already it will be overwritten. -func (r *Route) Name(name string) *Route { - if r.name != "" { - r.err = fmt.Errorf("mux: route already has name %q, can't set %q", - r.name, name) - } - if r.err == nil { - r.name = name - r.getNamedRoutes()[name] = r - } - return r -} - -// GetName returns the name for the route, if any. -func (r *Route) GetName() string { - return r.name -} - -// ---------------------------------------------------------------------------- -// Matchers -// ---------------------------------------------------------------------------- - -// matcher types try to match a request. -type matcher interface { - Match(*http.Request, *RouteMatch) bool -} - -// addMatcher adds a matcher to the route. -func (r *Route) addMatcher(m matcher) *Route { - if r.err == nil { - r.matchers = append(r.matchers, m) - } - return r -} - -// addRegexpMatcher adds a host or path matcher and builder to a route. -func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error { - if r.err != nil { - return r.err - } - r.regexp = r.getRegexpGroup() - if typ == regexpTypePath || typ == regexpTypePrefix { - if len(tpl) > 0 && tpl[0] != '/' { - return fmt.Errorf("mux: path must start with a slash, got %q", tpl) - } - if r.regexp.path != nil { - tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl - } - } - rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{ - strictSlash: r.strictSlash, - useEncodedPath: r.useEncodedPath, - }) - if err != nil { - return err - } - for _, q := range r.regexp.queries { - if err = uniqueVars(rr.varsN, q.varsN); err != nil { - return err - } - } - if typ == regexpTypeHost { - if r.regexp.path != nil { - if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { - return err - } - } - r.regexp.host = rr - } else { - if r.regexp.host != nil { - if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil { - return err - } - } - if typ == regexpTypeQuery { - r.regexp.queries = append(r.regexp.queries, rr) - } else { - r.regexp.path = rr - } - } - r.addMatcher(rr) - return nil -} - -// Headers -------------------------------------------------------------------- - -// headerMatcher matches the request against header values. -type headerMatcher map[string]string - -func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchMapWithString(m, r.Header, true) -} - -// Headers adds a matcher for request header values. -// It accepts a sequence of key/value pairs to be matched. For example: -// -// r := mux.NewRouter() -// r.Headers("Content-Type", "application/json", -// "X-Requested-With", "XMLHttpRequest") -// -// The above route will only match if both request header values match. -// If the value is an empty string, it will match any value if the key is set. -func (r *Route) Headers(pairs ...string) *Route { - if r.err == nil { - var headers map[string]string - headers, r.err = mapFromPairsToString(pairs...) - return r.addMatcher(headerMatcher(headers)) - } - return r -} - -// headerRegexMatcher matches the request against the route given a regex for the header -type headerRegexMatcher map[string]*regexp.Regexp - -func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchMapWithRegex(m, r.Header, true) -} - -// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex -// support. For example: -// -// r := mux.NewRouter() -// r.HeadersRegexp("Content-Type", "application/(text|json)", -// "X-Requested-With", "XMLHttpRequest") -// -// The above route will only match if both the request header matches both regular expressions. -// If the value is an empty string, it will match any value if the key is set. -// Use the start and end of string anchors (^ and $) to match an exact value. -func (r *Route) HeadersRegexp(pairs ...string) *Route { - if r.err == nil { - var headers map[string]*regexp.Regexp - headers, r.err = mapFromPairsToRegex(pairs...) - return r.addMatcher(headerRegexMatcher(headers)) - } - return r -} - -// Host ----------------------------------------------------------------------- - -// Host adds a matcher for the URL host. -// It accepts a template with zero or more URL variables enclosed by {}. -// Variables can define an optional regexp pattern to be matched: -// -// - {name} matches anything until the next dot. -// -// - {name:pattern} matches the given regexp pattern. -// -// For example: -// -// r := mux.NewRouter() -// r.Host("www.example.com") -// r.Host("{subdomain}.domain.com") -// r.Host("{subdomain:[a-z]+}.domain.com") -// -// Variable names must be unique in a given route. They can be retrieved -// calling mux.Vars(request). -func (r *Route) Host(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, regexpTypeHost) - return r -} - -// MatcherFunc ---------------------------------------------------------------- - -// MatcherFunc is the function signature used by custom matchers. -type MatcherFunc func(*http.Request, *RouteMatch) bool - -// Match returns the match for a given request. -func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { - return m(r, match) -} - -// MatcherFunc adds a custom function to be used as request matcher. -func (r *Route) MatcherFunc(f MatcherFunc) *Route { - return r.addMatcher(f) -} - -// Methods -------------------------------------------------------------------- - -// methodMatcher matches the request against HTTP methods. -type methodMatcher []string - -func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchInArray(m, r.Method) -} - -// Methods adds a matcher for HTTP methods. -// It accepts a sequence of one or more methods to be matched, e.g.: -// "GET", "POST", "PUT". -func (r *Route) Methods(methods ...string) *Route { - for k, v := range methods { - methods[k] = strings.ToUpper(v) - } - return r.addMatcher(methodMatcher(methods)) -} - -// Path ----------------------------------------------------------------------- - -// Path adds a matcher for the URL path. -// It accepts a template with zero or more URL variables enclosed by {}. The -// template must start with a "/". -// Variables can define an optional regexp pattern to be matched: -// -// - {name} matches anything until the next slash. -// -// - {name:pattern} matches the given regexp pattern. -// -// For example: -// -// r := mux.NewRouter() -// r.Path("/products/").Handler(ProductsHandler) -// r.Path("/products/{key}").Handler(ProductsHandler) -// r.Path("/articles/{category}/{id:[0-9]+}"). -// Handler(ArticleHandler) -// -// Variable names must be unique in a given route. They can be retrieved -// calling mux.Vars(request). -func (r *Route) Path(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, regexpTypePath) - return r -} - -// PathPrefix ----------------------------------------------------------------- - -// PathPrefix adds a matcher for the URL path prefix. This matches if the given -// template is a prefix of the full URL path. See Route.Path() for details on -// the tpl argument. -// -// Note that it does not treat slashes specially ("/foobar/" will be matched by -// the prefix "/foo") so you may want to use a trailing slash here. -// -// Also note that the setting of Router.StrictSlash() has no effect on routes -// with a PathPrefix matcher. -func (r *Route) PathPrefix(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, regexpTypePrefix) - return r -} - -// Query ---------------------------------------------------------------------- - -// Queries adds a matcher for URL query values. -// It accepts a sequence of key/value pairs. Values may define variables. -// For example: -// -// r := mux.NewRouter() -// r.Queries("foo", "bar", "id", "{id:[0-9]+}") -// -// The above route will only match if the URL contains the defined queries -// values, e.g.: ?foo=bar&id=42. -// -// It the value is an empty string, it will match any value if the key is set. -// -// Variables can define an optional regexp pattern to be matched: -// -// - {name} matches anything until the next slash. -// -// - {name:pattern} matches the given regexp pattern. -func (r *Route) Queries(pairs ...string) *Route { - length := len(pairs) - if length%2 != 0 { - r.err = fmt.Errorf( - "mux: number of parameters must be multiple of 2, got %v", pairs) - return nil - } - for i := 0; i < length; i += 2 { - if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil { - return r - } - } - - return r -} - -// Schemes -------------------------------------------------------------------- - -// schemeMatcher matches the request against URL schemes. -type schemeMatcher []string - -func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchInArray(m, r.URL.Scheme) -} - -// Schemes adds a matcher for URL schemes. -// It accepts a sequence of schemes to be matched, e.g.: "http", "https". -func (r *Route) Schemes(schemes ...string) *Route { - for k, v := range schemes { - schemes[k] = strings.ToLower(v) - } - if r.buildScheme == "" && len(schemes) > 0 { - r.buildScheme = schemes[0] - } - return r.addMatcher(schemeMatcher(schemes)) -} - -// BuildVarsFunc -------------------------------------------------------------- - -// BuildVarsFunc is the function signature used by custom build variable -// functions (which can modify route variables before a route's URL is built). -type BuildVarsFunc func(map[string]string) map[string]string - -// BuildVarsFunc adds a custom function to be used to modify build variables -// before a route's URL is built. -func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { - r.buildVarsFunc = f - return r -} - -// Subrouter ------------------------------------------------------------------ - -// Subrouter creates a subrouter for the route. -// -// It will test the inner routes only if the parent route matched. For example: -// -// r := mux.NewRouter() -// s := r.Host("www.example.com").Subrouter() -// s.HandleFunc("/products/", ProductsHandler) -// s.HandleFunc("/products/{key}", ProductHandler) -// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) -// -// Here, the routes registered in the subrouter won't be tested if the host -// doesn't match. -func (r *Route) Subrouter() *Router { - router := &Router{parent: r, strictSlash: r.strictSlash} - r.addMatcher(router) - return router -} - -// ---------------------------------------------------------------------------- -// URL building -// ---------------------------------------------------------------------------- - -// URL builds a URL for the route. -// -// It accepts a sequence of key/value pairs for the route variables. For -// example, given this route: -// -// r := mux.NewRouter() -// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). -// Name("article") -// -// ...a URL for it can be built using: -// -// url, err := r.Get("article").URL("category", "technology", "id", "42") -// -// ...which will return an url.URL with the following path: -// -// "/articles/technology/42" -// -// This also works for host variables: -// -// r := mux.NewRouter() -// r.Host("{subdomain}.domain.com"). -// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). -// Name("article") -// -// // url.String() will be "http://news.domain.com/articles/technology/42" -// url, err := r.Get("article").URL("subdomain", "news", -// "category", "technology", -// "id", "42") -// -// All variables defined in the route are required, and their values must -// conform to the corresponding patterns. -func (r *Route) URL(pairs ...string) (*url.URL, error) { - if r.err != nil { - return nil, r.err - } - if r.regexp == nil { - return nil, errors.New("mux: route doesn't have a host or path") - } - values, err := r.prepareVars(pairs...) - if err != nil { - return nil, err - } - var scheme, host, path string - queries := make([]string, 0, len(r.regexp.queries)) - if r.regexp.host != nil { - if host, err = r.regexp.host.url(values); err != nil { - return nil, err - } - scheme = "http" - if s := r.getBuildScheme(); s != "" { - scheme = s - } - } - if r.regexp.path != nil { - if path, err = r.regexp.path.url(values); err != nil { - return nil, err - } - } - for _, q := range r.regexp.queries { - var query string - if query, err = q.url(values); err != nil { - return nil, err - } - queries = append(queries, query) - } - return &url.URL{ - Scheme: scheme, - Host: host, - Path: path, - RawQuery: strings.Join(queries, "&"), - }, nil -} - -// URLHost builds the host part of the URL for a route. See Route.URL(). -// -// The route must have a host defined. -func (r *Route) URLHost(pairs ...string) (*url.URL, error) { - if r.err != nil { - return nil, r.err - } - if r.regexp == nil || r.regexp.host == nil { - return nil, errors.New("mux: route doesn't have a host") - } - values, err := r.prepareVars(pairs...) - if err != nil { - return nil, err - } - host, err := r.regexp.host.url(values) - if err != nil { - return nil, err - } - u := &url.URL{ - Scheme: "http", - Host: host, - } - if s := r.getBuildScheme(); s != "" { - u.Scheme = s - } - return u, nil -} - -// URLPath builds the path part of the URL for a route. See Route.URL(). -// -// The route must have a path defined. -func (r *Route) URLPath(pairs ...string) (*url.URL, error) { - if r.err != nil { - return nil, r.err - } - if r.regexp == nil || r.regexp.path == nil { - return nil, errors.New("mux: route doesn't have a path") - } - values, err := r.prepareVars(pairs...) - if err != nil { - return nil, err - } - path, err := r.regexp.path.url(values) - if err != nil { - return nil, err - } - return &url.URL{ - Path: path, - }, nil -} - -// GetPathTemplate returns the template used to build the -// route match. -// This is useful for building simple REST API documentation and for instrumentation -// against third-party services. -// An error will be returned if the route does not define a path. -func (r *Route) GetPathTemplate() (string, error) { - if r.err != nil { - return "", r.err - } - if r.regexp == nil || r.regexp.path == nil { - return "", errors.New("mux: route doesn't have a path") - } - return r.regexp.path.template, nil -} - -// GetPathRegexp returns the expanded regular expression used to match route path. -// This is useful for building simple REST API documentation and for instrumentation -// against third-party services. -// An error will be returned if the route does not define a path. -func (r *Route) GetPathRegexp() (string, error) { - if r.err != nil { - return "", r.err - } - if r.regexp == nil || r.regexp.path == nil { - return "", errors.New("mux: route does not have a path") - } - return r.regexp.path.regexp.String(), nil -} - -// GetQueriesRegexp returns the expanded regular expressions used to match the -// route queries. -// This is useful for building simple REST API documentation and for instrumentation -// against third-party services. -// An error will be returned if the route does not have queries. -func (r *Route) GetQueriesRegexp() ([]string, error) { - if r.err != nil { - return nil, r.err - } - if r.regexp == nil || r.regexp.queries == nil { - return nil, errors.New("mux: route doesn't have queries") - } - var queries []string - for _, query := range r.regexp.queries { - queries = append(queries, query.regexp.String()) - } - return queries, nil -} - -// GetQueriesTemplates returns the templates used to build the -// query matching. -// This is useful for building simple REST API documentation and for instrumentation -// against third-party services. -// An error will be returned if the route does not define queries. -func (r *Route) GetQueriesTemplates() ([]string, error) { - if r.err != nil { - return nil, r.err - } - if r.regexp == nil || r.regexp.queries == nil { - return nil, errors.New("mux: route doesn't have queries") - } - var queries []string - for _, query := range r.regexp.queries { - queries = append(queries, query.template) - } - return queries, nil -} - -// GetMethods returns the methods the route matches against -// This is useful for building simple REST API documentation and for instrumentation -// against third-party services. -// An error will be returned if route does not have methods. -func (r *Route) GetMethods() ([]string, error) { - if r.err != nil { - return nil, r.err - } - for _, m := range r.matchers { - if methods, ok := m.(methodMatcher); ok { - return []string(methods), nil - } - } - return nil, errors.New("mux: route doesn't have methods") -} - -// GetHostTemplate returns the template used to build the -// route match. -// This is useful for building simple REST API documentation and for instrumentation -// against third-party services. -// An error will be returned if the route does not define a host. -func (r *Route) GetHostTemplate() (string, error) { - if r.err != nil { - return "", r.err - } - if r.regexp == nil || r.regexp.host == nil { - return "", errors.New("mux: route doesn't have a host") - } - return r.regexp.host.template, nil -} - -// prepareVars converts the route variable pairs into a map. If the route has a -// BuildVarsFunc, it is invoked. -func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { - m, err := mapFromPairsToString(pairs...) - if err != nil { - return nil, err - } - return r.buildVars(m), nil -} - -func (r *Route) buildVars(m map[string]string) map[string]string { - if r.parent != nil { - m = r.parent.buildVars(m) - } - if r.buildVarsFunc != nil { - m = r.buildVarsFunc(m) - } - return m -} - -// ---------------------------------------------------------------------------- -// parentRoute -// ---------------------------------------------------------------------------- - -// parentRoute allows routes to know about parent host and path definitions. -type parentRoute interface { - getBuildScheme() string - getNamedRoutes() map[string]*Route - getRegexpGroup() *routeRegexpGroup - buildVars(map[string]string) map[string]string -} - -func (r *Route) getBuildScheme() string { - if r.buildScheme != "" { - return r.buildScheme - } - if r.parent != nil { - return r.parent.getBuildScheme() - } - return "" -} - -// getNamedRoutes returns the map where named routes are registered. -func (r *Route) getNamedRoutes() map[string]*Route { - if r.parent == nil { - // During tests router is not always set. - r.parent = NewRouter() - } - return r.parent.getNamedRoutes() -} - -// getRegexpGroup returns regexp definitions from this route. -func (r *Route) getRegexpGroup() *routeRegexpGroup { - if r.regexp == nil { - if r.parent == nil { - // During tests router is not always set. - r.parent = NewRouter() - } - regexp := r.parent.getRegexpGroup() - if regexp == nil { - r.regexp = new(routeRegexpGroup) - } else { - // Copy. - r.regexp = &routeRegexpGroup{ - host: regexp.host, - path: regexp.path, - queries: regexp.queries, - } - } - } - return r.regexp -} diff --git a/vendor/github.com/gorilla/mux/test_helpers.go b/vendor/github.com/gorilla/mux/test_helpers.go deleted file mode 100644 index 32ecffd..0000000 --- a/vendor/github.com/gorilla/mux/test_helpers.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mux - -import "net/http" - -// SetURLVars sets the URL variables for the given request, to be accessed via -// mux.Vars for testing route behaviour. Arguments are not modified, a shallow -// copy is returned. -// -// This API should only be used for testing purposes; it provides a way to -// inject variables into the request context. Alternatively, URL variables -// can be set by making a route that captures the required variables, -// starting a server and sending the request to that server. -func SetURLVars(r *http.Request, val map[string]string) *http.Request { - return setVars(r, val) -} diff --git a/vendor/github.com/juju/errors/.gitignore b/vendor/github.com/juju/errors/.gitignore new file mode 100644 index 0000000..8365624 --- /dev/null +++ b/vendor/github.com/juju/errors/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/vendor/github.com/juju/errors/LICENSE b/vendor/github.com/juju/errors/LICENSE new file mode 100644 index 0000000..ade9307 --- /dev/null +++ b/vendor/github.com/juju/errors/LICENSE @@ -0,0 +1,191 @@ +All files in this repository are licensed as follows. If you contribute +to this repository, it is assumed that you license your contribution +under the same license unless you state otherwise. + +All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file. + +This software is licensed under the LGPLv3, included below. + +As a special exception to the GNU Lesser General Public License version 3 +("LGPL3"), the copyright holders of this Library give you permission to +convey to a third party a Combined Work that links statically or dynamically +to this Library without providing any Minimal Corresponding Source or +Minimal Application Code as set out in 4d or providing the installation +information set out in section 4e, provided that you comply with the other +provisions of LGPL3 and provided that you meet, for the Application the +terms and conditions of the license(s) which apply to the Application. + +Except as stated in this special exception, the provisions of LGPL3 will +continue to comply in full to this Library. If you modify this Library, you +may apply this exception to your version of this Library, but you are not +obliged to do so. If you do not wish to do so, delete this exception +statement from your version. This exception does not (and cannot) modify any +license terms which apply to the Application, with which you must still +comply. + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/vendor/github.com/juju/errors/Makefile b/vendor/github.com/juju/errors/Makefile new file mode 100644 index 0000000..ab7c2e6 --- /dev/null +++ b/vendor/github.com/juju/errors/Makefile @@ -0,0 +1,11 @@ +default: check + +check: + go test && go test -compiler gccgo + +docs: + godoc2md github.com/juju/errors > README.md + sed -i 's|\[godoc-link-here\]|[![GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors)|' README.md + + +.PHONY: default check docs diff --git a/vendor/github.com/juju/errors/README.md b/vendor/github.com/juju/errors/README.md new file mode 100644 index 0000000..782a6f4 --- /dev/null +++ b/vendor/github.com/juju/errors/README.md @@ -0,0 +1,543 @@ + +# errors + import "github.com/juju/errors" + +[![GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors) + +The juju/errors provides an easy way to annotate errors without losing the +orginal error context. + +The exported `New` and `Errorf` functions are designed to replace the +`errors.New` and `fmt.Errorf` functions respectively. The same underlying +error is there, but the package also records the location at which the error +was created. + +A primary use case for this library is to add extra context any time an +error is returned from a function. + + + if err := SomeFunc(); err != nil { + return err + } + +This instead becomes: + + + if err := SomeFunc(); err != nil { + return errors.Trace(err) + } + +which just records the file and line number of the Trace call, or + + + if err := SomeFunc(); err != nil { + return errors.Annotate(err, "more context") + } + +which also adds an annotation to the error. + +When you want to check to see if an error is of a particular type, a helper +function is normally exported by the package that returned the error, like the +`os` package does. The underlying cause of the error is available using the +`Cause` function. + + + os.IsNotExist(errors.Cause(err)) + +The result of the `Error()` call on an annotated error is the annotations joined +with colons, then the result of the `Error()` method for the underlying error +that was the cause. + + + err := errors.Errorf("original") + err = errors.Annotatef(err, "context") + err = errors.Annotatef(err, "more context") + err.Error() -> "more context: context: original" + +Obviously recording the file, line and functions is not very useful if you +cannot get them back out again. + + + errors.ErrorStack(err) + +will return something like: + + + first error + github.com/juju/errors/annotation_test.go:193: + github.com/juju/errors/annotation_test.go:194: annotation + github.com/juju/errors/annotation_test.go:195: + github.com/juju/errors/annotation_test.go:196: more context + github.com/juju/errors/annotation_test.go:197: + +The first error was generated by an external system, so there was no location +associated. The second, fourth, and last lines were generated with Trace calls, +and the other two through Annotate. + +Sometimes when responding to an error you want to return a more specific error +for the situation. + + + if err := FindField(field); err != nil { + return errors.Wrap(err, errors.NotFoundf(field)) + } + +This returns an error where the complete error stack is still available, and +`errors.Cause()` will return the `NotFound` error. + + + + + + +## func AlreadyExistsf +``` go +func AlreadyExistsf(format string, args ...interface{}) error +``` +AlreadyExistsf returns an error which satisfies IsAlreadyExists(). + + +## func Annotate +``` go +func Annotate(other error, message string) error +``` +Annotate is used to add extra context to an existing error. The location of +the Annotate call is recorded with the annotations. The file, line and +function are also recorded. + +For example: + + + if err := SomeFunc(); err != nil { + return errors.Annotate(err, "failed to frombulate") + } + + +## func Annotatef +``` go +func Annotatef(other error, format string, args ...interface{}) error +``` +Annotatef is used to add extra context to an existing error. The location of +the Annotate call is recorded with the annotations. The file, line and +function are also recorded. + +For example: + + + if err := SomeFunc(); err != nil { + return errors.Annotatef(err, "failed to frombulate the %s", arg) + } + + +## func Cause +``` go +func Cause(err error) error +``` +Cause returns the cause of the given error. This will be either the +original error, or the result of a Wrap or Mask call. + +Cause is the usual way to diagnose errors that may have been wrapped by +the other errors functions. + + +## func DeferredAnnotatef +``` go +func DeferredAnnotatef(err *error, format string, args ...interface{}) +``` +DeferredAnnotatef annotates the given error (when it is not nil) with the given +format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef +does nothing. This method is used in a defer statement in order to annotate any +resulting error with the same message. + +For example: + + + defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg) + + +## func Details +``` go +func Details(err error) string +``` +Details returns information about the stack of errors wrapped by err, in +the format: + + + [{filename:99: error one} {otherfile:55: cause of error one}] + +This is a terse alternative to ErrorStack as it returns a single line. + + +## func ErrorStack +``` go +func ErrorStack(err error) string +``` +ErrorStack returns a string representation of the annotated error. If the +error passed as the parameter is not an annotated error, the result is +simply the result of the Error() method on that error. + +If the error is an annotated error, a multi-line string is returned where +each line represents one entry in the annotation stack. The full filename +from the call stack is used in the output. + + + first error + github.com/juju/errors/annotation_test.go:193: + github.com/juju/errors/annotation_test.go:194: annotation + github.com/juju/errors/annotation_test.go:195: + github.com/juju/errors/annotation_test.go:196: more context + github.com/juju/errors/annotation_test.go:197: + + +## func Errorf +``` go +func Errorf(format string, args ...interface{}) error +``` +Errorf creates a new annotated error and records the location that the +error is created. This should be a drop in replacement for fmt.Errorf. + +For example: + + + return errors.Errorf("validation failed: %s", message) + + +## func IsAlreadyExists +``` go +func IsAlreadyExists(err error) bool +``` +IsAlreadyExists reports whether the error was created with +AlreadyExistsf() or NewAlreadyExists(). + + +## func IsNotFound +``` go +func IsNotFound(err error) bool +``` +IsNotFound reports whether err was created with NotFoundf() or +NewNotFound(). + + +## func IsNotImplemented +``` go +func IsNotImplemented(err error) bool +``` +IsNotImplemented reports whether err was created with +NotImplementedf() or NewNotImplemented(). + + +## func IsNotSupported +``` go +func IsNotSupported(err error) bool +``` +IsNotSupported reports whether the error was created with +NotSupportedf() or NewNotSupported(). + + +## func IsNotValid +``` go +func IsNotValid(err error) bool +``` +IsNotValid reports whether the error was created with NotValidf() or +NewNotValid(). + + +## func IsUnauthorized +``` go +func IsUnauthorized(err error) bool +``` +IsUnauthorized reports whether err was created with Unauthorizedf() or +NewUnauthorized(). + + +## func Mask +``` go +func Mask(other error) error +``` +Mask hides the underlying error type, and records the location of the masking. + + +## func Maskf +``` go +func Maskf(other error, format string, args ...interface{}) error +``` +Mask masks the given error with the given format string and arguments (like +fmt.Sprintf), returning a new error that maintains the error stack, but +hides the underlying error type. The error string still contains the full +annotations. If you want to hide the annotations, call Wrap. + + +## func New +``` go +func New(message string) error +``` +New is a drop in replacement for the standard libary errors module that records +the location that the error is created. + +For example: + + + return errors.New("validation failed") + + +## func NewAlreadyExists +``` go +func NewAlreadyExists(err error, msg string) error +``` +NewAlreadyExists returns an error which wraps err and satisfies +IsAlreadyExists(). + + +## func NewNotFound +``` go +func NewNotFound(err error, msg string) error +``` +NewNotFound returns an error which wraps err that satisfies +IsNotFound(). + + +## func NewNotImplemented +``` go +func NewNotImplemented(err error, msg string) error +``` +NewNotImplemented returns an error which wraps err and satisfies +IsNotImplemented(). + + +## func NewNotSupported +``` go +func NewNotSupported(err error, msg string) error +``` +NewNotSupported returns an error which wraps err and satisfies +IsNotSupported(). + + +## func NewNotValid +``` go +func NewNotValid(err error, msg string) error +``` +NewNotValid returns an error which wraps err and satisfies IsNotValid(). + + +## func NewUnauthorized +``` go +func NewUnauthorized(err error, msg string) error +``` +NewUnauthorized returns an error which wraps err and satisfies +IsUnauthorized(). + + +## func NotFoundf +``` go +func NotFoundf(format string, args ...interface{}) error +``` +NotFoundf returns an error which satisfies IsNotFound(). + + +## func NotImplementedf +``` go +func NotImplementedf(format string, args ...interface{}) error +``` +NotImplementedf returns an error which satisfies IsNotImplemented(). + + +## func NotSupportedf +``` go +func NotSupportedf(format string, args ...interface{}) error +``` +NotSupportedf returns an error which satisfies IsNotSupported(). + + +## func NotValidf +``` go +func NotValidf(format string, args ...interface{}) error +``` +NotValidf returns an error which satisfies IsNotValid(). + + +## func Trace +``` go +func Trace(other error) error +``` +Trace adds the location of the Trace call to the stack. The Cause of the +resulting error is the same as the error parameter. If the other error is +nil, the result will be nil. + +For example: + + + if err := SomeFunc(); err != nil { + return errors.Trace(err) + } + + +## func Unauthorizedf +``` go +func Unauthorizedf(format string, args ...interface{}) error +``` +Unauthorizedf returns an error which satisfies IsUnauthorized(). + + +## func Forbiddenf +``` go +func Forbiddenf(format string, args ...interface{}) error +``` +Forbiddenf returns an error which satisfies IsForbidden(). + + +## func Wrap +``` go +func Wrap(other, newDescriptive error) error +``` +Wrap changes the Cause of the error. The location of the Wrap call is also +stored in the error stack. + +For example: + + + if err := SomeFunc(); err != nil { + newErr := &packageError{"more context", private_value} + return errors.Wrap(err, newErr) + } + + +## func Wrapf +``` go +func Wrapf(other, newDescriptive error, format string, args ...interface{}) error +``` +Wrapf changes the Cause of the error, and adds an annotation. The location +of the Wrap call is also stored in the error stack. + +For example: + + + if err := SomeFunc(); err != nil { + return errors.Wrapf(err, simpleErrorType, "invalid value %q", value) + } + + + +## type Err +``` go +type Err struct { + // contains filtered or unexported fields +} +``` +Err holds a description of an error along with information about +where the error was created. + +It may be embedded in custom error types to add extra information that +this errors package can understand. + + + + + + + + + +### func NewErr +``` go +func NewErr(format string, args ...interface{}) Err +``` +NewErr is used to return an Err for the purpose of embedding in other +structures. The location is not specified, and needs to be set with a call +to SetLocation. + +For example: + + + type FooError struct { + errors.Err + code int + } + + func NewFooError(code int) error { + err := &FooError{errors.NewErr("foo"), code} + err.SetLocation(1) + return err + } + + + + +### func (\*Err) Cause +``` go +func (e *Err) Cause() error +``` +The Cause of an error is the most recent error in the error stack that +meets one of these criteria: the original error that was raised; the new +error that was passed into the Wrap function; the most recently masked +error; or nil if the error itself is considered the Cause. Normally this +method is not invoked directly, but instead through the Cause stand alone +function. + + + +### func (\*Err) Error +``` go +func (e *Err) Error() string +``` +Error implements error.Error. + + + +### func (\*Err) Location +``` go +func (e *Err) Location() (filename string, line int) +``` +Location is the file and line of where the error was most recently +created or annotated. + + + +### func (\*Err) Message +``` go +func (e *Err) Message() string +``` +Message returns the message stored with the most recent location. This is +the empty string if the most recent call was Trace, or the message stored +with Annotate or Mask. + + + +### func (\*Err) SetLocation +``` go +func (e *Err) SetLocation(callDepth int) +``` +SetLocation records the source location of the error at callDepth stack +frames above the call. + + + +### func (\*Err) StackTrace +``` go +func (e *Err) StackTrace() []string +``` +StackTrace returns one string for each location recorded in the stack of +errors. The first value is the originating error, with a line for each +other annotation or tracing of the error. + + + +### func (\*Err) Underlying +``` go +func (e *Err) Underlying() error +``` +Underlying returns the previous error in the error stack, if any. A client +should not ever really call this method. It is used to build the error +stack and should not be introspected by client calls. Or more +specifically, clients should not depend on anything but the `Cause` of an +error. + + + + + + + + + +- - - +Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) \ No newline at end of file diff --git a/vendor/github.com/juju/errors/doc.go b/vendor/github.com/juju/errors/doc.go new file mode 100644 index 0000000..35b119a --- /dev/null +++ b/vendor/github.com/juju/errors/doc.go @@ -0,0 +1,81 @@ +// Copyright 2013, 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +/* +[godoc-link-here] + +The juju/errors provides an easy way to annotate errors without losing the +orginal error context. + +The exported `New` and `Errorf` functions are designed to replace the +`errors.New` and `fmt.Errorf` functions respectively. The same underlying +error is there, but the package also records the location at which the error +was created. + +A primary use case for this library is to add extra context any time an +error is returned from a function. + + if err := SomeFunc(); err != nil { + return err + } + +This instead becomes: + + if err := SomeFunc(); err != nil { + return errors.Trace(err) + } + +which just records the file and line number of the Trace call, or + + if err := SomeFunc(); err != nil { + return errors.Annotate(err, "more context") + } + +which also adds an annotation to the error. + +When you want to check to see if an error is of a particular type, a helper +function is normally exported by the package that returned the error, like the +`os` package does. The underlying cause of the error is available using the +`Cause` function. + + os.IsNotExist(errors.Cause(err)) + +The result of the `Error()` call on an annotated error is the annotations joined +with colons, then the result of the `Error()` method for the underlying error +that was the cause. + + err := errors.Errorf("original") + err = errors.Annotatef(err, "context") + err = errors.Annotatef(err, "more context") + err.Error() -> "more context: context: original" + +Obviously recording the file, line and functions is not very useful if you +cannot get them back out again. + + errors.ErrorStack(err) + +will return something like: + + first error + github.com/juju/errors/annotation_test.go:193: + github.com/juju/errors/annotation_test.go:194: annotation + github.com/juju/errors/annotation_test.go:195: + github.com/juju/errors/annotation_test.go:196: more context + github.com/juju/errors/annotation_test.go:197: + +The first error was generated by an external system, so there was no location +associated. The second, fourth, and last lines were generated with Trace calls, +and the other two through Annotate. + +Sometimes when responding to an error you want to return a more specific error +for the situation. + + if err := FindField(field); err != nil { + return errors.Wrap(err, errors.NotFoundf(field)) + } + +This returns an error where the complete error stack is still available, and +`errors.Cause()` will return the `NotFound` error. + +*/ +package errors diff --git a/vendor/github.com/juju/errors/error.go b/vendor/github.com/juju/errors/error.go new file mode 100644 index 0000000..b7df735 --- /dev/null +++ b/vendor/github.com/juju/errors/error.go @@ -0,0 +1,172 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package errors + +import ( + "fmt" + "reflect" + "runtime" +) + +// Err holds a description of an error along with information about +// where the error was created. +// +// It may be embedded in custom error types to add extra information that +// this errors package can understand. +type Err struct { + // message holds an annotation of the error. + message string + + // cause holds the cause of the error as returned + // by the Cause method. + cause error + + // previous holds the previous error in the error stack, if any. + previous error + + // file and line hold the source code location where the error was + // created. + file string + line int +} + +// NewErr is used to return an Err for the purpose of embedding in other +// structures. The location is not specified, and needs to be set with a call +// to SetLocation. +// +// For example: +// type FooError struct { +// errors.Err +// code int +// } +// +// func NewFooError(code int) error { +// err := &FooError{errors.NewErr("foo"), code} +// err.SetLocation(1) +// return err +// } +func NewErr(format string, args ...interface{}) Err { + return Err{ + message: fmt.Sprintf(format, args...), + } +} + +// NewErrWithCause is used to return an Err with case by other error for the purpose of embedding in other +// structures. The location is not specified, and needs to be set with a call +// to SetLocation. +// +// For example: +// type FooError struct { +// errors.Err +// code int +// } +// +// func (e *FooError) Annotate(format string, args ...interface{}) error { +// err := &FooError{errors.NewErrWithCause(e.Err, format, args...), e.code} +// err.SetLocation(1) +// return err +// }) +func NewErrWithCause(other error, format string, args ...interface{}) Err { + return Err{ + message: fmt.Sprintf(format, args...), + cause: Cause(other), + previous: other, + } +} + +// Location is the file and line of where the error was most recently +// created or annotated. +func (e *Err) Location() (filename string, line int) { + return e.file, e.line +} + +// Underlying returns the previous error in the error stack, if any. A client +// should not ever really call this method. It is used to build the error +// stack and should not be introspected by client calls. Or more +// specifically, clients should not depend on anything but the `Cause` of an +// error. +func (e *Err) Underlying() error { + return e.previous +} + +// The Cause of an error is the most recent error in the error stack that +// meets one of these criteria: the original error that was raised; the new +// error that was passed into the Wrap function; the most recently masked +// error; or nil if the error itself is considered the Cause. Normally this +// method is not invoked directly, but instead through the Cause stand alone +// function. +func (e *Err) Cause() error { + return e.cause +} + +// Message returns the message stored with the most recent location. This is +// the empty string if the most recent call was Trace, or the message stored +// with Annotate or Mask. +func (e *Err) Message() string { + return e.message +} + +// Error implements error.Error. +func (e *Err) Error() string { + // We want to walk up the stack of errors showing the annotations + // as long as the cause is the same. + err := e.previous + if !sameError(Cause(err), e.cause) && e.cause != nil { + err = e.cause + } + switch { + case err == nil: + return e.message + case e.message == "": + return err.Error() + } + return fmt.Sprintf("%s: %v", e.message, err) +} + +// Format implements fmt.Formatter +// When printing errors with %+v it also prints the stack trace. +// %#v unsurprisingly will print the real underlying type. +func (e *Err) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + fmt.Fprintf(s, "%s", ErrorStack(e)) + return + case s.Flag('#'): + // avoid infinite recursion by wrapping e into a type + // that doesn't implement Formatter. + fmt.Fprintf(s, "%#v", (*unformatter)(e)) + return + } + fallthrough + case 's': + fmt.Fprintf(s, "%s", e.Error()) + } +} + +// helper for Format +type unformatter Err + +func (unformatter) Format() { /* break the fmt.Formatter interface */ } + +// SetLocation records the source location of the error at callDepth stack +// frames above the call. +func (e *Err) SetLocation(callDepth int) { + _, file, line, _ := runtime.Caller(callDepth + 1) + e.file = trimGoPath(file) + e.line = line +} + +// StackTrace returns one string for each location recorded in the stack of +// errors. The first value is the originating error, with a line for each +// other annotation or tracing of the error. +func (e *Err) StackTrace() []string { + return errorStack(e) +} + +// Ideally we'd have a way to check identity, but deep equals will do. +func sameError(e1, e2 error) bool { + return reflect.DeepEqual(e1, e2) +} diff --git a/vendor/github.com/juju/errors/errortypes.go b/vendor/github.com/juju/errors/errortypes.go new file mode 100644 index 0000000..9b731c4 --- /dev/null +++ b/vendor/github.com/juju/errors/errortypes.go @@ -0,0 +1,309 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package errors + +import ( + "fmt" +) + +// wrap is a helper to construct an *wrapper. +func wrap(err error, format, suffix string, args ...interface{}) Err { + newErr := Err{ + message: fmt.Sprintf(format+suffix, args...), + previous: err, + } + newErr.SetLocation(2) + return newErr +} + +// notFound represents an error when something has not been found. +type notFound struct { + Err +} + +// NotFoundf returns an error which satisfies IsNotFound(). +func NotFoundf(format string, args ...interface{}) error { + return ¬Found{wrap(nil, format, " not found", args...)} +} + +// NewNotFound returns an error which wraps err that satisfies +// IsNotFound(). +func NewNotFound(err error, msg string) error { + return ¬Found{wrap(err, msg, "")} +} + +// IsNotFound reports whether err was created with NotFoundf() or +// NewNotFound(). +func IsNotFound(err error) bool { + err = Cause(err) + _, ok := err.(*notFound) + return ok +} + +// userNotFound represents an error when an inexistent user is looked up. +type userNotFound struct { + Err +} + +// UserNotFoundf returns an error which satisfies IsUserNotFound(). +func UserNotFoundf(format string, args ...interface{}) error { + return &userNotFound{wrap(nil, format, " user not found", args...)} +} + +// NewUserNotFound returns an error which wraps err and satisfies +// IsUserNotFound(). +func NewUserNotFound(err error, msg string) error { + return &userNotFound{wrap(err, msg, "")} +} + +// IsUserNotFound reports whether err was created with UserNotFoundf() or +// NewUserNotFound(). +func IsUserNotFound(err error) bool { + err = Cause(err) + _, ok := err.(*userNotFound) + return ok +} + +// unauthorized represents an error when an operation is unauthorized. +type unauthorized struct { + Err +} + +// Unauthorizedf returns an error which satisfies IsUnauthorized(). +func Unauthorizedf(format string, args ...interface{}) error { + return &unauthorized{wrap(nil, format, "", args...)} +} + +// NewUnauthorized returns an error which wraps err and satisfies +// IsUnauthorized(). +func NewUnauthorized(err error, msg string) error { + return &unauthorized{wrap(err, msg, "")} +} + +// IsUnauthorized reports whether err was created with Unauthorizedf() or +// NewUnauthorized(). +func IsUnauthorized(err error) bool { + err = Cause(err) + _, ok := err.(*unauthorized) + return ok +} + +// notImplemented represents an error when something is not +// implemented. +type notImplemented struct { + Err +} + +// NotImplementedf returns an error which satisfies IsNotImplemented(). +func NotImplementedf(format string, args ...interface{}) error { + return ¬Implemented{wrap(nil, format, " not implemented", args...)} +} + +// NewNotImplemented returns an error which wraps err and satisfies +// IsNotImplemented(). +func NewNotImplemented(err error, msg string) error { + return ¬Implemented{wrap(err, msg, "")} +} + +// IsNotImplemented reports whether err was created with +// NotImplementedf() or NewNotImplemented(). +func IsNotImplemented(err error) bool { + err = Cause(err) + _, ok := err.(*notImplemented) + return ok +} + +// alreadyExists represents and error when something already exists. +type alreadyExists struct { + Err +} + +// AlreadyExistsf returns an error which satisfies IsAlreadyExists(). +func AlreadyExistsf(format string, args ...interface{}) error { + return &alreadyExists{wrap(nil, format, " already exists", args...)} +} + +// NewAlreadyExists returns an error which wraps err and satisfies +// IsAlreadyExists(). +func NewAlreadyExists(err error, msg string) error { + return &alreadyExists{wrap(err, msg, "")} +} + +// IsAlreadyExists reports whether the error was created with +// AlreadyExistsf() or NewAlreadyExists(). +func IsAlreadyExists(err error) bool { + err = Cause(err) + _, ok := err.(*alreadyExists) + return ok +} + +// notSupported represents an error when something is not supported. +type notSupported struct { + Err +} + +// NotSupportedf returns an error which satisfies IsNotSupported(). +func NotSupportedf(format string, args ...interface{}) error { + return ¬Supported{wrap(nil, format, " not supported", args...)} +} + +// NewNotSupported returns an error which wraps err and satisfies +// IsNotSupported(). +func NewNotSupported(err error, msg string) error { + return ¬Supported{wrap(err, msg, "")} +} + +// IsNotSupported reports whether the error was created with +// NotSupportedf() or NewNotSupported(). +func IsNotSupported(err error) bool { + err = Cause(err) + _, ok := err.(*notSupported) + return ok +} + +// notValid represents an error when something is not valid. +type notValid struct { + Err +} + +// NotValidf returns an error which satisfies IsNotValid(). +func NotValidf(format string, args ...interface{}) error { + return ¬Valid{wrap(nil, format, " not valid", args...)} +} + +// NewNotValid returns an error which wraps err and satisfies IsNotValid(). +func NewNotValid(err error, msg string) error { + return ¬Valid{wrap(err, msg, "")} +} + +// IsNotValid reports whether the error was created with NotValidf() or +// NewNotValid(). +func IsNotValid(err error) bool { + err = Cause(err) + _, ok := err.(*notValid) + return ok +} + +// notProvisioned represents an error when something is not yet provisioned. +type notProvisioned struct { + Err +} + +// NotProvisionedf returns an error which satisfies IsNotProvisioned(). +func NotProvisionedf(format string, args ...interface{}) error { + return ¬Provisioned{wrap(nil, format, " not provisioned", args...)} +} + +// NewNotProvisioned returns an error which wraps err that satisfies +// IsNotProvisioned(). +func NewNotProvisioned(err error, msg string) error { + return ¬Provisioned{wrap(err, msg, "")} +} + +// IsNotProvisioned reports whether err was created with NotProvisionedf() or +// NewNotProvisioned(). +func IsNotProvisioned(err error) bool { + err = Cause(err) + _, ok := err.(*notProvisioned) + return ok +} + +// notAssigned represents an error when something is not yet assigned to +// something else. +type notAssigned struct { + Err +} + +// NotAssignedf returns an error which satisfies IsNotAssigned(). +func NotAssignedf(format string, args ...interface{}) error { + return ¬Assigned{wrap(nil, format, " not assigned", args...)} +} + +// NewNotAssigned returns an error which wraps err that satisfies +// IsNotAssigned(). +func NewNotAssigned(err error, msg string) error { + return ¬Assigned{wrap(err, msg, "")} +} + +// IsNotAssigned reports whether err was created with NotAssignedf() or +// NewNotAssigned(). +func IsNotAssigned(err error) bool { + err = Cause(err) + _, ok := err.(*notAssigned) + return ok +} + +// badRequest represents an error when a request has bad parameters. +type badRequest struct { + Err +} + +// BadRequestf returns an error which satisfies IsBadRequest(). +func BadRequestf(format string, args ...interface{}) error { + return &badRequest{wrap(nil, format, "", args...)} +} + +// NewBadRequest returns an error which wraps err that satisfies +// IsBadRequest(). +func NewBadRequest(err error, msg string) error { + return &badRequest{wrap(err, msg, "")} +} + +// IsBadRequest reports whether err was created with BadRequestf() or +// NewBadRequest(). +func IsBadRequest(err error) bool { + err = Cause(err) + _, ok := err.(*badRequest) + return ok +} + +// methodNotAllowed represents an error when an HTTP request +// is made with an inappropriate method. +type methodNotAllowed struct { + Err +} + +// MethodNotAllowedf returns an error which satisfies IsMethodNotAllowed(). +func MethodNotAllowedf(format string, args ...interface{}) error { + return &methodNotAllowed{wrap(nil, format, "", args...)} +} + +// NewMethodNotAllowed returns an error which wraps err that satisfies +// IsMethodNotAllowed(). +func NewMethodNotAllowed(err error, msg string) error { + return &methodNotAllowed{wrap(err, msg, "")} +} + +// IsMethodNotAllowed reports whether err was created with MethodNotAllowedf() or +// NewMethodNotAllowed(). +func IsMethodNotAllowed(err error) bool { + err = Cause(err) + _, ok := err.(*methodNotAllowed) + return ok +} + +// forbidden represents an error when a request cannot be completed because of +// missing privileges +type forbidden struct { + Err +} + +// Forbiddenf returns an error which satistifes IsForbidden() +func Forbiddenf(format string, args ...interface{}) error { + return &forbidden{wrap(nil, format, "", args...)} +} + +// NewForbidden returns an error which wraps err that satisfies +// IsForbidden(). +func NewForbidden(err error, msg string) error { + return &forbidden{wrap(err, msg, "")} +} + +// IsForbidden reports whether err was created with Forbiddenf() or +// NewForbidden(). +func IsForbidden(err error) bool { + err = Cause(err) + _, ok := err.(*forbidden) + return ok +} diff --git a/vendor/github.com/juju/errors/functions.go b/vendor/github.com/juju/errors/functions.go new file mode 100644 index 0000000..f86b09b --- /dev/null +++ b/vendor/github.com/juju/errors/functions.go @@ -0,0 +1,330 @@ +// Copyright 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package errors + +import ( + "fmt" + "strings" +) + +// New is a drop in replacement for the standard library errors module that records +// the location that the error is created. +// +// For example: +// return errors.New("validation failed") +// +func New(message string) error { + err := &Err{message: message} + err.SetLocation(1) + return err +} + +// Errorf creates a new annotated error and records the location that the +// error is created. This should be a drop in replacement for fmt.Errorf. +// +// For example: +// return errors.Errorf("validation failed: %s", message) +// +func Errorf(format string, args ...interface{}) error { + err := &Err{message: fmt.Sprintf(format, args...)} + err.SetLocation(1) + return err +} + +// Trace adds the location of the Trace call to the stack. The Cause of the +// resulting error is the same as the error parameter. If the other error is +// nil, the result will be nil. +// +// For example: +// if err := SomeFunc(); err != nil { +// return errors.Trace(err) +// } +// +func Trace(other error) error { + if other == nil { + return nil + } + err := &Err{previous: other, cause: Cause(other)} + err.SetLocation(1) + return err +} + +// Annotate is used to add extra context to an existing error. The location of +// the Annotate call is recorded with the annotations. The file, line and +// function are also recorded. +// +// For example: +// if err := SomeFunc(); err != nil { +// return errors.Annotate(err, "failed to frombulate") +// } +// +func Annotate(other error, message string) error { + if other == nil { + return nil + } + err := &Err{ + previous: other, + cause: Cause(other), + message: message, + } + err.SetLocation(1) + return err +} + +// Annotatef is used to add extra context to an existing error. The location of +// the Annotate call is recorded with the annotations. The file, line and +// function are also recorded. +// +// For example: +// if err := SomeFunc(); err != nil { +// return errors.Annotatef(err, "failed to frombulate the %s", arg) +// } +// +func Annotatef(other error, format string, args ...interface{}) error { + if other == nil { + return nil + } + err := &Err{ + previous: other, + cause: Cause(other), + message: fmt.Sprintf(format, args...), + } + err.SetLocation(1) + return err +} + +// DeferredAnnotatef annotates the given error (when it is not nil) with the given +// format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef +// does nothing. This method is used in a defer statement in order to annotate any +// resulting error with the same message. +// +// For example: +// +// defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg) +// +func DeferredAnnotatef(err *error, format string, args ...interface{}) { + if *err == nil { + return + } + newErr := &Err{ + message: fmt.Sprintf(format, args...), + cause: Cause(*err), + previous: *err, + } + newErr.SetLocation(1) + *err = newErr +} + +// Wrap changes the Cause of the error. The location of the Wrap call is also +// stored in the error stack. +// +// For example: +// if err := SomeFunc(); err != nil { +// newErr := &packageError{"more context", private_value} +// return errors.Wrap(err, newErr) +// } +// +func Wrap(other, newDescriptive error) error { + err := &Err{ + previous: other, + cause: newDescriptive, + } + err.SetLocation(1) + return err +} + +// Wrapf changes the Cause of the error, and adds an annotation. The location +// of the Wrap call is also stored in the error stack. +// +// For example: +// if err := SomeFunc(); err != nil { +// return errors.Wrapf(err, simpleErrorType, "invalid value %q", value) +// } +// +func Wrapf(other, newDescriptive error, format string, args ...interface{}) error { + err := &Err{ + message: fmt.Sprintf(format, args...), + previous: other, + cause: newDescriptive, + } + err.SetLocation(1) + return err +} + +// Mask masks the given error with the given format string and arguments (like +// fmt.Sprintf), returning a new error that maintains the error stack, but +// hides the underlying error type. The error string still contains the full +// annotations. If you want to hide the annotations, call Wrap. +func Maskf(other error, format string, args ...interface{}) error { + if other == nil { + return nil + } + err := &Err{ + message: fmt.Sprintf(format, args...), + previous: other, + } + err.SetLocation(1) + return err +} + +// Mask hides the underlying error type, and records the location of the masking. +func Mask(other error) error { + if other == nil { + return nil + } + err := &Err{ + previous: other, + } + err.SetLocation(1) + return err +} + +// Cause returns the cause of the given error. This will be either the +// original error, or the result of a Wrap or Mask call. +// +// Cause is the usual way to diagnose errors that may have been wrapped by +// the other errors functions. +func Cause(err error) error { + var diag error + if err, ok := err.(causer); ok { + diag = err.Cause() + } + if diag != nil { + return diag + } + return err +} + +type causer interface { + Cause() error +} + +type wrapper interface { + // Message returns the top level error message, + // not including the message from the Previous + // error. + Message() string + + // Underlying returns the Previous error, or nil + // if there is none. + Underlying() error +} + +type locationer interface { + Location() (string, int) +} + +var ( + _ wrapper = (*Err)(nil) + _ locationer = (*Err)(nil) + _ causer = (*Err)(nil) +) + +// Details returns information about the stack of errors wrapped by err, in +// the format: +// +// [{filename:99: error one} {otherfile:55: cause of error one}] +// +// This is a terse alternative to ErrorStack as it returns a single line. +func Details(err error) string { + if err == nil { + return "[]" + } + var s []byte + s = append(s, '[') + for { + s = append(s, '{') + if err, ok := err.(locationer); ok { + file, line := err.Location() + if file != "" { + s = append(s, fmt.Sprintf("%s:%d", file, line)...) + s = append(s, ": "...) + } + } + if cerr, ok := err.(wrapper); ok { + s = append(s, cerr.Message()...) + err = cerr.Underlying() + } else { + s = append(s, err.Error()...) + err = nil + } + s = append(s, '}') + if err == nil { + break + } + s = append(s, ' ') + } + s = append(s, ']') + return string(s) +} + +// ErrorStack returns a string representation of the annotated error. If the +// error passed as the parameter is not an annotated error, the result is +// simply the result of the Error() method on that error. +// +// If the error is an annotated error, a multi-line string is returned where +// each line represents one entry in the annotation stack. The full filename +// from the call stack is used in the output. +// +// first error +// github.com/juju/errors/annotation_test.go:193: +// github.com/juju/errors/annotation_test.go:194: annotation +// github.com/juju/errors/annotation_test.go:195: +// github.com/juju/errors/annotation_test.go:196: more context +// github.com/juju/errors/annotation_test.go:197: +func ErrorStack(err error) string { + return strings.Join(errorStack(err), "\n") +} + +func errorStack(err error) []string { + if err == nil { + return nil + } + + // We want the first error first + var lines []string + for { + var buff []byte + if err, ok := err.(locationer); ok { + file, line := err.Location() + // Strip off the leading GOPATH/src path elements. + file = trimGoPath(file) + if file != "" { + buff = append(buff, fmt.Sprintf("%s:%d", file, line)...) + buff = append(buff, ": "...) + } + } + if cerr, ok := err.(wrapper); ok { + message := cerr.Message() + buff = append(buff, message...) + // If there is a cause for this error, and it is different to the cause + // of the underlying error, then output the error string in the stack trace. + var cause error + if err1, ok := err.(causer); ok { + cause = err1.Cause() + } + err = cerr.Underlying() + if cause != nil && !sameError(Cause(err), cause) { + if message != "" { + buff = append(buff, ": "...) + } + buff = append(buff, cause.Error()...) + } + } else { + buff = append(buff, err.Error()...) + err = nil + } + lines = append(lines, string(buff)) + if err == nil { + break + } + } + // reverse the lines to get the original error, which was at the end of + // the list, back to the start. + var result []string + for i := len(lines); i > 0; i-- { + result = append(result, lines[i-1]) + } + return result +} diff --git a/vendor/github.com/juju/errors/path.go b/vendor/github.com/juju/errors/path.go new file mode 100644 index 0000000..a7b726a --- /dev/null +++ b/vendor/github.com/juju/errors/path.go @@ -0,0 +1,38 @@ +// Copyright 2013, 2014 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package errors + +import ( + "runtime" + "strings" +) + +// prefixSize is used internally to trim the user specific path from the +// front of the returned filenames from the runtime call stack. +var prefixSize int + +// goPath is the deduced path based on the location of this file as compiled. +var goPath string + +func init() { + _, file, _, ok := runtime.Caller(0) + if file == "?" { + return + } + if ok { + // We know that the end of the file should be: + // github.com/juju/errors/path.go + size := len(file) + suffix := len("github.com/juju/errors/path.go") + goPath = file[:size-suffix] + prefixSize = len(goPath) + } +} + +func trimGoPath(filename string) string { + if strings.HasPrefix(filename, goPath) { + return filename[prefixSize:] + } + return filename +}