1
0
Fork 0
mirror of https://github.com/Luzifer/promcertcheck.git synced 2024-11-09 16:30:04 +00:00

Force latest version of pongo2

v3 has a bug with sorted keyword

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2017-11-05 16:50:25 +01:00
parent 18716269e1
commit 2f67c05f2e
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
94 changed files with 4386 additions and 1265 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
certcheck certcheck
promcertcheck

12
Gopkg.lock generated
View file

@ -14,10 +14,10 @@
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9" revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"
[[projects]] [[projects]]
branch = "master"
name = "github.com/flosch/pongo2" name = "github.com/flosch/pongo2"
packages = ["."] packages = ["."]
revision = "5e81b817a0c48c1c57cdf1a9056cf76bdee02ca9" revision = "1f4be1efe3b3529b7e58861f75d70120a9567dc4"
version = "v3.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -37,6 +37,12 @@
revision = "24fca303ac6da784b9e8269f724ddeb0b2eea5e7" revision = "24fca303ac6da784b9e8269f724ddeb0b2eea5e7"
version = "v1.5.0" version = "v1.5.0"
[[projects]]
branch = "master"
name = "github.com/juju/errors"
packages = ["."]
revision = "c7d06af17c68cd34c835053720b21f6549d9b0ee"
[[projects]] [[projects]]
name = "github.com/matttproud/golang_protobuf_extensions" name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"] packages = ["pbutil"]
@ -112,6 +118,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "d0a90698569f49d4e7ef58586b857a1c7c60e48eaca07f1585d02e7ce91ca092" inputs-digest = "f88f786b9ff5e99b5589aea171923fd8d9dcef123a4d6be68d7659b923e8dc1e"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View file

@ -27,6 +27,7 @@
[[constraint]] [[constraint]]
name = "github.com/flosch/pongo2" name = "github.com/flosch/pongo2"
branch = "master"
[[constraint]] [[constraint]]
name = "github.com/gorilla/mux" name = "github.com/gorilla/mux"

Binary file not shown.

View file

@ -7,6 +7,7 @@
_obj _obj
_test _test
.idea .idea
.vscode
# Architecture specific extensions/prefixes # Architecture specific extensions/prefixes
*.[568vq] *.[568vq]

View file

@ -1,12 +1,13 @@
language: go language: go
go: go:
- 1.3 - 1.7
- tip - tip
install: 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 github.com/mattn/goveralls
- go get gopkg.in/check.v1 - go get gopkg.in/check.v1
- go get github.com/juju/errors
script: script:
- go test -v -covermode=count -coverprofile=coverage.out -bench . -cpu 1,4 - 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' - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN || true'

View file

@ -1,8 +1,9 @@
# [pongo](https://en.wikipedia.org/wiki/Pongo_%28genus%29)2 # [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) [![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/) [![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) [![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. 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`). * 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. * `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). * 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 ## 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 [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/improve template tests (see the `template_tests/` directory)
* Write middleware, libraries and websites using pongo2. :-) * Write middleware, libraries and websites using pongo2. :-)
@ -116,6 +118,7 @@ You can access pongo2's API documentation on [godoc](https://godoc.org/github.co
## Blog post series ## 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 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 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] * [pongo2 playground](https://www.florian-schlachter.de/post/pongo2-playground/) [August 1st 2014]
@ -154,7 +157,11 @@ 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. * [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. * [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 * [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. Please add your project to this list and send me a pull request when you've developed something nice for pongo2.

View file

@ -1,13 +1,14 @@
package pongo2 package pongo2
import ( import (
"fmt"
"regexp" "regexp"
"github.com/juju/errors"
) )
var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$") var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$")
// Use this Context type to provide constants, variables, instances or functions to your template. // A Context type provides constants, variables, instances or functions to a template.
// //
// pongo2 automatically provides meta-information or functions through the "pongo2"-key. // pongo2 automatically provides meta-information or functions through the "pongo2"-key.
// Currently, context["pongo2"] contains the following keys: // Currently, context["pongo2"] contains the following keys:
@ -25,13 +26,14 @@ func (c Context) checkForValidIdentifiers() *Error {
if !reIdentifiers.MatchString(k) { if !reIdentifiers.MatchString(k) {
return &Error{ return &Error{
Sender: "checkForValidIdentifiers", Sender: "checkForValidIdentifiers",
ErrorMsg: fmt.Sprintf("Context-key '%s' (value: '%+v') is not a valid identifier.", k, v), OrigError: errors.Errorf("context-key '%s' (value: '%+v') is not a valid identifier", k, v),
} }
} }
} }
return nil return nil
} }
// Update updates this context with the key/value-pairs from another context.
func (c Context) Update(other Context) Context { func (c Context) Update(other Context) Context {
for k, v := range other { for k, v := range other {
c[k] = v c[k] = v
@ -39,6 +41,8 @@ func (c Context) Update(other Context) Context {
return c 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 // If you're writing a custom tag, your tag's Execute()-function will
// have access to the ExecutionContext. This struct stores anything // have access to the ExecutionContext. This struct stores anything
// about the current rendering process's Context including // about the current rendering process's Context including
@ -97,6 +101,10 @@ func NewChildExecutionContext(parent *ExecutionContext) *ExecutionContext {
} }
func (ctx *ExecutionContext) Error(msg string, token *Token) *Error { 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 filename := ctx.template.name
var line, col int var line, col int
if token != nil { if token != nil {
@ -113,7 +121,7 @@ func (ctx *ExecutionContext) Error(msg string, token *Token) *Error {
Column: col, Column: col,
Token: token, Token: token,
Sender: "execution", Sender: "execution",
ErrorMsg: msg, OrigError: err,
} }
} }

View file

@ -6,7 +6,7 @@ import (
"os" "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 // 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. // 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 // Make sure "Sender" is always given (if you're returning an error within
@ -19,7 +19,7 @@ type Error struct {
Column int Column int
Token *Token Token *Token
Sender string Sender string
ErrorMsg string OrigError error
} }
func (e *Error) updateFromTokenIfNeeded(template *Template, t *Token) *Error { func (e *Error) updateFromTokenIfNeeded(template *Template, t *Token) *Error {
@ -54,14 +54,14 @@ func (e *Error) Error() string {
} }
} }
s += "] " s += "] "
s += e.ErrorMsg s += e.OrigError.Error()
return s return s
} }
// Returns the affected line from the original template, if available. // RawLine returns the affected line from the original template, if available.
func (e *Error) RawLine() (line string, available bool) { func (e *Error) RawLine() (line string, available bool, outErr error) {
if e.Line <= 0 || e.Filename == "<string>" { if e.Line <= 0 || e.Filename == "<string>" {
return "", false return "", false, nil
} }
filename := e.Filename filename := e.Filename
@ -70,17 +70,22 @@ func (e *Error) RawLine() (line string, available bool) {
} }
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { 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) scanner := bufio.NewScanner(file)
l := 0 l := 0
for scanner.Scan() { for scanner.Scan() {
l++ l++
if l == e.Line { if l == e.Line {
return scanner.Text(), true return scanner.Text(), true, nil
} }
} }
return "", false return "", false, nil
} }

View file

@ -2,8 +2,11 @@ package pongo2
import ( import (
"fmt" "fmt"
"github.com/juju/errors"
) )
// FilterFunction is the type filter functions must fulfil
type FilterFunction func(in *Value, param *Value) (out *Value, err *Error) type FilterFunction func(in *Value, param *Value) (out *Value, err *Error)
var filters map[string]FilterFunction var filters map[string]FilterFunction
@ -12,32 +15,38 @@ func init() {
filters = make(map[string]FilterFunction) 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 // name, RegisterFilter will panic. You usually want to call this
// function in the filter's init() function: // function in the filter's init() function:
// http://golang.org/doc/effective_go.html#init // http://golang.org/doc/effective_go.html#init
// //
// See http://www.florian-schlachter.de/post/pongo2/ for more about // See http://www.florian-schlachter.de/post/pongo2/ for more about
// writing filters and tags. // writing filters and tags.
func RegisterFilter(name string, fn FilterFunction) { func RegisterFilter(name string, fn FilterFunction) error {
_, existing := filters[name] if FilterExists(name) {
if existing { return errors.Errorf("filter with name '%s' is already registered", name)
panic(fmt.Sprintf("Filter with name '%s' is already registered.", name))
} }
filters[name] = fn 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. // function with caution since it allows you to change existing filter behaviour.
func ReplaceFilter(name string, fn FilterFunction) { func ReplaceFilter(name string, fn FilterFunction) error {
_, existing := filters[name] if !FilterExists(name) {
if !existing { return errors.Errorf("filter with name '%s' does not exist (therefore cannot be overridden)", name)
panic(fmt.Sprintf("Filter with name '%s' does not exist (therefore cannot be overridden).", name))
} }
filters[name] = fn 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 { func MustApplyFilter(name string, value *Value, param *Value) *Value {
val, err := ApplyFilter(name, value, param) val, err := ApplyFilter(name, value, param)
if err != nil { if err != nil {
@ -46,13 +55,14 @@ func MustApplyFilter(name string, value *Value, param *Value) *Value {
return val 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) { func ApplyFilter(name string, value *Value, param *Value) (*Value, *Error) {
fn, existing := filters[name] fn, existing := filters[name]
if !existing { if !existing {
return nil, &Error{ return nil, &Error{
Sender: "applyfilter", Sender: "applyfilter",
ErrorMsg: fmt.Sprintf("Filter with name '%s' not found.", name), 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) param = AsValue(nil)
} }
filtered_value, err := fc.filterFunc(v, param) filteredValue, err := fc.filterFunc(v, param)
if err != nil { if err != nil {
return nil, err.updateFromTokenIfNeeded(ctx.template, fc.token) return nil, err.updateFromTokenIfNeeded(ctx.template, fc.token)
} }
return filtered_value, nil return filteredValue, nil
} }
// Filter = IDENT | IDENT ":" FilterArg | IDENT "|" Filter // Filter = IDENT | IDENT ":" FilterArg | IDENT "|" Filter
func (p *Parser) parseFilter() (*filterCall, *Error) { func (p *Parser) parseFilter() (*filterCall, *Error) {
ident_token := p.MatchType(TokenIdentifier) identToken := p.MatchType(TokenIdentifier)
// Check filter ident // Check filter ident
if ident_token == nil { if identToken == nil {
return nil, p.Error("Filter name must be an identifier.", nil) return nil, p.Error("Filter name must be an identifier.", nil)
} }
filter := &filterCall{ filter := &filterCall{
token: ident_token, token: identToken,
name: ident_token.Val, name: identToken.Val,
} }
// Get the appropriate filter function and bind it // Get the appropriate filter function and bind it
filterFn, exists := filters[ident_token.Val] filterFn, exists := filters[identToken.Val]
if !exists { 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 filter.filterFunc = filterFn

View file

@ -35,6 +35,8 @@ import (
"strings" "strings"
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/juju/errors"
) )
func init() { func init() {
@ -73,14 +75,15 @@ func init() {
RegisterFilter("removetags", filterRemovetags) RegisterFilter("removetags", filterRemovetags)
RegisterFilter("rjust", filterRjust) RegisterFilter("rjust", filterRjust)
RegisterFilter("slice", filterSlice) RegisterFilter("slice", filterSlice)
RegisterFilter("split", filterSplit)
RegisterFilter("stringformat", filterStringformat) RegisterFilter("stringformat", filterStringformat)
RegisterFilter("striptags", filterStriptags) RegisterFilter("striptags", filterStriptags)
RegisterFilter("time", filterDate) // time uses filterDate (same golang-format) RegisterFilter("time", filterDate) // time uses filterDate (same golang-format)
RegisterFilter("title", filterTitle) RegisterFilter("title", filterTitle)
RegisterFilter("truncatechars", filterTruncatechars) RegisterFilter("truncatechars", filterTruncatechars)
RegisterFilter("truncatechars_html", filterTruncatecharsHtml) RegisterFilter("truncatechars_html", filterTruncatecharsHTML)
RegisterFilter("truncatewords", filterTruncatewords) RegisterFilter("truncatewords", filterTruncatewords)
RegisterFilter("truncatewords_html", filterTruncatewordsHtml) RegisterFilter("truncatewords_html", filterTruncatewordsHTML)
RegisterFilter("upper", filterUpper) RegisterFilter("upper", filterUpper)
RegisterFilter("urlencode", filterUrlencode) RegisterFilter("urlencode", filterUrlencode)
RegisterFilter("urlize", filterUrlize) RegisterFilter("urlize", filterUrlize)
@ -105,9 +108,9 @@ func filterTruncatecharsHelper(s string, newLen int) string {
return string(runes) 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) vLen := len(value)
tag_stack := make([]string, 0) var tagStack []string
idx := 0 idx := 0
for idx < vLen && !cond() { for idx < vLen && !cond() {
@ -118,17 +121,17 @@ func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func(
} }
if c == '<' { if c == '<' {
new_output.WriteRune(c) newOutput.WriteRune(c)
idx += s // consume "<" idx += s // consume "<"
if idx+1 < vLen { if idx+1 < vLen {
if value[idx] == '/' { if value[idx] == '/' {
// Close tag // Close tag
new_output.WriteString("/") newOutput.WriteString("/")
tag := "" tag := ""
idx += 1 // consume "/" idx++ // consume "/"
for idx < vLen { for idx < vLen {
c2, size2 := utf8.DecodeRuneInString(value[idx:]) c2, size2 := utf8.DecodeRuneInString(value[idx:])
@ -146,21 +149,21 @@ func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func(
idx += size2 idx += size2
} }
if len(tag_stack) > 0 { if len(tagStack) > 0 {
// Ideally, the close tag is TOP of tag stack // 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 // 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-- { for i := len(tagStack) - 1; i >= 0; i-- {
if tag_stack[i] == tag { if tagStack[i] == tag {
// Found the tag // Found the tag
tag_stack[i] = tag_stack[len(tag_stack)-1] tagStack[i] = tagStack[len(tagStack)-1]
tag_stack = tag_stack[:len(tag_stack)-1] tagStack = tagStack[:len(tagStack)-1]
break break
} }
} }
} }
new_output.WriteString(tag) newOutput.WriteString(tag)
new_output.WriteString(">") newOutput.WriteString(">")
} else { } else {
// Open tag // Open tag
@ -174,7 +177,7 @@ func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func(
continue continue
} }
new_output.WriteRune(c2) newOutput.WriteRune(c2)
// End of tag found // End of tag found
if c2 == '>' { if c2 == '>' {
@ -194,7 +197,7 @@ func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func(
} }
// Add tag to stack // Add tag to stack
tag_stack = append(tag_stack, tag) tagStack = append(tagStack, tag)
} }
} }
} else { } else {
@ -204,10 +207,10 @@ func filterTruncateHtmlHelper(value string, new_output *bytes.Buffer, cond func(
finalize() finalize()
for i := len(tag_stack) - 1; i >= 0; i-- { for i := len(tagStack) - 1; i >= 0; i-- {
tag := tag_stack[i] tag := tagStack[i]
// Close everything from the regular tag stack // Close everything from the regular tag stack
new_output.WriteString(fmt.Sprintf("</%s>", tag)) newOutput.WriteString(fmt.Sprintf("</%s>", tag))
} }
} }
@ -217,28 +220,28 @@ func filterTruncatechars(in *Value, param *Value) (*Value, *Error) {
return AsValue(filterTruncatecharsHelper(s, newLen)), nil 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() value := in.String()
newLen := max(param.Integer()-3, 0) newLen := max(param.Integer()-3, 0)
new_output := bytes.NewBuffer(nil) newOutput := bytes.NewBuffer(nil)
textcounter := 0 textcounter := 0
filterTruncateHtmlHelper(value, new_output, func() bool { filterTruncateHTMLHelper(value, newOutput, func() bool {
return textcounter >= newLen return textcounter >= newLen
}, func(c rune, s int, idx int) int { }, func(c rune, s int, idx int) int {
textcounter++ textcounter++
new_output.WriteRune(c) newOutput.WriteRune(c)
return idx + s return idx + s
}, func() { }, func() {
if textcounter >= newLen && textcounter < len(value) { 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) { 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 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() value := in.String()
newLen := max(param.Integer(), 0) newLen := max(param.Integer(), 0)
new_output := bytes.NewBuffer(nil) newOutput := bytes.NewBuffer(nil)
wordcounter := 0 wordcounter := 0
filterTruncateHtmlHelper(value, new_output, func() bool { filterTruncateHTMLHelper(value, newOutput, func() bool {
return wordcounter >= newLen return wordcounter >= newLen
}, func(_ rune, _ int, idx int) int { }, func(_ rune, _ int, idx int) int {
// Get next word // Get next word
word_found := false wordFound := false
for idx < len(value) { for idx < len(value) {
c2, size2 := utf8.DecodeRuneInString(value[idx:]) c2, size2 := utf8.DecodeRuneInString(value[idx:])
@ -286,29 +289,29 @@ func filterTruncatewordsHtml(in *Value, param *Value) (*Value, *Error) {
return idx return idx
} }
new_output.WriteRune(c2) newOutput.WriteRune(c2)
idx += size2 idx += size2
if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' { if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' {
// Word ends here, stop capturing it now // Word ends here, stop capturing it now
break break
} else { } else {
word_found = true wordFound = true
} }
} }
if word_found { if wordFound {
wordcounter++ wordcounter++
} }
return idx return idx
}, func() { }, func() {
if wordcounter >= newLen { 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) { 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.IsNumber() && param.IsNumber() {
if in.IsFloat() || param.IsFloat() { if in.IsFloat() || param.IsFloat() {
return AsValue(in.Float() + param.Float()), nil 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 // 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 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) { func filterDate(in *Value, param *Value) (*Value, *Error) {
t, is_time := in.Interface().(time.Time) t, isTime := in.Interface().(time.Time)
if !is_time { if !isTime {
return nil, &Error{ return nil, &Error{
Sender: "filter:date", Sender: "filter:date",
ErrorMsg: "Filter input argument must be of type 'time.Time'.", OrigError: errors.New("filter input argument must be of type 'time.Time'"),
} }
} }
return AsValue(t.Format(param.String())), nil return AsValue(t.Format(param.String())), nil
@ -612,6 +614,12 @@ func filterLinebreaks(in *Value, param *Value) (*Value, *Error) {
return AsValue(b.String()), nil 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) { func filterLinebreaksbr(in *Value, param *Value) (*Value, *Error) {
return AsValue(strings.Replace(in.String(), "\n", "<br />", -1)), nil return AsValue(strings.Replace(in.String(), "\n", "<br />", -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 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})`) 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 { sout := filterUrlizeURLRegexp.ReplaceAllStringFunc(input, func(raw_url string) string {
var prefix string var prefix string
var suffix 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) t, err := ApplyFilter("iriencode", AsValue(raw_url), nil)
if err != nil { if err != nil {
panic(err) soutErr = err
return ""
} }
url := t.String() url := t.String()
@ -673,16 +683,19 @@ func filterUrlizeHelper(input string, autoescape bool, trunc int) string {
if autoescape { if autoescape {
t, err := ApplyFilter("escape", AsValue(title), nil) t, err := ApplyFilter("escape", AsValue(title), nil)
if err != nil { if err != nil {
panic(err) soutErr = err
return ""
} }
title = t.String() title = t.String()
} }
return fmt.Sprintf(`%s<a href="%s" rel="nofollow">%s</a>%s`, prefix, url, title, suffix) return fmt.Sprintf(`%s<a href="%s" rel="nofollow">%s</a>%s`, prefix, url, title, suffix)
}) })
if soutErr != nil {
return "", soutErr
}
sout = filterUrlizeEmailRegexp.ReplaceAllStringFunc(sout, func(mail string) string { sout = filterUrlizeEmailRegexp.ReplaceAllStringFunc(sout, func(mail string) string {
title := mail title := mail
if trunc > 3 && len(title) > trunc { if trunc > 3 && len(title) > trunc {
@ -692,7 +705,7 @@ func filterUrlizeHelper(input string, autoescape bool, trunc int) string {
return fmt.Sprintf(`<a href="mailto:%s">%s</a>`, mail, title) return fmt.Sprintf(`<a href="mailto:%s">%s</a>`, mail, title)
}) })
return sout return sout, nil
} }
func filterUrlize(in *Value, param *Value) (*Value, *Error) { func filterUrlize(in *Value, param *Value) (*Value, *Error) {
@ -701,24 +714,36 @@ func filterUrlize(in *Value, param *Value) (*Value, *Error) {
autoescape = param.Bool() 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) { 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) { func filterStringformat(in *Value, param *Value) (*Value, *Error) {
return AsValue(fmt.Sprintf(param.String(), in.Interface())), nil 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) { func filterStriptags(in *Value, param *Value) (*Value, *Error) {
s := in.String() s := in.String()
// Strip all tags // Strip all tags
s = re_striptags.ReplaceAllString(s, "") s = reStriptags.ReplaceAllString(s, "")
return AsValue(strings.TrimSpace(s)), nil return AsValue(strings.TrimSpace(s)), nil
} }
@ -747,7 +772,7 @@ func filterPluralize(in *Value, param *Value) (*Value, *Error) {
if len(endings) > 2 { if len(endings) > 2 {
return nil, &Error{ return nil, &Error{
Sender: "filter:pluralize", Sender: "filter:pluralize",
ErrorMsg: "You cannot pass more than 2 arguments to filter 'pluralize'.", OrigError: errors.New("you cannot pass more than 2 arguments to filter 'pluralize'"),
} }
} }
if len(endings) == 1 { if len(endings) == 1 {
@ -770,11 +795,10 @@ func filterPluralize(in *Value, param *Value) (*Value, *Error) {
} }
return AsValue(""), nil return AsValue(""), nil
} else { }
return nil, &Error{ return nil, &Error{
Sender: "filter:pluralize", Sender: "filter:pluralize",
ErrorMsg: "Filter 'pluralize' does only work on numbers.", OrigError: errors.New("filter 'pluralize' does only work on numbers"),
}
} }
} }
@ -808,7 +832,7 @@ func filterSlice(in *Value, param *Value) (*Value, *Error) {
if len(comp) != 2 { if len(comp) != 2 {
return nil, &Error{ return nil, &Error{
Sender: "filter:slice", Sender: "filter:slice",
ErrorMsg: "Slice string must have the format 'from:to' [from/to can be omitted, but the ':' is required]", 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) { func filterWordwrap(in *Value, param *Value) (*Value, *Error) {
words := strings.Fields(in.String()) words := strings.Fields(in.String())
words_len := len(words) wordsLen := len(words)
wrap_at := param.Integer() wrapAt := param.Integer()
if wrap_at <= 0 { if wrapAt <= 0 {
return in, nil return in, nil
} }
linecount := words_len/wrap_at + words_len%wrap_at linecount := wordsLen/wrapAt + wordsLen%wrapAt
lines := make([]string, 0, linecount) lines := make([]string, 0, linecount)
for i := 0; i < linecount; i++ { 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 return AsValue(strings.Join(lines, "\n")), nil
} }
@ -864,27 +888,27 @@ func filterYesno(in *Value, param *Value) (*Value, *Error) {
1: "no", 1: "no",
2: "maybe", 2: "maybe",
} }
param_string := param.String() paramString := param.String()
custom_choices := strings.Split(param_string, ",") customChoices := strings.Split(paramString, ",")
if len(param_string) > 0 { if len(paramString) > 0 {
if len(custom_choices) > 3 { if len(customChoices) > 3 {
return nil, &Error{ return nil, &Error{
Sender: "filter:yesno", Sender: "filter:yesno",
ErrorMsg: fmt.Sprintf("You cannot pass more than 3 options to the 'yesno'-filter (got: '%s').", param_string), 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{ return nil, &Error{
Sender: "filter:yesno", Sender: "filter:yesno",
ErrorMsg: fmt.Sprintf("You must pass either no or at least 2 arguments to the 'yesno'-filter (got: '%s').", param_string), 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 // Map to the options now
choices[0] = custom_choices[0] choices[0] = customChoices[0]
choices[1] = custom_choices[1] choices[1] = customChoices[1]
if len(custom_choices) == 3 { if len(customChoices) == 3 {
choices[2] = custom_choices[2] choices[2] = customChoices[2]
} }
} }

View file

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"github.com/juju/errors"
) )
const ( const (
@ -63,8 +65,8 @@ type lexer struct {
line int line int
col int col int
in_verbatim bool inVerbatim bool
verbatim_name string verbatimName string
} }
func (t *Token) String() string { func (t *Token) String() string {
@ -115,7 +117,7 @@ func lex(name string, input string) ([]*Token, *Error) {
Line: errtoken.Line, Line: errtoken.Line,
Column: errtoken.Col, Column: errtoken.Col,
Sender: "lexer", Sender: "lexer",
ErrorMsg: errtoken.Val, OrigError: errors.New(errtoken.Val),
} }
} }
return l.tokens, nil return l.tokens, nil
@ -216,8 +218,8 @@ func (l *lexer) run() {
for { for {
// TODO: Support verbatim tag names // TODO: Support verbatim tag names
// https://docs.djangoproject.com/en/dev/ref/templates/builtins/#verbatim // https://docs.djangoproject.com/en/dev/ref/templates/builtins/#verbatim
if l.in_verbatim { if l.inVerbatim {
name := l.verbatim_name name := l.verbatimName
if name != "" { if name != "" {
name += " " name += " "
} }
@ -229,20 +231,20 @@ func (l *lexer) run() {
l.pos += w l.pos += w
l.col += w l.col += w
l.ignore() l.ignore()
l.in_verbatim = false l.inVerbatim = false
} }
} else if strings.HasPrefix(l.input[l.pos:], "{% verbatim %}") { // tag } else if strings.HasPrefix(l.input[l.pos:], "{% verbatim %}") { // tag
if l.pos > l.start { if l.pos > l.start {
l.emit(TokenHTML) l.emit(TokenHTML)
} }
l.in_verbatim = true l.inVerbatim = true
w := len("{% verbatim %}") w := len("{% verbatim %}")
l.pos += w l.pos += w
l.col += w l.col += w
l.ignore() l.ignore()
} }
if !l.in_verbatim { if !l.inVerbatim {
// Ignore single-line comments {# ... #} // Ignore single-line comments {# ... #}
if strings.HasPrefix(l.input[l.pos:], "{#") { if strings.HasPrefix(l.input[l.pos:], "{#") {
if l.pos > l.start { if l.pos > l.start {
@ -303,7 +305,7 @@ func (l *lexer) run() {
l.emit(TokenHTML) l.emit(TokenHTML)
} }
if l.in_verbatim { if l.inVerbatim {
l.errorf("verbatim-tag not closed, got EOF.") l.errorf("verbatim-tag not closed, got EOF.")
} }
} }
@ -328,7 +330,7 @@ outer_loop:
return l.stateIdentifier return l.stateIdentifier
case l.accept(tokenDigits): case l.accept(tokenDigits):
return l.stateNumber return l.stateNumber
case l.accept(`"`): case l.accept(`"'`):
return l.stateString return l.stateString
} }
@ -348,10 +350,6 @@ outer_loop:
} }
} }
if l.pos < len(l.input) {
return l.errorf("Unknown character: %q (%d)", l.peek(), l.peek())
}
break break
} }
@ -374,6 +372,11 @@ func (l *lexer) stateIdentifier() lexerStateFn {
func (l *lexer) stateNumber() lexerStateFn { func (l *lexer) stateNumber() lexerStateFn {
l.acceptRun(tokenDigits) 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? Maybe context-sensitive number lexing?
* comments.0.Text // first comment * comments.0.Text // first comment
@ -393,9 +396,10 @@ func (l *lexer) stateNumber() lexerStateFn {
} }
func (l *lexer) stateString() lexerStateFn { func (l *lexer) stateString() lexerStateFn {
quotationMark := l.value()
l.ignore() l.ignore()
l.startcol -= 1 // we're starting the position at the first " l.startcol-- // we're starting the position at the first "
for !l.accept(`"`) { for !l.accept(quotationMark) {
switch l.next() { switch l.next() {
case '\\': case '\\':
// escape sequence // escape sequence

View file

@ -1,17 +1,13 @@
package pongo2 package pongo2
import (
"bytes"
)
// The root document // The root document
type nodeDocument struct { type nodeDocument struct {
Nodes []INode 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 { for _, n := range doc.Nodes {
err := n.Execute(ctx, buffer) err := n.Execute(ctx, writer)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,14 +1,10 @@
package pongo2 package pongo2
import (
"bytes"
)
type nodeHTML struct { type nodeHTML struct {
token *Token token *Token
} }
func (n *nodeHTML) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (n *nodeHTML) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
buffer.WriteString(n.token.Val) writer.WriteString(n.token.Val)
return nil return nil
} }

View file

@ -1,17 +1,13 @@
package pongo2 package pongo2
import (
"bytes"
)
type NodeWrapper struct { type NodeWrapper struct {
Endtag string Endtag string
nodes []INode 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 { for _, n := range wrapper.nodes {
err := n.Execute(ctx, buffer) err := n.Execute(ctx, writer)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,13 +1,14 @@
package pongo2 package pongo2
import ( import (
"bytes"
"fmt" "fmt"
"strings" "strings"
"github.com/juju/errors"
) )
type INode interface { type INode interface {
Execute(*ExecutionContext, *bytes.Buffer) *Error Execute(*ExecutionContext, TemplateWriter) *Error
} }
type IEvaluator interface { type IEvaluator interface {
@ -30,7 +31,7 @@ type Parser struct {
name string name string
idx int idx int
tokens []*Token tokens []*Token
last_token *Token lastToken *Token
// if the parser parses a template document, here will be // if the parser parses a template document, here will be
// a reference to it (needed to access the template through Tags) // 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, template: template,
} }
if len(tokens) > 0 { if len(tokens) > 0 {
p.last_token = tokens[len(tokens)-1] p.lastToken = tokens[len(tokens)-1]
} }
return p return p
} }
@ -175,7 +176,7 @@ func (p *Parser) GetR(shift int) *Token {
return p.Get(i) 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'-argument is optional. If provided, it will take
// the token's position information. If not provided, it will // the token's position information. If not provided, it will
// automatically use the CURRENT token's position information. // automatically use the CURRENT token's position information.
@ -202,7 +203,7 @@ func (p *Parser) Error(msg string, token *Token) *Error {
Line: line, Line: line,
Column: col, Column: col,
Token: token, Token: token,
ErrorMsg: msg, 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) { func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) {
wrapper := &NodeWrapper{} wrapper := &NodeWrapper{}
tagArgs := make([]*Token, 0) var tagArgs []*Token
for p.Remaining() > 0 { for p.Remaining() > 0 {
// New tag, check whether we have to stop wrapping here // New tag, check whether we have to stop wrapping here
if p.Peek(TokenSymbol, "{%") != nil { 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 // We've found a (!) end-tag
found := false found := false
for _, n := range names { for _, n := range names {
if tag_ident.Val == n { if tagIdent.Val == n {
found = true found = true
break break
} }
@ -238,19 +239,18 @@ func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) {
for { for {
if p.Match(TokenSymbol, "%}") != nil { if p.Match(TokenSymbol, "%}") != nil {
// Okay, end the wrapping here // Okay, end the wrapping here
wrapper.Endtag = tag_ident.Val wrapper.Endtag = tagIdent.Val
return wrapper, newParser(p.template.name, tagArgs, p.template), nil return wrapper, newParser(p.template.name, tagArgs, p.template), nil
} else { }
t := p.Current() t := p.Current()
p.Consume() p.Consume()
if t == nil { if t == nil {
return nil, nil, p.Error("Unexpected EOF.", p.last_token) return nil, nil, p.Error("Unexpected EOF.", p.lastToken)
} }
tagArgs = append(tagArgs, t) 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 ")), 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)
} }

View file

@ -1,7 +1,6 @@
package pongo2 package pongo2
import ( import (
"bytes"
"fmt" "fmt"
"math" "math"
) )
@ -10,29 +9,29 @@ type Expression struct {
// TODO: Add location token? // TODO: Add location token?
expr1 IEvaluator expr1 IEvaluator
expr2 IEvaluator expr2 IEvaluator
op_token *Token opToken *Token
} }
type relationalExpression struct { type relationalExpression struct {
// TODO: Add location token? // TODO: Add location token?
expr1 IEvaluator expr1 IEvaluator
expr2 IEvaluator expr2 IEvaluator
op_token *Token opToken *Token
} }
type simpleExpression struct { type simpleExpression struct {
negate bool negate bool
negative_sign bool negativeSign bool
term1 IEvaluator term1 IEvaluator
term2 IEvaluator term2 IEvaluator
op_token *Token opToken *Token
} }
type term struct { type term struct {
// TODO: Add location token? // TODO: Add location token?
factor1 IEvaluator factor1 IEvaluator
factor2 IEvaluator factor2 IEvaluator
op_token *Token opToken *Token
} }
type power struct { type power struct {
@ -56,14 +55,14 @@ func (expr *simpleExpression) FilterApplied(name string) bool {
(expr.term2 != nil && expr.term2.FilterApplied(name))) (expr.term2 != nil && expr.term2.FilterApplied(name)))
} }
func (t *term) FilterApplied(name string) bool { func (expr *term) FilterApplied(name string) bool {
return t.factor1.FilterApplied(name) && (t.factor2 == nil || return expr.factor1.FilterApplied(name) && (expr.factor2 == nil ||
(t.factor2 != nil && t.factor2.FilterApplied(name))) (expr.factor2 != nil && expr.factor2.FilterApplied(name)))
} }
func (p *power) FilterApplied(name string) bool { func (expr *power) FilterApplied(name string) bool {
return p.power1.FilterApplied(name) && (p.power2 == nil || return expr.power1.FilterApplied(name) && (expr.power2 == nil ||
(p.power2 != nil && p.power2.FilterApplied(name))) (expr.power2 != nil && expr.power2.FilterApplied(name)))
} }
func (expr *Expression) GetPositionToken() *Token { func (expr *Expression) GetPositionToken() *Token {
@ -86,48 +85,48 @@ func (expr *power) GetPositionToken() *Token {
return expr.power1.GetPositionToken() 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) value, err := expr.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
buffer.WriteString(value.String()) writer.WriteString(value.String())
return nil 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) value, err := expr.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
buffer.WriteString(value.String()) writer.WriteString(value.String())
return nil 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) value, err := expr.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
buffer.WriteString(value.String()) writer.WriteString(value.String())
return nil 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) value, err := expr.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
buffer.WriteString(value.String()) writer.WriteString(value.String())
return nil 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) value, err := expr.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
buffer.WriteString(value.String()) writer.WriteString(value.String())
return nil return nil
} }
@ -141,13 +140,13 @@ func (expr *Expression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
switch expr.op_token.Val { switch expr.opToken.Val {
case "and", "&&": case "and", "&&":
return AsValue(v1.IsTrue() && v2.IsTrue()), nil return AsValue(v1.IsTrue() && v2.IsTrue()), nil
case "or", "||": case "or", "||":
return AsValue(v1.IsTrue() || v2.IsTrue()), nil return AsValue(v1.IsTrue() || v2.IsTrue()), nil
default: default:
panic(fmt.Sprintf("unimplemented: %s", expr.op_token.Val)) return nil, ctx.Error(fmt.Sprintf("unimplemented: %s", expr.opToken.Val), expr.opToken)
} }
} else { } else {
return v1, nil return v1, nil
@ -164,39 +163,35 @@ func (expr *relationalExpression) Evaluate(ctx *ExecutionContext) (*Value, *Erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
switch expr.op_token.Val { switch expr.opToken.Val {
case "<=": case "<=":
if v1.IsFloat() || v2.IsFloat() { if v1.IsFloat() || v2.IsFloat() {
return AsValue(v1.Float() <= v2.Float()), nil return AsValue(v1.Float() <= v2.Float()), nil
} else {
return AsValue(v1.Integer() <= v2.Integer()), nil
} }
return AsValue(v1.Integer() <= v2.Integer()), nil
case ">=": case ">=":
if v1.IsFloat() || v2.IsFloat() { if v1.IsFloat() || v2.IsFloat() {
return AsValue(v1.Float() >= v2.Float()), nil return AsValue(v1.Float() >= v2.Float()), nil
} else {
return AsValue(v1.Integer() >= v2.Integer()), nil
} }
return AsValue(v1.Integer() >= v2.Integer()), nil
case "==": case "==":
return AsValue(v1.EqualValueTo(v2)), nil return AsValue(v1.EqualValueTo(v2)), nil
case ">": case ">":
if v1.IsFloat() || v2.IsFloat() { if v1.IsFloat() || v2.IsFloat() {
return AsValue(v1.Float() > v2.Float()), nil return AsValue(v1.Float() > v2.Float()), nil
} else {
return AsValue(v1.Integer() > v2.Integer()), nil
} }
return AsValue(v1.Integer() > v2.Integer()), nil
case "<": case "<":
if v1.IsFloat() || v2.IsFloat() { if v1.IsFloat() || v2.IsFloat() {
return AsValue(v1.Float() < v2.Float()), nil return AsValue(v1.Float() < v2.Float()), nil
} else {
return AsValue(v1.Integer() < v2.Integer()), nil
} }
return AsValue(v1.Integer() < v2.Integer()), nil
case "!=", "<>": case "!=", "<>":
return AsValue(!v1.EqualValueTo(v2)), nil return AsValue(!v1.EqualValueTo(v2)), nil
case "in": case "in":
return AsValue(v2.Contains(v1)), nil return AsValue(v2.Contains(v1)), nil
default: default:
panic(fmt.Sprintf("unimplemented: %s", expr.op_token.Val)) return nil, ctx.Error(fmt.Sprintf("unimplemented: %s", expr.opToken.Val), expr.opToken)
} }
} else { } else {
return v1, nil return v1, nil
@ -214,7 +209,7 @@ func (expr *simpleExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
result = result.Negate() result = result.Negate()
} }
if expr.negative_sign { if expr.negativeSign {
if result.IsNumber() { if result.IsNumber() {
switch { switch {
case result.IsFloat(): case result.IsFloat():
@ -222,7 +217,7 @@ func (expr *simpleExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
case result.IsInteger(): case result.IsInteger():
result = AsValue(-1 * result.Integer()) result = AsValue(-1 * result.Integer())
default: default:
panic("not possible") return nil, ctx.Error("Operation between a number and a non-(float/integer) is not possible", nil)
} }
} else { } else {
return nil, ctx.Error("Negative sign on a non-number expression", expr.GetPositionToken()) 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 { if err != nil {
return nil, err return nil, err
} }
switch expr.op_token.Val { switch expr.opToken.Val {
case "+": case "+":
if result.IsFloat() || t2.IsFloat() { if result.IsFloat() || t2.IsFloat() {
// Result will be a float // Result will be a float
return AsValue(result.Float() + t2.Float()), nil return AsValue(result.Float() + t2.Float()), nil
} else { }
// Result will be an integer // Result will be an integer
return AsValue(result.Integer() + t2.Integer()), nil return AsValue(result.Integer() + t2.Integer()), nil
}
case "-": case "-":
if result.IsFloat() || t2.IsFloat() { if result.IsFloat() || t2.IsFloat() {
// Result will be a float // Result will be a float
return AsValue(result.Float() - t2.Float()), nil return AsValue(result.Float() - t2.Float()), nil
} else { }
// Result will be an integer // Result will be an integer
return AsValue(result.Integer() - t2.Integer()), nil return AsValue(result.Integer() - t2.Integer()), nil
}
default: default:
panic("unimplemented") return nil, ctx.Error("Unimplemented", expr.GetPositionToken())
} }
} }
return result, nil return result, nil
} }
func (t *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) { func (expr *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
f1, err := t.factor1.Evaluate(ctx) f1, err := expr.factor1.Evaluate(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if t.factor2 != nil { if expr.factor2 != nil {
f2, err := t.factor2.Evaluate(ctx) f2, err := expr.factor2.Evaluate(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
switch t.op_token.Val { switch expr.opToken.Val {
case "*": case "*":
if f1.IsFloat() || f2.IsFloat() { if f1.IsFloat() || f2.IsFloat() {
// Result will be float // Result will be float
@ -288,27 +281,26 @@ func (t *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
// Result will be int // Result will be int
return AsValue(f1.Integer() % f2.Integer()), nil return AsValue(f1.Integer() % f2.Integer()), nil
default: default:
panic("unimplemented") return nil, ctx.Error("unimplemented", expr.opToken)
} }
} else { } else {
return f1, nil return f1, nil
} }
} }
func (pw *power) Evaluate(ctx *ExecutionContext) (*Value, *Error) { func (expr *power) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
p1, err := pw.power1.Evaluate(ctx) p1, err := expr.power1.Evaluate(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if pw.power2 != nil { if expr.power2 != nil {
p2, err := pw.power2.Evaluate(ctx) p2, err := expr.power2.Evaluate(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return AsValue(math.Pow(p1.Float(), p2.Float())), nil return AsValue(math.Pow(p1.Float(), p2.Float())), nil
} else {
return p1, nil
} }
return p1, nil
} }
func (p *Parser) parseFactor() (IEvaluator, *Error) { func (p *Parser) parseFactor() (IEvaluator, *Error) {
@ -352,19 +344,19 @@ func (p *Parser) parsePower() (IEvaluator, *Error) {
} }
func (p *Parser) parseTerm() (IEvaluator, *Error) { func (p *Parser) parseTerm() (IEvaluator, *Error) {
return_term := new(term) returnTerm := new(term)
factor1, err := p.parsePower() factor1, err := p.parsePower()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return_term.factor1 = factor1 returnTerm.factor1 = factor1
for p.PeekOne(TokenSymbol, "*", "/", "%") != nil { for p.PeekOne(TokenSymbol, "*", "/", "%") != nil {
if return_term.op_token != nil { if returnTerm.opToken != nil {
// Create new sub-term // Create new sub-term
return_term = &term{ returnTerm = &term{
factor1: return_term, factor1: returnTerm,
} }
} }
@ -376,16 +368,16 @@ func (p *Parser) parseTerm() (IEvaluator, *Error) {
return nil, err return nil, err
} }
return_term.op_token = op returnTerm.opToken = op
return_term.factor2 = factor2 returnTerm.factor2 = factor2
} }
if return_term.op_token == nil { if returnTerm.opToken == nil {
// Shortcut for faster evaluation // 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) { 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 := p.MatchOne(TokenSymbol, "+", "-"); sign != nil {
if sign.Val == "-" { if sign.Val == "-" {
expr.negative_sign = true expr.negativeSign = true
} }
} }
@ -408,7 +400,7 @@ func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) {
expr.term1 = term1 expr.term1 = term1
for p.PeekOne(TokenSymbol, "+", "-") != nil { for p.PeekOne(TokenSymbol, "+", "-") != nil {
if expr.op_token != nil { if expr.opToken != nil {
// New sub expr // New sub expr
expr = &simpleExpression{ expr = &simpleExpression{
term1: expr, term1: expr,
@ -424,10 +416,10 @@ func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) {
} }
expr.term2 = term2 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 // Shortcut for faster evaluation
return expr.term1, nil return expr.term1, nil
} }
@ -450,14 +442,14 @@ func (p *Parser) parseRelationalExpression() (IEvaluator, *Error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
expr.op_token = t expr.opToken = t
expr.expr2 = expr2 expr.expr2 = expr2
} else if t := p.MatchOne(TokenKeyword, "in"); t != nil { } else if t := p.MatchOne(TokenKeyword, "in"); t != nil {
expr2, err := p.parseSimpleExpression() expr2, err := p.parseSimpleExpression()
if err != nil { if err != nil {
return nil, err return nil, err
} }
expr.op_token = t expr.opToken = t
expr.expr2 = expr2 expr.expr2 = expr2
} }
@ -487,7 +479,7 @@ func (p *Parser) ParseExpression() (IEvaluator, *Error) {
return nil, err return nil, err
} }
exp.expr2 = expr2 exp.expr2 = expr2
exp.op_token = op exp.opToken = op
} }
if exp.expr2 == nil { if exp.expr2 == nil {

View file

@ -1,10 +1,10 @@
package pongo2 package pongo2
// Version string // Version string
const Version = "v3" const Version = "dev"
// Helper function which panics, if a Template couldn't // Must panics, if a Template couldn't successfully parsed. This is how you
// successfully parsed. This is how you would use it: // would use it:
// var baseTemplate = pongo2.Must(pongo2.FromFile("templates/base.html")) // var baseTemplate = pongo2.Must(pongo2.FromFile("templates/base.html"))
func Must(tpl *Template, err error) *Template { func Must(tpl *Template, err error) *Template {
if err != nil { if err != nil {

View file

@ -1,20 +1,29 @@
package pongo2 package pongo2_test
import ( import (
"testing" "testing"
. "gopkg.in/check.v1" "github.com/flosch/pongo2"
) )
// Hook up gocheck into the "go test" runner. func TestIssue151(t *testing.T) {
tpl, err := pongo2.FromString("{{ mydict.51232_3 }}{{ 12345_123}}{{ 995189baz }}")
if err != nil {
t.Fatal(err)
}
func TestIssues(t *testing.T) { TestingT(t) } str, err := tpl.Execute(pongo2.Context{
"mydict": map[string]string{
"51232_3": "foo",
},
"12345_123": "bar",
"995189baz": "baz",
})
if err != nil {
t.Fatal(err)
}
type IssueTestSuite struct{} if str != "foobarbaz" {
t.Fatalf("Expected output 'foobarbaz', but got '%s'.", str)
var _ = Suite(&IssueTestSuite{}) }
func (s *TestSuite) TestIssues(c *C) {
// Add a test for any issue
c.Check(42, Equals, 42)
} }

View file

@ -1,4 +1,4 @@
package pongo2 package pongo2_test
import ( import (
"bytes" "bytes"
@ -9,9 +9,11 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/flosch/pongo2"
) )
var admin_list = []string{"user2"} var adminList = []string{"user2"}
var time1 = time.Date(2014, 06, 10, 15, 30, 15, 0, time.UTC) var time1 = time.Date(2014, 06, 10, 15, 30, 15, 0, time.UTC)
var time2 = time.Date(2011, 03, 21, 8, 37, 56, 12, time.UTC) var time2 = time.Date(2011, 03, 21, 8, 37, 56, 12, time.UTC)
@ -32,8 +34,8 @@ type comment struct {
Text string Text string
} }
func is_admin(u *user) bool { func isAdmin(u *user) bool {
for _, a := range admin_list { for _, a := range adminList {
if a == u.Name { if a == u.Name {
return true return true
} }
@ -41,12 +43,12 @@ func is_admin(u *user) bool {
return false return false
} }
func (u *user) Is_admin() *Value { func (u *user) Is_admin() *pongo2.Value {
return AsValue(is_admin(u)) return pongo2.AsValue(isAdmin(u))
} }
func (u *user) Is_admin2() bool { func (u *user) Is_admin2() bool {
return is_admin(u) return isAdmin(u)
} }
func (p *post) String() string { func (p *post) String() string {
@ -60,70 +62,49 @@ func (p *post) String() string {
type tagSandboxDemoTag struct { type tagSandboxDemoTag struct {
} }
func (node *tagSandboxDemoTag) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (node *tagSandboxDemoTag) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error {
buffer.WriteString("hello") writer.WriteString("hello")
return nil return nil
} }
func tagSandboxDemoTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagSandboxDemoTagParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) {
return &tagSandboxDemoTag{}, nil return &tagSandboxDemoTag{}, nil
} }
func BannedFilterFn(in *Value, params *Value) (*Value, *Error) { func BannedFilterFn(in *pongo2.Value, params *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
return in, nil return in, nil
} }
func init() { func init() {
DefaultSet.Debug = true pongo2.DefaultSet.Debug = true
RegisterFilter("banned_filter", BannedFilterFn) pongo2.RegisterFilter("banned_filter", BannedFilterFn)
RegisterFilter("unbanned_filter", BannedFilterFn) pongo2.RegisterFilter("unbanned_filter", BannedFilterFn)
RegisterTag("banned_tag", tagSandboxDemoTagParser) pongo2.RegisterTag("banned_tag", tagSandboxDemoTagParser)
RegisterTag("unbanned_tag", tagSandboxDemoTagParser) pongo2.RegisterTag("unbanned_tag", tagSandboxDemoTagParser)
DefaultSet.BanFilter("banned_filter") pongo2.DefaultSet.BanFilter("banned_filter")
DefaultSet.BanTag("banned_tag") pongo2.DefaultSet.BanTag("banned_tag")
// Allow different kind of levels inside template_tests/
abs_path, err := filepath.Abs("./template_tests/*")
if err != nil {
panic(err)
}
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, abs_path)
abs_path, err = filepath.Abs("./template_tests/*/*")
if err != nil {
panic(err)
}
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, abs_path)
abs_path, err = filepath.Abs("./template_tests/*/*/*")
if err != nil {
panic(err)
}
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, abs_path)
// Allow pongo2 temp files
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, "/tmp/pongo2_*")
f, err := ioutil.TempFile("/tmp/", "pongo2_") f, err := ioutil.TempFile("/tmp/", "pongo2_")
if err != nil { if err != nil {
panic("cannot write to /tmp/") panic("cannot write to /tmp/")
} }
f.Write([]byte("Hello from pongo2")) f.Write([]byte("Hello from pongo2"))
DefaultSet.Globals["temp_file"] = f.Name() pongo2.DefaultSet.Globals["temp_file"] = f.Name()
} }
/* /*
* End setup sandbox * End setup sandbox
*/ */
var tplContext = Context{ var tplContext = pongo2.Context{
"number": 11, "number": 11,
"simple": map[string]interface{}{ "simple": map[string]interface{}{
"number": 42, "number": 42,
"name": "john doe", "name": "john doe",
"included_file": "INCLUDES.helper", "included_file": "INCLUDES.helper",
"included_file_not_exists": "INCLUDES.helper.not_exists",
"nil": nil, "nil": nil,
"uint": uint(8), "uint": uint(8),
"float": float64(3.1415), "float": float64(3.1415),
@ -142,13 +123,23 @@ Yep!`,
"escape_js_test": `escape sequences \r\n\'\" special chars "?!=$<>`, "escape_js_test": `escape sequences \r\n\'\" special chars "?!=$<>`,
"one_item_list": []int{99}, "one_item_list": []int{99},
"multiple_item_list": []int{1, 1, 2, 3, 5, 8, 13, 21, 34, 55}, "multiple_item_list": []int{1, 1, 2, 3, 5, 8, 13, 21, 34, 55},
"unsorted_int_list": []int{192, 581, 22, 1, 249, 9999, 1828591, 8271},
"fixed_item_list": [...]int{1, 2, 3, 4},
"misc_list": []interface{}{"Hello", 99, 3.14, "good"}, "misc_list": []interface{}{"Hello", 99, 3.14, "good"},
"escape_text": "This is \\a Test. \"Yep\". 'Yep'.", "escape_text": "This is \\a Test. \"Yep\". 'Yep'.",
"xss": "<script>alert(\"uh oh\");</script>", "xss": "<script>alert(\"uh oh\");</script>",
"intmap": map[int]string{ "intmap": map[int]string{
1: "one", 1: "one",
2: "two",
5: "five", 5: "five",
2: "two",
},
"strmap": map[string]string{
"abc": "def",
"bcd": "efg",
"zab": "cde",
"gh": "kqm",
"ukq": "qqa",
"aab": "aba",
}, },
"func_add": func(a, b int) int { "func_add": func(a, b int) int {
return a + b return a + b
@ -167,17 +158,17 @@ Yep!`,
} }
return s return s
}, },
"func_variadic_sum_int2": func(args ...*Value) *Value { "func_variadic_sum_int2": func(args ...*pongo2.Value) *pongo2.Value {
// Create a sum // Create a sum
s := 0 s := 0
for _, i := range args { for _, i := range args {
s += i.Integer() s += i.Integer()
} }
return AsValue(s) return pongo2.AsValue(s)
}, },
}, },
"complex": map[string]interface{}{ "complex": map[string]interface{}{
"is_admin": is_admin, "is_admin": isAdmin,
"post": post{ "post": post{
Text: "<h2>Hello!</h2><p>Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.</p>", Text: "<h2>Hello!</h2><p>Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.</p>",
Created: time2, Created: time2,
@ -238,10 +229,8 @@ Yep!`,
} }
func TestTemplates(t *testing.T) { func TestTemplates(t *testing.T) {
debug = true
// Add a global to the default set // Add a global to the default set
Globals["this_is_a_global_variable"] = "this is a global text" pongo2.Globals["this_is_a_global_variable"] = "this is a global text"
matches, err := filepath.Glob("./template_tests/*.tpl") matches, err := filepath.Glob("./template_tests/*.tpl")
if err != nil { if err != nil {
@ -249,34 +238,34 @@ func TestTemplates(t *testing.T) {
} }
for idx, match := range matches { for idx, match := range matches {
t.Logf("[Template %3d] Testing '%s'", idx+1, match) t.Logf("[Template %3d] Testing '%s'", idx+1, match)
tpl, err := FromFile(match) tpl, err := pongo2.FromFile(match)
if err != nil { if err != nil {
t.Fatalf("Error on FromFile('%s'): %s", match, err.Error()) t.Fatalf("Error on FromFile('%s'): %s", match, err.Error())
} }
test_filename := fmt.Sprintf("%s.out", match) testFilename := fmt.Sprintf("%s.out", match)
test_out, rerr := ioutil.ReadFile(test_filename) testOut, rerr := ioutil.ReadFile(testFilename)
if rerr != nil { if rerr != nil {
t.Fatalf("Error on ReadFile('%s'): %s", test_filename, rerr.Error()) t.Fatalf("Error on ReadFile('%s'): %s", testFilename, rerr.Error())
} }
tpl_out, err := tpl.ExecuteBytes(tplContext) tplOut, err := tpl.ExecuteBytes(tplContext)
if err != nil { if err != nil {
t.Fatalf("Error on Execute('%s'): %s", match, err.Error()) t.Fatalf("Error on Execute('%s'): %s", match, err.Error())
} }
if bytes.Compare(test_out, tpl_out) != 0 { if bytes.Compare(testOut, tplOut) != 0 {
t.Logf("Template (rendered) '%s': '%s'", match, tpl_out) t.Logf("Template (rendered) '%s': '%s'", match, tplOut)
err_filename := filepath.Base(fmt.Sprintf("%s.error", match)) errFilename := filepath.Base(fmt.Sprintf("%s.error", match))
err := ioutil.WriteFile(err_filename, []byte(tpl_out), 0600) err := ioutil.WriteFile(errFilename, []byte(tplOut), 0600)
if err != nil { if err != nil {
t.Fatalf(err.Error()) t.Fatalf(err.Error())
} }
t.Logf("get a complete diff with command: 'diff -ya %s %s'", test_filename, err_filename) t.Logf("get a complete diff with command: 'diff -ya %s %s'", testFilename, errFilename)
t.Errorf("Failed: test_out != tpl_out for %s", match) t.Errorf("Failed: test_out != tpl_out for %s", match)
} }
} }
} }
func TestExecutionErrors(t *testing.T) { func TestExecutionErrors(t *testing.T) {
debug = true //debug = true
matches, err := filepath.Glob("./template_tests/*-execution.err") matches, err := filepath.Glob("./template_tests/*-execution.err")
if err != nil { if err != nil {
@ -285,15 +274,15 @@ func TestExecutionErrors(t *testing.T) {
for idx, match := range matches { for idx, match := range matches {
t.Logf("[Errors %3d] Testing '%s'", idx+1, match) t.Logf("[Errors %3d] Testing '%s'", idx+1, match)
test_data, err := ioutil.ReadFile(match) testData, err := ioutil.ReadFile(match)
tests := strings.Split(string(test_data), "\n") tests := strings.Split(string(testData), "\n")
check_filename := fmt.Sprintf("%s.out", match) checkFilename := fmt.Sprintf("%s.out", match)
check_data, err := ioutil.ReadFile(check_filename) checkData, err := ioutil.ReadFile(checkFilename)
if err != nil { if err != nil {
t.Fatalf("Error on ReadFile('%s'): %s", check_filename, err.Error()) t.Fatalf("Error on ReadFile('%s'): %s", checkFilename, err.Error())
} }
checks := strings.Split(string(check_data), "\n") checks := strings.Split(string(checkData), "\n")
if len(checks) != len(tests) { if len(checks) != len(tests) {
t.Fatal("Template lines != Checks lines") t.Fatal("Template lines != Checks lines")
@ -308,11 +297,16 @@ func TestExecutionErrors(t *testing.T) {
match, idx+1) match, idx+1)
} }
tpl, err := FromString(test) tpl, err := pongo2.FromString(test)
if err != nil { if err != nil {
t.Fatalf("Error on FromString('%s'): %s", test, err.Error()) t.Fatalf("Error on FromString('%s'): %s", test, err.Error())
} }
tpl, err = pongo2.FromBytes([]byte(test))
if err != nil {
t.Fatalf("Error on FromBytes('%s'): %s", test, err.Error())
}
_, err = tpl.ExecuteBytes(tplContext) _, err = tpl.ExecuteBytes(tplContext)
if err == nil { if err == nil {
t.Fatalf("[%s Line %d] Expected error for (got none): %s", t.Fatalf("[%s Line %d] Expected error for (got none): %s",
@ -329,7 +323,7 @@ func TestExecutionErrors(t *testing.T) {
} }
func TestCompilationErrors(t *testing.T) { func TestCompilationErrors(t *testing.T) {
debug = true //debug = true
matches, err := filepath.Glob("./template_tests/*-compilation.err") matches, err := filepath.Glob("./template_tests/*-compilation.err")
if err != nil { if err != nil {
@ -338,15 +332,15 @@ func TestCompilationErrors(t *testing.T) {
for idx, match := range matches { for idx, match := range matches {
t.Logf("[Errors %3d] Testing '%s'", idx+1, match) t.Logf("[Errors %3d] Testing '%s'", idx+1, match)
test_data, err := ioutil.ReadFile(match) testData, err := ioutil.ReadFile(match)
tests := strings.Split(string(test_data), "\n") tests := strings.Split(string(testData), "\n")
check_filename := fmt.Sprintf("%s.out", match) checkFilename := fmt.Sprintf("%s.out", match)
check_data, err := ioutil.ReadFile(check_filename) checkData, err := ioutil.ReadFile(checkFilename)
if err != nil { if err != nil {
t.Fatalf("Error on ReadFile('%s'): %s", check_filename, err.Error()) t.Fatalf("Error on ReadFile('%s'): %s", checkFilename, err.Error())
} }
checks := strings.Split(string(check_data), "\n") checks := strings.Split(string(checkData), "\n")
if len(checks) != len(tests) { if len(checks) != len(tests) {
t.Fatal("Template lines != Checks lines") t.Fatal("Template lines != Checks lines")
@ -361,7 +355,7 @@ func TestCompilationErrors(t *testing.T) {
match, idx+1) match, idx+1)
} }
_, err = FromString(test) _, err = pongo2.FromString(test)
if err == nil { if err == nil {
t.Fatalf("[%s | Line %d] Expected error for (got none): %s", match, idx+1, tests[idx]) t.Fatalf("[%s | Line %d] Expected error for (got none): %s", match, idx+1, tests[idx])
} }
@ -377,9 +371,10 @@ func TestCompilationErrors(t *testing.T) {
func TestBaseDirectory(t *testing.T) { func TestBaseDirectory(t *testing.T) {
mustStr := "Hello from template_tests/base_dir_test/" mustStr := "Hello from template_tests/base_dir_test/"
s := NewSet("test set with base directory") fs := pongo2.MustNewLocalFileSystemLoader("")
s := pongo2.NewSet("test set with base directory", fs)
s.Globals["base_directory"] = "template_tests/base_dir_test/" s.Globals["base_directory"] = "template_tests/base_dir_test/"
if err := s.SetBaseDirectory(s.Globals["base_directory"].(string)); err != nil { if err := fs.SetBaseDir(s.Globals["base_directory"].(string)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -405,13 +400,13 @@ func TestBaseDirectory(t *testing.T) {
} }
func BenchmarkCache(b *testing.B) { func BenchmarkCache(b *testing.B) {
cache_set := NewSet("cache set") cacheSet := pongo2.NewSet("cache set", pongo2.MustNewLocalFileSystemLoader(""))
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
tpl, err := cache_set.FromCache("template_tests/complex.tpl") tpl, err := cacheSet.FromCache("template_tests/complex.tpl")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
_, err = tpl.ExecuteBytes(tplContext) err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -419,14 +414,14 @@ func BenchmarkCache(b *testing.B) {
} }
func BenchmarkCacheDebugOn(b *testing.B) { func BenchmarkCacheDebugOn(b *testing.B) {
cache_debug_set := NewSet("cache set") cacheDebugSet := pongo2.NewSet("cache set", pongo2.MustNewLocalFileSystemLoader(""))
cache_debug_set.Debug = true cacheDebugSet.Debug = true
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
tpl, err := cache_debug_set.FromFile("template_tests/complex.tpl") tpl, err := cacheDebugSet.FromFile("template_tests/complex.tpl")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
_, err = tpl.ExecuteBytes(tplContext) err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -434,13 +429,13 @@ func BenchmarkCacheDebugOn(b *testing.B) {
} }
func BenchmarkExecuteComplexWithSandboxActive(b *testing.B) { func BenchmarkExecuteComplexWithSandboxActive(b *testing.B) {
tpl, err := FromFile("template_tests/complex.tpl") tpl, err := pongo2.FromFile("template_tests/complex.tpl")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err = tpl.ExecuteBytes(tplContext) err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -455,12 +450,12 @@ func BenchmarkCompileAndExecuteComplexWithSandboxActive(b *testing.B) {
preloadedTpl := string(buf) preloadedTpl := string(buf)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
tpl, err := FromString(preloadedTpl) tpl, err := pongo2.FromString(preloadedTpl)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
_, err = tpl.ExecuteBytes(tplContext) err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -468,14 +463,14 @@ func BenchmarkCompileAndExecuteComplexWithSandboxActive(b *testing.B) {
} }
func BenchmarkParallelExecuteComplexWithSandboxActive(b *testing.B) { func BenchmarkParallelExecuteComplexWithSandboxActive(b *testing.B) {
tpl, err := FromFile("template_tests/complex.tpl") tpl, err := pongo2.FromFile("template_tests/complex.tpl")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
_, err := tpl.ExecuteBytes(tplContext) err := tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -484,14 +479,14 @@ func BenchmarkParallelExecuteComplexWithSandboxActive(b *testing.B) {
} }
func BenchmarkExecuteComplexWithoutSandbox(b *testing.B) { func BenchmarkExecuteComplexWithoutSandbox(b *testing.B) {
s := NewSet("set without sandbox") s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader(""))
tpl, err := s.FromFile("template_tests/complex.tpl") tpl, err := s.FromFile("template_tests/complex.tpl")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err = tpl.ExecuteBytes(tplContext) err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -505,7 +500,7 @@ func BenchmarkCompileAndExecuteComplexWithoutSandbox(b *testing.B) {
} }
preloadedTpl := string(buf) preloadedTpl := string(buf)
s := NewSet("set without sandbox") s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader(""))
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -514,7 +509,7 @@ func BenchmarkCompileAndExecuteComplexWithoutSandbox(b *testing.B) {
b.Fatal(err) b.Fatal(err)
} }
_, err = tpl.ExecuteBytes(tplContext) err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@ -522,7 +517,7 @@ func BenchmarkCompileAndExecuteComplexWithoutSandbox(b *testing.B) {
} }
func BenchmarkParallelExecuteComplexWithoutSandbox(b *testing.B) { func BenchmarkParallelExecuteComplexWithoutSandbox(b *testing.B) {
s := NewSet("set without sandbox") s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader(""))
tpl, err := s.FromFile("template_tests/complex.tpl") tpl, err := s.FromFile("template_tests/complex.tpl")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -530,7 +525,7 @@ func BenchmarkParallelExecuteComplexWithoutSandbox(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
_, err := tpl.ExecuteBytes(tplContext) err := tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }

View file

@ -1,26 +1,26 @@
package pongo2 package pongo2_test
import ( import (
"testing" "testing"
"github.com/flosch/pongo2"
. "gopkg.in/check.v1" . "gopkg.in/check.v1"
) )
// Hook up gocheck into the "go test" runner. // Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) } func Test(t *testing.T) { TestingT(t) }
type TestSuite struct { type TestSuite struct {
tpl *Template tpl *pongo2.Template
} }
var ( var (
_ = Suite(&TestSuite{}) _ = Suite(&TestSuite{})
test_suite2 = NewSet("test suite 2") testSuite2 = pongo2.NewSet("test suite 2", pongo2.MustNewLocalFileSystemLoader(""))
) )
func parseTemplate(s string, c Context) string { func parseTemplate(s string, c pongo2.Context) string {
t, err := test_suite2.FromString(s) t, err := testSuite2.FromString(s)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -31,7 +31,7 @@ func parseTemplate(s string, c Context) string {
return out return out
} }
func parseTemplateFn(s string, c Context) func() { func parseTemplateFn(s string, c pongo2.Context) func() {
return func() { return func() {
parseTemplate(s, c) parseTemplate(s, c)
} }
@ -40,27 +40,64 @@ func parseTemplateFn(s string, c Context) func() {
func (s *TestSuite) TestMisc(c *C) { func (s *TestSuite) TestMisc(c *C) {
// Must // Must
// TODO: Add better error message (see issue #18) // TODO: Add better error message (see issue #18)
c.Check(func() { Must(test_suite2.FromFile("template_tests/inheritance/base2.tpl")) }, c.Check(
func() { pongo2.Must(testSuite2.FromFile("template_tests/inheritance/base2.tpl")) },
PanicMatches, PanicMatches,
`\[Error \(where: fromfile\) in template_tests/inheritance/doesnotexist.tpl | Line 1 Col 12 near 'doesnotexist.tpl'\] open template_tests/inheritance/doesnotexist.tpl: no such file or directory`) `\[Error \(where: fromfile\) in .*template_tests/inheritance/doesnotexist.tpl | Line 1 Col 12 near 'doesnotexist.tpl'\] open .*template_tests/inheritance/doesnotexist.tpl: no such file or directory`,
)
// Context // Context
c.Check(parseTemplateFn("", Context{"'illegal": nil}), PanicMatches, ".*not a valid identifier.*") c.Check(parseTemplateFn("", pongo2.Context{"'illegal": nil}), PanicMatches, ".*not a valid identifier.*")
// Registers // Registers
c.Check(func() { RegisterFilter("escape", nil) }, PanicMatches, ".*is already registered.*") c.Check(pongo2.RegisterFilter("escape", nil).Error(), Matches, ".*is already registered")
c.Check(func() { RegisterTag("for", nil) }, PanicMatches, ".*is already registered.*") c.Check(pongo2.RegisterTag("for", nil).Error(), Matches, ".*is already registered")
// ApplyFilter // ApplyFilter
v, err := ApplyFilter("title", AsValue("this is a title"), nil) v, err := pongo2.ApplyFilter("title", pongo2.AsValue("this is a title"), nil)
if err != nil { if err != nil {
c.Fatal(err) c.Fatal(err)
} }
c.Check(v.String(), Equals, "This Is A Title") c.Check(v.String(), Equals, "This Is A Title")
c.Check(func() { c.Check(func() {
_, err := ApplyFilter("doesnotexist", nil, nil) _, err := pongo2.ApplyFilter("doesnotexist", nil, nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }
}, PanicMatches, `\[Error \(where: applyfilter\)\] Filter with name 'doesnotexist' not found.`) }, PanicMatches, `\[Error \(where: applyfilter\)\] Filter with name 'doesnotexist' not found.`)
} }
func (s *TestSuite) TestImplicitExecCtx(c *C) {
tpl, err := pongo2.FromString("{{ ImplicitExec }}")
if err != nil {
c.Fatalf("Error in FromString: %v", err)
}
val := "a stringy thing"
res, err := tpl.Execute(pongo2.Context{
"Value": val,
"ImplicitExec": func(ctx *pongo2.ExecutionContext) string {
return ctx.Public["Value"].(string)
},
})
if err != nil {
c.Fatalf("Error executing template: %v", err)
}
c.Check(res, Equals, val)
// The implicit ctx should not be persisted from call-to-call
res, err = tpl.Execute(pongo2.Context{
"ImplicitExec": func() string {
return val
},
})
if err != nil {
c.Fatalf("Error executing template: %v", err)
}
c.Check(res, Equals, val)
}

View file

@ -21,6 +21,8 @@ package pongo2
import ( import (
"fmt" "fmt"
"github.com/juju/errors"
) )
type INodeTag interface { type INodeTag interface {
@ -53,80 +55,81 @@ func init() {
tags = make(map[string]*tag) tags = make(map[string]*tag)
} }
// Registers a new tag. If there's already a tag with the same // Registers a new tag. You usually want to call this
// name, RegisterTag will panic. You usually want to call this
// function in the tag's init() function: // function in the tag's init() function:
// http://golang.org/doc/effective_go.html#init // http://golang.org/doc/effective_go.html#init
// //
// See http://www.florian-schlachter.de/post/pongo2/ for more about // See http://www.florian-schlachter.de/post/pongo2/ for more about
// writing filters and tags. // writing filters and tags.
func RegisterTag(name string, parserFn TagParser) { func RegisterTag(name string, parserFn TagParser) error {
_, existing := tags[name] _, existing := tags[name]
if existing { 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{ tags[name] = &tag{
name: name, name: name,
parser: parserFn, parser: parserFn,
} }
return nil
} }
// Replaces an already registered tag with a new implementation. Use this // Replaces an already registered tag with a new implementation. Use this
// function with caution since it allows you to change existing tag behaviour. // 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] _, existing := tags[name]
if !existing { 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{ tags[name] = &tag{
name: name, name: name,
parser: parserFn, parser: parserFn,
} }
return nil
} }
// Tag = "{%" IDENT ARGS "%}" // Tag = "{%" IDENT ARGS "%}"
func (p *Parser) parseTagElement() (INodeTag, *Error) { func (p *Parser) parseTagElement() (INodeTag, *Error) {
p.Consume() // consume "{%" p.Consume() // consume "{%"
token_name := p.MatchType(TokenIdentifier) tokenName := p.MatchType(TokenIdentifier)
// Check for identifier // Check for identifier
if token_name == nil { if tokenName == nil {
return nil, p.Error("Tag name must be an identifier.", nil) return nil, p.Error("Tag name must be an identifier.", nil)
} }
// Check for the existing tag // Check for the existing tag
tag, exists := tags[token_name.Val] tag, exists := tags[tokenName.Val]
if !exists { if !exists {
// Does not 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 // Check sandbox tag restriction
if _, is_banned := p.template.set.bannedTags[token_name.Val]; is_banned { 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).", token_name.Val), token_name) 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 { for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 {
// Add token to args // Add token to args
args_token = append(args_token, p.Current()) argsToken = append(argsToken, p.Current())
p.Consume() // next token p.Consume() // next token
} }
// EOF? // EOF?
if p.Remaining() == 0 { 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, "%}") p.Match(TokenSymbol, "%}")
arg_parser := newParser(p.name, args_token, p.template) argParser := newParser(p.name, argsToken, p.template)
if len(args_token) == 0 { if len(argsToken) == 0 {
// This is done to have nice EOF error messages // This is done to have nice EOF error messages
arg_parser.last_token = token_name argParser.lastToken = tokenName
} }
p.template.level++ p.template.level++
defer func() { p.template.level-- }() defer func() { p.template.level-- }()
return tag.parser(p, token_name, arg_parser) return tag.parser(p, tokenName, argParser)
} }

View file

@ -1,19 +1,15 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagAutoescapeNode struct { type tagAutoescapeNode struct {
wrapper *NodeWrapper wrapper *NodeWrapper
autoescape bool autoescape bool
} }
func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
old := ctx.Autoescape old := ctx.Autoescape
ctx.Autoescape = node.autoescape ctx.Autoescape = node.autoescape
err := node.wrapper.Execute(ctx, buffer) err := node.wrapper.Execute(ctx, writer)
if err != nil { if err != nil {
return err 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) { func tagAutoescapeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
autoescape_node := &tagAutoescapeNode{} autoescapeNode := &tagAutoescapeNode{}
wrapper, _, err := doc.WrapUntilTag("endautoescape") wrapper, _, err := doc.WrapUntilTag("endautoescape")
if err != nil { if err != nil {
return nil, err return nil, err
} }
autoescape_node.wrapper = wrapper autoescapeNode.wrapper = wrapper
mode_token := arguments.MatchType(TokenIdentifier) modeToken := arguments.MatchType(TokenIdentifier)
if mode_token == nil { if modeToken == nil {
return nil, arguments.Error("A mode is required for autoescape-tag.", nil) return nil, arguments.Error("A mode is required for autoescape-tag.", nil)
} }
if mode_token.Val == "on" { if modeToken.Val == "on" {
autoescape_node.autoescape = true autoescapeNode.autoescape = true
} else if mode_token.Val == "off" { } else if modeToken.Val == "off" {
autoescape_node.autoescape = false autoescapeNode.autoescape = false
} else { } else {
return nil, arguments.Error("Only 'on' or 'off' is valid as an autoescape-mode.", nil) 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 nil, arguments.Error("Malformed autoescape-tag arguments.", nil)
} }
return autoescape_node, nil return autoescapeNode, nil
} }
func init() { func init() {

View file

@ -9,47 +9,82 @@ type tagBlockNode struct {
name string name string
} }
func (node *tagBlockNode) getBlockWrapperByName(tpl *Template) *NodeWrapper { func (node *tagBlockNode) getBlockWrappers(tpl *Template) []*NodeWrapper {
nodeWrappers := make([]*NodeWrapper, 0)
var t *NodeWrapper var t *NodeWrapper
if tpl.child != nil {
// First ask the child for the block for tpl != nil {
t = node.getBlockWrapperByName(tpl.child)
}
if t == nil {
// Child has no block, lets look up here at parent
t = tpl.blocks[node.name] t = tpl.blocks[node.name]
if t != nil {
nodeWrappers = append(nodeWrappers, t)
} }
return t tpl = tpl.child
}
return nodeWrappers
} }
func (node *tagBlockNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (node *tagBlockNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
tpl := ctx.template tpl := ctx.template
if tpl == nil { if tpl == nil {
panic("internal error: tpl == nil") panic("internal error: tpl == nil")
} }
// Determine the block to execute // Determine the block to execute
block_wrapper := node.getBlockWrapperByName(tpl) blockWrappers := node.getBlockWrappers(tpl)
if block_wrapper == nil { lenBlockWrappers := len(blockWrappers)
// fmt.Printf("could not find: %s\n", node.name)
return ctx.Error("internal error: block_wrapper == nil in tagBlockNode.Execute()", nil) 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 { if err != nil {
return err return err
} }
// TODO: Add support for {{ block.super }}
return nil 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) { func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
if arguments.Count() == 0 { if arguments.Count() == 0 {
return nil, arguments.Error("Tag 'block' requires an identifier.", nil) return nil, arguments.Error("Tag 'block' requires an identifier.", nil)
} }
name_token := arguments.MatchType(TokenIdentifier) nameToken := arguments.MatchType(TokenIdentifier)
if name_token == nil { if nameToken == nil {
return nil, arguments.Error("First argument for tag 'block' must be an identifier.", 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 return nil, err
} }
if endtagargs.Remaining() > 0 { if endtagargs.Remaining() > 0 {
endtagname_token := endtagargs.MatchType(TokenIdentifier) endtagnameToken := endtagargs.MatchType(TokenIdentifier)
if endtagname_token != nil { if endtagnameToken != nil {
if endtagname_token.Val != name_token.Val { if endtagnameToken.Val != nameToken.Val {
return nil, endtagargs.Error(fmt.Sprintf("Name for 'endblock' must equal to 'block'-tag's name ('%s' != '%s').", 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) 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 { if tpl == nil {
panic("internal error: tpl == nil") panic("internal error: tpl == nil")
} }
_, has_block := tpl.blocks[name_token.Val] _, hasBlock := tpl.blocks[nameToken.Val]
if !has_block { if !hasBlock {
tpl.blocks[name_token.Val] = wrapper tpl.blocks[nameToken.Val] = wrapper
} else { } 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() { func init() {

View file

@ -1,20 +1,16 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagCommentNode struct{} type tagCommentNode struct{}
func (node *tagCommentNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (node *tagCommentNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
return nil return nil
} }
func tagCommentParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 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) // TODO: Process the endtag's arguments (see django 'comment'-tag documentation)
_, _, err := doc.WrapUntilTag("endcomment") err := doc.SkipUntilTag("endcomment")
if err != nil { if err != nil {
return nil, err 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 nil, arguments.Error("Tag 'comment' does not take any argument.", nil)
} }
return comment_node, nil return commentNode, nil
} }
func init() { func init() {

View file

@ -1,9 +1,5 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagCycleValue struct { type tagCycleValue struct {
node *tagCycleNode node *tagCycleNode
value *Value value *Value
@ -13,7 +9,7 @@ type tagCycleNode struct {
position *Token position *Token
args []IEvaluator args []IEvaluator
idx int idx int
as_name string asName string
silent bool silent bool
} }
@ -21,7 +17,7 @@ func (cv *tagCycleValue) String() string {
return cv.value.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)] item := node.args[node.idx%len(node.args)]
node.idx++ node.idx++
@ -46,30 +42,30 @@ func (node *tagCycleNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *
t.value = val t.value = val
if !t.node.silent { if !t.node.silent {
buffer.WriteString(val.String()) writer.WriteString(val.String())
} }
} else { } else {
// Regular call // Regular call
cycle_value := &tagCycleValue{ cycleValue := &tagCycleValue{
node: node, node: node,
value: val, value: val,
} }
if node.as_name != "" { if node.asName != "" {
ctx.Private[node.as_name] = cycle_value ctx.Private[node.asName] = cycleValue
} }
if !node.silent { if !node.silent {
buffer.WriteString(val.String()) writer.WriteString(val.String())
} }
} }
return nil 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) { func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
cycle_node := &tagCycleNode{ cycleNode := &tagCycleNode{
position: start, position: start,
} }
@ -78,19 +74,19 @@ func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er
if err != nil { if err != nil {
return nil, err return nil, err
} }
cycle_node.args = append(cycle_node.args, node) cycleNode.args = append(cycleNode.args, node)
if arguments.MatchOne(TokenKeyword, "as") != nil { if arguments.MatchOne(TokenKeyword, "as") != nil {
// as // as
name_token := arguments.MatchType(TokenIdentifier) nameToken := arguments.MatchType(TokenIdentifier)
if name_token == nil { if nameToken == nil {
return nil, arguments.Error("Name (identifier) expected after 'as'.", 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 { if arguments.MatchOne(TokenIdentifier, "silent") != nil {
cycle_node.silent = true cycleNode.silent = true
} }
// Now we're finished // 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 nil, arguments.Error("Malformed cycle-tag.", nil)
} }
return cycle_node, nil return cycleNode, nil
} }
func init() { func init() {

View file

@ -1,19 +1,15 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagExtendsNode struct { type tagExtendsNode struct {
filename string filename string
} }
func (node *tagExtendsNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (node *tagExtendsNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
return nil return nil
} }
func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
extends_node := &tagExtendsNode{} extendsNode := &tagExtendsNode{}
if doc.template.level > 1 { if doc.template.level > 1 {
return nil, arguments.Error("The 'extends' tag can only defined on root level.", start) 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) 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 // prepared, static template
// Get parent's filename // 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 // Parse the parent
parent_template, err := doc.template.set.FromFile(parent_filename) parentTemplate, err := doc.template.set.FromFile(parentFilename)
if err != nil { if err != nil {
return nil, err.(*Error) return nil, err.(*Error)
} }
// Keep track of things // Keep track of things
parent_template.child = doc.template parentTemplate.child = doc.template
doc.template.parent = parent_template doc.template.parent = parentTemplate
extends_node.filename = parent_filename extendsNode.filename = parentFilename
} else { } else {
return nil, arguments.Error("Tag 'extends' requires a template filename as string.", nil) 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 nil, arguments.Error("Tag 'extends' does only take 1 argument.", nil)
} }
return extends_node, nil return extendsNode, nil
} }
func init() { func init() {

View file

@ -6,7 +6,7 @@ import (
type nodeFilterCall struct { type nodeFilterCall struct {
name string name string
param_expr IEvaluator paramExpr IEvaluator
} }
type tagFilterNode struct { type tagFilterNode struct {
@ -15,7 +15,7 @@ type tagFilterNode struct {
filterChain []*nodeFilterCall 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 temp := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB size
err := node.bodyWrapper.Execute(ctx, temp) err := node.bodyWrapper.Execute(ctx, temp)
@ -27,8 +27,8 @@ func (node *tagFilterNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer)
for _, call := range node.filterChain { for _, call := range node.filterChain {
var param *Value var param *Value
if call.param_expr != nil { if call.paramExpr != nil {
param, err = call.param_expr.Evaluate(ctx) param, err = call.paramExpr.Evaluate(ctx)
if err != nil { if err != nil {
return err 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 return nil
} }
func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
filter_node := &tagFilterNode{ filterNode := &tagFilterNode{
position: start, position: start,
} }
@ -55,16 +55,16 @@ func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *E
if err != nil { if err != nil {
return nil, err return nil, err
} }
filter_node.bodyWrapper = wrapper filterNode.bodyWrapper = wrapper
for arguments.Remaining() > 0 { for arguments.Remaining() > 0 {
filterCall := &nodeFilterCall{} filterCall := &nodeFilterCall{}
name_token := arguments.MatchType(TokenIdentifier) nameToken := arguments.MatchType(TokenIdentifier)
if name_token == nil { if nameToken == nil {
return nil, arguments.Error("Expected a filter name (identifier).", 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 { if arguments.MatchOne(TokenSymbol, ":") != nil {
// Filter parameter // Filter parameter
@ -73,10 +73,10 @@ func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *E
if err != nil { if err != nil {
return nil, err 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 { if arguments.MatchOne(TokenSymbol, "|") == nil {
break 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 nil, arguments.Error("Malformed filter-tag arguments.", nil)
} }
return filter_node, nil return filterNode, nil
} }
func init() { func init() {

View file

@ -1,15 +1,11 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagFirstofNode struct { type tagFirstofNode struct {
position *Token position *Token
args []IEvaluator 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 { for _, arg := range node.args {
val, err := arg.Evaluate(ctx) val, err := arg.Evaluate(ctx)
if err != nil { 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 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) { func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
firstof_node := &tagFirstofNode{ firstofNode := &tagFirstofNode{
position: start, position: start,
} }
@ -42,10 +38,10 @@ func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *
if err != nil { if err != nil {
return nil, err 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() { func init() {

View file

@ -1,14 +1,11 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagForNode struct { type tagForNode struct {
key string key string
value string // only for maps: for key, value in map value string // only for maps: for key, value in map
object_evaluator IEvaluator objectEvaluator IEvaluator
reversed bool reversed bool
sorted bool
bodyWrapper *NodeWrapper bodyWrapper *NodeWrapper
emptyWrapper *NodeWrapper emptyWrapper *NodeWrapper
@ -24,7 +21,7 @@ type tagForLoopInformation struct {
Parentloop *tagForLoopInformation 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 // Backup forloop (as parentloop in public context), key-name and value-name
forCtx := NewChildExecutionContext(ctx) forCtx := NewChildExecutionContext(ctx)
parentloop := forCtx.Private["forloop"] parentloop := forCtx.Private["forloop"]
@ -42,7 +39,7 @@ func (node *tagForNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) (fo
// Register loopInfo in public context // Register loopInfo in public context
forCtx.Private["forloop"] = loopInfo forCtx.Private["forloop"] = loopInfo
obj, err := node.object_evaluator.Evaluate(forCtx) obj, err := node.objectEvaluator.Evaluate(forCtx)
if err != nil { if err != nil {
return err 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 loopInfo.Revcounter0 = count - (idx + 1) // TODO: Not sure about this, have to look it up
// Render elements with updated context // Render elements with updated context
err := node.bodyWrapper.Execute(forCtx, buffer) err := node.bodyWrapper.Execute(forCtx, writer)
if err != nil { if err != nil {
forError = err forError = err
return false return false
@ -76,30 +73,30 @@ func (node *tagForNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) (fo
}, func() { }, func() {
// Nothing to iterate over (maybe wrong type or no items) // Nothing to iterate over (maybe wrong type or no items)
if node.emptyWrapper != nil { if node.emptyWrapper != nil {
err := node.emptyWrapper.Execute(forCtx, buffer) err := node.emptyWrapper.Execute(forCtx, writer)
if err != nil { if err != nil {
forError = err forError = err
} }
} }
}, node.reversed) }, node.reversed, node.sorted)
return nil return forError
} }
func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
for_node := &tagForNode{} forNode := &tagForNode{}
// Arguments parsing // Arguments parsing
var value_token *Token var valueToken *Token
key_token := arguments.MatchType(TokenIdentifier) keyToken := arguments.MatchType(TokenIdentifier)
if key_token == nil { if keyToken == nil {
return nil, arguments.Error("Expected an key identifier as first argument for 'for'-tag", nil) return nil, arguments.Error("Expected an key identifier as first argument for 'for'-tag", nil)
} }
if arguments.Match(TokenSymbol, ",") != nil { if arguments.Match(TokenSymbol, ",") != nil {
// Value name is provided // Value name is provided
value_token = arguments.MatchType(TokenIdentifier) valueToken = arguments.MatchType(TokenIdentifier)
if value_token == nil { if valueToken == nil {
return nil, arguments.Error("Value name must be an identifier.", 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) return nil, arguments.Error("Expected keyword 'in'.", nil)
} }
object_evaluator, err := arguments.ParseExpression() objectEvaluator, err := arguments.ParseExpression()
if err != nil { if err != nil {
return nil, err return nil, err
} }
for_node.object_evaluator = object_evaluator forNode.objectEvaluator = objectEvaluator
for_node.key = key_token.Val forNode.key = keyToken.Val
if value_token != nil { if valueToken != nil {
for_node.value = value_token.Val forNode.value = valueToken.Val
} }
if arguments.MatchOne(TokenIdentifier, "reversed") != nil { 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 { if arguments.Remaining() > 0 {
@ -131,7 +132,7 @@ func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
for_node.bodyWrapper = wrapper forNode.bodyWrapper = wrapper
if endargs.Count() > 0 { if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil) 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 { if err != nil {
return nil, err return nil, err
} }
for_node.emptyWrapper = wrapper forNode.emptyWrapper = wrapper
if endargs.Count() > 0 { if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil) return nil, endargs.Error("Arguments not allowed here.", nil)
} }
} }
return for_node, nil return forNode, nil
} }
func init() { func init() {

View file

@ -1,15 +1,11 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagIfNode struct { type tagIfNode struct {
conditions []IEvaluator conditions []IEvaluator
wrappers []*NodeWrapper 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 { for i, condition := range node.conditions {
result, err := condition.Evaluate(ctx) result, err := condition.Evaluate(ctx)
if err != nil { if err != nil {
@ -17,26 +13,25 @@ func (node *tagIfNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Err
} }
if result.IsTrue() { if result.IsTrue() {
return node.wrappers[i].Execute(ctx, buffer) return node.wrappers[i].Execute(ctx, writer)
} else { }
// Last condition? // Last condition?
if len(node.conditions) == i+1 && len(node.wrappers) > i+1 { if len(node.conditions) == i+1 && len(node.wrappers) > i+1 {
return node.wrappers[i+1].Execute(ctx, buffer) return node.wrappers[i+1].Execute(ctx, writer)
}
} }
} }
return nil return nil
} }
func tagIfParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagIfParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
if_node := &tagIfNode{} ifNode := &tagIfNode{}
// Parse first and main IF condition // Parse first and main IF condition
condition, err := arguments.ParseExpression() condition, err := arguments.ParseExpression()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if_node.conditions = append(if_node.conditions, condition) ifNode.conditions = append(ifNode.conditions, condition)
if arguments.Remaining() > 0 { if arguments.Remaining() > 0 {
return nil, arguments.Error("If-condition is malformed.", nil) 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 // Check the rest
for { for {
wrapper, tag_args, err := doc.WrapUntilTag("elif", "else", "endif") wrapper, tagArgs, err := doc.WrapUntilTag("elif", "else", "endif")
if err != nil { if err != nil {
return nil, err return nil, err
} }
if_node.wrappers = append(if_node.wrappers, wrapper) ifNode.wrappers = append(ifNode.wrappers, wrapper)
if wrapper.Endtag == "elif" { if wrapper.Endtag == "elif" {
// elif can take a condition // elif can take a condition
condition, err := tag_args.ParseExpression() condition, err = tagArgs.ParseExpression()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if_node.conditions = append(if_node.conditions, condition) ifNode.conditions = append(ifNode.conditions, condition)
if tag_args.Remaining() > 0 { if tagArgs.Remaining() > 0 {
return nil, tag_args.Error("Elif-condition is malformed.", nil) return nil, tagArgs.Error("Elif-condition is malformed.", nil)
} }
} else { } else {
if tag_args.Count() > 0 { if tagArgs.Count() > 0 {
// else/endif can't take any conditions // 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() { func init() {

View file

@ -5,16 +5,15 @@ import (
) )
type tagIfchangedNode struct { type tagIfchangedNode struct {
watched_expr []IEvaluator watchedExpr []IEvaluator
last_values []*Value lastValues []*Value
last_content []byte lastContent []byte
thenWrapper *NodeWrapper thenWrapper *NodeWrapper
elseWrapper *NodeWrapper elseWrapper *NodeWrapper
} }
func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
if len(node.watchedExpr) == 0 {
if len(node.watched_expr) == 0 {
// Check against own rendered body // Check against own rendered body
buf := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB buf := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB
@ -23,43 +22,43 @@ func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffe
return err return err
} }
buf_bytes := buf.Bytes() bufBytes := buf.Bytes()
if !bytes.Equal(node.last_content, buf_bytes) { if !bytes.Equal(node.lastContent, bufBytes) {
// Rendered content changed, output it // Rendered content changed, output it
buffer.Write(buf_bytes) writer.Write(bufBytes)
node.last_content = buf_bytes node.lastContent = bufBytes
} }
} else { } else {
now_values := make([]*Value, 0, len(node.watched_expr)) nowValues := make([]*Value, 0, len(node.watchedExpr))
for _, expr := range node.watched_expr { for _, expr := range node.watchedExpr {
val, err := expr.Evaluate(ctx) val, err := expr.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
now_values = append(now_values, val) nowValues = append(nowValues, val)
} }
// Compare old to new values now // 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 { for idx, oldVal := range node.lastValues {
if !old_val.EqualValueTo(now_values[idx]) { if !oldVal.EqualValueTo(nowValues[idx]) {
changed = true changed = true
break // we can stop here because ONE value changed break // we can stop here because ONE value changed
} }
} }
node.last_values = now_values node.lastValues = nowValues
if changed { if changed {
// Render thenWrapper // Render thenWrapper
err := node.thenWrapper.Execute(ctx, buffer) err := node.thenWrapper.Execute(ctx, writer)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
// Render elseWrapper // Render elseWrapper
err := node.elseWrapper.Execute(ctx, buffer) err := node.elseWrapper.Execute(ctx, writer)
if err != nil { if err != nil {
return err 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) { func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ifchanged_node := &tagIfchangedNode{} ifchangedNode := &tagIfchangedNode{}
for arguments.Remaining() > 0 { for arguments.Remaining() > 0 {
// Parse condition // Parse condition
@ -78,7 +77,7 @@ func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag,
if err != nil { if err != nil {
return nil, err return nil, err
} }
ifchanged_node.watched_expr = append(ifchanged_node.watched_expr, expr) ifchangedNode.watchedExpr = append(ifchangedNode.watchedExpr, expr)
} }
if arguments.Remaining() > 0 { if arguments.Remaining() > 0 {
@ -90,7 +89,7 @@ func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag,
if err != nil { if err != nil {
return nil, err return nil, err
} }
ifchanged_node.thenWrapper = wrapper ifchangedNode.thenWrapper = wrapper
if endargs.Count() > 0 { if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil) 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 { if err != nil {
return nil, err return nil, err
} }
ifchanged_node.elseWrapper = wrapper ifchangedNode.elseWrapper = wrapper
if endargs.Count() > 0 { if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil) return nil, endargs.Error("Arguments not allowed here.", nil)
} }
} }
return ifchanged_node, nil return ifchangedNode, nil
} }
func init() { func init() {

View file

@ -1,16 +1,12 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagIfEqualNode struct { type tagIfEqualNode struct {
var1, var2 IEvaluator var1, var2 IEvaluator
thenWrapper *NodeWrapper thenWrapper *NodeWrapper
elseWrapper *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) r1, err := node.var1.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
@ -23,17 +19,16 @@ func (node *tagIfEqualNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer)
result := r1.EqualValueTo(r2) result := r1.EqualValueTo(r2)
if result { if result {
return node.thenWrapper.Execute(ctx, buffer) return node.thenWrapper.Execute(ctx, writer)
} else {
if node.elseWrapper != nil {
return node.elseWrapper.Execute(ctx, buffer)
} }
if node.elseWrapper != nil {
return node.elseWrapper.Execute(ctx, writer)
} }
return nil return nil
} }
func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ifequal_node := &tagIfEqualNode{} ifequalNode := &tagIfEqualNode{}
// Parse two expressions // Parse two expressions
var1, err := arguments.ParseExpression() var1, err := arguments.ParseExpression()
@ -44,8 +39,8 @@ func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *
if err != nil { if err != nil {
return nil, err return nil, err
} }
ifequal_node.var1 = var1 ifequalNode.var1 = var1
ifequal_node.var2 = var2 ifequalNode.var2 = var2
if arguments.Remaining() > 0 { if arguments.Remaining() > 0 {
return nil, arguments.Error("ifequal only takes 2 arguments.", nil) 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 { if err != nil {
return nil, err return nil, err
} }
ifequal_node.thenWrapper = wrapper ifequalNode.thenWrapper = wrapper
if endargs.Count() > 0 { if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil) 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 { if err != nil {
return nil, err return nil, err
} }
ifequal_node.elseWrapper = wrapper ifequalNode.elseWrapper = wrapper
if endargs.Count() > 0 { if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil) return nil, endargs.Error("Arguments not allowed here.", nil)
} }
} }
return ifequal_node, nil return ifequalNode, nil
} }
func init() { func init() {

View file

@ -1,16 +1,12 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagIfNotEqualNode struct { type tagIfNotEqualNode struct {
var1, var2 IEvaluator var1, var2 IEvaluator
thenWrapper *NodeWrapper thenWrapper *NodeWrapper
elseWrapper *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) r1, err := node.var1.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
@ -23,17 +19,16 @@ func (node *tagIfNotEqualNode) Execute(ctx *ExecutionContext, buffer *bytes.Buff
result := !r1.EqualValueTo(r2) result := !r1.EqualValueTo(r2)
if result { if result {
return node.thenWrapper.Execute(ctx, buffer) return node.thenWrapper.Execute(ctx, writer)
} else {
if node.elseWrapper != nil {
return node.elseWrapper.Execute(ctx, buffer)
} }
if node.elseWrapper != nil {
return node.elseWrapper.Execute(ctx, writer)
} }
return nil return nil
} }
func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ifnotequal_node := &tagIfNotEqualNode{} ifnotequalNode := &tagIfNotEqualNode{}
// Parse two expressions // Parse two expressions
var1, err := arguments.ParseExpression() var1, err := arguments.ParseExpression()
@ -44,19 +39,19 @@ func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag
if err != nil { if err != nil {
return nil, err return nil, err
} }
ifnotequal_node.var1 = var1 ifnotequalNode.var1 = var1
ifnotequal_node.var2 = var2 ifnotequalNode.var2 = var2
if arguments.Remaining() > 0 { if arguments.Remaining() > 0 {
return nil, arguments.Error("ifequal only takes 2 arguments.", nil) return nil, arguments.Error("ifequal only takes 2 arguments.", nil)
} }
// Wrap then/else-blocks // Wrap then/else-blocks
wrapper, endargs, err := doc.WrapUntilTag("else", "endifequal") wrapper, endargs, err := doc.WrapUntilTag("else", "endifnotequal")
if err != nil { if err != nil {
return nil, err return nil, err
} }
ifnotequal_node.thenWrapper = wrapper ifnotequalNode.thenWrapper = wrapper
if endargs.Count() > 0 { if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil) 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 wrapper.Endtag == "else" {
// if there's an else in the if-statement, we need the else-Block as well // 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 { if err != nil {
return nil, err return nil, err
} }
ifnotequal_node.elseWrapper = wrapper ifnotequalNode.elseWrapper = wrapper
if endargs.Count() > 0 { if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil) return nil, endargs.Error("Arguments not allowed here.", nil)
} }
} }
return ifnotequal_node, nil return ifnotequalNode, nil
} }
func init() { func init() {

View file

@ -1,18 +1,16 @@
package pongo2 package pongo2
import ( import (
"bytes"
"fmt" "fmt"
) )
type tagImportNode struct { type tagImportNode struct {
position *Token position *Token
filename string filename string
template *Template
macros map[string]*tagMacroNode // alias/name -> macro instance 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 { for name, macro := range node.macros {
func(name string, macro *tagMacroNode) { func(name string, macro *tagMacroNode) {
ctx.Private[name] = func(args ...*Value) *Value { 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) { func tagImportParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
import_node := &tagImportNode{ importNode := &tagImportNode{
position: start, position: start,
macros: make(map[string]*tagMacroNode), macros: make(map[string]*tagMacroNode),
} }
filename_token := arguments.MatchType(TokenString) filenameToken := arguments.MatchType(TokenString)
if filename_token == nil { if filenameToken == nil {
return nil, arguments.Error("Import-tag needs a filename as string.", 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 { if arguments.Remaining() == 0 {
return nil, arguments.Error("You must at least specify one macro to import.", nil) return nil, arguments.Error("You must at least specify one macro to import.", nil)
} }
// Compile the given template // Compile the given template
tpl, err := doc.template.set.FromFile(import_node.filename) tpl, err := doc.template.set.FromFile(importNode.filename)
if err != nil { if err != nil {
return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, start) return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, start)
} }
for arguments.Remaining() > 0 { for arguments.Remaining() > 0 {
macro_name_token := arguments.MatchType(TokenIdentifier) macroNameToken := arguments.MatchType(TokenIdentifier)
if macro_name_token == nil { if macroNameToken == nil {
return nil, arguments.Error("Expected macro name (identifier).", 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 { if arguments.Match(TokenKeyword, "as") != nil {
alias_token := arguments.MatchType(TokenIdentifier) aliasToken := arguments.MatchType(TokenIdentifier)
if alias_token == nil { if aliasToken == nil {
return nil, arguments.Error("Expected macro alias name (identifier).", 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 { if !has {
return nil, arguments.Error(fmt.Sprintf("Macro '%s' not found (or not exported) in '%s'.", macro_name_token.Val, return nil, arguments.Error(fmt.Sprintf("Macro '%s' not found (or not exported) in '%s'.", macroNameToken.Val,
import_node.filename), macro_name_token) importNode.filename), macroNameToken)
} }
import_node.macros[as_name] = macro_instance importNode.macros[asName] = macroInstance
if arguments.Remaining() == 0 { if arguments.Remaining() == 0 {
break break
@ -78,7 +76,7 @@ func tagImportParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *E
} }
} }
return import_node, nil return importNode, nil
} }
func init() { func init() {

View file

@ -1,41 +1,38 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagIncludeNode struct { type tagIncludeNode struct {
tpl *Template tpl *Template
filename_evaluator IEvaluator filenameEvaluator IEvaluator
lazy bool lazy bool
only bool only bool
filename string filename string
with_pairs map[string]IEvaluator 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 // Building the context for the template
include_ctx := make(Context) includeCtx := make(Context)
// Fill the context with all data from the parent // Fill the context with all data from the parent
if !node.only { if !node.only {
include_ctx.Update(ctx.Public) includeCtx.Update(ctx.Public)
include_ctx.Update(ctx.Private) includeCtx.Update(ctx.Private)
} }
// Put all custom with-pairs into the context // 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) val, err := value.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
include_ctx[key] = val includeCtx[key] = val
} }
// Execute the template // Execute the template
if node.lazy { if node.lazy {
// Evaluate the filename // Evaluate the filename
filename, err := node.filename_evaluator.Evaluate(ctx) filename, err := node.filenameEvaluator.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -45,76 +42,93 @@ func (node *tagIncludeNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer)
} }
// Get include-filename // 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 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) return err2.(*Error)
} }
err2 = included_tpl.ExecuteWriter(include_ctx, buffer) err2 = includedTpl.ExecuteWriter(includeCtx, writer)
if err2 != nil { if err2 != nil {
return err2.(*Error) return err2.(*Error)
} }
return nil return nil
} else { }
// Template is already parsed with static filename // Template is already parsed with static filename
err := node.tpl.ExecuteWriter(include_ctx, buffer) err := node.tpl.ExecuteWriter(includeCtx, writer)
if err != nil { if err != nil {
return err.(*Error) return err.(*Error)
} }
return nil 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) { func tagIncludeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
include_node := &tagIncludeNode{ includeNode := &tagIncludeNode{
with_pairs: make(map[string]IEvaluator), withPairs: make(map[string]IEvaluator),
} }
if filename_token := arguments.MatchType(TokenString); filename_token != nil { if filenameToken := arguments.MatchType(TokenString); filenameToken != nil {
// prepared, static template // prepared, static template
// "if_exists" flag
ifExists := arguments.Match(TokenIdentifier, "if_exists") != nil
// Get include-filename // 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 // Parse the parent
include_node.filename = included_filename includeNode.filename = includedFilename
included_tpl, err := doc.template.set.FromFile(included_filename) includedTpl, err := doc.template.set.FromFile(includedFilename)
if err != nil { 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
} }
include_node.tpl = included_tpl return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, filenameToken)
}
includeNode.tpl = includedTpl
} else { } else {
// No String, then the user wants to use lazy-evaluation (slower, but possible) // 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 { if err != nil {
return nil, err.updateFromTokenIfNeeded(doc.template, filename_token) return nil, err.updateFromTokenIfNeeded(doc.template, filenameToken)
} }
include_node.filename_evaluator = filename_evaluator includeNode.filenameEvaluator = filenameEvaluator
include_node.lazy = true 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 // After having parsed the filename we're gonna parse the with+only options
if arguments.Match(TokenIdentifier, "with") != nil { if arguments.Match(TokenIdentifier, "with") != nil {
for arguments.Remaining() > 0 { for arguments.Remaining() > 0 {
// We have at least one key=expr pair (because of starting "with") // We have at least one key=expr pair (because of starting "with")
key_token := arguments.MatchType(TokenIdentifier) keyToken := arguments.MatchType(TokenIdentifier)
if key_token == nil { if keyToken == nil {
return nil, arguments.Error("Expected an identifier", nil) return nil, arguments.Error("Expected an identifier", nil)
} }
if arguments.Match(TokenSymbol, "=") == nil { if arguments.Match(TokenSymbol, "=") == nil {
return nil, arguments.Error("Expected '='.", nil) return nil, arguments.Error("Expected '='.", nil)
} }
value_expr, err := arguments.ParseExpression() valueExpr, err := arguments.ParseExpression()
if err != nil { 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? // Only?
if arguments.Match(TokenIdentifier, "only") != nil { if arguments.Match(TokenIdentifier, "only") != nil {
include_node.only = true includeNode.only = true
break // stop parsing arguments because it's the last option 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 nil, arguments.Error("Malformed 'include'-tag arguments.", nil)
} }
return include_node, nil return includeNode, nil
} }
func init() { func init() {

View file

@ -1,10 +1,11 @@
package pongo2 package pongo2
import ( import (
"bytes"
"math/rand" "math/rand"
"strings" "strings"
"time" "time"
"github.com/juju/errors"
) )
var ( var (
@ -19,102 +20,102 @@ type tagLoremNode struct {
random bool // does not use the default paragraph "Lorem ipsum dolor sit amet, ..." 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 { switch node.method {
case "b": case "b":
if node.random { if node.random {
for i := 0; i < node.count; i++ { for i := 0; i < node.count; i++ {
if i > 0 { if i > 0 {
buffer.WriteString("\n") writer.WriteString("\n")
} }
par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))] par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))]
buffer.WriteString(par) writer.WriteString(par)
} }
} else { } else {
for i := 0; i < node.count; i++ { for i := 0; i < node.count; i++ {
if i > 0 { if i > 0 {
buffer.WriteString("\n") writer.WriteString("\n")
} }
par := tagLoremParagraphs[i%len(tagLoremParagraphs)] par := tagLoremParagraphs[i%len(tagLoremParagraphs)]
buffer.WriteString(par) writer.WriteString(par)
} }
} }
case "w": case "w":
if node.random { if node.random {
for i := 0; i < node.count; i++ { for i := 0; i < node.count; i++ {
if i > 0 { if i > 0 {
buffer.WriteString(" ") writer.WriteString(" ")
} }
word := tagLoremWords[rand.Intn(len(tagLoremWords))] word := tagLoremWords[rand.Intn(len(tagLoremWords))]
buffer.WriteString(word) writer.WriteString(word)
} }
} else { } else {
for i := 0; i < node.count; i++ { for i := 0; i < node.count; i++ {
if i > 0 { if i > 0 {
buffer.WriteString(" ") writer.WriteString(" ")
} }
word := tagLoremWords[i%len(tagLoremWords)] word := tagLoremWords[i%len(tagLoremWords)]
buffer.WriteString(word) writer.WriteString(word)
} }
} }
case "p": case "p":
if node.random { if node.random {
for i := 0; i < node.count; i++ { for i := 0; i < node.count; i++ {
if i > 0 { if i > 0 {
buffer.WriteString("\n") writer.WriteString("\n")
} }
buffer.WriteString("<p>") writer.WriteString("<p>")
par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))] par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))]
buffer.WriteString(par) writer.WriteString(par)
buffer.WriteString("</p>") writer.WriteString("</p>")
} }
} else { } else {
for i := 0; i < node.count; i++ { for i := 0; i < node.count; i++ {
if i > 0 { if i > 0 {
buffer.WriteString("\n") writer.WriteString("\n")
} }
buffer.WriteString("<p>") writer.WriteString("<p>")
par := tagLoremParagraphs[i%len(tagLoremParagraphs)] par := tagLoremParagraphs[i%len(tagLoremParagraphs)]
buffer.WriteString(par) writer.WriteString(par)
buffer.WriteString("</p>") writer.WriteString("</p>")
} }
} }
default: default:
panic("unsupported method") return ctx.OrigError(errors.Errorf("unsupported method: %s", node.method), nil)
} }
return nil return nil
} }
func tagLoremParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagLoremParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
lorem_node := &tagLoremNode{ loremNode := &tagLoremNode{
position: start, position: start,
count: 1, count: 1,
method: "b", method: "b",
} }
if count_token := arguments.MatchType(TokenNumber); count_token != nil { if countToken := arguments.MatchType(TokenNumber); countToken != nil {
lorem_node.count = AsValue(count_token.Val).Integer() loremNode.count = AsValue(countToken.Val).Integer()
} }
if method_token := arguments.MatchType(TokenIdentifier); method_token != nil { if methodToken := arguments.MatchType(TokenIdentifier); methodToken != nil {
if method_token.Val != "w" && method_token.Val != "p" && method_token.Val != "b" { if methodToken.Val != "w" && methodToken.Val != "p" && methodToken.Val != "b" {
return nil, arguments.Error("lorem-method must be either 'w', 'p' or 'b'.", nil) 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 { if arguments.MatchOne(TokenIdentifier, "random") != nil {
lorem_node.random = true loremNode.random = true
} }
if arguments.Remaining() > 0 { if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed lorem-tag arguments.", nil) return nil, arguments.Error("Malformed lorem-tag arguments.", nil)
} }
return lorem_node, nil return loremNode, nil
} }
func init() { func init() {

View file

@ -8,14 +8,14 @@ import (
type tagMacroNode struct { type tagMacroNode struct {
position *Token position *Token
name string name string
args_order []string argsOrder []string
args map[string]IEvaluator args map[string]IEvaluator
exported bool exported bool
wrapper *NodeWrapper 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 { ctx.Private[node.name] = func(args ...*Value) *Value {
return node.call(ctx, args...) 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 { func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value {
args_ctx := make(Context) argsCtx := make(Context)
for k, v := range node.args { for k, v := range node.args {
if v == nil { if v == nil {
// User did not provided a default value // User did not provided a default value
args_ctx[k] = nil argsCtx[k] = nil
} else { } else {
// Evaluate the default value // Evaluate the default value
value_expr, err := v.Evaluate(ctx) valueExpr, err := v.Evaluate(ctx)
if err != nil { if err != nil {
ctx.Logf(err.Error()) ctx.Logf(err.Error())
return AsSafeValue(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. // 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).", 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 ctx.Logf(err.Error()) // TODO: This is a workaround, because the error is not returned yet to the Execution()-methods
return AsSafeValue(err.Error()) return AsSafeValue(err.Error())
@ -55,10 +55,10 @@ func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value {
macroCtx := NewChildExecutionContext(ctx) macroCtx := NewChildExecutionContext(ctx)
// Register all arguments in the private context // Register all arguments in the private context
macroCtx.Private.Update(args_ctx) macroCtx.Private.Update(argsCtx)
for idx, arg_value := range args { for idx, argValue := range args {
macroCtx.Private[node.args_order[idx]] = arg_value.Interface() macroCtx.Private[node.argsOrder[idx]] = argValue.Interface()
} }
var b bytes.Buffer 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) { func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
macro_node := &tagMacroNode{ macroNode := &tagMacroNode{
position: start, position: start,
args: make(map[string]IEvaluator), args: make(map[string]IEvaluator),
} }
name_token := arguments.MatchType(TokenIdentifier) nameToken := arguments.MatchType(TokenIdentifier)
if name_token == nil { if nameToken == nil {
return nil, arguments.Error("Macro-tag needs at least an identifier as name.", 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 { if arguments.MatchOne(TokenSymbol, "(") == nil {
return nil, arguments.Error("Expected '('.", nil) return nil, arguments.Error("Expected '('.", nil)
} }
for arguments.Match(TokenSymbol, ")") == nil { for arguments.Match(TokenSymbol, ")") == nil {
arg_name_token := arguments.MatchType(TokenIdentifier) argNameToken := arguments.MatchType(TokenIdentifier)
if arg_name_token == nil { if argNameToken == nil {
return nil, arguments.Error("Expected argument name as identifier.", 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 { if arguments.Match(TokenSymbol, "=") != nil {
// Default expression follows // Default expression follows
arg_default_expr, err := arguments.ParseExpression() argDefaultExpr, err := arguments.ParseExpression()
if err != nil { if err != nil {
return nil, err return nil, err
} }
macro_node.args[arg_name_token.Val] = arg_default_expr macroNode.args[argNameToken.Val] = argDefaultExpr
} else { } else {
// No default expression // No default expression
macro_node.args[arg_name_token.Val] = nil macroNode.args[argNameToken.Val] = nil
} }
if arguments.Match(TokenSymbol, ")") != 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 { if arguments.Match(TokenKeyword, "export") != nil {
macro_node.exported = true macroNode.exported = true
} }
if arguments.Remaining() > 0 { if arguments.Remaining() > 0 {
@ -126,22 +126,22 @@ func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er
if err != nil { if err != nil {
return nil, err return nil, err
} }
macro_node.wrapper = wrapper macroNode.wrapper = wrapper
if endargs.Count() > 0 { if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil) 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 // 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 { 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() { func init() {

View file

@ -1,7 +1,6 @@
package pongo2 package pongo2
import ( import (
"bytes"
"time" "time"
) )
@ -11,7 +10,7 @@ type tagNowNode struct {
fake bool 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 var t time.Time
if node.fake { if node.fake {
t = time.Date(2014, time.February, 05, 18, 31, 45, 00, time.UTC) 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() t = time.Now()
} }
buffer.WriteString(t.Format(node.format)) writer.WriteString(t.Format(node.format))
return nil return nil
} }
func tagNowParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagNowParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
now_node := &tagNowNode{ nowNode := &tagNowNode{
position: start, position: start,
} }
format_token := arguments.MatchType(TokenString) formatToken := arguments.MatchType(TokenString)
if format_token == nil { if formatToken == nil {
return nil, arguments.Error("Expected a format string.", 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 { if arguments.MatchOne(TokenIdentifier, "fake") != nil {
now_node.fake = true nowNode.fake = true
} }
if arguments.Remaining() > 0 { if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed now-tag arguments.", nil) return nil, arguments.Error("Malformed now-tag arguments.", nil)
} }
return now_node, nil return nowNode, nil
} }
func init() { func init() {

View file

@ -1,13 +1,11 @@
package pongo2 package pongo2
import "bytes"
type tagSetNode struct { type tagSetNode struct {
name string name string
expression IEvaluator expression IEvaluator
} }
func (node *tagSetNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (node *tagSetNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
// Evaluate expression // Evaluate expression
value, err := node.expression.Evaluate(ctx) value, err := node.expression.Evaluate(ctx)
if err != nil { if err != nil {

View file

@ -11,7 +11,7 @@ type tagSpacelessNode struct {
var tagSpacelessRegexp = regexp.MustCompile(`(?U:(<.*>))([\t\n\v\f\r ]+)(?U:(<.*>))`) 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 b := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB
err := node.wrapper.Execute(ctx, b) err := node.wrapper.Execute(ctx, b)
@ -28,25 +28,25 @@ func (node *tagSpacelessNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffe
s = s2 s = s2
} }
buffer.WriteString(s) writer.WriteString(s)
return nil return nil
} }
func tagSpacelessParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagSpacelessParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
spaceless_node := &tagSpacelessNode{} spacelessNode := &tagSpacelessNode{}
wrapper, _, err := doc.WrapUntilTag("endspaceless") wrapper, _, err := doc.WrapUntilTag("endspaceless")
if err != nil { if err != nil {
return nil, err return nil, err
} }
spaceless_node.wrapper = wrapper spacelessNode.wrapper = wrapper
if arguments.Remaining() > 0 { if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed spaceless-tag arguments.", nil) return nil, arguments.Error("Malformed spaceless-tag arguments.", nil)
} }
return spaceless_node, nil return spacelessNode, nil
} }
func init() { func init() {

View file

@ -1,7 +1,6 @@
package pongo2 package pongo2
import ( import (
"bytes"
"io/ioutil" "io/ioutil"
) )
@ -11,47 +10,47 @@ type tagSSINode struct {
template *Template 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 { if node.template != nil {
// Execute the template within the current context // Execute the template within the current context
includeCtx := make(Context) includeCtx := make(Context)
includeCtx.Update(ctx.Public) includeCtx.Update(ctx.Public)
includeCtx.Update(ctx.Private) includeCtx.Update(ctx.Private)
err := node.template.ExecuteWriter(includeCtx, buffer) err := node.template.execute(includeCtx, writer)
if err != nil { if err != nil {
return err.(*Error) return err.(*Error)
} }
} else { } else {
// Just print out the content // Just print out the content
buffer.WriteString(node.content) writer.WriteString(node.content)
} }
return nil return nil
} }
func tagSSIParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagSSIParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ssi_node := &tagSSINode{} SSINode := &tagSSINode{}
if file_token := arguments.MatchType(TokenString); file_token != nil { if fileToken := arguments.MatchType(TokenString); fileToken != nil {
ssi_node.filename = file_token.Val SSINode.filename = fileToken.Val
if arguments.Match(TokenIdentifier, "parsed") != nil { if arguments.Match(TokenIdentifier, "parsed") != nil {
// parsed // 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 { 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 { } else {
// plaintext // 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 { if err != nil {
return nil, (&Error{ return nil, (&Error{
Sender: "tag:ssi", Sender: "tag:ssi",
ErrorMsg: err.Error(), OrigError: err,
}).updateFromTokenIfNeeded(doc.template, file_token) }).updateFromTokenIfNeeded(doc.template, fileToken)
} }
ssi_node.content = string(buf) SSINode.content = string(buf)
} }
} else { } else {
return nil, arguments.Error("First argument must be a string.", nil) 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 nil, arguments.Error("Malformed SSI-tag argument.", nil)
} }
return ssi_node, nil return SSINode, nil
} }
func init() { func init() {

View file

@ -1,9 +1,5 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagTemplateTagNode struct { type tagTemplateTagNode struct {
content string content string
} }
@ -19,20 +15,20 @@ var templateTagMapping = map[string]string{
"closecomment": "#}", "closecomment": "#}",
} }
func (node *tagTemplateTagNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (node *tagTemplateTagNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
buffer.WriteString(node.content) writer.WriteString(node.content)
return nil return nil
} }
func tagTemplateTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagTemplateTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
tt_node := &tagTemplateTagNode{} ttNode := &tagTemplateTagNode{}
if arg_token := arguments.MatchType(TokenIdentifier); arg_token != nil { if argToken := arguments.MatchType(TokenIdentifier); argToken != nil {
output, found := templateTagMapping[arg_token.Val] output, found := templateTagMapping[argToken.Val]
if !found { 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 { } else {
return nil, arguments.Error("Identifier expected.", nil) 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 nil, arguments.Error("Malformed templatetag-tag argument.", nil)
} }
return tt_node, nil return ttNode, nil
} }
func init() { func init() {

View file

@ -1,7 +1,6 @@
package pongo2 package pongo2
import ( import (
"bytes"
"fmt" "fmt"
"math" "math"
) )
@ -10,10 +9,10 @@ type tagWidthratioNode struct {
position *Token position *Token
current, max IEvaluator current, max IEvaluator
width 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) current, err := node.current.Evaluate(ctx)
if err != nil { if err != nil {
return err 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)) value := int(math.Ceil(current.Float()/max.Float()*width.Float() + 0.5))
if node.ctx_name == "" { if node.ctxName == "" {
buffer.WriteString(fmt.Sprintf("%d", value)) writer.WriteString(fmt.Sprintf("%d", value))
} else { } else {
ctx.Private[node.ctx_name] = value ctx.Private[node.ctxName] = value
} }
return nil return nil
} }
func tagWidthratioParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { func tagWidthratioParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
widthratio_node := &tagWidthratioNode{ widthratioNode := &tagWidthratioNode{
position: start, position: start,
} }
@ -49,34 +48,34 @@ func tagWidthratioParser(doc *Parser, start *Token, arguments *Parser) (INodeTag
if err != nil { if err != nil {
return nil, err return nil, err
} }
widthratio_node.current = current widthratioNode.current = current
max, err := arguments.ParseExpression() max, err := arguments.ParseExpression()
if err != nil { if err != nil {
return nil, err return nil, err
} }
widthratio_node.max = max widthratioNode.max = max
width, err := arguments.ParseExpression() width, err := arguments.ParseExpression()
if err != nil { if err != nil {
return nil, err return nil, err
} }
widthratio_node.width = width widthratioNode.width = width
if arguments.MatchOne(TokenKeyword, "as") != nil { if arguments.MatchOne(TokenKeyword, "as") != nil {
// Name follows // Name follows
name_token := arguments.MatchType(TokenIdentifier) nameToken := arguments.MatchType(TokenIdentifier)
if name_token == nil { if nameToken == nil {
return nil, arguments.Error("Expected name (identifier).", nil) return nil, arguments.Error("Expected name (identifier).", nil)
} }
widthratio_node.ctx_name = name_token.Val widthratioNode.ctxName = nameToken.Val
} }
if arguments.Remaining() > 0 { if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed widthratio-tag arguments.", nil) return nil, arguments.Error("Malformed widthratio-tag arguments.", nil)
} }
return widthratio_node, nil return widthratioNode, nil
} }
func init() { func init() {

View file

@ -1,20 +1,16 @@
package pongo2 package pongo2
import (
"bytes"
)
type tagWithNode struct { type tagWithNode struct {
with_pairs map[string]IEvaluator withPairs map[string]IEvaluator
wrapper *NodeWrapper 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 //new context for block
withctx := NewChildExecutionContext(ctx) withctx := NewChildExecutionContext(ctx)
// Put all custom with-pairs into the context // 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) val, err := value.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
@ -22,12 +18,12 @@ func (node *tagWithNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *E
withctx.Private[key] = val 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) { func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
with_node := &tagWithNode{ withNode := &tagWithNode{
with_pairs: make(map[string]IEvaluator), withPairs: make(map[string]IEvaluator),
} }
if arguments.Count() == 0 { if arguments.Count() == 0 {
@ -38,7 +34,7 @@ func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Err
if err != nil { if err != nil {
return nil, err return nil, err
} }
with_node.wrapper = wrapper withNode.wrapper = wrapper
if endargs.Count() > 0 { if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil) 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). // 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. // 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++ { for i := 0; i < arguments.Count(); i++ {
if arguments.PeekN(i, TokenKeyword, "as") != nil { if arguments.PeekN(i, TokenKeyword, "as") != nil {
old_style = true oldStyle = true
break break
} }
} }
for arguments.Remaining() > 0 { for arguments.Remaining() > 0 {
if old_style { if oldStyle {
value_expr, err := arguments.ParseExpression() valueExpr, err := arguments.ParseExpression()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if arguments.Match(TokenKeyword, "as") == nil { if arguments.Match(TokenKeyword, "as") == nil {
return nil, arguments.Error("Expected 'as' keyword.", nil) return nil, arguments.Error("Expected 'as' keyword.", nil)
} }
key_token := arguments.MatchType(TokenIdentifier) keyToken := arguments.MatchType(TokenIdentifier)
if key_token == nil { if keyToken == nil {
return nil, arguments.Error("Expected an identifier", nil) return nil, arguments.Error("Expected an identifier", nil)
} }
with_node.with_pairs[key_token.Val] = value_expr withNode.withPairs[keyToken.Val] = valueExpr
} else { } else {
key_token := arguments.MatchType(TokenIdentifier) keyToken := arguments.MatchType(TokenIdentifier)
if key_token == nil { if keyToken == nil {
return nil, arguments.Error("Expected an identifier", nil) return nil, arguments.Error("Expected an identifier", nil)
} }
if arguments.Match(TokenSymbol, "=") == nil { if arguments.Match(TokenSymbol, "=") == nil {
return nil, arguments.Error("Expected '='.", nil) return nil, arguments.Error("Expected '='.", nil)
} }
value_expr, err := arguments.ParseExpression() valueExpr, err := arguments.ParseExpression()
if err != nil { if err != nil {
return nil, err 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() { func init() {

View file

@ -2,15 +2,33 @@ package pongo2
import ( import (
"bytes" "bytes"
"fmt"
"io" "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 { type Template struct {
set *TemplateSet set *TemplateSet
// Input // Input
is_tpl_string bool isTplString bool
name string name string
tpl string tpl string
size int size int
@ -24,30 +42,32 @@ type Template struct {
parent *Template parent *Template
child *Template child *Template
blocks map[string]*NodeWrapper blocks map[string]*NodeWrapper
exported_macros map[string]*tagMacroNode exportedMacros map[string]*tagMacroNode
// Output // Output
root *nodeDocument root *nodeDocument
} }
func newTemplateString(set *TemplateSet, tpl string) (*Template, error) { func newTemplateString(set *TemplateSet, tpl []byte) (*Template, error) {
return newTemplate(set, "<string>", true, tpl) return newTemplate(set, "<string>", 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 // Create the template
t := &Template{ t := &Template{
set: set, set: set,
is_tpl_string: is_tpl_string, isTplString: isTplString,
name: name, name: name,
tpl: tpl, tpl: strTpl,
size: len(tpl), size: len(strTpl),
blocks: make(map[string]*NodeWrapper), blocks: make(map[string]*NodeWrapper),
exported_macros: make(map[string]*tagMacroNode), exportedMacros: make(map[string]*tagMacroNode),
} }
// Tokenize it // Tokenize it
tokens, err := lex(name, tpl) tokens, err := lex(name, strTpl)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -67,11 +87,7 @@ func newTemplate(set *TemplateSet, name string, is_tpl_string bool, tpl string)
return t, nil return t, nil
} }
func (tpl *Template) execute(context Context) (*bytes.Buffer, error) { func (tpl *Template) execute(context Context, writer TemplateWriter) 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)))
// Determine the parent to be executed (for template inheritance) // Determine the parent to be executed (for template inheritance)
parent := tpl parent := tpl
for parent.parent != nil { for parent.parent != nil {
@ -89,17 +105,17 @@ func (tpl *Template) execute(context Context) (*bytes.Buffer, error) {
// Check for context name syntax // Check for context name syntax
err := newContext.checkForValidIdentifiers() err := newContext.checkForValidIdentifiers()
if err != nil { if err != nil {
return nil, err return err
} }
// Check for clashes with macro names // Check for clashes with macro names
for k, _ := range newContext { for k := range newContext {
_, has := tpl.exported_macros[k] _, has := tpl.exportedMacros[k]
if has { if has {
return nil, &Error{ return &Error{
Filename: tpl.name, Filename: tpl.name,
Sender: "execution", Sender: "execution",
ErrorMsg: fmt.Sprintf("Context key name '%s' clashes with macro '%s'.", k, k), 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) ctx := newExecutionContext(parent, newContext)
// Run the selected document // Run the selected document
err := parent.root.Execute(ctx, buffer) if err := parent.root.Execute(ctx, writer); err != nil {
if 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 nil, err
} }
return buffer, nil 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 // on success. Context can be nil. Nothing is written on error; instead the error
// is being returned. // is being returned.
func (tpl *Template) ExecuteWriter(context Context, writer io.Writer) error { func (tpl *Template) ExecuteWriter(context Context, writer io.Writer) error {
buffer, err := tpl.execute(context) buf, err := tpl.newBufferAndExecute(context)
if err != nil { if err != nil {
return err return err
} }
_, err = buf.WriteTo(writer)
l := buffer.Len() if err != nil {
n, werr := buffer.WriteTo(writer) return err
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(),
}
} }
return nil 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 // Executes the template and returns the rendered template as a []byte
func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) { func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) {
// Execute template // Execute template
buffer, err := tpl.execute(context) buffer, err := tpl.newBufferAndExecute(context)
if err != nil { if err != nil {
return nil, err 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 // Executes the template and returns the rendered template as a string
func (tpl *Template) Execute(context Context) (string, error) { func (tpl *Template) Execute(context Context) (string, error) {
// Execute template // Execute template
buffer, err := tpl.execute(context) buffer, err := tpl.newBufferAndExecute(context)
if err != nil { if err != nil {
return "", err return "", err
} }

157
vendor/github.com/flosch/pongo2/template_loader.go generated vendored Normal file
View file

@ -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 = ""
}()
}
*/

View file

@ -2,48 +2,49 @@ package pongo2
import ( import (
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"path/filepath"
"sync" "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 // TemplateLoader allows to implement a virtual file system.
// among all members of the set), their own configuration (like a specific base directory) and their own sandbox. type TemplateLoader interface {
// It's useful for a separation of different kind of templates (e. g. web templates vs. mail templates). // 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 { type TemplateSet struct {
name string name string
loader TemplateLoader
// Globals will be provided to all templates created within this template set // Globals will be provided to all templates created within this template set
Globals Context Globals Context
// If debug is true (default false), ExecutionContext.Logf() will work and output to STDOUT. Furthermore, // If debug is true (default false), ExecutionContext.Logf() will work and output
// FromCache() won't cache the templates. Make sure to synchronize the access to it in case you're changing this // 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). // variable during program execution (and template compilation/execution).
Debug bool 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 // Sandbox features
// - Limit access to directories (using SandboxDirectories)
// - Disallow access to specific tags and/or filters (using BanTag() and BanFilter()) // - 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) // For efficiency reasons you can ban tags/filters only *before* you have
// to these sandbox directories. All default pongo2 filters/tags are respecting these restrictions. // added your first template to the set (restrictions are statically checked).
// For example, if you only have your base directory in the list, a {% ssi "/etc/passwd" %} will not work. // After you added one, it's not possible anymore (for your personal security).
// 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
firstTemplateCreated bool firstTemplateCreated bool
bannedTags map[string]bool bannedTags map[string]bool
bannedFilters map[string]bool bannedFilters map[string]bool
@ -53,11 +54,13 @@ type TemplateSet struct {
templateCacheMutex sync.Mutex templateCacheMutex sync.Mutex
} }
// Create your own template sets to separate different kind of templates (e. g. web from mail templates) with // NewSet can be used to create sets with different kind of templates
// different globals or other configurations (like base directories). // (e. g. web from mail templates), with different globals or
func NewSet(name string) *TemplateSet { // other configurations.
func NewSet(name string, loader TemplateLoader) *TemplateSet {
return &TemplateSet{ return &TemplateSet{
name: name, name: name,
loader: loader,
Globals: make(Context), Globals: make(Context),
bannedTags: make(map[string]bool), bannedTags: make(map[string]bool),
bannedFilters: 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 func (set *TemplateSet) resolveFilename(tpl *Template, path string) string {
// path in filters, tags and From*-functions to determine your template. name := ""
func (set *TemplateSet) SetBaseDirectory(name string) error { if tpl != nil && tpl.isTplString {
// Make the path absolute return path
if !filepath.IsAbs(name) {
abs, err := filepath.Abs(name)
if err != nil {
return err
} }
name = abs if tpl != nil {
name = tpl.name
} }
return set.loader.Abs(name, path)
// Check for existence
fi, err := os.Stat(name)
if err != nil {
return err
}
if !fi.IsDir() {
return fmt.Errorf("The given path '%s' is not a directory.")
}
set.baseDirectory = name
return nil
} }
func (set *TemplateSet) BaseDirectory() string { // BanTag bans a specific tag for this template set. See more in the documentation for TemplateSet.
return set.baseDirectory func (set *TemplateSet) BanTag(name string) error {
}
// Ban a specific tag for this template set. See more in the documentation for TemplateSet.
func (set *TemplateSet) BanTag(name string) {
_, has := tags[name] _, has := tags[name]
if !has { if !has {
panic(fmt.Sprintf("Tag '%s' not found.", name)) return errors.Errorf("tag '%s' not found", name)
} }
if set.firstTemplateCreated { 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] _, has = set.bannedTags[name]
if has { if has {
panic(fmt.Sprintf("Tag '%s' is already banned.", name)) return errors.Errorf("tag '%s' is already banned", name)
} }
set.bannedTags[name] = true set.bannedTags[name] = true
return nil
} }
// Ban a specific filter for this template set. See more in the documentation for TemplateSet. // BanFilter bans a specific filter for this template set. See more in the documentation for TemplateSet.
func (set *TemplateSet) BanFilter(name string) { func (set *TemplateSet) BanFilter(name string) error {
_, has := filters[name] _, has := filters[name]
if !has { if !has {
panic(fmt.Sprintf("Filter '%s' not found.", name)) return errors.Errorf("filter '%s' not found", name)
} }
if set.firstTemplateCreated { 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] _, has = set.bannedFilters[name]
if has { if has {
panic(fmt.Sprintf("Filter '%s' is already banned.", name)) return errors.Errorf("filter '%s' is already banned", name)
} }
set.bannedFilters[name] = true 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. // and will only compile the template associated with a filename once.
// If TemplateSet.Debug is true (for example during development phase), // If TemplateSet.Debug is true (for example during development phase),
// FromCache() will not cache the template and instead recompile it on any // FromCache() will not cache the template and instead recompile it on any
// call (to make changes to a template live instantaneously). // 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) { func (set *TemplateSet) FromCache(filename string) (*Template, error) {
if set.Debug { if set.Debug {
// Recompile on any request // Recompile on any request
return set.FromFile(filename) return set.FromFile(filename)
} else { }
// Cache the template // Cache the template
cleaned_filename := set.resolveFilename(nil, filename) cleanedFilename := set.resolveFilename(nil, filename)
set.templateCacheMutex.Lock() set.templateCacheMutex.Lock()
defer set.templateCacheMutex.Unlock() defer set.templateCacheMutex.Unlock()
tpl, has := set.templateCache[cleaned_filename] tpl, has := set.templateCache[cleanedFilename]
// Cache miss // Cache miss
if !has { if !has {
tpl, err := set.FromFile(cleaned_filename) tpl, err := set.FromFile(cleanedFilename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
set.templateCache[cleaned_filename] = tpl set.templateCache[cleanedFilename] = tpl
return tpl, nil return tpl, nil
} }
// Cache hit // Cache hit
return tpl, nil 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) { func (set *TemplateSet) FromString(tpl string) (*Template, error) {
set.firstTemplateCreated = true 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) return newTemplateString(set, tpl)
} }
// Loads a template from a filename and returns a Template instance. // FromFile 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.
func (set *TemplateSet) FromFile(filename string) (*Template, error) { func (set *TemplateSet) FromFile(filename string) (*Template, error) {
set.firstTemplateCreated = true set.firstTemplateCreated = true
buf, err := ioutil.ReadFile(set.resolveFilename(nil, filename)) fd, err := set.loader.Get(set.resolveFilename(nil, filename))
if err != nil { if err != nil {
return nil, &Error{ return nil, &Error{
Filename: filename, Filename: filename,
Sender: "fromfile", Sender: "fromfile",
ErrorMsg: err.Error(), 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 // RenderTemplateString is a shortcut and renders a template string directly.
// malformed template or an error occurs during execution. func (set *TemplateSet) RenderTemplateString(s string, ctx Context) (string, error) {
func (set *TemplateSet) RenderTemplateString(s string, ctx Context) string {
set.firstTemplateCreated = true set.firstTemplateCreated = true
tpl := Must(set.FromString(s)) tpl := Must(set.FromString(s))
result, err := tpl.Execute(ctx) result, err := tpl.Execute(ctx)
if err != nil { if err != nil {
panic(err) return "", err
} }
return result return result, nil
} }
// Shortcut; renders a template file directly. Panics when providing a // RenderTemplateBytes is a shortcut and renders template bytes directly.
// malformed template or an error occurs during execution. func (set *TemplateSet) RenderTemplateBytes(b []byte, ctx Context) (string, error) {
func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) string { 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 set.firstTemplateCreated = true
tpl := Must(set.FromFile(fn)) tpl := Must(set.FromFile(fn))
result, err := tpl.Execute(ctx) result, err := tpl.Execute(ctx)
if err != nil { if err != nil {
panic(err) return "", err
} }
return result return result, nil
} }
func (set *TemplateSet) logf(format string, args ...interface{}) { 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) // Logging function (internally used)
func logf(format string, items ...interface{}) { func logf(format string, items ...interface{}) {
if debug { if debug {
@ -279,13 +236,18 @@ func logf(format string, items ...interface{}) {
var ( var (
debug bool // internal debugging 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 // DefaultLoader allows the default un-sandboxed access to the local file
DefaultSet = NewSet("default") // 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 // Methods on the default set
FromString = DefaultSet.FromString FromString = DefaultSet.FromString
FromBytes = DefaultSet.FromBytes
FromFile = DefaultSet.FromFile FromFile = DefaultSet.FromFile
FromCache = DefaultSet.FromCache FromCache = DefaultSet.FromCache
RenderTemplateString = DefaultSet.RenderTemplateString RenderTemplateString = DefaultSet.RenderTemplateString

View file

@ -0,0 +1,50 @@
empty single line comment
{# #}
filled single line comment
{# testing single line comment #}
filled single line comment with valid tags
{# testing single line comment {% if thing %}{% endif %} #}
filled single line comment with invalid tags
{# testing single line comment {% if thing %} #}
filled single line comment with invalid syntax
{# testing single line comment {% if thing('') %}wow{% endif %} #}
empty block comment
{% comment %}{% endcomment %}
filled text single line block comment
{% comment %}filled block comment {% endcomment %}
empty multi line block comment
{% comment %}
{% endcomment %}
block comment with other tags inside of it
{% comment %}
{{ thing_goes_here }}
{% if stuff %}do stuff{% endif %}
{% endcomment %}
block comment with invalid tags inside of it
{% comment %}
{% if thing %}
{% endcomment %}
block comment with invalid syntax inside of it
{% comment %}
{% thing('') %}
{% endcomment %}
Regular tags between comments to verify it doesn't break in the lexer
{% if hello %}
{% endif %}
after if
{% comment %}All done{% endcomment %}
end of file

View file

@ -0,0 +1,39 @@
empty single line comment
filled single line comment
filled single line comment with valid tags
filled single line comment with invalid tags
filled single line comment with invalid syntax
empty block comment
filled text single line block comment
empty multi line block comment
block comment with other tags inside of it
block comment with invalid tags inside of it
block comment with invalid syntax inside of it
Regular tags between comments to verify it doesn't break in the lexer
after if
end of file

View file

@ -46,8 +46,24 @@ in/not in
{{ 7 in simple.intmap }} {{ 7 in simple.intmap }}
{{ !(5 in simple.intmap) }} {{ !(5 in simple.intmap) }}
{{ not(7 in simple.intmap) }} {{ not(7 in simple.intmap) }}
{{ 1 in simple.multiple_item_list }}
{{ 4 in simple.multiple_item_list }}
{{ !(4 in simple.multiple_item_list) }}
{{ "Hello" in simple.misc_list }}
{{ "Hello2" in simple.misc_list }}
{{ 99 in simple.misc_list }}
{{ False in simple.misc_list }}
issue #48 (associativity for infix operators) issue #48 (associativity for infix operators)
{{ 34/3*3 }} {{ 34/3*3 }}
{{ 10 + 24 / 6 / 2 }} {{ 10 + 24 / 6 / 2 }}
{{ 6 - 4 - 2 }} {{ 6 - 4 - 2 }}
issue #64 (uint comparison with int const)
{{ simple.uint }}
{{ simple.uint == 8 }}
{{ simple.uint == 9 }}
{{ simple.uint >= 8 }}
{{ simple.uint <= 8 }}
{{ simple.uint < 8 }}
{{ simple.uint > 8 }}

View file

@ -46,8 +46,24 @@ True
False False
False False
True True
True
False
True
True
False
True
False
issue #48 (associativity for infix operators) issue #48 (associativity for infix operators)
33 33
12 12
0 0
issue #64 (uint comparison with int const)
8
True
False
True
True
False
False

View file

@ -0,0 +1,3 @@
{% extends "inheritance/base.tpl" %}
{% block content %}{{ block.Super }}extends-level-1{% endblock %}

View file

@ -0,0 +1 @@
Start#This is base's bodyDefault contentextends-level-1#End

View file

@ -0,0 +1,3 @@
{% extends "extends_super.tpl" %}
{% block content %}{{ block.Super }}extends-level-2{% endblock %}

View file

@ -0,0 +1 @@
Start#This is base's bodyDefault contentextends-level-1extends-level-2#End

View file

@ -1,3 +1,4 @@
{{ -(true || false) }} {{ -(true || false) }}
{{ simple.func_add("test", 5) }} {{ simple.func_add("test", 5) }}
{% for item in simple.multiple_item_list %} {{ simple.func_add("test", 5) }} {% endfor %}
{{ simple.func_variadic_sum_int("foo") }} {{ simple.func_variadic_sum_int("foo") }}

View file

@ -1,3 +1,4 @@
.*where: execution.*Negative sign on a non\-number expression .*where: execution.*Negative sign on a non\-number expression
.*Function input argument 0 of 'simple.func_add' must be of type int or \*pongo2.Value \(not string\). .*Function input argument 0 of 'simple.func_add' must be of type int or \*pongo2.Value \(not string\).
.*Function input argument 0 of 'simple.func_add' must be of type int or \*pongo2.Value \(not string\).
.*Function variadic input argument of 'simple.func_variadic_sum_int' must be of type int or \*pongo2.Value \(not string\). .*Function variadic input argument of 'simple.func_variadic_sum_int' must be of type int or \*pongo2.Value \(not string\).

View file

@ -176,6 +176,9 @@ floatformat
join join
{{ simple.misc_list|join:", " }} {{ simple.misc_list|join:", " }}
split
{{ "Hello, 99, 3.140000, good"|split:", "|join:", " }}
stringformat stringformat
{{ simple.float|stringformat:"%.2f" }} {{ simple.float|stringformat:"%.2f" }}
{{ simple.uint|stringformat:"Test: %d" }} {{ simple.uint|stringformat:"Test: %d" }}

View file

@ -105,7 +105,7 @@ h
first first
T T
<pongo2.comment Value> <pongo2_test.comment Value>
@ -113,7 +113,7 @@ T
last last
t t
<pongo2.comment Value> <pongo2_test.comment Value>
@ -176,6 +176,9 @@ floatformat
join join
Hello, 99, 3.140000, good Hello, 99, 3.140000, good
split
Hello, 99, 3.140000, good
stringformat stringformat
3.14 3.14
Test: 8 Test: 8

View file

@ -7,3 +7,21 @@
reversed reversed
'{% for item in simple.multiple_item_list reversed %}{{ item }} {% endfor %}' '{% for item in simple.multiple_item_list reversed %}{{ item }} {% endfor %}'
sorted string map
'{% for key in simple.strmap sorted %}{{ key }} {% endfor %}'
sorted int map
'{% for key in simple.intmap sorted %}{{ key }} {% endfor %}'
sorted int list
'{% for key in simple.unsorted_int_list sorted %}{{ key }} {% endfor %}'
reversed sorted int list
'{% for key in simple.unsorted_int_list reversed sorted %}{{ key }} {% endfor %}'
reversed sorted string map
'{% for key in simple.strmap reversed sorted %}{{ key }} {% endfor %}'
reversed sorted int map
'{% for key in simple.intmap reversed sorted %}{{ key }} {% endfor %}'

View file

@ -17,3 +17,21 @@
reversed reversed
'55 34 21 13 8 5 3 2 1 1 ' '55 34 21 13 8 5 3 2 1 1 '
sorted string map
'aab abc bcd gh ukq zab '
sorted int map
'1 2 5 '
sorted int list
'1 22 192 249 581 8271 9999 1828591 '
reversed sorted int list
'1828591 9999 8271 581 249 192 22 1 '
reversed sorted string map
'zab ukq gh bcd abc aab '
reversed sorted int map
'5 2 1 '

View file

@ -9,6 +9,7 @@
{% if 5 in simple.intmap %}5 in simple.intmap{% endif %} {% if 5 in simple.intmap %}5 in simple.intmap{% endif %}
{% if !0.0 %}!0.0{% endif %} {% if !0.0 %}!0.0{% endif %}
{% if !0 %}!0{% endif %} {% if !0 %}!0{% endif %}
{% if not complex.post %}true{% else %}false{% endif %}
{% if simple.number == 43 %}no{% else %}42{% endif %} {% if simple.number == 43 %}no{% else %}42{% endif %}
{% if simple.number < 42 %}false{% elif simple.number > 42 %}no{% elif simple.number >= 42 %}yes{% else %}no{% endif %} {% if simple.number < 42 %}false{% elif simple.number > 42 %}no{% elif simple.number >= 42 %}yes{% else %}no{% endif %}
{% if simple.number < 42 %}false{% elif simple.number > 42 %}no{% elif simple.number != 42 %}no{% else %}yes{% endif %} {% if simple.number < 42 %}false{% elif simple.number > 42 %}no{% elif simple.number != 42 %}no{% else %}yes{% endif %}

View file

@ -9,6 +9,7 @@ text field in complex.post
5 in simple.intmap 5 in simple.intmap
!0.0 !0.0
!0 !0
false
42 42
yes yes
yes yes

View file

@ -1,4 +1,7 @@
Start '{% include "includes.helper" %}' End Start '{% include "includes.helper" %}' End
Start '{% include "includes.helper" if_exists %}' End
Start '{% include "includes.helper" with what_am_i=simple.name only %}' End Start '{% include "includes.helper" with what_am_i=simple.name only %}' End
Start '{% include "includes.helper" with what_am_i=simple.name %}' End Start '{% include "includes.helper" with what_am_i=simple.name %}' End
Start '{% include simple.included_file|lower with number=7 what_am_i="guest" %}' End Start '{% include simple.included_file|lower with number=7 what_am_i="guest" %}' End
Start '{% include "includes.helper.not_exists" if_exists %}' End
Start '{% include simple.included_file_not_exists if_exists with number=7 what_am_i="guest" %}' End

View file

@ -1,4 +1,7 @@
Start 'I'm 11' End Start 'I'm 11' End
Start 'I'm 11' End
Start 'I'm john doe' End Start 'I'm john doe' End
Start 'I'm john doe11' End Start 'I'm john doe11' End
Start 'I'm guest7' End Start 'I'm guest7' End
Start '' End
Start '' End

View file

@ -1 +1 @@
.*Another macro with name 'test_override' already exported. .*another macro with name 'test_override' already exported

View file

@ -1 +1 @@
.*Context key name 'number' clashes with macro 'number'. .*context key name 'number' clashes with macro 'number'

View file

@ -1 +1 @@
v3 dev

View file

@ -0,0 +1,15 @@
Variables
{{ "hello" }}
{{ 'hello' }}
{{ "hell'o" }}
Filters
{{ 'Test'|slice:'1:3' }}
{{ '<div class=\"foo\"><ul class=\"foo\"><li class=\"foo\"><p class=\"foo\">This is a long test which will be cutted after some chars.</p></li></ul></div>'|truncatechars_html:25 }}
{{ '<a name="link"><p>This </a>is a long test which will be cutted after some chars.</p>'|truncatechars_html:25 }}
Tags
{% if 'Text' in complex.post %}text field in complex.post{% endif %}
Functions
{{ simple.func_variadic('hello') }}

View file

@ -0,0 +1,15 @@
Variables
hello
hello
hell&#39;o
Filters
es
<div class="foo"><ul class="foo"><li class="foo"><p class="foo">This is a long test wh...</p></li></ul></div>
<a name="link"><p>This </a>is a long test wh...</p>
Tags
text field in complex.post
Functions
hello

View file

@ -3,3 +3,4 @@
{{ new_var }}{% for item in simple.misc_list %} {{ new_var }}{% for item in simple.misc_list %}
{% set new_var = item %}{{ new_var }}{% endfor %} {% set new_var = item %}{{ new_var }}{% endfor %}
{{ new_var }} {{ new_var }}
{% set car=someUndefinedVar %}{{ car.Drive }}No Panic

View file

@ -6,3 +6,4 @@ Hello
3.140000 3.140000
good good
world world
No Panic

View file

@ -11,3 +11,5 @@
{{ simple.uint }} {{ simple.uint }}
{{ simple.uint|integer }} {{ simple.uint|integer }}
{{ simple.uint|float }} {{ simple.uint|float }}
{{ simple.multiple_item_list.10 }}
{{ simple.multiple_item_list.4 }}

View file

@ -11,3 +11,5 @@ True
8 8
8 8
8.000000 8.000000
5

View file

@ -11,6 +11,6 @@ Start 'I'm guest7' End
Start 'Hi number 10! Will not be overridden inside the block. I'm john doe, 50 years old.I have 10 children.' End Start 'Hi number 10! Will not be overridden inside the block. I'm john doe, 50 years old.I have 10 children.' End
more with tests more with tests
<pongo2.user Value> <pongo2_test.user Value>
user1 user1
user3 user3

View file

@ -3,6 +3,7 @@ package pongo2
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"sort"
"strconv" "strconv"
"strings" "strings"
) )
@ -12,7 +13,7 @@ type Value struct {
safe bool // used to indicate whether a Value needs explicit escaping in the template 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 // Usually being used within own functions passed to a template
// through a Context or within filter functions. // 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 { func AsSafeValue(i interface{}) *Value {
return &Value{ return &Value{
val: reflect.ValueOf(i), val: reflect.ValueOf(i),
@ -39,23 +40,23 @@ func (v *Value) getResolvedValue() reflect.Value {
return v.val 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 { func (v *Value) IsString() bool {
return v.getResolvedValue().Kind() == reflect.String 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 { func (v *Value) IsBool() bool {
return v.getResolvedValue().Kind() == reflect.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 { func (v *Value) IsFloat() bool {
return v.getResolvedValue().Kind() == reflect.Float32 || return v.getResolvedValue().Kind() == reflect.Float32 ||
v.getResolvedValue().Kind() == reflect.Float64 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 { func (v *Value) IsInteger() bool {
return v.getResolvedValue().Kind() == reflect.Int || return v.getResolvedValue().Kind() == reflect.Int ||
v.getResolvedValue().Kind() == reflect.Int8 || v.getResolvedValue().Kind() == reflect.Int8 ||
@ -69,19 +70,19 @@ func (v *Value) IsInteger() bool {
v.getResolvedValue().Kind() == reflect.Uint64 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. // or a float.
func (v *Value) IsNumber() bool { func (v *Value) IsNumber() bool {
return v.IsInteger() || v.IsFloat() 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 { func (v *Value) IsNil() bool {
//fmt.Printf("%+v\n", v.getResolvedValue().Type().String()) //fmt.Printf("%+v\n", v.getResolvedValue().Type().String())
return !v.getResolvedValue().IsValid() 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 // of type string, pongo2 tries to convert it. Currently the following
// types for underlying values are supported: // types for underlying values are supported:
// //
@ -111,9 +112,8 @@ func (v *Value) String() string {
case reflect.Bool: case reflect.Bool:
if v.Bool() { if v.Bool() {
return "True" return "True"
} else {
return "False"
} }
return "False"
case reflect.Struct: case reflect.Struct:
if t, ok := v.Interface().(fmt.Stringer); ok { if t, ok := v.Interface().(fmt.Stringer); ok {
return t.String() return t.String()
@ -124,7 +124,7 @@ func (v *Value) String() string {
return v.getResolvedValue().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, // value, if necessary). If it's not possible to convert the underlying value,
// it will return 0. // it will return 0.
func (v *Value) Integer() int { 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, // value, if necessary). If it's not possible to convert the underlying value,
// it will return 0.0. // it will return 0.0.
func (v *Value) Float() float64 { 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 // will always be returned. If you're looking for true/false-evaluation of the
// underlying value, have a look on the IsTrue()-function. // underlying value, have a look on the IsTrue()-function.
func (v *Value) Bool() bool { 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: // 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 // the NOT-operator and in conjunction with a call to
// return_value.IsTrue() afterwards. // return_value.IsTrue() afterwards.
// //
@ -229,26 +229,26 @@ func (v *Value) Negate() *Value {
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if v.Integer() != 0 { if v.Integer() != 0 {
return AsValue(0) return AsValue(0)
} else {
return AsValue(1)
} }
return AsValue(1)
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
if v.Float() != 0.0 { if v.Float() != 0.0 {
return AsValue(float64(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: case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
return AsValue(v.getResolvedValue().Len() == 0) return AsValue(v.getResolvedValue().Len() == 0)
case reflect.Bool: case reflect.Bool:
return AsValue(!v.getResolvedValue().Bool()) return AsValue(!v.getResolvedValue().Bool())
case reflect.Struct:
return AsValue(false)
default: default:
logf("Value.IsTrue() not available for type: %s\n", v.getResolvedValue().Kind().String()) logf("Value.IsTrue() not available for type: %s\n", v.getResolvedValue().Kind().String())
return AsValue(true) 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. // Otherwise it will return 0.
func (v *Value) Len() int { func (v *Value) Len() int {
switch v.getResolvedValue().Kind() { 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. // return an empty []int.
func (v *Value) Slice(i, j int) *Value { func (v *Value) Slice(i, j int) *Value {
switch v.getResolvedValue().Kind() { 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. // it will return NIL.
func (v *Value) Index(i int) *Value { func (v *Value) Index(i int) *Value {
switch v.getResolvedValue().Kind() { 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 // 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). // 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 { func (v *Value) Contains(other *Value) bool {
switch v.getResolvedValue().Kind() { switch v.getResolvedValue().Kind() {
case reflect.Struct: case reflect.Struct:
field_value := v.getResolvedValue().FieldByName(other.String()) fieldValue := v.getResolvedValue().FieldByName(other.String())
return field_value.IsValid() return fieldValue.IsValid()
case reflect.Map: case reflect.Map:
var map_value reflect.Value var mapValue reflect.Value
switch other.Interface().(type) { switch other.Interface().(type) {
case int: case int:
map_value = v.getResolvedValue().MapIndex(other.getResolvedValue()) mapValue = v.getResolvedValue().MapIndex(other.getResolvedValue())
case string: case string:
map_value = v.getResolvedValue().MapIndex(other.getResolvedValue()) mapValue = v.getResolvedValue().MapIndex(other.getResolvedValue())
default: default:
logf("Value.Contains() does not support lookup type '%s'\n", other.getResolvedValue().Kind().String()) logf("Value.Contains() does not support lookup type '%s'\n", other.getResolvedValue().Kind().String())
return false return false
} }
return map_value.IsValid() return mapValue.IsValid()
case reflect.String: case reflect.String:
return strings.Contains(v.getResolvedValue().String(), other.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: default:
logf("Value.Contains() not available for type: %s\n", v.getResolvedValue().Kind().String()) 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. // You normally would use CanSlice() before using the Slice() operation.
func (v *Value) CanSlice() bool { func (v *Value) CanSlice() bool {
switch v.getResolvedValue().Kind() { switch v.getResolvedValue().Kind() {
@ -346,7 +353,7 @@ func (v *Value) CanSlice() bool {
return false 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: // function's first argument for every value with the following arguments:
// //
// idx current 0-index // 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, // 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. // 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()) { 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. // 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() { switch v.getResolvedValue().Kind() {
case reflect.Map: case reflect.Map:
// Reverse not needed for maps, since they are not ordered keys := sortedKeys(v.getResolvedValue().MapKeys())
keys := v.getResolvedValue().MapKeys() if sorted {
if reverse {
sort.Sort(sort.Reverse(keys))
} else {
sort.Sort(keys)
}
}
keyLen := len(keys) keyLen := len(keys)
for idx, key := range keys { for idx, key := range keys {
value := v.getResolvedValue().MapIndex(key) value := v.getResolvedValue().MapIndex(key)
@ -379,27 +393,44 @@ func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, em
} }
return // done return // done
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
var items valuesList
itemCount := v.getResolvedValue().Len() itemCount := v.getResolvedValue().Len()
if itemCount > 0 { for i := 0; i < itemCount; i++ {
if reverse { items = append(items, &Value{val: v.getResolvedValue().Index(i)})
for i := itemCount - 1; i >= 0; i-- {
if !fn(i, itemCount, &Value{val: v.getResolvedValue().Index(i)}, nil) {
return
} }
if sorted {
if reverse {
sort.Sort(sort.Reverse(items))
} else {
sort.Sort(items)
} }
} else { } else {
for i := 0; i < itemCount; i++ { if reverse {
if !fn(i, itemCount, &Value{val: v.getResolvedValue().Index(i)}, nil) { for i := 0; i < itemCount/2; i++ {
return 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 { } else {
empty() empty()
} }
return // done return // done
case reflect.String: 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() charCount := v.getResolvedValue().Len()
if charCount > 0 { if charCount > 0 {
if reverse { if reverse {
@ -425,7 +456,7 @@ func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, em
empty() empty()
} }
// Gives you access to the underlying value. // Interface gives you access to the underlying value.
func (v *Value) Interface() interface{} { func (v *Value) Interface() interface{} {
if v.val.IsValid() { if v.val.IsValid() {
return v.val.Interface() return v.val.Interface()
@ -433,7 +464,57 @@ func (v *Value) Interface() interface{} {
return nil 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 { 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() 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]
}

View file

@ -1,11 +1,12 @@
package pongo2 package pongo2
import ( import (
"bytes"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"github.com/juju/errors"
) )
const ( const (
@ -13,13 +14,18 @@ const (
varTypeIdent varTypeIdent
) )
var (
typeOfValuePtr = reflect.TypeOf(new(Value))
typeOfExecCtxPtr = reflect.TypeOf(new(ExecutionContext))
)
type variablePart struct { type variablePart struct {
typ int typ int
s string s string
i int i int
is_function_call bool isFunctionCall bool
calling_args []functionCallArgument // needed for a function call, represents all argument nodes (INode supports nested function calls) callingArgs []functionCallArgument // needed for a function call, represents all argument nodes (INode supports nested function calls)
} }
type functionCallArgument interface { type functionCallArgument interface {
@ -28,119 +34,121 @@ type functionCallArgument interface {
// TODO: Add location tokens // TODO: Add location tokens
type stringResolver struct { type stringResolver struct {
location_token *Token locationToken *Token
val string val string
} }
type intResolver struct { type intResolver struct {
location_token *Token locationToken *Token
val int val int
} }
type floatResolver struct { type floatResolver struct {
location_token *Token locationToken *Token
val float64 val float64
} }
type boolResolver struct { type boolResolver struct {
location_token *Token locationToken *Token
val bool val bool
} }
type variableResolver struct { type variableResolver struct {
location_token *Token locationToken *Token
parts []*variablePart parts []*variablePart
} }
type nodeFilteredVariable struct { type nodeFilteredVariable struct {
location_token *Token locationToken *Token
resolver IEvaluator resolver IEvaluator
filterChain []*filterCall filterChain []*filterCall
} }
type nodeVariable struct { type nodeVariable struct {
location_token *Token locationToken *Token
expr IEvaluator expr IEvaluator
} }
func (expr *nodeFilteredVariable) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { type executionCtxEval struct{}
value, err := expr.Evaluate(ctx)
func (v *nodeFilteredVariable) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := v.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
buffer.WriteString(value.String()) writer.WriteString(value.String())
return nil return nil
} }
func (expr *variableResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (vr *variableResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := expr.Evaluate(ctx) value, err := vr.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
buffer.WriteString(value.String()) writer.WriteString(value.String())
return nil return nil
} }
func (expr *stringResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (s *stringResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := expr.Evaluate(ctx) value, err := s.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
buffer.WriteString(value.String()) writer.WriteString(value.String())
return nil return nil
} }
func (expr *intResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (i *intResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := expr.Evaluate(ctx) value, err := i.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
buffer.WriteString(value.String()) writer.WriteString(value.String())
return nil return nil
} }
func (expr *floatResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (f *floatResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := expr.Evaluate(ctx) value, err := f.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
buffer.WriteString(value.String()) writer.WriteString(value.String())
return nil return nil
} }
func (expr *boolResolver) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error { func (b *boolResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := expr.Evaluate(ctx) value, err := b.Evaluate(ctx)
if err != nil { if err != nil {
return err return err
} }
buffer.WriteString(value.String()) writer.WriteString(value.String())
return nil return nil
} }
func (v *nodeFilteredVariable) GetPositionToken() *Token { func (v *nodeFilteredVariable) GetPositionToken() *Token {
return v.location_token return v.locationToken
} }
func (v *variableResolver) GetPositionToken() *Token { func (vr *variableResolver) GetPositionToken() *Token {
return v.location_token return vr.locationToken
} }
func (v *stringResolver) GetPositionToken() *Token { func (s *stringResolver) GetPositionToken() *Token {
return v.location_token return s.locationToken
} }
func (v *intResolver) GetPositionToken() *Token { func (i *intResolver) GetPositionToken() *Token {
return v.location_token return i.locationToken
} }
func (v *floatResolver) GetPositionToken() *Token { func (f *floatResolver) GetPositionToken() *Token {
return v.location_token return f.locationToken
} }
func (v *boolResolver) GetPositionToken() *Token { func (b *boolResolver) GetPositionToken() *Token {
return v.location_token return b.locationToken
} }
func (s *stringResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) { func (s *stringResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
@ -179,7 +187,7 @@ func (nv *nodeVariable) FilterApplied(name string) bool {
return nv.expr.FilterApplied(name) 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) value, err := nv.expr.Evaluate(ctx)
if err != nil { if err != nil {
return err 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 return nil
} }
func (executionCtxEval) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
return AsValue(ctx), nil
}
func (vr *variableResolver) FilterApplied(name string) bool { func (vr *variableResolver) FilterApplied(name string) bool {
return false return false
} }
@ -218,15 +230,15 @@ func (vr *variableResolver) String() string {
func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) { func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) {
var current reflect.Value var current reflect.Value
var is_safe bool var isSafe bool
for idx, part := range vr.parts { for idx, part := range vr.parts {
if idx == 0 { if idx == 0 {
// We're looking up the first part of the variable. // We're looking up the first part of the variable.
// First we're having a look in our private // First we're having a look in our private
// context (e. g. information provided by tags, like the forloop) // context (e. g. information provided by tags, like the forloop)
val, in_private := ctx.Private[vr.parts[0].s] val, inPrivate := ctx.Private[vr.parts[0].s]
if !in_private { if !inPrivate {
// Nothing found? Then have a final lookup in the public context // Nothing found? Then have a final lookup in the public context
val = ctx.Public[vr.parts[0].s] 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 // 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 // Problem with resolving the pointer is we're changing the receiver
is_func := false isFunc := false
if part.typ == varTypeIdent { if part.typ == varTypeIdent {
func_value := current.MethodByName(part.s) funcValue := current.MethodByName(part.s)
if func_value.IsValid() { if funcValue.IsValid() {
current = func_value current = funcValue
is_func = true isFunc = true
} }
} }
if !is_func { if !isFunc {
// If current a pointer, resolve it // If current a pointer, resolve it
if current.Kind() == reflect.Ptr { if current.Kind() == reflect.Ptr {
current = current.Elem() current = current.Elem()
@ -262,9 +274,14 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) {
// * slices/arrays/strings // * slices/arrays/strings
switch current.Kind() { switch current.Kind() {
case reflect.String, reflect.Array, reflect.Slice: case reflect.String, reflect.Array, reflect.Slice:
if part.i >= 0 && current.Len() > part.i {
current = current.Index(part.i) current = current.Index(part.i)
} else {
// In Django, exceeding the length of a list is just empty.
return AsValue(nil), nil
}
default: 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()) current.Kind().String(), vr.String())
} }
case varTypeIdent: case varTypeIdent:
@ -278,7 +295,7 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) {
case reflect.Map: case reflect.Map:
current = current.MapIndex(reflect.ValueOf(part.s)) current = current.MapIndex(reflect.ValueOf(part.s))
default: 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()) current.Kind().String(), vr.String())
} }
default: default:
@ -295,10 +312,10 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) {
// If current is a reflect.ValueOf(pongo2.Value), then unpack it // If current is a reflect.ValueOf(pongo2.Value), then unpack it
// Happens in function calls (as a return value) or by injecting // Happens in function calls (as a return value) or by injecting
// into the execution context (e.g. in a for-loop) // into the execution context (e.g. in a for-loop)
if current.Type() == reflect.TypeOf(&Value{}) { if current.Type() == typeOfValuePtr {
tmp_value := current.Interface().(*Value) tmpValue := current.Interface().(*Value)
current = tmp_value.val current = tmpValue.val
is_safe = tmp_value.safe isSafe = tmpValue.safe
} }
// Check whether this is an interface and resolve it where required // Check whether this is an interface and resolve it where required
@ -307,86 +324,96 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) {
} }
// Check if the part is a function call // 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 // Check for callable
if current.Kind() != reflect.Func { 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 // Check for correct function syntax and types
// func(*Value, ...) *Value // func(*Value, ...) *Value
t := current.Type() 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 // 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, return nil,
fmt.Errorf("Function input argument count (%d) of '%s' must be equal to the calling argument count (%d).", errors.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)) t.NumIn(), vr.String(), len(currArgs))
} }
// Output arguments // Output arguments
if t.NumOut() != 1 { 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 // Evaluate all parameters
parameters := make([]reflect.Value, 0) var parameters []reflect.Value
num_args := t.NumIn() numArgs := t.NumIn()
is_variadic := t.IsVariadic() isVariadic := t.IsVariadic()
var fn_arg reflect.Type var fnArg reflect.Type
for idx, arg := range part.calling_args { for idx, arg := range currArgs {
pv, err := arg.Evaluate(ctx) pv, err := arg.Evaluate(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if is_variadic { if isVariadic {
if idx >= t.NumIn()-1 { if idx >= t.NumIn()-1 {
fn_arg = t.In(num_args - 1).Elem() fnArg = t.In(numArgs - 1).Elem()
} else { } else {
fn_arg = t.In(idx) fnArg = t.In(idx)
} }
} else { } 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 // 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 !isVariadic {
if fn_arg != reflect.TypeOf(pv.Interface()) && fn_arg.Kind() != reflect.Interface { if fnArg != reflect.TypeOf(pv.Interface()) && fnArg.Kind() != reflect.Interface {
return nil, fmt.Errorf("Function input argument %d of '%s' must be of type %s or *pongo2.Value (not %T).", return nil, errors.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()) idx, vr.String(), fnArg.String(), pv.Interface())
} else { }
// Function's argument has another type, using the interface-value // Function's argument has another type, using the interface-value
parameters = append(parameters, reflect.ValueOf(pv.Interface())) parameters = append(parameters, reflect.ValueOf(pv.Interface()))
} else {
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())
} }
} 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 // Function's argument has another type, using the interface-value
parameters = append(parameters, reflect.ValueOf(pv.Interface())) parameters = append(parameters, reflect.ValueOf(pv.Interface()))
} }
}
} else { } else {
// Function's argument is a *pongo2.Value // Function's argument is a *pongo2.Value
parameters = append(parameters, reflect.ValueOf(pv)) parameters = append(parameters, reflect.ValueOf(pv))
} }
} }
// 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 // Call it and get first return parameter back
rv := current.Call(parameters)[0] rv := current.Call(parameters)[0]
if rv.Type() != reflect.TypeOf(new(Value)) { if rv.Type() != typeOfValuePtr {
current = reflect.ValueOf(rv.Interface()) current = reflect.ValueOf(rv.Interface())
} else { } else {
// Return the function call value // Return the function call value
current = rv.Interface().(*Value).val current = rv.Interface().(*Value).val
is_safe = rv.Interface().(*Value).safe isSafe = rv.Interface().(*Value).safe
}
} }
} }
@ -394,14 +421,15 @@ func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) {
// Value is not valid (e. g. NIL value) // Value is not valid (e. g. NIL value)
return AsValue(nil), nil 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) { func (vr *variableResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
value, err := vr.resolve(ctx) value, err := vr.resolve(ctx)
if err != nil { 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 return value, nil
} }
@ -436,7 +464,7 @@ func (p *Parser) parseVariableOrLiteral() (IEvaluator, *Error) {
t := p.Current() t := p.Current()
if t == nil { 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) // Is first part a number or a string, there's nothing to resolve (because there's only to return the value then)
@ -460,25 +488,25 @@ func (p *Parser) parseVariableOrLiteral() (IEvaluator, *Error) {
return nil, p.Error(err.Error(), t) return nil, p.Error(err.Error(), t)
} }
fr := &floatResolver{ fr := &floatResolver{
location_token: t, locationToken: t,
val: f, val: f,
} }
return fr, nil return fr, nil
} else { }
i, err := strconv.Atoi(t.Val) i, err := strconv.Atoi(t.Val)
if err != nil { if err != nil {
return nil, p.Error(err.Error(), t) return nil, p.Error(err.Error(), t)
} }
nr := &intResolver{ nr := &intResolver{
location_token: t, locationToken: t,
val: i, val: i,
} }
return nr, nil return nr, nil
}
case TokenString: case TokenString:
p.Consume() p.Consume()
sr := &stringResolver{ sr := &stringResolver{
location_token: t, locationToken: t,
val: t.Val, val: t.Val,
} }
return sr, nil return sr, nil
@ -487,13 +515,13 @@ func (p *Parser) parseVariableOrLiteral() (IEvaluator, *Error) {
switch t.Val { switch t.Val {
case "true": case "true":
br := &boolResolver{ br := &boolResolver{
location_token: t, locationToken: t,
val: true, val: true,
} }
return br, nil return br, nil
case "false": case "false":
br := &boolResolver{ br := &boolResolver{
location_token: t, locationToken: t,
val: false, val: false,
} }
return br, nil return br, nil
@ -503,7 +531,7 @@ func (p *Parser) parseVariableOrLiteral() (IEvaluator, *Error) {
} }
resolver := &variableResolver{ resolver := &variableResolver{
location_token: t, locationToken: t,
} }
// First part of a variable MUST be an identifier // First part of a variable MUST be an identifier
@ -551,26 +579,26 @@ variableLoop:
} else { } else {
// EOF // EOF
return nil, p.Error("Unexpected EOF, expected either IDENTIFIER or NUMBER after DOT.", return nil, p.Error("Unexpected EOF, expected either IDENTIFIER or NUMBER after DOT.",
p.last_token) p.lastToken)
} }
} else if p.Match(TokenSymbol, "(") != nil { } else if p.Match(TokenSymbol, "(") != nil {
// Function call // Function call
// FunctionName '(' Comma-separated list of expressions ')' // FunctionName '(' Comma-separated list of expressions ')'
part := resolver.parts[len(resolver.parts)-1] part := resolver.parts[len(resolver.parts)-1]
part.is_function_call = true part.isFunctionCall = true
argumentLoop: argumentLoop:
for { for {
if p.Remaining() == 0 { 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 { if p.Peek(TokenSymbol, ")") == nil {
// No closing bracket, so we're parsing an expression // No closing bracket, so we're parsing an expression
expr_arg, err := p.ParseExpression() exprArg, err := p.ParseExpression()
if err != nil { if err != nil {
return nil, err return nil, err
} }
part.calling_args = append(part.calling_args, expr_arg) part.callingArgs = append(part.callingArgs, exprArg)
if p.Match(TokenSymbol, ")") != nil { if p.Match(TokenSymbol, ")") != nil {
// If there's a closing bracket after an expression, we will stop parsing the arguments // 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) { func (p *Parser) parseVariableOrLiteralWithFilter() (*nodeFilteredVariable, *Error) {
v := &nodeFilteredVariable{ v := &nodeFilteredVariable{
location_token: p.Current(), locationToken: p.Current(),
} }
// Parse the variable name // Parse the variable name
@ -621,15 +649,13 @@ filterLoop:
} }
// Check sandbox filter restriction // 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) 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) v.filterChain = append(v.filterChain, filter)
continue filterLoop continue filterLoop
return nil, p.Error("This token is not allowed within a variable.", nil)
} }
return v, nil return v, nil
@ -637,7 +663,7 @@ filterLoop:
func (p *Parser) parseVariableElement() (INode, *Error) { func (p *Parser) parseVariableElement() (INode, *Error) {
node := &nodeVariable{ node := &nodeVariable{
location_token: p.Current(), locationToken: p.Current(),
} }
p.Consume() // consume '{{' p.Consume() // consume '{{'

23
vendor/github.com/juju/errors/.gitignore generated vendored Normal file
View file

@ -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

191
vendor/github.com/juju/errors/LICENSE generated vendored Normal file
View file

@ -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. <http://fsf.org/>
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.

11
vendor/github.com/juju/errors/Makefile generated vendored Normal file
View file

@ -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

543
vendor/github.com/juju/errors/README.md generated vendored Normal file
View file

@ -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)

81
vendor/github.com/juju/errors/doc.go generated vendored Normal file
View file

@ -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

172
vendor/github.com/juju/errors/error.go generated vendored Normal file
View file

@ -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)
}

178
vendor/github.com/juju/errors/error_test.go generated vendored Normal file
View file

@ -0,0 +1,178 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
"fmt"
"runtime"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"github.com/juju/errors"
)
type errorsSuite struct{}
var _ = gc.Suite(&errorsSuite{})
var someErr = errors.New("some error") //err varSomeErr
func (*errorsSuite) TestErrorString(c *gc.C) {
for i, test := range []struct {
message string
generator func() error
expected string
}{
{
message: "uncomparable errors",
generator: func() error {
err := errors.Annotatef(newNonComparableError("uncomparable"), "annotation")
return errors.Annotatef(err, "another")
},
expected: "another: annotation: uncomparable",
}, {
message: "Errorf",
generator: func() error {
return errors.Errorf("first error")
},
expected: "first error",
}, {
message: "annotated error",
generator: func() error {
err := errors.Errorf("first error")
return errors.Annotatef(err, "annotation")
},
expected: "annotation: first error",
}, {
message: "test annotation format",
generator: func() error {
err := errors.Errorf("first %s", "error")
return errors.Annotatef(err, "%s", "annotation")
},
expected: "annotation: first error",
}, {
message: "wrapped error",
generator: func() error {
err := newError("first error")
return errors.Wrap(err, newError("detailed error"))
},
expected: "detailed error",
}, {
message: "wrapped annotated error",
generator: func() error {
err := errors.Errorf("first error")
err = errors.Annotatef(err, "annotated")
return errors.Wrap(err, fmt.Errorf("detailed error"))
},
expected: "detailed error",
}, {
message: "annotated wrapped error",
generator: func() error {
err := errors.Errorf("first error")
err = errors.Wrap(err, fmt.Errorf("detailed error"))
return errors.Annotatef(err, "annotated")
},
expected: "annotated: detailed error",
}, {
message: "traced, and annotated",
generator: func() error {
err := errors.New("first error")
err = errors.Trace(err)
err = errors.Annotate(err, "some context")
err = errors.Trace(err)
err = errors.Annotate(err, "more context")
return errors.Trace(err)
},
expected: "more context: some context: first error",
}, {
message: "traced, and annotated, masked and annotated",
generator: func() error {
err := errors.New("first error")
err = errors.Trace(err)
err = errors.Annotate(err, "some context")
err = errors.Maskf(err, "masked")
err = errors.Annotate(err, "more context")
return errors.Trace(err)
},
expected: "more context: masked: some context: first error",
},
} {
c.Logf("%v: %s", i, test.message)
err := test.generator()
ok := c.Check(err.Error(), gc.Equals, test.expected)
if !ok {
c.Logf("%#v", test.generator())
}
}
}
type embed struct {
errors.Err
}
func newEmbed(format string, args ...interface{}) *embed {
err := &embed{errors.NewErr(format, args...)}
err.SetLocation(1)
return err
}
func (*errorsSuite) TestNewErr(c *gc.C) {
if runtime.Compiler == "gccgo" {
c.Skip("gccgo can't determine the location")
}
err := newEmbed("testing %d", 42) //err embedErr
c.Assert(err.Error(), gc.Equals, "testing 42")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["embedErr"].String())
}
func newEmbedWithCause(other error, format string, args ...interface{}) *embed {
err := &embed{errors.NewErrWithCause(other, format, args...)}
err.SetLocation(1)
return err
}
func (*errorsSuite) TestNewErrWithCause(c *gc.C) {
if runtime.Compiler == "gccgo" {
c.Skip("gccgo can't determine the location")
}
causeErr := fmt.Errorf("external error")
err := newEmbedWithCause(causeErr, "testing %d", 43) //err embedCause
c.Assert(err.Error(), gc.Equals, "testing 43: external error")
c.Assert(errors.Cause(err), gc.Equals, causeErr)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["embedCause"].String())
}
var _ error = (*embed)(nil)
// This is an uncomparable error type, as it is a struct that supports the
// error interface (as opposed to a pointer type).
type error_ struct {
info string
slice []string
}
// Create a non-comparable error
func newNonComparableError(message string) error {
return error_{info: message}
}
func (e error_) Error() string {
return e.info
}
func newError(message string) error {
return testError{message}
}
// The testError is a value type error for ease of seeing results
// when the test fails.
type testError struct {
message string
}
func (e testError) Error() string {
return e.message
}

309
vendor/github.com/juju/errors/errortypes.go generated vendored Normal file
View file

@ -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 &notFound{wrap(nil, format, " not found", args...)}
}
// NewNotFound returns an error which wraps err that satisfies
// IsNotFound().
func NewNotFound(err error, msg string) error {
return &notFound{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 &notImplemented{wrap(nil, format, " not implemented", args...)}
}
// NewNotImplemented returns an error which wraps err and satisfies
// IsNotImplemented().
func NewNotImplemented(err error, msg string) error {
return &notImplemented{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 &notSupported{wrap(nil, format, " not supported", args...)}
}
// NewNotSupported returns an error which wraps err and satisfies
// IsNotSupported().
func NewNotSupported(err error, msg string) error {
return &notSupported{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 &notValid{wrap(nil, format, " not valid", args...)}
}
// NewNotValid returns an error which wraps err and satisfies IsNotValid().
func NewNotValid(err error, msg string) error {
return &notValid{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 &notProvisioned{wrap(nil, format, " not provisioned", args...)}
}
// NewNotProvisioned returns an error which wraps err that satisfies
// IsNotProvisioned().
func NewNotProvisioned(err error, msg string) error {
return &notProvisioned{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 &notAssigned{wrap(nil, format, " not assigned", args...)}
}
// NewNotAssigned returns an error which wraps err that satisfies
// IsNotAssigned().
func NewNotAssigned(err error, msg string) error {
return &notAssigned{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
}

174
vendor/github.com/juju/errors/errortypes_test.go generated vendored Normal file
View file

@ -0,0 +1,174 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
stderrors "errors"
"fmt"
"reflect"
"runtime"
"github.com/juju/errors"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
)
// errorInfo holds information about a single error type: a satisfier
// function, wrapping and variable arguments constructors and message
// suffix.
type errorInfo struct {
satisfier func(error) bool
argsConstructor func(string, ...interface{}) error
wrapConstructor func(error, string) error
suffix string
}
// allErrors holds information for all defined errors. When adding new
// errors, add them here as well to include them in tests.
var allErrors = []*errorInfo{
{errors.IsNotFound, errors.NotFoundf, errors.NewNotFound, " not found"},
{errors.IsUserNotFound, errors.UserNotFoundf, errors.NewUserNotFound, " user not found"},
{errors.IsUnauthorized, errors.Unauthorizedf, errors.NewUnauthorized, ""},
{errors.IsNotImplemented, errors.NotImplementedf, errors.NewNotImplemented, " not implemented"},
{errors.IsAlreadyExists, errors.AlreadyExistsf, errors.NewAlreadyExists, " already exists"},
{errors.IsNotSupported, errors.NotSupportedf, errors.NewNotSupported, " not supported"},
{errors.IsNotValid, errors.NotValidf, errors.NewNotValid, " not valid"},
{errors.IsNotProvisioned, errors.NotProvisionedf, errors.NewNotProvisioned, " not provisioned"},
{errors.IsNotAssigned, errors.NotAssignedf, errors.NewNotAssigned, " not assigned"},
{errors.IsMethodNotAllowed, errors.MethodNotAllowedf, errors.NewMethodNotAllowed, ""},
{errors.IsBadRequest, errors.BadRequestf, errors.NewBadRequest, ""},
{errors.IsForbidden, errors.Forbiddenf, errors.NewForbidden, ""},
}
type errorTypeSuite struct{}
var _ = gc.Suite(&errorTypeSuite{})
func (t *errorInfo) satisfierName() string {
value := reflect.ValueOf(t.satisfier)
f := runtime.FuncForPC(value.Pointer())
return f.Name()
}
func (t *errorInfo) equal(t0 *errorInfo) bool {
if t0 == nil {
return false
}
return t.satisfierName() == t0.satisfierName()
}
type errorTest struct {
err error
message string
errInfo *errorInfo
}
func deferredAnnotatef(err error, format string, args ...interface{}) error {
errors.DeferredAnnotatef(&err, format, args...)
return err
}
func mustSatisfy(c *gc.C, err error, errInfo *errorInfo) {
if errInfo != nil {
msg := fmt.Sprintf("%#v must satisfy %v", err, errInfo.satisfierName())
c.Check(err, jc.Satisfies, errInfo.satisfier, gc.Commentf(msg))
}
}
func mustNotSatisfy(c *gc.C, err error, errInfo *errorInfo) {
if errInfo != nil {
msg := fmt.Sprintf("%#v must not satisfy %v", err, errInfo.satisfierName())
c.Check(err, gc.Not(jc.Satisfies), errInfo.satisfier, gc.Commentf(msg))
}
}
func checkErrorMatches(c *gc.C, err error, message string, errInfo *errorInfo) {
if message == "<nil>" {
c.Check(err, gc.IsNil)
c.Check(errInfo, gc.IsNil)
} else {
c.Check(err, gc.ErrorMatches, message)
}
}
func runErrorTests(c *gc.C, errorTests []errorTest, checkMustSatisfy bool) {
for i, t := range errorTests {
c.Logf("test %d: %T: %v", i, t.err, t.err)
checkErrorMatches(c, t.err, t.message, t.errInfo)
if checkMustSatisfy {
mustSatisfy(c, t.err, t.errInfo)
}
// Check all other satisfiers to make sure none match.
for _, otherErrInfo := range allErrors {
if checkMustSatisfy && otherErrInfo.equal(t.errInfo) {
continue
}
mustNotSatisfy(c, t.err, otherErrInfo)
}
}
}
func (*errorTypeSuite) TestDeferredAnnotatef(c *gc.C) {
// Ensure DeferredAnnotatef annotates the errors.
errorTests := []errorTest{}
for _, errInfo := range allErrors {
errorTests = append(errorTests, []errorTest{{
deferredAnnotatef(nil, "comment"),
"<nil>",
nil,
}, {
deferredAnnotatef(stderrors.New("blast"), "comment"),
"comment: blast",
nil,
}, {
deferredAnnotatef(errInfo.argsConstructor("foo %d", 42), "comment %d", 69),
"comment 69: foo 42" + errInfo.suffix,
errInfo,
}, {
deferredAnnotatef(errInfo.argsConstructor(""), "comment"),
"comment: " + errInfo.suffix,
errInfo,
}, {
deferredAnnotatef(errInfo.wrapConstructor(stderrors.New("pow!"), "woo"), "comment"),
"comment: woo: pow!",
errInfo,
}}...)
}
runErrorTests(c, errorTests, true)
}
func (*errorTypeSuite) TestAllErrors(c *gc.C) {
errorTests := []errorTest{}
for _, errInfo := range allErrors {
errorTests = append(errorTests, []errorTest{{
nil,
"<nil>",
nil,
}, {
errInfo.argsConstructor("foo %d", 42),
"foo 42" + errInfo.suffix,
errInfo,
}, {
errInfo.argsConstructor(""),
errInfo.suffix,
errInfo,
}, {
errInfo.wrapConstructor(stderrors.New("pow!"), "prefix"),
"prefix: pow!",
errInfo,
}, {
errInfo.wrapConstructor(stderrors.New("pow!"), ""),
"pow!",
errInfo,
}, {
errInfo.wrapConstructor(nil, "prefix"),
"prefix",
errInfo,
}}...)
}
runErrorTests(c, errorTests, true)
}

23
vendor/github.com/juju/errors/example_test.go generated vendored Normal file
View file

@ -0,0 +1,23 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
"fmt"
"github.com/juju/errors"
)
func ExampleTrace() {
var err1 error = fmt.Errorf("something wicked this way comes")
var err2 error = nil
// Tracing a non nil error will return an error
fmt.Println(errors.Trace(err1))
// Tracing nil will return nil
fmt.Println(errors.Trace(err2))
// Output: something wicked this way comes
// <nil>
}

12
vendor/github.com/juju/errors/export_test.go generated vendored Normal file
View file

@ -0,0 +1,12 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors
// Since variables are declared before the init block, in order to get the goPath
// we need to return it rather than just reference it.
func GoPath() string {
return goPath
}
var TrimGoPath = trimGoPath

330
vendor/github.com/juju/errors/functions.go generated vendored Normal file
View file

@ -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
}

305
vendor/github.com/juju/errors/functions_test.go generated vendored Normal file
View file

@ -0,0 +1,305 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"github.com/juju/errors"
)
type functionSuite struct {
}
var _ = gc.Suite(&functionSuite{})
func (*functionSuite) TestNew(c *gc.C) {
err := errors.New("testing") //err newTest
c.Assert(err.Error(), gc.Equals, "testing")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["newTest"].String())
}
func (*functionSuite) TestErrorf(c *gc.C) {
err := errors.Errorf("testing %d", 42) //err errorfTest
c.Assert(err.Error(), gc.Equals, "testing 42")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["errorfTest"].String())
}
func (*functionSuite) TestTrace(c *gc.C) {
first := errors.New("first")
err := errors.Trace(first) //err traceTest
c.Assert(err.Error(), gc.Equals, "first")
c.Assert(errors.Cause(err), gc.Equals, first)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["traceTest"].String())
c.Assert(errors.Trace(nil), gc.IsNil)
}
func (*functionSuite) TestAnnotate(c *gc.C) {
first := errors.New("first")
err := errors.Annotate(first, "annotation") //err annotateTest
c.Assert(err.Error(), gc.Equals, "annotation: first")
c.Assert(errors.Cause(err), gc.Equals, first)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["annotateTest"].String())
c.Assert(errors.Annotate(nil, "annotate"), gc.IsNil)
}
func (*functionSuite) TestAnnotatef(c *gc.C) {
first := errors.New("first")
err := errors.Annotatef(first, "annotation %d", 2) //err annotatefTest
c.Assert(err.Error(), gc.Equals, "annotation 2: first")
c.Assert(errors.Cause(err), gc.Equals, first)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["annotatefTest"].String())
c.Assert(errors.Annotatef(nil, "annotate"), gc.IsNil)
}
func (*functionSuite) TestDeferredAnnotatef(c *gc.C) {
// NOTE: this test fails with gccgo
if runtime.Compiler == "gccgo" {
c.Skip("gccgo can't determine the location")
}
first := errors.New("first")
test := func() (err error) {
defer errors.DeferredAnnotatef(&err, "deferred %s", "annotate")
return first //err deferredAnnotate
}
err := test()
c.Assert(err.Error(), gc.Equals, "deferred annotate: first")
c.Assert(errors.Cause(err), gc.Equals, first)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["deferredAnnotate"].String())
err = nil
errors.DeferredAnnotatef(&err, "deferred %s", "annotate")
c.Assert(err, gc.IsNil)
}
func (*functionSuite) TestWrap(c *gc.C) {
first := errors.New("first") //err wrapFirst
detailed := errors.New("detailed")
err := errors.Wrap(first, detailed) //err wrapTest
c.Assert(err.Error(), gc.Equals, "detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapFirst"].String())
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapTest"].String())
}
func (*functionSuite) TestWrapOfNil(c *gc.C) {
detailed := errors.New("detailed")
err := errors.Wrap(nil, detailed) //err nilWrapTest
c.Assert(err.Error(), gc.Equals, "detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["nilWrapTest"].String())
}
func (*functionSuite) TestWrapf(c *gc.C) {
first := errors.New("first") //err wrapfFirst
detailed := errors.New("detailed")
err := errors.Wrapf(first, detailed, "value %d", 42) //err wrapfTest
c.Assert(err.Error(), gc.Equals, "value 42: detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapfFirst"].String())
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapfTest"].String())
}
func (*functionSuite) TestWrapfOfNil(c *gc.C) {
detailed := errors.New("detailed")
err := errors.Wrapf(nil, detailed, "value %d", 42) //err nilWrapfTest
c.Assert(err.Error(), gc.Equals, "value 42: detailed")
c.Assert(errors.Cause(err), gc.Equals, detailed)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["nilWrapfTest"].String())
}
func (*functionSuite) TestMask(c *gc.C) {
first := errors.New("first")
err := errors.Mask(first) //err maskTest
c.Assert(err.Error(), gc.Equals, "first")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["maskTest"].String())
c.Assert(errors.Mask(nil), gc.IsNil)
}
func (*functionSuite) TestMaskf(c *gc.C) {
first := errors.New("first")
err := errors.Maskf(first, "masked %d", 42) //err maskfTest
c.Assert(err.Error(), gc.Equals, "masked 42: first")
c.Assert(errors.Cause(err), gc.Equals, err)
c.Assert(errors.Details(err), jc.Contains, tagToLocation["maskfTest"].String())
c.Assert(errors.Maskf(nil, "mask"), gc.IsNil)
}
func (*functionSuite) TestCause(c *gc.C) {
c.Assert(errors.Cause(nil), gc.IsNil)
c.Assert(errors.Cause(someErr), gc.Equals, someErr)
fmtErr := fmt.Errorf("simple")
c.Assert(errors.Cause(fmtErr), gc.Equals, fmtErr)
err := errors.Wrap(someErr, fmtErr)
c.Assert(errors.Cause(err), gc.Equals, fmtErr)
err = errors.Annotate(err, "annotated")
c.Assert(errors.Cause(err), gc.Equals, fmtErr)
err = errors.Maskf(err, "maksed")
c.Assert(errors.Cause(err), gc.Equals, err)
// Look for a file that we know isn't there.
dir := c.MkDir()
_, err = os.Stat(filepath.Join(dir, "not-there"))
c.Assert(os.IsNotExist(err), jc.IsTrue)
err = errors.Annotatef(err, "wrap it")
// Now the error itself isn't a 'IsNotExist'.
c.Assert(os.IsNotExist(err), jc.IsFalse)
// However if we use the Check method, it is.
c.Assert(os.IsNotExist(errors.Cause(err)), jc.IsTrue)
}
func (s *functionSuite) TestDetails(c *gc.C) {
if runtime.Compiler == "gccgo" {
c.Skip("gccgo can't determine the location")
}
c.Assert(errors.Details(nil), gc.Equals, "[]")
otherErr := fmt.Errorf("other")
checkDetails(c, otherErr, "[{other}]")
err0 := newEmbed("foo") //err TestStack#0
checkDetails(c, err0, "[{$TestStack#0$: foo}]")
err1 := errors.Annotate(err0, "bar") //err TestStack#1
checkDetails(c, err1, "[{$TestStack#1$: bar} {$TestStack#0$: foo}]")
err2 := errors.Trace(err1) //err TestStack#2
checkDetails(c, err2, "[{$TestStack#2$: } {$TestStack#1$: bar} {$TestStack#0$: foo}]")
}
type tracer interface {
StackTrace() []string
}
func (*functionSuite) TestErrorStack(c *gc.C) {
for i, test := range []struct {
message string
generator func() error
expected string
tracer bool
}{
{
message: "nil",
generator: func() error {
return nil
},
}, {
message: "raw error",
generator: func() error {
return fmt.Errorf("raw")
},
expected: "raw",
}, {
message: "single error stack",
generator: func() error {
return errors.New("first error") //err single
},
expected: "$single$: first error",
tracer: true,
}, {
message: "annotated error",
generator: func() error {
err := errors.New("first error") //err annotated-0
return errors.Annotate(err, "annotation") //err annotated-1
},
expected: "" +
"$annotated-0$: first error\n" +
"$annotated-1$: annotation",
tracer: true,
}, {
message: "wrapped error",
generator: func() error {
err := errors.New("first error") //err wrapped-0
return errors.Wrap(err, newError("detailed error")) //err wrapped-1
},
expected: "" +
"$wrapped-0$: first error\n" +
"$wrapped-1$: detailed error",
tracer: true,
}, {
message: "annotated wrapped error",
generator: func() error {
err := errors.Errorf("first error") //err ann-wrap-0
err = errors.Wrap(err, fmt.Errorf("detailed error")) //err ann-wrap-1
return errors.Annotatef(err, "annotated") //err ann-wrap-2
},
expected: "" +
"$ann-wrap-0$: first error\n" +
"$ann-wrap-1$: detailed error\n" +
"$ann-wrap-2$: annotated",
tracer: true,
}, {
message: "traced, and annotated",
generator: func() error {
err := errors.New("first error") //err stack-0
err = errors.Trace(err) //err stack-1
err = errors.Annotate(err, "some context") //err stack-2
err = errors.Trace(err) //err stack-3
err = errors.Annotate(err, "more context") //err stack-4
return errors.Trace(err) //err stack-5
},
expected: "" +
"$stack-0$: first error\n" +
"$stack-1$: \n" +
"$stack-2$: some context\n" +
"$stack-3$: \n" +
"$stack-4$: more context\n" +
"$stack-5$: ",
tracer: true,
}, {
message: "uncomparable, wrapped with a value error",
generator: func() error {
err := newNonComparableError("first error") //err mixed-0
err = errors.Trace(err) //err mixed-1
err = errors.Wrap(err, newError("value error")) //err mixed-2
err = errors.Maskf(err, "masked") //err mixed-3
err = errors.Annotate(err, "more context") //err mixed-4
return errors.Trace(err) //err mixed-5
},
expected: "" +
"first error\n" +
"$mixed-1$: \n" +
"$mixed-2$: value error\n" +
"$mixed-3$: masked\n" +
"$mixed-4$: more context\n" +
"$mixed-5$: ",
tracer: true,
},
} {
c.Logf("%v: %s", i, test.message)
err := test.generator()
expected := replaceLocations(test.expected)
stack := errors.ErrorStack(err)
ok := c.Check(stack, gc.Equals, expected)
if !ok {
c.Logf("%#v", err)
}
tracer, ok := err.(tracer)
c.Check(ok, gc.Equals, test.tracer)
if ok {
stackTrace := tracer.StackTrace()
c.Check(stackTrace, gc.DeepEquals, strings.Split(stack, "\n"))
}
}
}

95
vendor/github.com/juju/errors/package_test.go generated vendored Normal file
View file

@ -0,0 +1,95 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
"fmt"
"io/ioutil"
"strings"
"testing"
gc "gopkg.in/check.v1"
"github.com/juju/errors"
)
func Test(t *testing.T) {
gc.TestingT(t)
}
func checkDetails(c *gc.C, err error, details string) {
c.Assert(err, gc.NotNil)
expectedDetails := replaceLocations(details)
c.Assert(errors.Details(err), gc.Equals, expectedDetails)
}
func checkErr(c *gc.C, err, cause error, msg string, details string) {
c.Assert(err, gc.NotNil)
c.Assert(err.Error(), gc.Equals, msg)
c.Assert(errors.Cause(err), gc.Equals, cause)
expectedDetails := replaceLocations(details)
c.Assert(errors.Details(err), gc.Equals, expectedDetails)
}
func replaceLocations(line string) string {
result := ""
for {
i := strings.Index(line, "$")
if i == -1 {
break
}
result += line[0:i]
line = line[i+1:]
i = strings.Index(line, "$")
if i == -1 {
panic("no second $")
}
result += location(line[0:i]).String()
line = line[i+1:]
}
result += line
return result
}
func location(tag string) Location {
loc, ok := tagToLocation[tag]
if !ok {
panic(fmt.Sprintf("tag %q not found", tag))
}
return loc
}
type Location struct {
file string
line int
}
func (loc Location) String() string {
return fmt.Sprintf("%s:%d", loc.file, loc.line)
}
var tagToLocation = make(map[string]Location)
func setLocationsForErrorTags(filename string) {
data, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
filename = "github.com/juju/errors/" + filename
lines := strings.Split(string(data), "\n")
for i, line := range lines {
if j := strings.Index(line, "//err "); j >= 0 {
tag := line[j+len("//err "):]
if _, found := tagToLocation[tag]; found {
panic(fmt.Sprintf("tag %q already processed previously", tag))
}
tagToLocation[tag] = Location{file: filename, line: i + 1}
}
}
}
func init() {
setLocationsForErrorTags("error_test.go")
setLocationsForErrorTags("functions_test.go")
}

38
vendor/github.com/juju/errors/path.go generated vendored Normal file
View file

@ -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
}

29
vendor/github.com/juju/errors/path_test.go generated vendored Normal file
View file

@ -0,0 +1,29 @@
// Copyright 2013, 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package errors_test
import (
"path"
gc "gopkg.in/check.v1"
"github.com/juju/errors"
)
type pathSuite struct{}
var _ = gc.Suite(&pathSuite{})
func (*pathSuite) TestGoPathSet(c *gc.C) {
c.Assert(errors.GoPath(), gc.Not(gc.Equals), "")
}
func (*pathSuite) TestTrimGoPath(c *gc.C) {
relativeImport := "github.com/foo/bar/baz.go"
filename := path.Join(errors.GoPath(), relativeImport)
c.Assert(errors.TrimGoPath(filename), gc.Equals, relativeImport)
absoluteImport := "/usr/share/foo/bar/baz.go"
c.Assert(errors.TrimGoPath(absoluteImport), gc.Equals, absoluteImport)
}