1
0
mirror of https://github.com/Luzifer/cloudkeys-go.git synced 2024-09-18 23:22:59 +00:00

updated pongo to newer version

This commit is contained in:
Martin Thielecke 2017-12-28 01:56:23 +00:00
parent 9522094655
commit b2c7ec5e07
159 changed files with 32411 additions and 1303 deletions

View File

@ -1,40 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
.idea
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
.project
EBNF.txt
test1.tpl
pongo2_internal_test.go
tpl-error.out
/count.out
/cover.out
*.swp
*.iml
/cpu.out
/mem.out
/pongo2.test
*.error
/profile
/coverage.out
/pongo2_internal_test.ignore

View File

@ -1,12 +0,0 @@
language: go
go:
- 1.3
- tip
install:
- go get code.google.com/p/go.tools/cmd/cover
- go get github.com/mattn/goveralls
- go get gopkg.in/check.v1
script:
- go test -v -covermode=count -coverprofile=coverage.out -bench . -cpu 1,4
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN || true'

View File

@ -1,8 +1,9 @@
# [pongo](https://en.wikipedia.org/wiki/Pongo_%28genus%29)2
[![GoDoc](https://godoc.org/github.com/flosch/pongo2?status.png)](https://godoc.org/github.com/flosch/pongo2)
[![Join the chat at https://gitter.im/flosch/pongo2](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/flosch/pongo2)
[![GoDoc](https://godoc.org/github.com/flosch/pongo2?status.svg)](https://godoc.org/github.com/flosch/pongo2)
[![Build Status](https://travis-ci.org/flosch/pongo2.svg?branch=master)](https://travis-ci.org/flosch/pongo2)
[![Coverage Status](https://coveralls.io/repos/flosch/pongo2/badge.png?branch=master)](https://coveralls.io/r/flosch/pongo2?branch=master)
[![Coverage Status](https://coveralls.io/repos/flosch/pongo2/badge.svg?branch=master)](https://coveralls.io/r/flosch/pongo2?branch=master)
[![gratipay](http://img.shields.io/badge/gratipay-support%20pongo-brightgreen.svg)](https://gratipay.com/flosch/)
[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=3654947)](https://www.bountysource.com/trackers/3654947-pongo2?utm_source=3654947&utm_medium=shield&utm_campaign=TRACKER_BADGE)
@ -95,6 +96,7 @@ Please also have a look on the [caveats](https://github.com/flosch/pongo2#caveat
If you're using the `master`-branch of pongo2, you might be interested in this section. Since pongo2 is still in development (even though there is a first stable release!), there could be (backwards-incompatible) API changes over time. To keep track of these and therefore make it painless for you to adapt your codebase, I'll list them here.
* Function signature for tag execution changed: not taking a `bytes.Buffer` anymore; instead `Execute()`-functions are now taking a `TemplateWriter` interface.
* Function signature for tag and filter parsing/execution changed (`error` return type changed to `*Error`).
* `INodeEvaluator` has been removed and got replaced by `IEvaluator`. You can change your existing tags/filters by simply replacing the interface.
* Two new helper functions: [`RenderTemplateFile()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateFile) and [`RenderTemplateString()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateString).
@ -104,7 +106,7 @@ If you're using the `master`-branch of pongo2, you might be interested in this s
## How you can help
* Write [filters](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3) / [tags](https://github.com/flosch/pongo2/blob/master/tags.go#L4) (see [tutorial](https://www.florian-schlachter.de/post/pongo2/)) by forking pongo2 and sending pull requests
* Write/improve code tests (use the following command to see what tests are missing: `go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out`)
* Write/improve code tests (use the following command to see what tests are missing: `go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out` or have a look on [gocover.io/github.com/flosch/pongo2](http://gocover.io/github.com/flosch/pongo2))
* Write/improve template tests (see the `template_tests/` directory)
* Write middleware, libraries and websites using pongo2. :-)
@ -115,7 +117,8 @@ For a documentation on how the templating language works you can [head over to t
You can access pongo2's API documentation on [godoc](https://godoc.org/github.com/flosch/pongo2).
## Blog post series
* [pongo2 v3 released](https://www.florian-schlachter.de/post/pongo2-v3/)
* [pongo2 v2 released](https://www.florian-schlachter.de/post/pongo2-v2/)
* [pongo2 1.0 released](https://www.florian-schlachter.de/post/pongo2-10/) [August 8th 2014]
* [pongo2 playground](https://www.florian-schlachter.de/post/pongo2-playground/) [August 1st 2014]
@ -154,8 +157,12 @@ You can access pongo2's API documentation on [godoc](https://godoc.org/github.co
* [beego-pongo2.v2](https://github.com/ipfans/beego-pongo2.v2) - Same as `beego-pongo2`, but for pongo2 v2.
* [macaron-pongo2](https://github.com/macaron-contrib/pongo2) - pongo2 support for [Macaron](https://github.com/Unknwon/macaron), a modular web framework.
* [ginpongo2](https://github.com/ngerakines/ginpongo2) - middleware for [gin](github.com/gin-gonic/gin) to use pongo2 templates
* [pongo2-trans](https://github.com/fromYukki/pongo2trans) - `trans`-tag implementation for internationalization
* [Build'n support for Iris' template engine](https://github.com/kataras/iris)
* [pongo2gin](https://github.com/robvdl/pongo2gin) - alternative renderer for [gin](github.com/gin-gonic/gin) to use pongo2 templates
* [pongo2-trans](https://github.com/digitalcrab/pongo2trans) - `trans`-tag implementation for internationalization
* [tpongo2](https://github.com/tango-contrib/tpongo2) - pongo2 support for [Tango](https://github.com/lunny/tango), a micro-kernel & pluggable web framework.
* [p2cli](https://github.com/wrouesnel/p2cli) - command line templating utility based on pongo2
Please add your project to this list and send me a pull request when you've developed something nice for pongo2.
# API-usage examples

View File

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

View File

@ -6,20 +6,20 @@ import (
"os"
)
// This Error type is being used to address an error during lexing, parsing or
// The Error type is being used to address an error during lexing, parsing or
// execution. If you want to return an error object (for example in your own
// tag or filter) fill this object with as much information as you have.
// Make sure "Sender" is always given (if you're returning an error within
// a filter, make Sender equals 'filter:yourfilter'; same goes for tags: 'tag:mytag').
// It's okay if you only fill in ErrorMsg if you don't have any other details at hand.
type Error struct {
Template *Template
Filename string
Line int
Column int
Token *Token
Sender string
ErrorMsg string
Template *Template
Filename string
Line int
Column int
Token *Token
Sender string
OrigError error
}
func (e *Error) updateFromTokenIfNeeded(template *Template, t *Token) *Error {
@ -54,14 +54,14 @@ func (e *Error) Error() string {
}
}
s += "] "
s += e.ErrorMsg
s += e.OrigError.Error()
return s
}
// Returns the affected line from the original template, if available.
func (e *Error) RawLine() (line string, available bool) {
// RawLine returns the affected line from the original template, if available.
func (e *Error) RawLine() (line string, available bool, outErr error) {
if e.Line <= 0 || e.Filename == "<string>" {
return "", false
return "", false, nil
}
filename := e.Filename
@ -70,17 +70,22 @@ func (e *Error) RawLine() (line string, available bool) {
}
file, err := os.Open(filename)
if err != nil {
panic(err)
return "", false, err
}
defer file.Close()
defer func() {
err := file.Close()
if err != nil && outErr == nil {
outErr = err
}
}()
scanner := bufio.NewScanner(file)
l := 0
for scanner.Scan() {
l++
if l == e.Line {
return scanner.Text(), true
return scanner.Text(), true, nil
}
}
return "", false
return "", false, nil
}

View File

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

View File

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

View File

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

View File

@ -1,17 +1,13 @@
package pongo2
import (
"bytes"
)
// The root document
type nodeDocument struct {
Nodes []INode
}
func (doc *nodeDocument) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
func (doc *nodeDocument) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
for _, n := range doc.Nodes {
err := n.Execute(ctx, buffer)
err := n.Execute(ctx, writer)
if err != nil {
return err
}

View File

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

View File

@ -1,17 +1,13 @@
package pongo2
import (
"bytes"
)
type NodeWrapper struct {
Endtag string
nodes []INode
}
func (wrapper *NodeWrapper) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
func (wrapper *NodeWrapper) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
for _, n := range wrapper.nodes {
err := n.Execute(ctx, buffer)
err := n.Execute(ctx, writer)
if err != nil {
return err
}

View File

@ -1,13 +1,14 @@
package pongo2
import (
"bytes"
"fmt"
"strings"
"github.com/juju/errors"
)
type INode interface {
Execute(*ExecutionContext, *bytes.Buffer) *Error
Execute(*ExecutionContext, TemplateWriter) *Error
}
type IEvaluator interface {
@ -27,10 +28,10 @@ type IEvaluator interface {
//
// (See Token's documentation for more about tokens)
type Parser struct {
name string
idx int
tokens []*Token
last_token *Token
name string
idx int
tokens []*Token
lastToken *Token
// if the parser parses a template document, here will be
// a reference to it (needed to access the template through Tags)
@ -47,7 +48,7 @@ func newParser(name string, tokens []*Token, template *Template) *Parser {
template: template,
}
if len(tokens) > 0 {
p.last_token = tokens[len(tokens)-1]
p.lastToken = tokens[len(tokens)-1]
}
return p
}
@ -175,7 +176,7 @@ func (p *Parser) GetR(shift int) *Token {
return p.Get(i)
}
// Produces a nice error message and returns an error-object.
// Error produces a nice error message and returns an error-object.
// The 'token'-argument is optional. If provided, it will take
// the token's position information. If not provided, it will
// automatically use the CURRENT token's position information.
@ -196,13 +197,13 @@ func (p *Parser) Error(msg string, token *Token) *Error {
col = token.Col
}
return &Error{
Template: p.template,
Filename: p.name,
Sender: "parser",
Line: line,
Column: col,
Token: token,
ErrorMsg: msg,
Template: p.template,
Filename: p.name,
Sender: "parser",
Line: line,
Column: col,
Token: token,
OrigError: errors.New(msg),
}
}
@ -212,19 +213,19 @@ func (p *Parser) Error(msg string, token *Token) *Error {
func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) {
wrapper := &NodeWrapper{}
tagArgs := make([]*Token, 0)
var tagArgs []*Token
for p.Remaining() > 0 {
// New tag, check whether we have to stop wrapping here
if p.Peek(TokenSymbol, "{%") != nil {
tag_ident := p.PeekTypeN(1, TokenIdentifier)
tagIdent := p.PeekTypeN(1, TokenIdentifier)
if tag_ident != nil {
if tagIdent != nil {
// We've found a (!) end-tag
found := false
for _, n := range names {
if tag_ident.Val == n {
if tagIdent.Val == n {
found = true
break
}
@ -238,16 +239,15 @@ func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) {
for {
if p.Match(TokenSymbol, "%}") != nil {
// Okay, end the wrapping here
wrapper.Endtag = tag_ident.Val
wrapper.Endtag = tagIdent.Val
return wrapper, newParser(p.template.name, tagArgs, p.template), nil
} else {
t := p.Current()
p.Consume()
if t == nil {
return nil, nil, p.Error("Unexpected EOF.", p.last_token)
}
tagArgs = append(tagArgs, t)
}
t := p.Current()
p.Consume()
if t == nil {
return nil, nil, p.Error("Unexpected EOF.", p.lastToken)
}
tagArgs = append(tagArgs, t)
}
}
}
@ -263,5 +263,47 @@ func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) {
}
return nil, nil, p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")),
p.last_token)
p.lastToken)
}
// Skips all nodes between starting tag and "{% endtag %}"
func (p *Parser) SkipUntilTag(names ...string) *Error {
for p.Remaining() > 0 {
// New tag, check whether we have to stop wrapping here
if p.Peek(TokenSymbol, "{%") != nil {
tagIdent := p.PeekTypeN(1, TokenIdentifier)
if tagIdent != nil {
// We've found a (!) end-tag
found := false
for _, n := range names {
if tagIdent.Val == n {
found = true
break
}
}
// We only process the tag if we've found an end tag
if found {
// Okay, endtag found.
p.ConsumeN(2) // '{%' tagname
for {
if p.Match(TokenSymbol, "%}") != nil {
// Done skipping, exit.
return nil
}
}
}
}
}
t := p.Current()
p.Consume()
if t == nil {
return p.Error("Unexpected EOF.", p.lastToken)
}
}
return p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")), p.lastToken)
}

View File

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

View File

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

View File

@ -1,20 +1,29 @@
package pongo2
package pongo2_test
import (
"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{}
var _ = Suite(&IssueTestSuite{})
func (s *TestSuite) TestIssues(c *C) {
// Add a test for any issue
c.Check(42, Equals, 42)
if str != "foobarbaz" {
t.Fatalf("Expected output 'foobarbaz', but got '%s'.", str)
}
}

View File

@ -1,4 +1,4 @@
package pongo2
package pongo2_test
import (
"bytes"
@ -9,9 +9,11 @@ import (
"strings"
"testing"
"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 time2 = time.Date(2011, 03, 21, 8, 37, 56, 12, time.UTC)
@ -32,8 +34,8 @@ type comment struct {
Text string
}
func is_admin(u *user) bool {
for _, a := range admin_list {
func isAdmin(u *user) bool {
for _, a := range adminList {
if a == u.Name {
return true
}
@ -41,12 +43,12 @@ func is_admin(u *user) bool {
return false
}
func (u *user) Is_admin() *Value {
return AsValue(is_admin(u))
func (u *user) Is_admin() *pongo2.Value {
return pongo2.AsValue(isAdmin(u))
}
func (u *user) Is_admin2() bool {
return is_admin(u)
return isAdmin(u)
}
func (p *post) String() string {
@ -60,74 +62,53 @@ func (p *post) String() string {
type tagSandboxDemoTag struct {
}
func (node *tagSandboxDemoTag) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
buffer.WriteString("hello")
func (node *tagSandboxDemoTag) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error {
writer.WriteString("hello")
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
}
func BannedFilterFn(in *Value, params *Value) (*Value, *Error) {
func BannedFilterFn(in *pongo2.Value, params *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
return in, nil
}
func init() {
DefaultSet.Debug = true
pongo2.DefaultSet.Debug = true
RegisterFilter("banned_filter", BannedFilterFn)
RegisterFilter("unbanned_filter", BannedFilterFn)
RegisterTag("banned_tag", tagSandboxDemoTagParser)
RegisterTag("unbanned_tag", tagSandboxDemoTagParser)
pongo2.RegisterFilter("banned_filter", BannedFilterFn)
pongo2.RegisterFilter("unbanned_filter", BannedFilterFn)
pongo2.RegisterTag("banned_tag", tagSandboxDemoTagParser)
pongo2.RegisterTag("unbanned_tag", tagSandboxDemoTagParser)
DefaultSet.BanFilter("banned_filter")
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_*")
pongo2.DefaultSet.BanFilter("banned_filter")
pongo2.DefaultSet.BanTag("banned_tag")
f, err := ioutil.TempFile("/tmp/", "pongo2_")
if err != nil {
panic("cannot write to /tmp/")
}
f.Write([]byte("Hello from pongo2"))
DefaultSet.Globals["temp_file"] = f.Name()
pongo2.DefaultSet.Globals["temp_file"] = f.Name()
}
/*
* End setup sandbox
*/
var tplContext = Context{
var tplContext = pongo2.Context{
"number": 11,
"simple": map[string]interface{}{
"number": 42,
"name": "john doe",
"included_file": "INCLUDES.helper",
"nil": nil,
"uint": uint(8),
"float": float64(3.1415),
"str": "string",
"number": 42,
"name": "john doe",
"included_file": "INCLUDES.helper",
"included_file_not_exists": "INCLUDES.helper.not_exists",
"nil": nil,
"uint": uint(8),
"float": float64(3.1415),
"str": "string",
"chinese_hello_world": "你好世界",
"bool_true": true,
"bool_false": false,
@ -142,13 +123,23 @@ Yep!`,
"escape_js_test": `escape sequences \r\n\'\" special chars "?!=$<>`,
"one_item_list": []int{99},
"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"},
"escape_text": "This is \\a Test. \"Yep\". 'Yep'.",
"xss": "<script>alert(\"uh oh\");</script>",
"intmap": map[int]string{
1: "one",
2: "two",
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 {
return a + b
@ -167,17 +158,17 @@ Yep!`,
}
return s
},
"func_variadic_sum_int2": func(args ...*Value) *Value {
"func_variadic_sum_int2": func(args ...*pongo2.Value) *pongo2.Value {
// Create a sum
s := 0
for _, i := range args {
s += i.Integer()
}
return AsValue(s)
return pongo2.AsValue(s)
},
},
"complex": map[string]interface{}{
"is_admin": is_admin,
"is_admin": isAdmin,
"post": post{
Text: "<h2>Hello!</h2><p>Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.</p>",
Created: time2,
@ -238,10 +229,8 @@ Yep!`,
}
func TestTemplates(t *testing.T) {
debug = true
// 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")
if err != nil {
@ -249,34 +238,34 @@ func TestTemplates(t *testing.T) {
}
for idx, match := range matches {
t.Logf("[Template %3d] Testing '%s'", idx+1, match)
tpl, err := FromFile(match)
tpl, err := pongo2.FromFile(match)
if err != nil {
t.Fatalf("Error on FromFile('%s'): %s", match, err.Error())
}
test_filename := fmt.Sprintf("%s.out", match)
test_out, rerr := ioutil.ReadFile(test_filename)
testFilename := fmt.Sprintf("%s.out", match)
testOut, rerr := ioutil.ReadFile(testFilename)
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 {
t.Fatalf("Error on Execute('%s'): %s", match, err.Error())
}
if bytes.Compare(test_out, tpl_out) != 0 {
t.Logf("Template (rendered) '%s': '%s'", match, tpl_out)
err_filename := filepath.Base(fmt.Sprintf("%s.error", match))
err := ioutil.WriteFile(err_filename, []byte(tpl_out), 0600)
if bytes.Compare(testOut, tplOut) != 0 {
t.Logf("Template (rendered) '%s': '%s'", match, tplOut)
errFilename := filepath.Base(fmt.Sprintf("%s.error", match))
err := ioutil.WriteFile(errFilename, []byte(tplOut), 0600)
if err != nil {
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)
}
}
}
func TestExecutionErrors(t *testing.T) {
debug = true
//debug = true
matches, err := filepath.Glob("./template_tests/*-execution.err")
if err != nil {
@ -285,15 +274,15 @@ func TestExecutionErrors(t *testing.T) {
for idx, match := range matches {
t.Logf("[Errors %3d] Testing '%s'", idx+1, match)
test_data, err := ioutil.ReadFile(match)
tests := strings.Split(string(test_data), "\n")
testData, err := ioutil.ReadFile(match)
tests := strings.Split(string(testData), "\n")
check_filename := fmt.Sprintf("%s.out", match)
check_data, err := ioutil.ReadFile(check_filename)
checkFilename := fmt.Sprintf("%s.out", match)
checkData, err := ioutil.ReadFile(checkFilename)
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) {
t.Fatal("Template lines != Checks lines")
@ -308,11 +297,16 @@ func TestExecutionErrors(t *testing.T) {
match, idx+1)
}
tpl, err := FromString(test)
tpl, err := pongo2.FromString(test)
if err != nil {
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)
if err == nil {
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) {
debug = true
//debug = true
matches, err := filepath.Glob("./template_tests/*-compilation.err")
if err != nil {
@ -338,15 +332,15 @@ func TestCompilationErrors(t *testing.T) {
for idx, match := range matches {
t.Logf("[Errors %3d] Testing '%s'", idx+1, match)
test_data, err := ioutil.ReadFile(match)
tests := strings.Split(string(test_data), "\n")
testData, err := ioutil.ReadFile(match)
tests := strings.Split(string(testData), "\n")
check_filename := fmt.Sprintf("%s.out", match)
check_data, err := ioutil.ReadFile(check_filename)
checkFilename := fmt.Sprintf("%s.out", match)
checkData, err := ioutil.ReadFile(checkFilename)
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) {
t.Fatal("Template lines != Checks lines")
@ -361,7 +355,7 @@ func TestCompilationErrors(t *testing.T) {
match, idx+1)
}
_, err = FromString(test)
_, err = pongo2.FromString(test)
if err == nil {
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) {
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/"
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)
}
@ -405,13 +400,13 @@ func TestBaseDirectory(t *testing.T) {
}
func BenchmarkCache(b *testing.B) {
cache_set := NewSet("cache set")
cacheSet := pongo2.NewSet("cache set", pongo2.MustNewLocalFileSystemLoader(""))
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 {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
@ -419,14 +414,14 @@ func BenchmarkCache(b *testing.B) {
}
func BenchmarkCacheDebugOn(b *testing.B) {
cache_debug_set := NewSet("cache set")
cache_debug_set.Debug = true
cacheDebugSet := pongo2.NewSet("cache set", pongo2.MustNewLocalFileSystemLoader(""))
cacheDebugSet.Debug = true
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 {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
@ -434,13 +429,13 @@ func BenchmarkCacheDebugOn(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 {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err = tpl.ExecuteBytes(tplContext)
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
@ -455,12 +450,12 @@ func BenchmarkCompileAndExecuteComplexWithSandboxActive(b *testing.B) {
preloadedTpl := string(buf)
b.ResetTimer()
for i := 0; i < b.N; i++ {
tpl, err := FromString(preloadedTpl)
tpl, err := pongo2.FromString(preloadedTpl)
if err != nil {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
@ -468,14 +463,14 @@ func BenchmarkCompileAndExecuteComplexWithSandboxActive(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 {
b.Fatal(err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err := tpl.ExecuteBytes(tplContext)
err := tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
@ -484,14 +479,14 @@ func BenchmarkParallelExecuteComplexWithSandboxActive(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")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err = tpl.ExecuteBytes(tplContext)
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
@ -505,7 +500,7 @@ func BenchmarkCompileAndExecuteComplexWithoutSandbox(b *testing.B) {
}
preloadedTpl := string(buf)
s := NewSet("set without sandbox")
s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader(""))
b.ResetTimer()
for i := 0; i < b.N; i++ {
@ -514,7 +509,7 @@ func BenchmarkCompileAndExecuteComplexWithoutSandbox(b *testing.B) {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
@ -522,7 +517,7 @@ func BenchmarkCompileAndExecuteComplexWithoutSandbox(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")
if err != nil {
b.Fatal(err)
@ -530,7 +525,7 @@ func BenchmarkParallelExecuteComplexWithoutSandbox(b *testing.B) {
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err := tpl.ExecuteBytes(tplContext)
err := tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}

View File

@ -1,26 +1,26 @@
package pongo2
package pongo2_test
import (
"testing"
"github.com/flosch/pongo2"
. "gopkg.in/check.v1"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct {
tpl *Template
tpl *pongo2.Template
}
var (
_ = Suite(&TestSuite{})
test_suite2 = NewSet("test suite 2")
_ = Suite(&TestSuite{})
testSuite2 = pongo2.NewSet("test suite 2", pongo2.MustNewLocalFileSystemLoader(""))
)
func parseTemplate(s string, c Context) string {
t, err := test_suite2.FromString(s)
func parseTemplate(s string, c pongo2.Context) string {
t, err := testSuite2.FromString(s)
if err != nil {
panic(err)
}
@ -31,7 +31,7 @@ func parseTemplate(s string, c Context) string {
return out
}
func parseTemplateFn(s string, c Context) func() {
func parseTemplateFn(s string, c pongo2.Context) func() {
return func() {
parseTemplate(s, c)
}
@ -40,27 +40,64 @@ func parseTemplateFn(s string, c Context) func() {
func (s *TestSuite) TestMisc(c *C) {
// Must
// 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,
`\[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
c.Check(parseTemplateFn("", Context{"'illegal": nil}), PanicMatches, ".*not a valid identifier.*")
c.Check(parseTemplateFn("", pongo2.Context{"'illegal": nil}), PanicMatches, ".*not a valid identifier.*")
// Registers
c.Check(func() { RegisterFilter("escape", nil) }, PanicMatches, ".*is already registered.*")
c.Check(func() { RegisterTag("for", nil) }, PanicMatches, ".*is already registered.*")
c.Check(pongo2.RegisterFilter("escape", nil).Error(), Matches, ".*is already registered")
c.Check(pongo2.RegisterTag("for", nil).Error(), Matches, ".*is already registered")
// 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 {
c.Fatal(err)
}
c.Check(v.String(), Equals, "This Is A Title")
c.Check(func() {
_, err := ApplyFilter("doesnotexist", nil, nil)
_, err := pongo2.ApplyFilter("doesnotexist", nil, nil)
if err != nil {
panic(err)
}
}, 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 (
"fmt"
"github.com/juju/errors"
)
type INodeTag interface {
@ -53,80 +55,81 @@ func init() {
tags = make(map[string]*tag)
}
// Registers a new tag. If there's already a tag with the same
// name, RegisterTag will panic. You usually want to call this
// Registers a new tag. You usually want to call this
// function in the tag's init() function:
// http://golang.org/doc/effective_go.html#init
//
// See http://www.florian-schlachter.de/post/pongo2/ for more about
// writing filters and tags.
func RegisterTag(name string, parserFn TagParser) {
func RegisterTag(name string, parserFn TagParser) error {
_, existing := tags[name]
if existing {
panic(fmt.Sprintf("Tag with name '%s' is already registered.", name))
return errors.Errorf("tag with name '%s' is already registered", name)
}
tags[name] = &tag{
name: name,
parser: parserFn,
}
return nil
}
// Replaces an already registered tag with a new implementation. Use this
// function with caution since it allows you to change existing tag behaviour.
func ReplaceTag(name string, parserFn TagParser) {
func ReplaceTag(name string, parserFn TagParser) error {
_, existing := tags[name]
if !existing {
panic(fmt.Sprintf("Tag with name '%s' does not exist (therefore cannot be overridden).", name))
return errors.Errorf("tag with name '%s' does not exist (therefore cannot be overridden)", name)
}
tags[name] = &tag{
name: name,
parser: parserFn,
}
return nil
}
// Tag = "{%" IDENT ARGS "%}"
func (p *Parser) parseTagElement() (INodeTag, *Error) {
p.Consume() // consume "{%"
token_name := p.MatchType(TokenIdentifier)
tokenName := p.MatchType(TokenIdentifier)
// Check for identifier
if token_name == nil {
if tokenName == nil {
return nil, p.Error("Tag name must be an identifier.", nil)
}
// Check for the existing tag
tag, exists := tags[token_name.Val]
tag, exists := tags[tokenName.Val]
if !exists {
// Does not exists
return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", token_name.Val), token_name)
return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", tokenName.Val), tokenName)
}
// Check sandbox tag restriction
if _, is_banned := p.template.set.bannedTags[token_name.Val]; is_banned {
return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", token_name.Val), token_name)
if _, isBanned := p.template.set.bannedTags[tokenName.Val]; isBanned {
return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", tokenName.Val), tokenName)
}
args_token := make([]*Token, 0)
var argsToken []*Token
for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 {
// Add token to args
args_token = append(args_token, p.Current())
argsToken = append(argsToken, p.Current())
p.Consume() // next token
}
// EOF?
if p.Remaining() == 0 {
return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.last_token)
return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.lastToken)
}
p.Match(TokenSymbol, "%}")
arg_parser := newParser(p.name, args_token, p.template)
if len(args_token) == 0 {
argParser := newParser(p.name, argsToken, p.template)
if len(argsToken) == 0 {
// This is done to have nice EOF error messages
arg_parser.last_token = token_name
argParser.lastToken = tokenName
}
p.template.level++
defer func() { p.template.level-- }()
return tag.parser(p, token_name, arg_parser)
return tag.parser(p, tokenName, argParser)
}

View File

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

View File

@ -9,47 +9,82 @@ type tagBlockNode struct {
name string
}
func (node *tagBlockNode) getBlockWrapperByName(tpl *Template) *NodeWrapper {
func (node *tagBlockNode) getBlockWrappers(tpl *Template) []*NodeWrapper {
nodeWrappers := make([]*NodeWrapper, 0)
var t *NodeWrapper
if tpl.child != nil {
// First ask the child for the block
t = node.getBlockWrapperByName(tpl.child)
}
if t == nil {
// Child has no block, lets look up here at parent
for tpl != nil {
t = tpl.blocks[node.name]
if t != nil {
nodeWrappers = append(nodeWrappers, t)
}
tpl = tpl.child
}
return t
return nodeWrappers
}
func (node *tagBlockNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
func (node *tagBlockNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
tpl := ctx.template
if tpl == nil {
panic("internal error: tpl == nil")
}
// Determine the block to execute
block_wrapper := node.getBlockWrapperByName(tpl)
if block_wrapper == nil {
// fmt.Printf("could not find: %s\n", node.name)
return ctx.Error("internal error: block_wrapper == nil in tagBlockNode.Execute()", nil)
blockWrappers := node.getBlockWrappers(tpl)
lenBlockWrappers := len(blockWrappers)
if lenBlockWrappers == 0 {
return ctx.Error("internal error: len(block_wrappers) == 0 in tagBlockNode.Execute()", nil)
}
err := block_wrapper.Execute(ctx, buffer)
blockWrapper := blockWrappers[lenBlockWrappers-1]
ctx.Private["block"] = tagBlockInformation{
ctx: ctx,
wrappers: blockWrappers[0 : lenBlockWrappers-1],
}
err := blockWrapper.Execute(ctx, writer)
if err != nil {
return err
}
// TODO: Add support for {{ block.super }}
return nil
}
type tagBlockInformation struct {
ctx *ExecutionContext
wrappers []*NodeWrapper
}
func (t tagBlockInformation) Super() string {
lenWrappers := len(t.wrappers)
if lenWrappers == 0 {
return ""
}
superCtx := NewChildExecutionContext(t.ctx)
superCtx.Private["block"] = tagBlockInformation{
ctx: t.ctx,
wrappers: t.wrappers[0 : lenWrappers-1],
}
blockWrapper := t.wrappers[lenWrappers-1]
buf := bytes.NewBufferString("")
err := blockWrapper.Execute(superCtx, &templateWriter{buf})
if err != nil {
return ""
}
return buf.String()
}
func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
if arguments.Count() == 0 {
return nil, arguments.Error("Tag 'block' requires an identifier.", nil)
}
name_token := arguments.MatchType(TokenIdentifier)
if name_token == nil {
nameToken := arguments.MatchType(TokenIdentifier)
if nameToken == nil {
return nil, arguments.Error("First argument for tag 'block' must be an identifier.", nil)
}
@ -62,15 +97,15 @@ func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er
return nil, err
}
if endtagargs.Remaining() > 0 {
endtagname_token := endtagargs.MatchType(TokenIdentifier)
if endtagname_token != nil {
if endtagname_token.Val != name_token.Val {
endtagnameToken := endtagargs.MatchType(TokenIdentifier)
if endtagnameToken != nil {
if endtagnameToken.Val != nameToken.Val {
return nil, endtagargs.Error(fmt.Sprintf("Name for 'endblock' must equal to 'block'-tag's name ('%s' != '%s').",
name_token.Val, endtagname_token.Val), nil)
nameToken.Val, endtagnameToken.Val), nil)
}
}
if endtagname_token == nil || endtagargs.Remaining() > 0 {
if endtagnameToken == nil || endtagargs.Remaining() > 0 {
return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endblock'.", nil)
}
}
@ -79,14 +114,14 @@ func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er
if tpl == nil {
panic("internal error: tpl == nil")
}
_, has_block := tpl.blocks[name_token.Val]
if !has_block {
tpl.blocks[name_token.Val] = wrapper
_, hasBlock := tpl.blocks[nameToken.Val]
if !hasBlock {
tpl.blocks[nameToken.Val] = wrapper
} else {
return nil, arguments.Error(fmt.Sprintf("Block named '%s' already defined", name_token.Val), nil)
return nil, arguments.Error(fmt.Sprintf("Block named '%s' already defined", nameToken.Val), nil)
}
return &tagBlockNode{name: name_token.Val}, nil
return &tagBlockNode{name: nameToken.Val}, nil
}
func init() {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,11 @@
package pongo2
import (
"bytes"
)
type tagFirstofNode struct {
position *Token
args []IEvaluator
}
func (node *tagFirstofNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
func (node *tagFirstofNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
for _, arg := range node.args {
val, err := arg.Evaluate(ctx)
if err != nil {
@ -24,7 +20,7 @@ func (node *tagFirstofNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer)
}
}
buffer.WriteString(val.String())
writer.WriteString(val.String())
return nil
}
}
@ -33,7 +29,7 @@ func (node *tagFirstofNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer)
}
func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
firstof_node := &tagFirstofNode{
firstofNode := &tagFirstofNode{
position: start,
}
@ -42,10 +38,10 @@ func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *
if err != nil {
return nil, err
}
firstof_node.args = append(firstof_node.args, node)
firstofNode.args = append(firstofNode.args, node)
}
return firstof_node, nil
return firstofNode, nil
}
func init() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,16 +6,16 @@ import (
)
type tagMacroNode struct {
position *Token
name string
args_order []string
args map[string]IEvaluator
exported bool
position *Token
name string
argsOrder []string
args map[string]IEvaluator
exported bool
wrapper *NodeWrapper
}
func (node *tagMacroNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
func (node *tagMacroNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
ctx.Private[node.name] = func(args ...*Value) *Value {
return node.call(ctx, args...)
}
@ -24,28 +24,28 @@ func (node *tagMacroNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *
}
func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value {
args_ctx := make(Context)
argsCtx := make(Context)
for k, v := range node.args {
if v == nil {
// User did not provided a default value
args_ctx[k] = nil
argsCtx[k] = nil
} else {
// Evaluate the default value
value_expr, err := v.Evaluate(ctx)
valueExpr, err := v.Evaluate(ctx)
if err != nil {
ctx.Logf(err.Error())
return AsSafeValue(err.Error())
}
args_ctx[k] = value_expr
argsCtx[k] = valueExpr
}
}
if len(args) > len(node.args_order) {
if len(args) > len(node.argsOrder) {
// Too many arguments, we're ignoring them and just logging into debug mode.
err := ctx.Error(fmt.Sprintf("Macro '%s' called with too many arguments (%d instead of %d).",
node.name, len(args), len(node.args_order)), nil).updateFromTokenIfNeeded(ctx.template, node.position)
node.name, len(args), len(node.argsOrder)), nil).updateFromTokenIfNeeded(ctx.template, node.position)
ctx.Logf(err.Error()) // TODO: This is a workaround, because the error is not returned yet to the Execution()-methods
return AsSafeValue(err.Error())
@ -55,10 +55,10 @@ func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value {
macroCtx := NewChildExecutionContext(ctx)
// Register all arguments in the private context
macroCtx.Private.Update(args_ctx)
macroCtx.Private.Update(argsCtx)
for idx, arg_value := range args {
macroCtx.Private[node.args_order[idx]] = arg_value.Interface()
for idx, argValue := range args {
macroCtx.Private[node.argsOrder[idx]] = argValue.Interface()
}
var b bytes.Buffer
@ -71,38 +71,38 @@ func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value {
}
func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
macro_node := &tagMacroNode{
macroNode := &tagMacroNode{
position: start,
args: make(map[string]IEvaluator),
}
name_token := arguments.MatchType(TokenIdentifier)
if name_token == nil {
nameToken := arguments.MatchType(TokenIdentifier)
if nameToken == nil {
return nil, arguments.Error("Macro-tag needs at least an identifier as name.", nil)
}
macro_node.name = name_token.Val
macroNode.name = nameToken.Val
if arguments.MatchOne(TokenSymbol, "(") == nil {
return nil, arguments.Error("Expected '('.", nil)
}
for arguments.Match(TokenSymbol, ")") == nil {
arg_name_token := arguments.MatchType(TokenIdentifier)
if arg_name_token == nil {
argNameToken := arguments.MatchType(TokenIdentifier)
if argNameToken == nil {
return nil, arguments.Error("Expected argument name as identifier.", nil)
}
macro_node.args_order = append(macro_node.args_order, arg_name_token.Val)
macroNode.argsOrder = append(macroNode.argsOrder, argNameToken.Val)
if arguments.Match(TokenSymbol, "=") != nil {
// Default expression follows
arg_default_expr, err := arguments.ParseExpression()
argDefaultExpr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
macro_node.args[arg_name_token.Val] = arg_default_expr
macroNode.args[argNameToken.Val] = argDefaultExpr
} else {
// No default expression
macro_node.args[arg_name_token.Val] = nil
macroNode.args[argNameToken.Val] = nil
}
if arguments.Match(TokenSymbol, ")") != nil {
@ -114,7 +114,7 @@ func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er
}
if arguments.Match(TokenKeyword, "export") != nil {
macro_node.exported = true
macroNode.exported = true
}
if arguments.Remaining() > 0 {
@ -126,22 +126,22 @@ func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er
if err != nil {
return nil, err
}
macro_node.wrapper = wrapper
macroNode.wrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
if macro_node.exported {
if macroNode.exported {
// Now register the macro if it wants to be exported
_, has := doc.template.exported_macros[macro_node.name]
_, has := doc.template.exportedMacros[macroNode.name]
if has {
return nil, doc.Error(fmt.Sprintf("Another macro with name '%s' already exported.", macro_node.name), start)
return nil, doc.Error(fmt.Sprintf("another macro with name '%s' already exported", macroNode.name), start)
}
doc.template.exported_macros[macro_node.name] = macro_node
doc.template.exportedMacros[macroNode.name] = macroNode
}
return macro_node, nil
return macroNode, nil
}
func init() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +1,16 @@
package pongo2
import (
"bytes"
)
type tagWithNode struct {
with_pairs map[string]IEvaluator
wrapper *NodeWrapper
withPairs map[string]IEvaluator
wrapper *NodeWrapper
}
func (node *tagWithNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
func (node *tagWithNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
//new context for block
withctx := NewChildExecutionContext(ctx)
// Put all custom with-pairs into the context
for key, value := range node.with_pairs {
for key, value := range node.withPairs {
val, err := value.Evaluate(ctx)
if err != nil {
return err
@ -22,12 +18,12 @@ func (node *tagWithNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *E
withctx.Private[key] = val
}
return node.wrapper.Execute(withctx, buffer)
return node.wrapper.Execute(withctx, writer)
}
func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
with_node := &tagWithNode{
with_pairs: make(map[string]IEvaluator),
withNode := &tagWithNode{
withPairs: make(map[string]IEvaluator),
}
if arguments.Count() == 0 {
@ -38,7 +34,7 @@ func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Err
if err != nil {
return nil, err
}
with_node.wrapper = wrapper
withNode.wrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
@ -46,45 +42,45 @@ func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Err
// Scan through all arguments to see which style the user uses (old or new style).
// If we find any "as" keyword we will enforce old style; otherwise we will use new style.
old_style := false // by default we're using the new_style
oldStyle := false // by default we're using the new_style
for i := 0; i < arguments.Count(); i++ {
if arguments.PeekN(i, TokenKeyword, "as") != nil {
old_style = true
oldStyle = true
break
}
}
for arguments.Remaining() > 0 {
if old_style {
value_expr, err := arguments.ParseExpression()
if oldStyle {
valueExpr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
if arguments.Match(TokenKeyword, "as") == nil {
return nil, arguments.Error("Expected 'as' keyword.", nil)
}
key_token := arguments.MatchType(TokenIdentifier)
if key_token == nil {
keyToken := arguments.MatchType(TokenIdentifier)
if keyToken == nil {
return nil, arguments.Error("Expected an identifier", nil)
}
with_node.with_pairs[key_token.Val] = value_expr
withNode.withPairs[keyToken.Val] = valueExpr
} else {
key_token := arguments.MatchType(TokenIdentifier)
if key_token == nil {
keyToken := arguments.MatchType(TokenIdentifier)
if keyToken == nil {
return nil, arguments.Error("Expected an identifier", nil)
}
if arguments.Match(TokenSymbol, "=") == nil {
return nil, arguments.Error("Expected '='.", nil)
}
value_expr, err := arguments.ParseExpression()
valueExpr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
with_node.with_pairs[key_token.Val] = value_expr
withNode.withPairs[keyToken.Val] = valueExpr
}
}
return with_node, nil
return withNode, nil
}
func init() {

View File

@ -2,52 +2,72 @@ package pongo2
import (
"bytes"
"fmt"
"io"
"github.com/juju/errors"
)
type TemplateWriter interface {
io.Writer
WriteString(string) (int, error)
}
type templateWriter struct {
w io.Writer
}
func (tw *templateWriter) WriteString(s string) (int, error) {
return tw.w.Write([]byte(s))
}
func (tw *templateWriter) Write(b []byte) (int, error) {
return tw.w.Write(b)
}
type Template struct {
set *TemplateSet
// Input
is_tpl_string bool
name string
tpl string
size int
isTplString bool
name string
tpl string
size int
// Calculation
tokens []*Token
parser *Parser
// first come, first serve (it's important to not override existing entries in here)
level int
parent *Template
child *Template
blocks map[string]*NodeWrapper
exported_macros map[string]*tagMacroNode
level int
parent *Template
child *Template
blocks map[string]*NodeWrapper
exportedMacros map[string]*tagMacroNode
// Output
root *nodeDocument
}
func newTemplateString(set *TemplateSet, tpl string) (*Template, error) {
func newTemplateString(set *TemplateSet, tpl []byte) (*Template, error) {
return newTemplate(set, "<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
t := &Template{
set: set,
is_tpl_string: is_tpl_string,
name: name,
tpl: tpl,
size: len(tpl),
blocks: make(map[string]*NodeWrapper),
exported_macros: make(map[string]*tagMacroNode),
set: set,
isTplString: isTplString,
name: name,
tpl: strTpl,
size: len(strTpl),
blocks: make(map[string]*NodeWrapper),
exportedMacros: make(map[string]*tagMacroNode),
}
// Tokenize it
tokens, err := lex(name, tpl)
tokens, err := lex(name, strTpl)
if err != nil {
return nil, err
}
@ -67,11 +87,7 @@ func newTemplate(set *TemplateSet, name string, is_tpl_string bool, tpl string)
return t, nil
}
func (tpl *Template) execute(context Context) (*bytes.Buffer, error) {
// Create output buffer
// We assume that the rendered template will be 30% larger
buffer := bytes.NewBuffer(make([]byte, 0, int(float64(tpl.size)*1.3)))
func (tpl *Template) execute(context Context, writer TemplateWriter) error {
// Determine the parent to be executed (for template inheritance)
parent := tpl
for parent.parent != nil {
@ -89,17 +105,17 @@ func (tpl *Template) execute(context Context) (*bytes.Buffer, error) {
// Check for context name syntax
err := newContext.checkForValidIdentifiers()
if err != nil {
return nil, err
return err
}
// Check for clashes with macro names
for k, _ := range newContext {
_, has := tpl.exported_macros[k]
for k := range newContext {
_, has := tpl.exportedMacros[k]
if has {
return nil, &Error{
Filename: tpl.name,
Sender: "execution",
ErrorMsg: fmt.Sprintf("Context key name '%s' clashes with macro '%s'.", k, k),
return &Error{
Filename: tpl.name,
Sender: "execution",
OrigError: errors.Errorf("context key name '%s' clashes with macro '%s'", k, k),
}
}
}
@ -110,8 +126,22 @@ func (tpl *Template) execute(context Context) (*bytes.Buffer, error) {
ctx := newExecutionContext(parent, newContext)
// Run the selected document
err := parent.root.Execute(ctx, buffer)
if err != nil {
if err := parent.root.Execute(ctx, writer); err != nil {
return err
}
return nil
}
func (tpl *Template) newTemplateWriterAndExecute(context Context, writer io.Writer) error {
return tpl.execute(context, &templateWriter{w: writer})
}
func (tpl *Template) newBufferAndExecute(context Context) (*bytes.Buffer, error) {
// Create output buffer
// We assume that the rendered template will be 30% larger
buffer := bytes.NewBuffer(make([]byte, 0, int(float64(tpl.size)*1.3)))
if err := tpl.execute(context, buffer); err != nil {
return nil, err
}
return buffer, nil
@ -121,30 +151,30 @@ func (tpl *Template) execute(context Context) (*bytes.Buffer, error) {
// on success. Context can be nil. Nothing is written on error; instead the error
// is being returned.
func (tpl *Template) ExecuteWriter(context Context, writer io.Writer) error {
buffer, err := tpl.execute(context)
buf, err := tpl.newBufferAndExecute(context)
if err != nil {
return err
}
l := buffer.Len()
n, werr := buffer.WriteTo(writer)
if int(n) != l {
panic(fmt.Sprintf("error on writing template: n(%d) != buffer.Len(%d)", n, l))
}
if werr != nil {
return &Error{
Filename: tpl.name,
Sender: "execution",
ErrorMsg: werr.Error(),
}
_, err = buf.WriteTo(writer)
if err != nil {
return err
}
return nil
}
// Same as ExecuteWriter. The only difference between both functions is that
// this function might already have written parts of the generated template in the
// case of an execution error because there's no intermediate buffer involved for
// performance reasons. This is handy if you need high performance template
// generation or if you want to manage your own pool of buffers.
func (tpl *Template) ExecuteWriterUnbuffered(context Context, writer io.Writer) error {
return tpl.newTemplateWriterAndExecute(context, writer)
}
// Executes the template and returns the rendered template as a []byte
func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) {
// Execute template
buffer, err := tpl.execute(context)
buffer, err := tpl.newBufferAndExecute(context)
if err != nil {
return nil, err
}
@ -154,7 +184,7 @@ func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) {
// Executes the template and returns the rendered template as a string
func (tpl *Template) Execute(context Context) (string, error) {
// Execute template
buffer, err := tpl.execute(context)
buffer, err := tpl.newBufferAndExecute(context)
if err != nil {
return "", err
}

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 (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"sync"
"github.com/juju/errors"
)
// A template set allows you to create your own group of templates with their own global context (which is shared
// among all members of the set), their own configuration (like a specific base directory) and their own sandbox.
// It's useful for a separation of different kind of templates (e. g. web templates vs. mail templates).
// TemplateLoader allows to implement a virtual file system.
type TemplateLoader interface {
// Abs calculates the path to a given template. Whenever a path must be resolved
// due to an import from another template, the base equals the parent template's path.
Abs(base, name string) string
// Get returns an io.Reader where the template's content can be read from.
Get(path string) (io.Reader, error)
}
// TemplateSet allows you to create your own group of templates with their own
// global context (which is shared among all members of the set) and their own
// configuration.
// It's useful for a separation of different kind of templates
// (e. g. web templates vs. mail templates).
type TemplateSet struct {
name string
name string
loader TemplateLoader
// Globals will be provided to all templates created within this template set
Globals Context
// If debug is true (default false), ExecutionContext.Logf() will work and output to STDOUT. Furthermore,
// FromCache() won't cache the templates. Make sure to synchronize the access to it in case you're changing this
// If debug is true (default false), ExecutionContext.Logf() will work and output
// to STDOUT. Furthermore, FromCache() won't cache the templates.
// Make sure to synchronize the access to it in case you're changing this
// variable during program execution (and template compilation/execution).
Debug bool
// Base directory: If you set the base directory (string is non-empty), all filename lookups in tags/filters are
// relative to this directory. If it's empty, all lookups are relative to the current filename which is importing.
baseDirectory string
// Sandbox features
// - Limit access to directories (using SandboxDirectories)
// - Disallow access to specific tags and/or filters (using BanTag() and BanFilter())
//
// You can limit file accesses (for all tags/filters which are using pongo2's file resolver technique)
// to these sandbox directories. All default pongo2 filters/tags are respecting these restrictions.
// For example, if you only have your base directory in the list, a {% ssi "/etc/passwd" %} will not work.
// No items in SandboxDirectories means no restrictions at all.
//
// For efficiency reasons you can ban tags/filters only *before* you have added your first
// template to the set (restrictions are statically checked). After you added one, it's not possible anymore
// (for your personal security).
//
// SandboxDirectories can be changed at runtime. Please synchronize the access to it if you need to change it
// after you've added your first template to the set. You *must* use this match pattern for your directories:
// http://golang.org/pkg/path/filepath/#Match
SandboxDirectories []string
// For efficiency reasons you can ban tags/filters only *before* you have
// added your first template to the set (restrictions are statically checked).
// After you added one, it's not possible anymore (for your personal security).
firstTemplateCreated bool
bannedTags map[string]bool
bannedFilters map[string]bool
@ -53,11 +54,13 @@ type TemplateSet struct {
templateCacheMutex sync.Mutex
}
// Create your own template sets to separate different kind of templates (e. g. web from mail templates) with
// different globals or other configurations (like base directories).
func NewSet(name string) *TemplateSet {
// NewSet can be used to create sets with different kind of templates
// (e. g. web from mail templates), with different globals or
// other configurations.
func NewSet(name string, loader TemplateLoader) *TemplateSet {
return &TemplateSet{
name: name,
loader: loader,
Globals: make(Context),
bannedTags: make(map[string]bool),
bannedFilters: make(map[string]bool),
@ -65,151 +68,157 @@ func NewSet(name string) *TemplateSet {
}
}
// Use this function to set your template set's base directory. This directory will be used for any relative
// path in filters, tags and From*-functions to determine your template.
func (set *TemplateSet) SetBaseDirectory(name string) error {
// Make the path absolute
if !filepath.IsAbs(name) {
abs, err := filepath.Abs(name)
if err != nil {
return err
}
name = abs
func (set *TemplateSet) resolveFilename(tpl *Template, path string) string {
name := ""
if tpl != nil && tpl.isTplString {
return path
}
// Check for existence
fi, err := os.Stat(name)
if err != nil {
return err
if tpl != nil {
name = tpl.name
}
if !fi.IsDir() {
return fmt.Errorf("The given path '%s' is not a directory.")
}
set.baseDirectory = name
return nil
return set.loader.Abs(name, path)
}
func (set *TemplateSet) BaseDirectory() string {
return set.baseDirectory
}
// Ban a specific tag for this template set. See more in the documentation for TemplateSet.
func (set *TemplateSet) BanTag(name string) {
// BanTag bans a specific tag for this template set. See more in the documentation for TemplateSet.
func (set *TemplateSet) BanTag(name string) error {
_, has := tags[name]
if !has {
panic(fmt.Sprintf("Tag '%s' not found.", name))
return errors.Errorf("tag '%s' not found", name)
}
if set.firstTemplateCreated {
panic("You cannot ban any tags after you've added your first template to your template set.")
return errors.New("you cannot ban any tags after you've added your first template to your template set")
}
_, has = set.bannedTags[name]
if has {
panic(fmt.Sprintf("Tag '%s' is already banned.", name))
return errors.Errorf("tag '%s' is already banned", name)
}
set.bannedTags[name] = true
return nil
}
// Ban a specific filter for this template set. See more in the documentation for TemplateSet.
func (set *TemplateSet) BanFilter(name string) {
// BanFilter bans a specific filter for this template set. See more in the documentation for TemplateSet.
func (set *TemplateSet) BanFilter(name string) error {
_, has := filters[name]
if !has {
panic(fmt.Sprintf("Filter '%s' not found.", name))
return errors.Errorf("filter '%s' not found", name)
}
if set.firstTemplateCreated {
panic("You cannot ban any filters after you've added your first template to your template set.")
return errors.New("you cannot ban any filters after you've added your first template to your template set")
}
_, has = set.bannedFilters[name]
if has {
panic(fmt.Sprintf("Filter '%s' is already banned.", name))
return errors.Errorf("filter '%s' is already banned", name)
}
set.bannedFilters[name] = true
return nil
}
// FromCache() is a convenient method to cache templates. It is thread-safe
// FromCache is a convenient method to cache templates. It is thread-safe
// and will only compile the template associated with a filename once.
// If TemplateSet.Debug is true (for example during development phase),
// FromCache() will not cache the template and instead recompile it on any
// call (to make changes to a template live instantaneously).
// Like FromFile(), FromCache() takes a relative path to a set base directory.
// Sandbox restrictions apply (if given).
func (set *TemplateSet) FromCache(filename string) (*Template, error) {
if set.Debug {
// Recompile on any request
return set.FromFile(filename)
} else {
// Cache the template
cleaned_filename := set.resolveFilename(nil, filename)
}
// Cache the template
cleanedFilename := set.resolveFilename(nil, filename)
set.templateCacheMutex.Lock()
defer set.templateCacheMutex.Unlock()
set.templateCacheMutex.Lock()
defer set.templateCacheMutex.Unlock()
tpl, has := set.templateCache[cleaned_filename]
tpl, has := set.templateCache[cleanedFilename]
// Cache miss
if !has {
tpl, err := set.FromFile(cleaned_filename)
if err != nil {
return nil, err
}
set.templateCache[cleaned_filename] = tpl
return tpl, nil
// Cache miss
if !has {
tpl, err := set.FromFile(cleanedFilename)
if err != nil {
return nil, err
}
// Cache hit
set.templateCache[cleanedFilename] = tpl
return tpl, nil
}
// Cache hit
return tpl, nil
}
// Loads a template from string and returns a Template instance.
// FromString loads a template from string and returns a Template instance.
func (set *TemplateSet) FromString(tpl string) (*Template, error) {
set.firstTemplateCreated = true
return newTemplateString(set, []byte(tpl))
}
// FromBytes loads a template from bytes and returns a Template instance.
func (set *TemplateSet) FromBytes(tpl []byte) (*Template, error) {
set.firstTemplateCreated = true
return newTemplateString(set, tpl)
}
// Loads a template from a filename and returns a Template instance.
// If a base directory is set, the filename must be either relative to it
// or be an absolute path. Sandbox restrictions (SandboxDirectories) apply
// if given.
// FromFile loads a template from a filename and returns a Template instance.
func (set *TemplateSet) FromFile(filename string) (*Template, error) {
set.firstTemplateCreated = true
buf, err := ioutil.ReadFile(set.resolveFilename(nil, filename))
fd, err := set.loader.Get(set.resolveFilename(nil, filename))
if err != nil {
return nil, &Error{
Filename: filename,
Sender: "fromfile",
ErrorMsg: err.Error(),
Filename: filename,
Sender: "fromfile",
OrigError: err,
}
}
return newTemplate(set, filename, false, string(buf))
buf, err := ioutil.ReadAll(fd)
if err != nil {
return nil, &Error{
Filename: filename,
Sender: "fromfile",
OrigError: err,
}
}
return newTemplate(set, filename, false, buf)
}
// Shortcut; renders a template string directly. Panics when providing a
// malformed template or an error occurs during execution.
func (set *TemplateSet) RenderTemplateString(s string, ctx Context) string {
// RenderTemplateString is a shortcut and renders a template string directly.
func (set *TemplateSet) RenderTemplateString(s string, ctx Context) (string, error) {
set.firstTemplateCreated = true
tpl := Must(set.FromString(s))
result, err := tpl.Execute(ctx)
if err != nil {
panic(err)
return "", err
}
return result
return result, nil
}
// Shortcut; renders a template file directly. Panics when providing a
// malformed template or an error occurs during execution.
func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) string {
// RenderTemplateBytes is a shortcut and renders template bytes directly.
func (set *TemplateSet) RenderTemplateBytes(b []byte, ctx Context) (string, error) {
set.firstTemplateCreated = true
tpl := Must(set.FromBytes(b))
result, err := tpl.Execute(ctx)
if err != nil {
return "", err
}
return result, nil
}
// RenderTemplateFile is a shortcut and renders a template file directly.
func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) (string, error) {
set.firstTemplateCreated = true
tpl := Must(set.FromFile(fn))
result, err := tpl.Execute(ctx)
if err != nil {
panic(err)
return "", err
}
return result
return result, nil
}
func (set *TemplateSet) logf(format string, args ...interface{}) {
@ -218,58 +227,6 @@ func (set *TemplateSet) logf(format string, args ...interface{}) {
}
}
// Resolves a filename relative to the base directory. Absolute paths are allowed.
// If sandbox restrictions are given (SandboxDirectories), they will be respected and checked.
// On sandbox restriction violation, resolveFilename() panics.
func (set *TemplateSet) resolveFilename(tpl *Template, filename string) (resolved_path string) {
if len(set.SandboxDirectories) > 0 {
defer func() {
// Remove any ".." or other crap
resolved_path = filepath.Clean(resolved_path)
// Make the path absolute
abs_path, err := filepath.Abs(resolved_path)
if err != nil {
panic(err)
}
resolved_path = abs_path
// Check against the sandbox directories (once one pattern matches, we're done and can allow it)
for _, pattern := range set.SandboxDirectories {
matched, err := filepath.Match(pattern, resolved_path)
if err != nil {
panic("Wrong sandbox directory match pattern (see http://golang.org/pkg/path/filepath/#Match).")
}
if matched {
// OK!
return
}
}
// No pattern matched, we have to log+deny the request
set.logf("Access attempt outside of the sandbox directories (blocked): '%s'", resolved_path)
resolved_path = ""
}()
}
if filepath.IsAbs(filename) {
return filename
}
if set.baseDirectory == "" {
if tpl != nil {
if tpl.is_tpl_string {
return filename
}
base := filepath.Dir(tpl.name)
return filepath.Join(base, filename)
}
return filename
} else {
return filepath.Join(set.baseDirectory, filename)
}
}
// Logging function (internally used)
func logf(format string, items ...interface{}) {
if debug {
@ -279,13 +236,18 @@ func logf(format string, items ...interface{}) {
var (
debug bool // internal debugging
logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags)
logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags|log.Lshortfile)
// Creating a default set
DefaultSet = NewSet("default")
// DefaultLoader allows the default un-sandboxed access to the local file
// system and is being used by the DefaultSet.
DefaultLoader = MustNewLocalFileSystemLoader("")
// DefaultSet is a set created for you for convinience reasons.
DefaultSet = NewSet("default", DefaultLoader)
// Methods on the default set
FromString = DefaultSet.FromString
FromBytes = DefaultSet.FromBytes
FromFile = DefaultSet.FromFile
FromCache = DefaultSet.FromCache
RenderTemplateString = DefaultSet.RenderTemplateString

View File

@ -3,6 +3,7 @@ package pongo2
import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
)
@ -12,7 +13,7 @@ type Value struct {
safe bool // used to indicate whether a Value needs explicit escaping in the template
}
// Converts any given value to a pongo2.Value
// AsValue converts any given value to a pongo2.Value
// Usually being used within own functions passed to a template
// through a Context or within filter functions.
//
@ -24,7 +25,7 @@ func AsValue(i interface{}) *Value {
}
}
// Like AsValue, but does not apply the 'escape' filter.
// AsSafeValue works like AsValue, but does not apply the 'escape' filter.
func AsSafeValue(i interface{}) *Value {
return &Value{
val: reflect.ValueOf(i),
@ -39,23 +40,23 @@ func (v *Value) getResolvedValue() reflect.Value {
return v.val
}
// Checks whether the underlying value is a string
// IsString checks whether the underlying value is a string
func (v *Value) IsString() bool {
return v.getResolvedValue().Kind() == reflect.String
}
// Checks whether the underlying value is a bool
// IsBool checks whether the underlying value is a bool
func (v *Value) IsBool() bool {
return v.getResolvedValue().Kind() == reflect.Bool
}
// Checks whether the underlying value is a float
// IsFloat checks whether the underlying value is a float
func (v *Value) IsFloat() bool {
return v.getResolvedValue().Kind() == reflect.Float32 ||
v.getResolvedValue().Kind() == reflect.Float64
}
// Checks whether the underlying value is an integer
// IsInteger checks whether the underlying value is an integer
func (v *Value) IsInteger() bool {
return v.getResolvedValue().Kind() == reflect.Int ||
v.getResolvedValue().Kind() == reflect.Int8 ||
@ -69,19 +70,19 @@ func (v *Value) IsInteger() bool {
v.getResolvedValue().Kind() == reflect.Uint64
}
// Checks whether the underlying value is either an integer
// IsNumber checks whether the underlying value is either an integer
// or a float.
func (v *Value) IsNumber() bool {
return v.IsInteger() || v.IsFloat()
}
// Checks whether the underlying value is NIL
// IsNil checks whether the underlying value is NIL
func (v *Value) IsNil() bool {
//fmt.Printf("%+v\n", v.getResolvedValue().Type().String())
return !v.getResolvedValue().IsValid()
}
// Returns a string for the underlying value. If this value is not
// String returns a string for the underlying value. If this value is not
// of type string, pongo2 tries to convert it. Currently the following
// types for underlying values are supported:
//
@ -111,9 +112,8 @@ func (v *Value) String() string {
case reflect.Bool:
if v.Bool() {
return "True"
} else {
return "False"
}
return "False"
case reflect.Struct:
if t, ok := v.Interface().(fmt.Stringer); ok {
return t.String()
@ -124,7 +124,7 @@ func (v *Value) String() string {
return v.getResolvedValue().String()
}
// Returns the underlying value as an integer (converts the underlying
// Integer returns the underlying value as an integer (converts the underlying
// value, if necessary). If it's not possible to convert the underlying value,
// it will return 0.
func (v *Value) Integer() int {
@ -148,7 +148,7 @@ func (v *Value) Integer() int {
}
}
// Returns the underlying value as a float (converts the underlying
// Float returns the underlying value as a float (converts the underlying
// value, if necessary). If it's not possible to convert the underlying value,
// it will return 0.0.
func (v *Value) Float() float64 {
@ -172,7 +172,7 @@ func (v *Value) Float() float64 {
}
}
// Returns the underlying value as bool. If the value is not bool, false
// Bool returns the underlying value as bool. If the value is not bool, false
// will always be returned. If you're looking for true/false-evaluation of the
// underlying value, have a look on the IsTrue()-function.
func (v *Value) Bool() bool {
@ -185,7 +185,7 @@ func (v *Value) Bool() bool {
}
}
// Tries to evaluate the underlying value the Pythonic-way:
// IsTrue tries to evaluate the underlying value the Pythonic-way:
//
// Returns TRUE in one the following cases:
//
@ -217,7 +217,7 @@ func (v *Value) IsTrue() bool {
}
}
// Tries to negate the underlying value. It's mainly used for
// Negate tries to negate the underlying value. It's mainly used for
// the NOT-operator and in conjunction with a call to
// return_value.IsTrue() afterwards.
//
@ -229,26 +229,26 @@ func (v *Value) Negate() *Value {
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if v.Integer() != 0 {
return AsValue(0)
} else {
return AsValue(1)
}
return AsValue(1)
case reflect.Float32, reflect.Float64:
if v.Float() != 0.0 {
return AsValue(float64(0.0))
} else {
return AsValue(float64(1.1))
}
return AsValue(float64(1.1))
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
return AsValue(v.getResolvedValue().Len() == 0)
case reflect.Bool:
return AsValue(!v.getResolvedValue().Bool())
case reflect.Struct:
return AsValue(false)
default:
logf("Value.IsTrue() not available for type: %s\n", v.getResolvedValue().Kind().String())
return AsValue(true)
}
}
// Returns the length for an array, chan, map, slice or string.
// Len returns the length for an array, chan, map, slice or string.
// Otherwise it will return 0.
func (v *Value) Len() int {
switch v.getResolvedValue().Kind() {
@ -263,7 +263,7 @@ func (v *Value) Len() int {
}
}
// Slices an array, slice or string. Otherwise it will
// Slice slices an array, slice or string. Otherwise it will
// return an empty []int.
func (v *Value) Slice(i, j int) *Value {
switch v.getResolvedValue().Kind() {
@ -278,7 +278,7 @@ func (v *Value) Slice(i, j int) *Value {
}
}
// Get the i-th item of an array, slice or string. Otherwise
// Index gets the i-th item of an array, slice or string. Otherwise
// it will return NIL.
func (v *Value) Index(i int) *Value {
switch v.getResolvedValue().Kind() {
@ -301,7 +301,7 @@ func (v *Value) Index(i int) *Value {
}
}
// Checks whether the underlying value (which must be of type struct, map,
// Contains checks whether the underlying value (which must be of type struct, map,
// string, array or slice) contains of another Value (e. g. used to check
// whether a struct contains of a specific field or a map contains a specific key).
//
@ -310,25 +310,32 @@ func (v *Value) Index(i int) *Value {
func (v *Value) Contains(other *Value) bool {
switch v.getResolvedValue().Kind() {
case reflect.Struct:
field_value := v.getResolvedValue().FieldByName(other.String())
return field_value.IsValid()
fieldValue := v.getResolvedValue().FieldByName(other.String())
return fieldValue.IsValid()
case reflect.Map:
var map_value reflect.Value
var mapValue reflect.Value
switch other.Interface().(type) {
case int:
map_value = v.getResolvedValue().MapIndex(other.getResolvedValue())
mapValue = v.getResolvedValue().MapIndex(other.getResolvedValue())
case string:
map_value = v.getResolvedValue().MapIndex(other.getResolvedValue())
mapValue = v.getResolvedValue().MapIndex(other.getResolvedValue())
default:
logf("Value.Contains() does not support lookup type '%s'\n", other.getResolvedValue().Kind().String())
return false
}
return map_value.IsValid()
return mapValue.IsValid()
case reflect.String:
return strings.Contains(v.getResolvedValue().String(), other.String())
// TODO: reflect.Array, reflect.Slice
case reflect.Slice, reflect.Array:
for i := 0; i < v.getResolvedValue().Len(); i++ {
item := v.getResolvedValue().Index(i)
if other.Interface() == item.Interface() {
return true
}
}
return false
default:
logf("Value.Contains() not available for type: %s\n", v.getResolvedValue().Kind().String())
@ -336,7 +343,7 @@ func (v *Value) Contains(other *Value) bool {
}
}
// Checks whether the underlying value is of type array, slice or string.
// CanSlice checks whether the underlying value is of type array, slice or string.
// You normally would use CanSlice() before using the Slice() operation.
func (v *Value) CanSlice() bool {
switch v.getResolvedValue().Kind() {
@ -346,7 +353,7 @@ func (v *Value) CanSlice() bool {
return false
}
// Iterates over a map, array, slice or a string. It calls the
// Iterate iterates over a map, array, slice or a string. It calls the
// function's first argument for every value with the following arguments:
//
// idx current 0-index
@ -357,16 +364,23 @@ func (v *Value) CanSlice() bool {
// If the underlying value has no items or is not one of the types above,
// the empty function (function's second argument) will be called.
func (v *Value) Iterate(fn func(idx, count int, key, value *Value) bool, empty func()) {
v.IterateOrder(fn, empty, false)
v.IterateOrder(fn, empty, false, false)
}
// Like Value.Iterate, but can iterate through an array/slice/string in reverse. Does
// IterateOrder behaves like Value.Iterate, but can iterate through an array/slice/string in reverse. Does
// not affect the iteration through a map because maps don't have any particular order.
func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, empty func(), reverse bool) {
// However, you can force an order using the `sorted` keyword (and even use `reversed sorted`).
func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, empty func(), reverse bool, sorted bool) {
switch v.getResolvedValue().Kind() {
case reflect.Map:
// Reverse not needed for maps, since they are not ordered
keys := v.getResolvedValue().MapKeys()
keys := sortedKeys(v.getResolvedValue().MapKeys())
if sorted {
if reverse {
sort.Sort(sort.Reverse(keys))
} else {
sort.Sort(keys)
}
}
keyLen := len(keys)
for idx, key := range keys {
value := v.getResolvedValue().MapIndex(key)
@ -379,19 +393,31 @@ func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, em
}
return // done
case reflect.Array, reflect.Slice:
var items valuesList
itemCount := v.getResolvedValue().Len()
if itemCount > 0 {
for i := 0; i < itemCount; i++ {
items = append(items, &Value{val: v.getResolvedValue().Index(i)})
}
if sorted {
if reverse {
for i := itemCount - 1; i >= 0; i-- {
if !fn(i, itemCount, &Value{val: v.getResolvedValue().Index(i)}, nil) {
return
}
}
sort.Sort(sort.Reverse(items))
} else {
for i := 0; i < itemCount; i++ {
if !fn(i, itemCount, &Value{val: v.getResolvedValue().Index(i)}, nil) {
return
}
sort.Sort(items)
}
} else {
if reverse {
for i := 0; i < itemCount/2; i++ {
items[i], items[itemCount-1-i] = items[itemCount-1-i], items[i]
}
}
}
if len(items) > 0 {
for idx, item := range items {
if !fn(idx, itemCount, item, nil) {
return
}
}
} else {
@ -399,7 +425,12 @@ func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, em
}
return // done
case reflect.String:
// TODO: Not utf8-compatible (utf8-decoding neccessary)
if sorted {
// TODO(flosch): Handle sorted
panic("TODO: handle sort for type string")
}
// TODO(flosch): Not utf8-compatible (utf8-decoding necessary)
charCount := v.getResolvedValue().Len()
if charCount > 0 {
if reverse {
@ -425,7 +456,7 @@ func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, em
empty()
}
// Gives you access to the underlying value.
// Interface gives you access to the underlying value.
func (v *Value) Interface() interface{} {
if v.val.IsValid() {
return v.val.Interface()
@ -433,7 +464,57 @@ func (v *Value) Interface() interface{} {
return nil
}
// Checks whether two values are containing the same value or object.
// EqualValueTo checks whether two values are containing the same value or object.
func (v *Value) EqualValueTo(other *Value) bool {
// comparison of uint with int fails using .Interface()-comparison (see issue #64)
if v.IsInteger() && other.IsInteger() {
return v.Integer() == other.Integer()
}
return v.Interface() == other.Interface()
}
type sortedKeys []reflect.Value
func (sk sortedKeys) Len() int {
return len(sk)
}
func (sk sortedKeys) Less(i, j int) bool {
vi := &Value{val: sk[i]}
vj := &Value{val: sk[j]}
switch {
case vi.IsInteger() && vj.IsInteger():
return vi.Integer() < vj.Integer()
case vi.IsFloat() && vj.IsFloat():
return vi.Float() < vj.Float()
default:
return vi.String() < vj.String()
}
}
func (sk sortedKeys) Swap(i, j int) {
sk[i], sk[j] = sk[j], sk[i]
}
type valuesList []*Value
func (vl valuesList) Len() int {
return len(vl)
}
func (vl valuesList) Less(i, j int) bool {
vi := vl[i]
vj := vl[j]
switch {
case vi.IsInteger() && vj.IsInteger():
return vi.Integer() < vj.Integer()
case vi.IsFloat() && vj.IsFloat():
return vi.Float() < vj.Float()
default:
return vi.String() < vj.String()
}
}
func (vl valuesList) Swap(i, j int) {
vl[i], vl[j] = vl[j], vl[i]
}

View File

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

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

191
vendor/github.com/juju/loggo/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/loggo/Makefile generated vendored Normal file
View File

@ -0,0 +1,11 @@
default: check
check:
go test
docs:
godoc2md github.com/juju/loggo > README.md
sed -i 's|\[godoc-link-here\]|[![GoDoc](https://godoc.org/github.com/juju/loggo?status.svg)](https://godoc.org/github.com/juju/loggo)|' README.md
.PHONY: default check docs

711
vendor/github.com/juju/loggo/README.md generated vendored Normal file
View File

@ -0,0 +1,711 @@
# loggo
import "github.com/juju/loggo"
[![GoDoc](https://godoc.org/github.com/juju/loggo?status.svg)](https://godoc.org/github.com/juju/loggo)
### Module level logging for Go
This package provides an alternative to the standard library log package.
The actual logging functions never return errors. If you are logging
something, you really don't want to be worried about the logging
having trouble.
Modules have names that are defined by dotted strings.
"first.second.third"
There is a root module that has the name `""`. Each module
(except the root module) has a parent, identified by the part of
the name without the last dotted value.
* the parent of "first.second.third" is "first.second"
* the parent of "first.second" is "first"
* the parent of "first" is "" (the root module)
Each module can specify its own severity level. Logging calls that are of
a lower severity than the module's effective severity level are not written
out.
Loggers are created using the GetLogger function.
logger := loggo.GetLogger("foo.bar")
By default there is one writer registered, which will write to Stderr,
and the root module, which will only emit warnings and above.
If you want to continue using the default
logger, but have it emit all logging levels you need to do the following.
writer, _, err := loggo.RemoveWriter("default")
// err is non-nil if and only if the name isn't found.
loggo.RegisterWriter("default", writer, loggo.TRACE)
## Constants
``` go
const DefaultWriterName = "default"
```
DefaultWriterName is the name of the default writer for
a Context.
## Variables
``` go
var (
// SeverityColor defines the colors for the levels output by the ColorWriter.
SeverityColor = map[Level]*ansiterm.Context{
TRACE: ansiterm.Foreground(ansiterm.Default),
DEBUG: ansiterm.Foreground(ansiterm.Green),
INFO: ansiterm.Foreground(ansiterm.BrightBlue),
WARNING: ansiterm.Foreground(ansiterm.Yellow),
ERROR: ansiterm.Foreground(ansiterm.BrightRed),
CRITICAL: &ansiterm.Context{
Foreground: ansiterm.White,
Background: ansiterm.Red,
},
}
// LocationColor defines the colors for the location output by the ColorWriter.
LocationColor = ansiterm.Foreground(ansiterm.BrightBlue)
)
```
``` go
var TimeFormat = initTimeFormat()
```
TimeFormat is the time format used for the default writer.
This can be set with the environment variable LOGGO_TIME_FORMAT.
## func ConfigureLoggers
``` go
func ConfigureLoggers(specification string) error
```
ConfigureLoggers configures loggers according to the given string
specification, which specifies a set of modules and their associated
logging levels. Loggers are colon- or semicolon-separated; each
module is specified as <modulename>=<level>. White space outside of
module names and levels is ignored. The root module is specified
with the name "<root>".
An example specification:
`<root>=ERROR; foo.bar=WARNING`
## func DefaultFormatter
``` go
func DefaultFormatter(entry Entry) string
```
DefaultFormatter returns the parameters separated by spaces except for
filename and line which are separated by a colon. The timestamp is shown
to second resolution in UTC. For example:
2016-07-02 15:04:05
## func LoggerInfo
``` go
func LoggerInfo() string
```
LoggerInfo returns information about the configured loggers and their
logging levels. The information is returned in the format expected by
ConfigureLoggers. Loggers with UNSPECIFIED level will not
be included.
## func RegisterWriter
``` go
func RegisterWriter(name string, writer Writer) error
```
RegisterWriter adds the writer to the list of writers in the DefaultContext
that get notified when logging. If there is already a registered writer
with that name, an error is returned.
## func ResetLogging
``` go
func ResetLogging()
```
ResetLogging iterates through the known modules and sets the levels of all
to UNSPECIFIED, except for <root> which is set to WARNING. The call also
removes all writers in the DefaultContext and puts the original default
writer back as the only writer.
## func ResetWriters
``` go
func ResetWriters()
```
ResetWriters puts the list of writers back into the initial state.
## type Config
``` go
type Config map[string]Level
```
Config is a mapping of logger module names to logging severity levels.
### func ParseConfigString
``` go
func ParseConfigString(specification string) (Config, error)
```
ParseConfigString parses a logger configuration string into a map of logger
names and their associated log level. This method is provided to allow
other programs to pre-validate a configuration string rather than just
calling ConfigureLoggers.
Logging modules are colon- or semicolon-separated; each module is specified
as <modulename>=<level>. White space outside of module names and levels is
ignored. The root module is specified with the name "<root>".
As a special case, a log level may be specified on its own.
This is equivalent to specifying the level of the root module,
so "DEBUG" is equivalent to `<root>=DEBUG`
An example specification:
`<root>=ERROR; foo.bar=WARNING`
### func (Config) String
``` go
func (c Config) String() string
```
String returns a logger configuration string that may be parsed
using ParseConfigurationString.
## type Context
``` go
type Context struct {
// contains filtered or unexported fields
}
```
Context produces loggers for a hierarchy of modules. The context holds
a collection of hierarchical loggers and their writers.
### func DefaultContext
``` go
func DefaultContext() *Context
```
DefaultContext returns the global default logging context.
### func NewContext
``` go
func NewContext(rootLevel Level) *Context
```
NewLoggers returns a new Context with no writers set.
If the root level is UNSPECIFIED, WARNING is used.
### func (\*Context) AddWriter
``` go
func (c *Context) AddWriter(name string, writer Writer) error
```
AddWriter adds a writer to the list to be called for each logging call.
The name cannot be empty, and the writer cannot be nil. If an existing
writer exists with the specified name, an error is returned.
### func (\*Context) ApplyConfig
``` go
func (c *Context) ApplyConfig(config Config)
```
ApplyConfig configures the logging modules according to the provided config.
### func (\*Context) CompleteConfig
``` go
func (c *Context) CompleteConfig() Config
```
CompleteConfig returns all the loggers and their defined levels,
even if that level is UNSPECIFIED.
### func (\*Context) Config
``` go
func (c *Context) Config() Config
```
Config returns the current configuration of the Loggers. Loggers
with UNSPECIFIED level will not be included.
### func (\*Context) GetLogger
``` go
func (c *Context) GetLogger(name string) Logger
```
GetLogger returns a Logger for the given module name, creating it and
its parents if necessary.
### func (\*Context) RemoveWriter
``` go
func (c *Context) RemoveWriter(name string) (Writer, error)
```
RemoveWriter remotes the specified writer. If a writer is not found with
the specified name an error is returned. The writer that was removed is also
returned.
### func (\*Context) ReplaceWriter
``` go
func (c *Context) ReplaceWriter(name string, writer Writer) (Writer, error)
```
ReplaceWriter is a convenience method that does the equivalent of RemoveWriter
followed by AddWriter with the same name. The replaced writer is returned.
### func (\*Context) ResetLoggerLevels
``` go
func (c *Context) ResetLoggerLevels()
```
ResetLoggerLevels iterates through the known logging modules and sets the
levels of all to UNSPECIFIED, except for <root> which is set to WARNING.
### func (\*Context) ResetWriters
``` go
func (c *Context) ResetWriters()
```
ResetWriters is generally only used in testing and removes all the writers.
## type Entry
``` go
type Entry struct {
// Level is the severity of the log message.
Level Level
// Module is the dotted module name from the logger.
Module string
// Filename is the full path the file that logged the message.
Filename string
// Line is the line number of the Filename.
Line int
// Timestamp is when the log message was created
Timestamp time.Time
// Message is the formatted string from teh log call.
Message string
}
```
Entry represents a single log message.
## type Level
``` go
type Level uint32
```
Level holds a severity level.
``` go
const (
UNSPECIFIED Level = iota
TRACE
DEBUG
INFO
WARNING
ERROR
CRITICAL
)
```
The severity levels. Higher values are more considered more
important.
### func ParseLevel
``` go
func ParseLevel(level string) (Level, bool)
```
ParseLevel converts a string representation of a logging level to a
Level. It returns the level and whether it was valid or not.
### func (Level) Short
``` go
func (level Level) Short() string
```
Short returns a five character string to use in
aligned logging output.
### func (Level) String
``` go
func (level Level) String() string
```
String implements Stringer.
## type Logger
``` go
type Logger struct {
// contains filtered or unexported fields
}
```
A Logger represents a logging module. It has an associated logging
level which can be changed; messages of lesser severity will
be dropped. Loggers have a hierarchical relationship - see
the package documentation.
The zero Logger value is usable - any messages logged
to it will be sent to the root Logger.
### func GetLogger
``` go
func GetLogger(name string) Logger
```
GetLogger returns a Logger for the given module name,
creating it and its parents if necessary.
### func (Logger) Criticalf
``` go
func (logger Logger) Criticalf(message string, args ...interface{})
```
Criticalf logs the printf-formatted message at critical level.
### func (Logger) Debugf
``` go
func (logger Logger) Debugf(message string, args ...interface{})
```
Debugf logs the printf-formatted message at debug level.
### func (Logger) EffectiveLogLevel
``` go
func (logger Logger) EffectiveLogLevel() Level
```
EffectiveLogLevel returns the effective min log level of
the receiver - that is, messages with a lesser severity
level will be discarded.
If the log level of the receiver is unspecified,
it will be taken from the effective log level of its
parent.
### func (Logger) Errorf
``` go
func (logger Logger) Errorf(message string, args ...interface{})
```
Errorf logs the printf-formatted message at error level.
### func (Logger) Infof
``` go
func (logger Logger) Infof(message string, args ...interface{})
```
Infof logs the printf-formatted message at info level.
### func (Logger) IsDebugEnabled
``` go
func (logger Logger) IsDebugEnabled() bool
```
IsDebugEnabled returns whether debugging is enabled
at debug level.
### func (Logger) IsErrorEnabled
``` go
func (logger Logger) IsErrorEnabled() bool
```
IsErrorEnabled returns whether debugging is enabled
at error level.
### func (Logger) IsInfoEnabled
``` go
func (logger Logger) IsInfoEnabled() bool
```
IsInfoEnabled returns whether debugging is enabled
at info level.
### func (Logger) IsLevelEnabled
``` go
func (logger Logger) IsLevelEnabled(level Level) bool
```
IsLevelEnabled returns whether debugging is enabled
for the given log level.
### func (Logger) IsTraceEnabled
``` go
func (logger Logger) IsTraceEnabled() bool
```
IsTraceEnabled returns whether debugging is enabled
at trace level.
### func (Logger) IsWarningEnabled
``` go
func (logger Logger) IsWarningEnabled() bool
```
IsWarningEnabled returns whether debugging is enabled
at warning level.
### func (Logger) LogCallf
``` go
func (logger Logger) LogCallf(calldepth int, level Level, message string, args ...interface{})
```
LogCallf logs a printf-formatted message at the given level.
The location of the call is indicated by the calldepth argument.
A calldepth of 1 means the function that called this function.
A message will be discarded if level is less than the
the effective log level of the logger.
Note that the writers may also filter out messages that
are less than their registered minimum severity level.
### func (Logger) LogLevel
``` go
func (logger Logger) LogLevel() Level
```
LogLevel returns the configured min log level of the logger.
### func (Logger) Logf
``` go
func (logger Logger) Logf(level Level, message string, args ...interface{})
```
Logf logs a printf-formatted message at the given level.
A message will be discarded if level is less than the
the effective log level of the logger.
Note that the writers may also filter out messages that
are less than their registered minimum severity level.
### func (Logger) Name
``` go
func (logger Logger) Name() string
```
Name returns the logger's module name.
### func (Logger) SetLogLevel
``` go
func (logger Logger) SetLogLevel(level Level)
```
SetLogLevel sets the severity level of the given logger.
The root logger cannot be set to UNSPECIFIED level.
See EffectiveLogLevel for how this affects the
actual messages logged.
### func (Logger) Tracef
``` go
func (logger Logger) Tracef(message string, args ...interface{})
```
Tracef logs the printf-formatted message at trace level.
### func (Logger) Warningf
``` go
func (logger Logger) Warningf(message string, args ...interface{})
```
Warningf logs the printf-formatted message at warning level.
## type TestWriter
``` go
type TestWriter struct {
// contains filtered or unexported fields
}
```
TestWriter is a useful Writer for testing purposes. Each component of the
logging message is stored in the Log array.
### func (\*TestWriter) Clear
``` go
func (writer *TestWriter) Clear()
```
Clear removes any saved log messages.
### func (\*TestWriter) Log
``` go
func (writer *TestWriter) Log() []Entry
```
Log returns a copy of the current logged values.
### func (\*TestWriter) Write
``` go
func (writer *TestWriter) Write(entry Entry)
```
Write saves the params as members in the TestLogValues struct appended to the Log array.
## type Writer
``` go
type Writer interface {
// Write writes a message to the Writer with the given level and module
// name. The filename and line hold the file name and line number of the
// code that is generating the log message; the time stamp holds the time
// the log message was generated, and message holds the log message
// itself.
Write(entry Entry)
}
```
Writer is implemented by any recipient of log messages.
### func NewColorWriter
``` go
func NewColorWriter(writer io.Writer) Writer
```
NewColorWriter will write out colored severity levels if the writer is
outputting to a terminal.
### func NewMinimumLevelWriter
``` go
func NewMinimumLevelWriter(writer Writer, minLevel Level) Writer
```
NewMinLevelWriter returns a Writer that will only pass on the Write calls
to the provided writer if the log level is at or above the specified
minimum level.
### func NewSimpleWriter
``` go
func NewSimpleWriter(writer io.Writer, formatter func(entry Entry) string) Writer
```
NewSimpleWriter returns a new writer that writes log messages to the given
io.Writer formatting the messages with the given formatter.
### func RemoveWriter
``` go
func RemoveWriter(name string) (Writer, error)
```
RemoveWriter removes the Writer identified by 'name' and returns it.
If the Writer is not found, an error is returned.
### func ReplaceDefaultWriter
``` go
func ReplaceDefaultWriter(writer Writer) (Writer, error)
```
ReplaceDefaultWriter is a convenience method that does the equivalent of
RemoveWriter and then RegisterWriter with the name "default". The previous
default writer, if any is returned.
- - -
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)

105
vendor/github.com/juju/loggo/benchmarks_test.go generated vendored Normal file
View File

@ -0,0 +1,105 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"io/ioutil"
"os"
"github.com/juju/loggo"
gc "gopkg.in/check.v1"
)
type BenchmarksSuite struct {
logger loggo.Logger
writer *writer
}
var _ = gc.Suite(&BenchmarksSuite{})
func (s *BenchmarksSuite) SetUpTest(c *gc.C) {
loggo.ResetLogging()
s.logger = loggo.GetLogger("test.writer")
s.writer = &writer{}
err := loggo.RegisterWriter("test", s.writer)
c.Assert(err, gc.IsNil)
}
func (s *BenchmarksSuite) BenchmarkLoggingNoWriters(c *gc.C) {
// No writers
loggo.RemoveWriter("test")
for i := 0; i < c.N; i++ {
s.logger.Warningf("just a simple warning for %d", i)
}
}
func (s *BenchmarksSuite) BenchmarkLoggingNoWritersNoFormat(c *gc.C) {
// No writers
loggo.RemoveWriter("test")
for i := 0; i < c.N; i++ {
s.logger.Warningf("just a simple warning")
}
}
func (s *BenchmarksSuite) BenchmarkLoggingTestWriters(c *gc.C) {
for i := 0; i < c.N; i++ {
s.logger.Warningf("just a simple warning for %d", i)
}
c.Assert(s.writer.Log(), gc.HasLen, c.N)
}
func (s *BenchmarksSuite) BenchmarkLoggingDiskWriter(c *gc.C) {
logFile := s.setupTempFileWriter(c)
defer logFile.Close()
msg := "just a simple warning for %d"
for i := 0; i < c.N; i++ {
s.logger.Warningf(msg, i)
}
offset, err := logFile.Seek(0, os.SEEK_CUR)
c.Assert(err, gc.IsNil)
c.Assert((offset > int64(len(msg))*int64(c.N)), gc.Equals, true,
gc.Commentf("Not enough data was written to the log file."))
}
func (s *BenchmarksSuite) BenchmarkLoggingDiskWriterNoMessages(c *gc.C) {
logFile := s.setupTempFileWriter(c)
defer logFile.Close()
// Change the log level
writer, err := loggo.RemoveWriter("testfile")
c.Assert(err, gc.IsNil)
loggo.RegisterWriter("testfile", loggo.NewMinimumLevelWriter(writer, loggo.WARNING))
msg := "just a simple warning for %d"
for i := 0; i < c.N; i++ {
s.logger.Debugf(msg, i)
}
offset, err := logFile.Seek(0, os.SEEK_CUR)
c.Assert(err, gc.IsNil)
c.Assert(offset, gc.Equals, int64(0),
gc.Commentf("Data was written to the log file."))
}
func (s *BenchmarksSuite) BenchmarkLoggingDiskWriterNoMessagesLogLevel(c *gc.C) {
logFile := s.setupTempFileWriter(c)
defer logFile.Close()
// Change the log level
s.logger.SetLogLevel(loggo.WARNING)
msg := "just a simple warning for %d"
for i := 0; i < c.N; i++ {
s.logger.Debugf(msg, i)
}
offset, err := logFile.Seek(0, os.SEEK_CUR)
c.Assert(err, gc.IsNil)
c.Assert(offset, gc.Equals, int64(0),
gc.Commentf("Data was written to the log file."))
}
func (s *BenchmarksSuite) setupTempFileWriter(c *gc.C) *os.File {
loggo.RemoveWriter("test")
logFile, err := ioutil.TempFile(c.MkDir(), "loggo-test")
c.Assert(err, gc.IsNil)
writer := loggo.NewSimpleWriter(logFile, loggo.DefaultFormatter)
err = loggo.RegisterWriter("testfile", writer)
c.Assert(err, gc.IsNil)
return logFile
}

44
vendor/github.com/juju/loggo/checkers_test.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"fmt"
"time"
gc "gopkg.in/check.v1"
)
func Between(start, end time.Time) gc.Checker {
if end.Before(start) {
return &betweenChecker{end, start}
}
return &betweenChecker{start, end}
}
type betweenChecker struct {
start, end time.Time
}
func (checker *betweenChecker) Info() *gc.CheckerInfo {
info := gc.CheckerInfo{
Name: "Between",
Params: []string{"obtained"},
}
return &info
}
func (checker *betweenChecker) Check(params []interface{}, names []string) (result bool, error string) {
when, ok := params[0].(time.Time)
if !ok {
return false, "obtained value type must be time.Time"
}
if when.Before(checker.start) {
return false, fmt.Sprintf("obtained time %q is before start time %q", when, checker.start)
}
if when.After(checker.end) {
return false, fmt.Sprintf("obtained time %q is after end time %q", when, checker.end)
}
return true, ""
}

96
vendor/github.com/juju/loggo/config.go generated vendored Normal file
View File

@ -0,0 +1,96 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"sort"
"strings"
)
// Config is a mapping of logger module names to logging severity levels.
type Config map[string]Level
// String returns a logger configuration string that may be parsed
// using ParseConfigurationString.
func (c Config) String() string {
if c == nil {
return ""
}
// output in alphabetical order.
names := []string{}
for name := range c {
names = append(names, name)
}
sort.Strings(names)
var entries []string
for _, name := range names {
level := c[name]
if name == "" {
name = rootString
}
entry := fmt.Sprintf("%s=%s", name, level)
entries = append(entries, entry)
}
return strings.Join(entries, ";")
}
func parseConfigValue(value string) (string, Level, error) {
pair := strings.SplitN(value, "=", 2)
if len(pair) < 2 {
return "", UNSPECIFIED, fmt.Errorf("config value expected '=', found %q", value)
}
name := strings.TrimSpace(pair[0])
if name == "" {
return "", UNSPECIFIED, fmt.Errorf("config value %q has missing module name", value)
}
levelStr := strings.TrimSpace(pair[1])
level, ok := ParseLevel(levelStr)
if !ok {
return "", UNSPECIFIED, fmt.Errorf("unknown severity level %q", levelStr)
}
if name == rootString {
name = ""
}
return name, level, nil
}
// ParseConfigString parses a logger configuration string into a map of logger
// names and their associated log level. This method is provided to allow
// other programs to pre-validate a configuration string rather than just
// calling ConfigureLoggers.
//
// Logging modules are colon- or semicolon-separated; each module is specified
// as <modulename>=<level>. White space outside of module names and levels is
// ignored. The root module is specified with the name "<root>".
//
// As a special case, a log level may be specified on its own.
// This is equivalent to specifying the level of the root module,
// so "DEBUG" is equivalent to `<root>=DEBUG`
//
// An example specification:
// `<root>=ERROR; foo.bar=WARNING`
func ParseConfigString(specification string) (Config, error) {
specification = strings.TrimSpace(specification)
if specification == "" {
return nil, nil
}
cfg := make(Config)
if level, ok := ParseLevel(specification); ok {
cfg[""] = level
return cfg, nil
}
values := strings.FieldsFunc(specification, func(r rune) bool { return r == ';' || r == ':' })
for _, value := range values {
name, level, err := parseConfigValue(value)
if err != nil {
return nil, err
}
cfg[name] = level
}
return cfg, nil
}

152
vendor/github.com/juju/loggo/config_test.go generated vendored Normal file
View File

@ -0,0 +1,152 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import gc "gopkg.in/check.v1"
type ConfigSuite struct{}
var _ = gc.Suite(&ConfigSuite{})
func (*ConfigSuite) TestParseConfigValue(c *gc.C) {
for i, test := range []struct {
value string
module string
level Level
err string
}{{
err: `config value expected '=', found ""`,
}, {
value: "WARNING",
err: `config value expected '=', found "WARNING"`,
}, {
value: "=WARNING",
err: `config value "=WARNING" has missing module name`,
}, {
value: " name = WARNING ",
module: "name",
level: WARNING,
}, {
value: "name = foo",
err: `unknown severity level "foo"`,
}, {
value: "name=DEBUG=INFO",
err: `unknown severity level "DEBUG=INFO"`,
}, {
value: "<root> = info",
module: "",
level: INFO,
}} {
c.Logf("%d: %s", i, test.value)
module, level, err := parseConfigValue(test.value)
if test.err == "" {
c.Check(err, gc.IsNil)
c.Check(module, gc.Equals, test.module)
c.Check(level, gc.Equals, test.level)
} else {
c.Check(module, gc.Equals, "")
c.Check(level, gc.Equals, UNSPECIFIED)
c.Check(err.Error(), gc.Equals, test.err)
}
}
}
func (*ConfigSuite) TestPaarseConfigurationString(c *gc.C) {
for i, test := range []struct {
configuration string
expected Config
err string
}{{
configuration: "",
// nil Config, no error
}, {
configuration: "INFO",
expected: Config{"": INFO},
}, {
configuration: "=INFO",
err: `config value "=INFO" has missing module name`,
}, {
configuration: "<root>=UNSPECIFIED",
expected: Config{"": UNSPECIFIED},
}, {
configuration: "<root>=DEBUG",
expected: Config{"": DEBUG},
}, {
configuration: "test.module=debug",
expected: Config{"test.module": DEBUG},
}, {
configuration: "module=info; sub.module=debug; other.module=warning",
expected: Config{
"module": INFO,
"sub.module": DEBUG,
"other.module": WARNING,
},
}, {
// colons not semicolons
configuration: "module=info: sub.module=debug: other.module=warning",
expected: Config{
"module": INFO,
"sub.module": DEBUG,
"other.module": WARNING,
},
}, {
configuration: " foo.bar \t\r\n= \t\r\nCRITICAL \t\r\n; \t\r\nfoo \r\t\n = DEBUG",
expected: Config{
"foo": DEBUG,
"foo.bar": CRITICAL,
},
}, {
configuration: "foo;bar",
err: `config value expected '=', found "foo"`,
}, {
configuration: "foo=",
err: `unknown severity level ""`,
}, {
configuration: "foo=unknown",
err: `unknown severity level "unknown"`,
}} {
c.Logf("%d: %q", i, test.configuration)
config, err := ParseConfigString(test.configuration)
if test.err == "" {
c.Check(err, gc.IsNil)
c.Check(config, gc.DeepEquals, test.expected)
} else {
c.Check(config, gc.IsNil)
c.Check(err.Error(), gc.Equals, test.err)
}
}
}
func (*ConfigSuite) TestConfigString(c *gc.C) {
for i, test := range []struct {
config Config
expected string
}{{
config: nil,
expected: "",
}, {
config: Config{"": INFO},
expected: "<root>=INFO",
}, {
config: Config{"": UNSPECIFIED},
expected: "<root>=UNSPECIFIED",
}, {
config: Config{"": DEBUG},
expected: "<root>=DEBUG",
}, {
config: Config{"test.module": DEBUG},
expected: "test.module=DEBUG",
}, {
config: Config{
"": WARNING,
"module": INFO,
"sub.module": DEBUG,
"other.module": WARNING,
},
expected: "<root>=WARNING;module=INFO;other.module=WARNING;sub.module=DEBUG",
}} {
c.Logf("%d: %q", i, test.expected)
c.Check(test.config.String(), gc.Equals, test.expected)
}
}

198
vendor/github.com/juju/loggo/context.go generated vendored Normal file
View File

@ -0,0 +1,198 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"strings"
"sync"
)
// Context produces loggers for a hierarchy of modules. The context holds
// a collection of hierarchical loggers and their writers.
type Context struct {
root *module
// Perhaps have one mutex?
modulesMutex sync.Mutex
modules map[string]*module
writersMutex sync.Mutex
writers map[string]Writer
// writeMuxtex is used to serialise write operations.
writeMutex sync.Mutex
}
// NewLoggers returns a new Context with no writers set.
// If the root level is UNSPECIFIED, WARNING is used.
func NewContext(rootLevel Level) *Context {
if rootLevel < TRACE || rootLevel > CRITICAL {
rootLevel = WARNING
}
context := &Context{
modules: make(map[string]*module),
writers: make(map[string]Writer),
}
context.root = &module{
level: rootLevel,
context: context,
}
context.modules[""] = context.root
return context
}
// GetLogger returns a Logger for the given module name, creating it and
// its parents if necessary.
func (c *Context) GetLogger(name string) Logger {
name = strings.TrimSpace(strings.ToLower(name))
c.modulesMutex.Lock()
defer c.modulesMutex.Unlock()
return Logger{c.getLoggerModule(name)}
}
func (c *Context) getLoggerModule(name string) *module {
if name == rootString {
name = ""
}
impl, found := c.modules[name]
if found {
return impl
}
parentName := ""
if i := strings.LastIndex(name, "."); i >= 0 {
parentName = name[0:i]
}
parent := c.getLoggerModule(parentName)
impl = &module{name, UNSPECIFIED, parent, c}
c.modules[name] = impl
return impl
}
// Config returns the current configuration of the Loggers. Loggers
// with UNSPECIFIED level will not be included.
func (c *Context) Config() Config {
result := make(Config)
c.modulesMutex.Lock()
defer c.modulesMutex.Unlock()
for name, module := range c.modules {
if module.level != UNSPECIFIED {
result[name] = module.level
}
}
return result
}
// CompleteConfig returns all the loggers and their defined levels,
// even if that level is UNSPECIFIED.
func (c *Context) CompleteConfig() Config {
result := make(Config)
c.modulesMutex.Lock()
defer c.modulesMutex.Unlock()
for name, module := range c.modules {
result[name] = module.level
}
return result
}
// ApplyConfig configures the logging modules according to the provided config.
func (c *Context) ApplyConfig(config Config) {
c.modulesMutex.Lock()
defer c.modulesMutex.Unlock()
for name, level := range config {
module := c.getLoggerModule(name)
module.setLevel(level)
}
}
// ResetLoggerLevels iterates through the known logging modules and sets the
// levels of all to UNSPECIFIED, except for <root> which is set to WARNING.
func (c *Context) ResetLoggerLevels() {
c.modulesMutex.Lock()
defer c.modulesMutex.Unlock()
// Setting the root module to UNSPECIFIED will set it to WARNING.
for _, module := range c.modules {
module.setLevel(UNSPECIFIED)
}
}
func (c *Context) write(entry Entry) {
c.writeMutex.Lock()
defer c.writeMutex.Unlock()
for _, writer := range c.getWriters() {
writer.Write(entry)
}
}
func (c *Context) getWriters() []Writer {
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
var result []Writer
for _, writer := range c.writers {
result = append(result, writer)
}
return result
}
// AddWriter adds a writer to the list to be called for each logging call.
// The name cannot be empty, and the writer cannot be nil. If an existing
// writer exists with the specified name, an error is returned.
func (c *Context) AddWriter(name string, writer Writer) error {
if name == "" {
return fmt.Errorf("name cannot be empty")
}
if writer == nil {
return fmt.Errorf("writer cannot be nil")
}
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
if _, found := c.writers[name]; found {
return fmt.Errorf("context already has a writer named %q", name)
}
c.writers[name] = writer
return nil
}
// RemoveWriter remotes the specified writer. If a writer is not found with
// the specified name an error is returned. The writer that was removed is also
// returned.
func (c *Context) RemoveWriter(name string) (Writer, error) {
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
reg, found := c.writers[name]
if !found {
return nil, fmt.Errorf("context has no writer named %q", name)
}
delete(c.writers, name)
return reg, nil
}
// ReplaceWriter is a convenience method that does the equivalent of RemoveWriter
// followed by AddWriter with the same name. The replaced writer is returned.
func (c *Context) ReplaceWriter(name string, writer Writer) (Writer, error) {
if name == "" {
return nil, fmt.Errorf("name cannot be empty")
}
if writer == nil {
return nil, fmt.Errorf("writer cannot be nil")
}
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
reg, found := c.writers[name]
if !found {
return nil, fmt.Errorf("context has no writer named %q", name)
}
oldWriter := reg
c.writers[name] = writer
return oldWriter, nil
}
// ResetWriters is generally only used in testing and removes all the writers.
func (c *Context) ResetWriters() {
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
c.writers = make(map[string]Writer)
}

328
vendor/github.com/juju/loggo/context_test.go generated vendored Normal file
View File

@ -0,0 +1,328 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"github.com/juju/loggo"
gc "gopkg.in/check.v1"
)
type ContextSuite struct{}
var _ = gc.Suite(&ContextSuite{})
func (*ContextSuite) TestNewContextRootLevel(c *gc.C) {
for i, test := range []struct {
level loggo.Level
expected loggo.Level
}{{
level: loggo.UNSPECIFIED,
expected: loggo.WARNING,
}, {
level: loggo.DEBUG,
expected: loggo.DEBUG,
}, {
level: loggo.INFO,
expected: loggo.INFO,
}, {
level: loggo.WARNING,
expected: loggo.WARNING,
}, {
level: loggo.ERROR,
expected: loggo.ERROR,
}, {
level: loggo.CRITICAL,
expected: loggo.CRITICAL,
}, {
level: loggo.Level(42),
expected: loggo.WARNING,
}} {
c.Log("%d: %s", i, test.level)
context := loggo.NewContext(test.level)
cfg := context.Config()
c.Check(cfg, gc.HasLen, 1)
value, found := cfg[""]
c.Check(found, gc.Equals, true)
c.Check(value, gc.Equals, test.expected)
}
}
func logAllSeverities(logger loggo.Logger) {
logger.Criticalf("something critical")
logger.Errorf("an error")
logger.Warningf("a warning message")
logger.Infof("an info message")
logger.Debugf("a debug message")
logger.Tracef("a trace message")
}
func checkLogEntry(c *gc.C, entry, expected loggo.Entry) {
c.Check(entry.Level, gc.Equals, expected.Level)
c.Check(entry.Module, gc.Equals, expected.Module)
c.Check(entry.Message, gc.Equals, expected.Message)
}
func checkLogEntries(c *gc.C, obtained, expected []loggo.Entry) {
if c.Check(len(obtained), gc.Equals, len(expected)) {
for i := range obtained {
checkLogEntry(c, obtained[i], expected[i])
}
}
}
func (*ContextSuite) TestGetLoggerRoot(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
blank := context.GetLogger("")
root := context.GetLogger("<root>")
c.Assert(blank, gc.Equals, root)
}
func (*ContextSuite) TestGetLoggerCase(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
upper := context.GetLogger("TEST")
lower := context.GetLogger("test")
c.Assert(upper, gc.Equals, lower)
c.Assert(upper.Name(), gc.Equals, "test")
}
func (*ContextSuite) TestGetLoggerSpace(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
space := context.GetLogger(" test ")
lower := context.GetLogger("test")
c.Assert(space, gc.Equals, lower)
c.Assert(space.Name(), gc.Equals, "test")
}
func (*ContextSuite) TestNewContextNoWriter(c *gc.C) {
// Should be no output.
context := loggo.NewContext(loggo.DEBUG)
logger := context.GetLogger("test")
logAllSeverities(logger)
}
func (*ContextSuite) newContextWithTestWriter(c *gc.C, level loggo.Level) (*loggo.Context, *loggo.TestWriter) {
writer := &loggo.TestWriter{}
context := loggo.NewContext(level)
context.AddWriter("test", writer)
return context, writer
}
func (s *ContextSuite) TestNewContextRootSeverityWarning(c *gc.C) {
context, writer := s.newContextWithTestWriter(c, loggo.WARNING)
logger := context.GetLogger("test")
logAllSeverities(logger)
checkLogEntries(c, writer.Log(), []loggo.Entry{
{Level: loggo.CRITICAL, Module: "test", Message: "something critical"},
{Level: loggo.ERROR, Module: "test", Message: "an error"},
{Level: loggo.WARNING, Module: "test", Message: "a warning message"},
})
}
func (s *ContextSuite) TestNewContextRootSeverityTrace(c *gc.C) {
context, writer := s.newContextWithTestWriter(c, loggo.TRACE)
logger := context.GetLogger("test")
logAllSeverities(logger)
checkLogEntries(c, writer.Log(), []loggo.Entry{
{Level: loggo.CRITICAL, Module: "test", Message: "something critical"},
{Level: loggo.ERROR, Module: "test", Message: "an error"},
{Level: loggo.WARNING, Module: "test", Message: "a warning message"},
{Level: loggo.INFO, Module: "test", Message: "an info message"},
{Level: loggo.DEBUG, Module: "test", Message: "a debug message"},
{Level: loggo.TRACE, Module: "test", Message: "a trace message"},
})
}
func (*ContextSuite) TestNewContextConfig(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
config := context.Config()
c.Assert(config, gc.DeepEquals, loggo.Config{"": loggo.DEBUG})
}
func (*ContextSuite) TestNewLoggerAddsConfig(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
_ = context.GetLogger("test.module")
c.Assert(context.Config(), gc.DeepEquals, loggo.Config{
"": loggo.DEBUG,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals, loggo.Config{
"": loggo.DEBUG,
"test": loggo.UNSPECIFIED,
"test.module": loggo.UNSPECIFIED,
})
}
func (*ContextSuite) TestApplyNilConfig(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
context.ApplyConfig(nil)
c.Assert(context.Config(), gc.DeepEquals, loggo.Config{"": loggo.DEBUG})
}
func (*ContextSuite) TestApplyConfigRootUnspecified(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
context.ApplyConfig(loggo.Config{"": loggo.UNSPECIFIED})
c.Assert(context.Config(), gc.DeepEquals, loggo.Config{"": loggo.WARNING})
}
func (*ContextSuite) TestApplyConfigRootTrace(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.ApplyConfig(loggo.Config{"": loggo.TRACE})
c.Assert(context.Config(), gc.DeepEquals, loggo.Config{"": loggo.TRACE})
}
func (*ContextSuite) TestApplyConfigCreatesModules(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.ApplyConfig(loggo.Config{"first.second": loggo.TRACE})
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"first.second": loggo.TRACE,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"first": loggo.UNSPECIFIED,
"first.second": loggo.TRACE,
})
}
func (*ContextSuite) TestApplyConfigAdditive(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.ApplyConfig(loggo.Config{"first.second": loggo.TRACE})
context.ApplyConfig(loggo.Config{"other.module": loggo.DEBUG})
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"first.second": loggo.TRACE,
"other.module": loggo.DEBUG,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"first": loggo.UNSPECIFIED,
"first.second": loggo.TRACE,
"other": loggo.UNSPECIFIED,
"other.module": loggo.DEBUG,
})
}
func (*ContextSuite) TestResetLoggerLevels(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
context.ApplyConfig(loggo.Config{"first.second": loggo.TRACE})
context.ResetLoggerLevels()
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"first": loggo.UNSPECIFIED,
"first.second": loggo.UNSPECIFIED,
})
}
func (*ContextSuite) TestWriterNamesNone(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
writers := context.WriterNames()
c.Assert(writers, gc.HasLen, 0)
}
func (*ContextSuite) TestAddWriterNoName(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
err := context.AddWriter("", nil)
c.Assert(err.Error(), gc.Equals, "name cannot be empty")
}
func (*ContextSuite) TestAddWriterNil(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
err := context.AddWriter("foo", nil)
c.Assert(err.Error(), gc.Equals, "writer cannot be nil")
}
func (*ContextSuite) TestNamedAddWriter(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
err := context.AddWriter("foo", &writer{name: "foo"})
c.Assert(err, gc.IsNil)
err = context.AddWriter("foo", &writer{name: "foo"})
c.Assert(err.Error(), gc.Equals, `context already has a writer named "foo"`)
writers := context.WriterNames()
c.Assert(writers, gc.DeepEquals, []string{"foo"})
}
func (*ContextSuite) TestRemoveWriter(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
w, err := context.RemoveWriter("unknown")
c.Assert(err.Error(), gc.Equals, `context has no writer named "unknown"`)
c.Assert(w, gc.IsNil)
}
func (*ContextSuite) TestRemoveWriterFound(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
original := &writer{name: "foo"}
err := context.AddWriter("foo", original)
c.Assert(err, gc.IsNil)
existing, err := context.RemoveWriter("foo")
c.Assert(err, gc.IsNil)
c.Assert(existing, gc.Equals, original)
writers := context.WriterNames()
c.Assert(writers, gc.HasLen, 0)
}
func (*ContextSuite) TestReplaceWriterNoName(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
existing, err := context.ReplaceWriter("", nil)
c.Assert(err.Error(), gc.Equals, "name cannot be empty")
c.Assert(existing, gc.IsNil)
}
func (*ContextSuite) TestReplaceWriterNil(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
existing, err := context.ReplaceWriter("foo", nil)
c.Assert(err.Error(), gc.Equals, "writer cannot be nil")
c.Assert(existing, gc.IsNil)
}
func (*ContextSuite) TestReplaceWriterNotFound(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
existing, err := context.ReplaceWriter("foo", &writer{})
c.Assert(err.Error(), gc.Equals, `context has no writer named "foo"`)
c.Assert(existing, gc.IsNil)
}
func (*ContextSuite) TestMultipleWriters(c *gc.C) {
first := &writer{}
second := &writer{}
third := &writer{}
context := loggo.NewContext(loggo.TRACE)
err := context.AddWriter("first", first)
c.Assert(err, gc.IsNil)
err = context.AddWriter("second", second)
c.Assert(err, gc.IsNil)
err = context.AddWriter("third", third)
c.Assert(err, gc.IsNil)
logger := context.GetLogger("test")
logAllSeverities(logger)
expected := []loggo.Entry{
{Level: loggo.CRITICAL, Module: "test", Message: "something critical"},
{Level: loggo.ERROR, Module: "test", Message: "an error"},
{Level: loggo.WARNING, Module: "test", Message: "a warning message"},
{Level: loggo.INFO, Module: "test", Message: "an info message"},
{Level: loggo.DEBUG, Module: "test", Message: "a debug message"},
{Level: loggo.TRACE, Module: "test", Message: "a trace message"},
}
checkLogEntries(c, first.Log(), expected)
checkLogEntries(c, second.Log(), expected)
checkLogEntries(c, third.Log(), expected)
}
type writer struct {
loggo.TestWriter
// The name exists to discriminate writer equality.
name string
}

6
vendor/github.com/juju/loggo/dependencies.tsv generated vendored Normal file
View File

@ -0,0 +1,6 @@
github.com/juju/ansiterm git b99631de12cf04a906c1d4e4ec54fb86eae5863d 2016-09-07T23:45:32Z
github.com/lunixbochs/vtclean git 4fbf7632a2c6d3fbdb9931439bdbbeded02cbe36 2016-01-25T03:51:06Z
github.com/mattn/go-colorable git ed8eb9e318d7a84ce5915b495b7d35e0cfe7b5a8 2016-07-31T23:54:17Z
github.com/mattn/go-isatty git 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8 2016-08-06T12:27:52Z
golang.org/x/sys git 9bb9f0998d48b31547d975974935ae9b48c7a03c 2016-10-12T00:19:20Z
gopkg.in/check.v1 git 4f90aeace3a26ad7021961c297b22c42160c7b25 2016-01-05T16:49:36Z
1 github.com/juju/ansiterm git b99631de12cf04a906c1d4e4ec54fb86eae5863d 2016-09-07T23:45:32Z
2 github.com/lunixbochs/vtclean git 4fbf7632a2c6d3fbdb9931439bdbbeded02cbe36 2016-01-25T03:51:06Z
3 github.com/mattn/go-colorable git ed8eb9e318d7a84ce5915b495b7d35e0cfe7b5a8 2016-07-31T23:54:17Z
4 github.com/mattn/go-isatty git 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8 2016-08-06T12:27:52Z
5 golang.org/x/sys git 9bb9f0998d48b31547d975974935ae9b48c7a03c 2016-10-12T00:19:20Z
6 gopkg.in/check.v1 git 4f90aeace3a26ad7021961c297b22c42160c7b25 2016-01-05T16:49:36Z

47
vendor/github.com/juju/loggo/doc.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
/*
[godoc-link-here]
Module level logging for Go
This package provides an alternative to the standard library log package.
The actual logging functions never return errors. If you are logging
something, you really don't want to be worried about the logging
having trouble.
Modules have names that are defined by dotted strings.
"first.second.third"
There is a root module that has the name `""`. Each module
(except the root module) has a parent, identified by the part of
the name without the last dotted value.
* the parent of "first.second.third" is "first.second"
* the parent of "first.second" is "first"
* the parent of "first" is "" (the root module)
Each module can specify its own severity level. Logging calls that are of
a lower severity than the module's effective severity level are not written
out.
Loggers are created using the GetLogger function.
logger := loggo.GetLogger("foo.bar")
By default there is one writer registered, which will write to Stderr,
and the root module, which will only emit warnings and above.
If you want to continue using the default
logger, but have it emit all logging levels you need to do the following.
writer, _, err := loggo.RemoveWriter("default")
// err is non-nil if and only if the name isn't found.
loggo.RegisterWriter("default", writer)
To make loggo produce colored output, you can do the following,
having imported github.com/juju/loggo/loggocolor:
loggo.RemoveWriter("default")
loggo.RegisterWriter("default", loggocolor.NewWriter(os.Stderr))
*/
package loggo

22
vendor/github.com/juju/loggo/entry.go generated vendored Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import "time"
// Entry represents a single log message.
type Entry struct {
// Level is the severity of the log message.
Level Level
// Module is the dotted module name from the logger.
Module string
// Filename is the full path the file that logged the message.
Filename string
// Line is the line number of the Filename.
Line int
// Timestamp is when the log message was created
Timestamp time.Time
// Message is the formatted string from teh log call.
Message string
}

20
vendor/github.com/juju/loggo/export_test.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
// WriterNames returns the names of the context's writers for testing purposes.
func (c *Context) WriterNames() []string {
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
var result []string
for name := range c.writers {
result = append(result, name)
}
return result
}
func ResetDefaultContext() {
ResetLogging()
DefaultContext().AddWriter(DefaultWriterName, defaultWriter())
}

38
vendor/github.com/juju/loggo/formatter.go generated vendored Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"os"
"path/filepath"
"time"
)
// DefaultFormatter returns the parameters separated by spaces except for
// filename and line which are separated by a colon. The timestamp is shown
// to second resolution in UTC. For example:
// 2016-07-02 15:04:05
func DefaultFormatter(entry Entry) string {
ts := entry.Timestamp.In(time.UTC).Format("2006-01-02 15:04:05")
// Just get the basename from the filename
filename := filepath.Base(entry.Filename)
return fmt.Sprintf("%s %s %s %s:%d %s", ts, entry.Level, entry.Module, filename, entry.Line, entry.Message)
}
// TimeFormat is the time format used for the default writer.
// This can be set with the environment variable LOGGO_TIME_FORMAT.
var TimeFormat = initTimeFormat()
func initTimeFormat() string {
format := os.Getenv("LOGGO_TIME_FORMAT")
if format != "" {
return format
}
return "15:04:05"
}
func formatTime(ts time.Time) string {
return ts.Format(TimeFormat)
}

32
vendor/github.com/juju/loggo/formatter_test.go generated vendored Normal file
View File

@ -0,0 +1,32 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"time"
gc "gopkg.in/check.v1"
"github.com/juju/loggo"
)
type formatterSuite struct{}
var _ = gc.Suite(&formatterSuite{})
func (*formatterSuite) TestDefaultFormat(c *gc.C) {
location, err := time.LoadLocation("UTC")
testTime := time.Date(2013, 5, 3, 10, 53, 24, 123456, location)
c.Assert(err, gc.IsNil)
entry := loggo.Entry{
Level: loggo.WARNING,
Module: "test.module",
Filename: "some/deep/filename",
Line: 42,
Timestamp: testTime,
Message: "hello world!",
}
formatted := loggo.DefaultFormatter(entry)
c.Assert(formatted, gc.Equals, "2013-05-03 10:53:24 WARNING test.module filename:42 hello world!")
}

85
vendor/github.com/juju/loggo/global.go generated vendored Normal file
View File

@ -0,0 +1,85 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
var (
defaultContext = newDefaultContxt()
)
func newDefaultContxt() *Context {
ctx := NewContext(WARNING)
ctx.AddWriter(DefaultWriterName, defaultWriter())
return ctx
}
// DefaultContext returns the global default logging context.
func DefaultContext() *Context {
return defaultContext
}
// LoggerInfo returns information about the configured loggers and their
// logging levels. The information is returned in the format expected by
// ConfigureLoggers. Loggers with UNSPECIFIED level will not
// be included.
func LoggerInfo() string {
return defaultContext.Config().String()
}
// GetLogger returns a Logger for the given module name,
// creating it and its parents if necessary.
func GetLogger(name string) Logger {
return defaultContext.GetLogger(name)
}
// ResetLogging iterates through the known modules and sets the levels of all
// to UNSPECIFIED, except for <root> which is set to WARNING. The call also
// removes all writers in the DefaultContext and puts the original default
// writer back as the only writer.
func ResetLogging() {
defaultContext.ResetLoggerLevels()
defaultContext.ResetWriters()
}
// ResetWriters puts the list of writers back into the initial state.
func ResetWriters() {
defaultContext.ResetWriters()
}
// ReplaceDefaultWriter is a convenience method that does the equivalent of
// RemoveWriter and then RegisterWriter with the name "default". The previous
// default writer, if any is returned.
func ReplaceDefaultWriter(writer Writer) (Writer, error) {
return defaultContext.ReplaceWriter(DefaultWriterName, writer)
}
// RegisterWriter adds the writer to the list of writers in the DefaultContext
// that get notified when logging. If there is already a registered writer
// with that name, an error is returned.
func RegisterWriter(name string, writer Writer) error {
return defaultContext.AddWriter(name, writer)
}
// RemoveWriter removes the Writer identified by 'name' and returns it.
// If the Writer is not found, an error is returned.
func RemoveWriter(name string) (Writer, error) {
return defaultContext.RemoveWriter(name)
}
// ConfigureLoggers configures loggers according to the given string
// specification, which specifies a set of modules and their associated
// logging levels. Loggers are colon- or semicolon-separated; each
// module is specified as <modulename>=<level>. White space outside of
// module names and levels is ignored. The root module is specified
// with the name "<root>".
//
// An example specification:
// `<root>=ERROR; foo.bar=WARNING`
func ConfigureLoggers(specification string) error {
config, err := ParseConfigString(specification)
if err != nil {
return err
}
defaultContext.ApplyConfig(config)
return nil
}

87
vendor/github.com/juju/loggo/global_test.go generated vendored Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"github.com/juju/loggo"
gc "gopkg.in/check.v1"
)
type GlobalSuite struct{}
var _ = gc.Suite(&GlobalSuite{})
func (*GlobalSuite) SetUpTest(c *gc.C) {
loggo.ResetDefaultContext()
}
func (*GlobalSuite) TestRootLogger(c *gc.C) {
var root loggo.Logger
got := loggo.GetLogger("")
c.Check(got.Name(), gc.Equals, root.Name())
c.Check(got.LogLevel(), gc.Equals, root.LogLevel())
}
func (*GlobalSuite) TestModuleName(c *gc.C) {
logger := loggo.GetLogger("loggo.testing")
c.Check(logger.Name(), gc.Equals, "loggo.testing")
}
func (*GlobalSuite) TestLevel(c *gc.C) {
logger := loggo.GetLogger("testing")
level := logger.LogLevel()
c.Check(level, gc.Equals, loggo.UNSPECIFIED)
}
func (*GlobalSuite) TestEffectiveLevel(c *gc.C) {
logger := loggo.GetLogger("testing")
level := logger.EffectiveLogLevel()
c.Check(level, gc.Equals, loggo.WARNING)
}
func (*GlobalSuite) TestLevelsSharedForSameModule(c *gc.C) {
logger1 := loggo.GetLogger("testing.module")
logger2 := loggo.GetLogger("testing.module")
logger1.SetLogLevel(loggo.INFO)
c.Assert(logger1.IsInfoEnabled(), gc.Equals, true)
c.Assert(logger2.IsInfoEnabled(), gc.Equals, true)
}
func (*GlobalSuite) TestModuleLowered(c *gc.C) {
logger1 := loggo.GetLogger("TESTING.MODULE")
logger2 := loggo.GetLogger("Testing")
c.Assert(logger1.Name(), gc.Equals, "testing.module")
c.Assert(logger2.Name(), gc.Equals, "testing")
}
func (s *GlobalSuite) TestConfigureLoggers(c *gc.C) {
err := loggo.ConfigureLoggers("testing.module=debug")
c.Assert(err, gc.IsNil)
expected := "<root>=WARNING;testing.module=DEBUG"
c.Assert(loggo.DefaultContext().Config().String(), gc.Equals, expected)
c.Assert(loggo.LoggerInfo(), gc.Equals, expected)
}
func (*GlobalSuite) TestRegisterWriterExistingName(c *gc.C) {
err := loggo.RegisterWriter("default", &writer{})
c.Assert(err, gc.ErrorMatches, `context already has a writer named "default"`)
}
func (*GlobalSuite) TestReplaceDefaultWriter(c *gc.C) {
oldWriter, err := loggo.ReplaceDefaultWriter(&writer{})
c.Assert(oldWriter, gc.NotNil)
c.Assert(err, gc.IsNil)
c.Assert(loggo.DefaultContext().WriterNames(), gc.DeepEquals, []string{"default"})
}
func (*GlobalSuite) TestRemoveWriter(c *gc.C) {
oldWriter, err := loggo.RemoveWriter("default")
c.Assert(oldWriter, gc.NotNil)
c.Assert(err, gc.IsNil)
c.Assert(loggo.DefaultContext().WriterNames(), gc.HasLen, 0)
}

102
vendor/github.com/juju/loggo/level.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"strings"
"sync/atomic"
)
// The severity levels. Higher values are more considered more
// important.
const (
UNSPECIFIED Level = iota
TRACE
DEBUG
INFO
WARNING
ERROR
CRITICAL
)
// Level holds a severity level.
type Level uint32
// ParseLevel converts a string representation of a logging level to a
// Level. It returns the level and whether it was valid or not.
func ParseLevel(level string) (Level, bool) {
level = strings.ToUpper(level)
switch level {
case "UNSPECIFIED":
return UNSPECIFIED, true
case "TRACE":
return TRACE, true
case "DEBUG":
return DEBUG, true
case "INFO":
return INFO, true
case "WARN", "WARNING":
return WARNING, true
case "ERROR":
return ERROR, true
case "CRITICAL":
return CRITICAL, true
default:
return UNSPECIFIED, false
}
}
// String implements Stringer.
func (level Level) String() string {
switch level {
case UNSPECIFIED:
return "UNSPECIFIED"
case TRACE:
return "TRACE"
case DEBUG:
return "DEBUG"
case INFO:
return "INFO"
case WARNING:
return "WARNING"
case ERROR:
return "ERROR"
case CRITICAL:
return "CRITICAL"
default:
return "<unknown>"
}
}
// Short returns a five character string to use in
// aligned logging output.
func (level Level) Short() string {
switch level {
case TRACE:
return "TRACE"
case DEBUG:
return "DEBUG"
case INFO:
return "INFO "
case WARNING:
return "WARN "
case ERROR:
return "ERROR"
case CRITICAL:
return "CRITC"
default:
return " "
}
}
// get atomically gets the value of the given level.
func (level *Level) get() Level {
return Level(atomic.LoadUint32((*uint32)(level)))
}
// set atomically sets the value of the receiver
// to the given level.
func (level *Level) set(newLevel Level) {
atomic.StoreUint32((*uint32)(level), uint32(newLevel))
}

96
vendor/github.com/juju/loggo/level_test.go generated vendored Normal file
View File

@ -0,0 +1,96 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/loggo"
)
type LevelSuite struct{}
var _ = gc.Suite(&LevelSuite{})
var parseLevelTests = []struct {
str string
level loggo.Level
fail bool
}{{
str: "trace",
level: loggo.TRACE,
}, {
str: "TrAce",
level: loggo.TRACE,
}, {
str: "TRACE",
level: loggo.TRACE,
}, {
str: "debug",
level: loggo.DEBUG,
}, {
str: "DEBUG",
level: loggo.DEBUG,
}, {
str: "info",
level: loggo.INFO,
}, {
str: "INFO",
level: loggo.INFO,
}, {
str: "warn",
level: loggo.WARNING,
}, {
str: "WARN",
level: loggo.WARNING,
}, {
str: "warning",
level: loggo.WARNING,
}, {
str: "WARNING",
level: loggo.WARNING,
}, {
str: "error",
level: loggo.ERROR,
}, {
str: "ERROR",
level: loggo.ERROR,
}, {
str: "critical",
level: loggo.CRITICAL,
}, {
str: "not_specified",
fail: true,
}, {
str: "other",
fail: true,
}, {
str: "",
fail: true,
}}
func (s *LevelSuite) TestParseLevel(c *gc.C) {
for _, test := range parseLevelTests {
level, ok := loggo.ParseLevel(test.str)
c.Assert(level, gc.Equals, test.level)
c.Assert(ok, gc.Equals, !test.fail)
}
}
var levelStringValueTests = map[loggo.Level]string{
loggo.UNSPECIFIED: "UNSPECIFIED",
loggo.DEBUG: "DEBUG",
loggo.TRACE: "TRACE",
loggo.INFO: "INFO",
loggo.WARNING: "WARNING",
loggo.ERROR: "ERROR",
loggo.CRITICAL: "CRITICAL",
loggo.Level(42): "<unknown>", // other values are unknown
}
func (s *LevelSuite) TestLevelStringValue(c *gc.C) {
for level, str := range levelStringValueTests {
c.Assert(level.String(), gc.Equals, str)
}
}

176
vendor/github.com/juju/loggo/logger.go generated vendored Normal file
View File

@ -0,0 +1,176 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"runtime"
"time"
)
// A Logger represents a logging module. It has an associated logging
// level which can be changed; messages of lesser severity will
// be dropped. Loggers have a hierarchical relationship - see
// the package documentation.
//
// The zero Logger value is usable - any messages logged
// to it will be sent to the root Logger.
type Logger struct {
impl *module
}
func (logger Logger) getModule() *module {
if logger.impl == nil {
return defaultContext.root
}
return logger.impl
}
// Name returns the logger's module name.
func (logger Logger) Name() string {
return logger.getModule().Name()
}
// LogLevel returns the configured min log level of the logger.
func (logger Logger) LogLevel() Level {
return logger.getModule().level
}
// EffectiveLogLevel returns the effective min log level of
// the receiver - that is, messages with a lesser severity
// level will be discarded.
//
// If the log level of the receiver is unspecified,
// it will be taken from the effective log level of its
// parent.
func (logger Logger) EffectiveLogLevel() Level {
return logger.getModule().getEffectiveLogLevel()
}
// SetLogLevel sets the severity level of the given logger.
// The root logger cannot be set to UNSPECIFIED level.
// See EffectiveLogLevel for how this affects the
// actual messages logged.
func (logger Logger) SetLogLevel(level Level) {
logger.getModule().setLevel(level)
}
// Logf logs a printf-formatted message at the given level.
// A message will be discarded if level is less than the
// the effective log level of the logger.
// Note that the writers may also filter out messages that
// are less than their registered minimum severity level.
func (logger Logger) Logf(level Level, message string, args ...interface{}) {
logger.LogCallf(2, level, message, args...)
}
// LogCallf logs a printf-formatted message at the given level.
// The location of the call is indicated by the calldepth argument.
// A calldepth of 1 means the function that called this function.
// A message will be discarded if level is less than the
// the effective log level of the logger.
// Note that the writers may also filter out messages that
// are less than their registered minimum severity level.
func (logger Logger) LogCallf(calldepth int, level Level, message string, args ...interface{}) {
module := logger.getModule()
if !module.willWrite(level) {
return
}
// Gather time, and filename, line number.
now := time.Now() // get this early.
// Param to Caller is the call depth. Since this method is called from
// the Logger methods, we want the place that those were called from.
_, file, line, ok := runtime.Caller(calldepth + 1)
if !ok {
file = "???"
line = 0
}
// Trim newline off format string, following usual
// Go logging conventions.
if len(message) > 0 && message[len(message)-1] == '\n' {
message = message[0 : len(message)-1]
}
// To avoid having a proliferation of Info/Infof methods,
// only use Sprintf if there are any args, and rely on the
// `go vet` tool for the obvious cases where someone has forgotten
// to provide an arg.
formattedMessage := message
if len(args) > 0 {
formattedMessage = fmt.Sprintf(message, args...)
}
module.write(Entry{
Level: level,
Filename: file,
Line: line,
Timestamp: now,
Message: formattedMessage,
})
}
// Criticalf logs the printf-formatted message at critical level.
func (logger Logger) Criticalf(message string, args ...interface{}) {
logger.Logf(CRITICAL, message, args...)
}
// Errorf logs the printf-formatted message at error level.
func (logger Logger) Errorf(message string, args ...interface{}) {
logger.Logf(ERROR, message, args...)
}
// Warningf logs the printf-formatted message at warning level.
func (logger Logger) Warningf(message string, args ...interface{}) {
logger.Logf(WARNING, message, args...)
}
// Infof logs the printf-formatted message at info level.
func (logger Logger) Infof(message string, args ...interface{}) {
logger.Logf(INFO, message, args...)
}
// Debugf logs the printf-formatted message at debug level.
func (logger Logger) Debugf(message string, args ...interface{}) {
logger.Logf(DEBUG, message, args...)
}
// Tracef logs the printf-formatted message at trace level.
func (logger Logger) Tracef(message string, args ...interface{}) {
logger.Logf(TRACE, message, args...)
}
// IsLevelEnabled returns whether debugging is enabled
// for the given log level.
func (logger Logger) IsLevelEnabled(level Level) bool {
return logger.getModule().willWrite(level)
}
// IsErrorEnabled returns whether debugging is enabled
// at error level.
func (logger Logger) IsErrorEnabled() bool {
return logger.IsLevelEnabled(ERROR)
}
// IsWarningEnabled returns whether debugging is enabled
// at warning level.
func (logger Logger) IsWarningEnabled() bool {
return logger.IsLevelEnabled(WARNING)
}
// IsInfoEnabled returns whether debugging is enabled
// at info level.
func (logger Logger) IsInfoEnabled() bool {
return logger.IsLevelEnabled(INFO)
}
// IsDebugEnabled returns whether debugging is enabled
// at debug level.
func (logger Logger) IsDebugEnabled() bool {
return logger.IsLevelEnabled(DEBUG)
}
// IsTraceEnabled returns whether debugging is enabled
// at trace level.
func (logger Logger) IsTraceEnabled() bool {
return logger.IsLevelEnabled(TRACE)
}

139
vendor/github.com/juju/loggo/logger_test.go generated vendored Normal file
View File

@ -0,0 +1,139 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/loggo"
)
type LoggerSuite struct{}
var _ = gc.Suite(&LoggerSuite{})
func (*LoggerSuite) SetUpTest(c *gc.C) {
loggo.ResetDefaultContext()
}
func (s *LoggerSuite) TestRootLogger(c *gc.C) {
root := loggo.Logger{}
c.Check(root.Name(), gc.Equals, "<root>")
c.Check(root.LogLevel(), gc.Equals, loggo.WARNING)
c.Check(root.IsErrorEnabled(), gc.Equals, true)
c.Check(root.IsWarningEnabled(), gc.Equals, true)
c.Check(root.IsInfoEnabled(), gc.Equals, false)
c.Check(root.IsDebugEnabled(), gc.Equals, false)
c.Check(root.IsTraceEnabled(), gc.Equals, false)
}
func (s *LoggerSuite) TestSetLevel(c *gc.C) {
logger := loggo.GetLogger("testing")
c.Assert(logger.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.WARNING)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, true)
c.Assert(logger.IsInfoEnabled(), gc.Equals, false)
c.Assert(logger.IsDebugEnabled(), gc.Equals, false)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
logger.SetLogLevel(loggo.TRACE)
c.Assert(logger.LogLevel(), gc.Equals, loggo.TRACE)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.TRACE)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, true)
c.Assert(logger.IsInfoEnabled(), gc.Equals, true)
c.Assert(logger.IsDebugEnabled(), gc.Equals, true)
c.Assert(logger.IsTraceEnabled(), gc.Equals, true)
logger.SetLogLevel(loggo.DEBUG)
c.Assert(logger.LogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, true)
c.Assert(logger.IsInfoEnabled(), gc.Equals, true)
c.Assert(logger.IsDebugEnabled(), gc.Equals, true)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
logger.SetLogLevel(loggo.INFO)
c.Assert(logger.LogLevel(), gc.Equals, loggo.INFO)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.INFO)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, true)
c.Assert(logger.IsInfoEnabled(), gc.Equals, true)
c.Assert(logger.IsDebugEnabled(), gc.Equals, false)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
logger.SetLogLevel(loggo.WARNING)
c.Assert(logger.LogLevel(), gc.Equals, loggo.WARNING)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.WARNING)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, true)
c.Assert(logger.IsInfoEnabled(), gc.Equals, false)
c.Assert(logger.IsDebugEnabled(), gc.Equals, false)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
logger.SetLogLevel(loggo.ERROR)
c.Assert(logger.LogLevel(), gc.Equals, loggo.ERROR)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, false)
c.Assert(logger.IsInfoEnabled(), gc.Equals, false)
c.Assert(logger.IsDebugEnabled(), gc.Equals, false)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
// This is added for completeness, but not really expected to be used.
logger.SetLogLevel(loggo.CRITICAL)
c.Assert(logger.LogLevel(), gc.Equals, loggo.CRITICAL)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.CRITICAL)
c.Assert(logger.IsErrorEnabled(), gc.Equals, false)
c.Assert(logger.IsWarningEnabled(), gc.Equals, false)
c.Assert(logger.IsInfoEnabled(), gc.Equals, false)
c.Assert(logger.IsDebugEnabled(), gc.Equals, false)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
logger.SetLogLevel(loggo.UNSPECIFIED)
c.Assert(logger.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.WARNING)
}
func (s *LoggerSuite) TestModuleLowered(c *gc.C) {
logger1 := loggo.GetLogger("TESTING.MODULE")
logger2 := loggo.GetLogger("Testing")
c.Assert(logger1.Name(), gc.Equals, "testing.module")
c.Assert(logger2.Name(), gc.Equals, "testing")
}
func (s *LoggerSuite) TestLevelsInherited(c *gc.C) {
root := loggo.GetLogger("")
first := loggo.GetLogger("first")
second := loggo.GetLogger("first.second")
root.SetLogLevel(loggo.ERROR)
c.Assert(root.LogLevel(), gc.Equals, loggo.ERROR)
c.Assert(root.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(first.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(first.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(second.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(second.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
first.SetLogLevel(loggo.DEBUG)
c.Assert(root.LogLevel(), gc.Equals, loggo.ERROR)
c.Assert(root.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(first.LogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(first.EffectiveLogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(second.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(second.EffectiveLogLevel(), gc.Equals, loggo.DEBUG)
second.SetLogLevel(loggo.INFO)
c.Assert(root.LogLevel(), gc.Equals, loggo.ERROR)
c.Assert(root.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(first.LogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(first.EffectiveLogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(second.LogLevel(), gc.Equals, loggo.INFO)
c.Assert(second.EffectiveLogLevel(), gc.Equals, loggo.INFO)
first.SetLogLevel(loggo.UNSPECIFIED)
c.Assert(root.LogLevel(), gc.Equals, loggo.ERROR)
c.Assert(root.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(first.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(first.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(second.LogLevel(), gc.Equals, loggo.INFO)
c.Assert(second.EffectiveLogLevel(), gc.Equals, loggo.INFO)
}

92
vendor/github.com/juju/loggo/logging_test.go generated vendored Normal file
View File

@ -0,0 +1,92 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"time"
gc "gopkg.in/check.v1"
"github.com/juju/loggo"
)
type LoggingSuite struct {
context *loggo.Context
writer *writer
logger loggo.Logger
}
var _ = gc.Suite(&LoggingSuite{})
func (s *LoggingSuite) SetUpTest(c *gc.C) {
s.writer = &writer{}
s.context = loggo.NewContext(loggo.TRACE)
s.context.AddWriter("test", s.writer)
s.logger = s.context.GetLogger("test")
}
func (s *LoggingSuite) TestLoggingStrings(c *gc.C) {
s.logger.Infof("simple")
s.logger.Infof("with args %d", 42)
s.logger.Infof("working 100%")
s.logger.Infof("missing %s")
checkLogEntries(c, s.writer.Log(), []loggo.Entry{
{Level: loggo.INFO, Module: "test", Message: "simple"},
{Level: loggo.INFO, Module: "test", Message: "with args 42"},
{Level: loggo.INFO, Module: "test", Message: "working 100%"},
{Level: loggo.INFO, Module: "test", Message: "missing %s"},
})
}
func (s *LoggingSuite) TestLoggingLimitWarning(c *gc.C) {
s.logger.SetLogLevel(loggo.WARNING)
start := time.Now()
logAllSeverities(s.logger)
end := time.Now()
entries := s.writer.Log()
checkLogEntries(c, entries, []loggo.Entry{
{Level: loggo.CRITICAL, Module: "test", Message: "something critical"},
{Level: loggo.ERROR, Module: "test", Message: "an error"},
{Level: loggo.WARNING, Module: "test", Message: "a warning message"},
})
for _, entry := range entries {
c.Check(entry.Timestamp, Between(start, end))
}
}
func (s *LoggingSuite) TestLocationCapture(c *gc.C) {
s.logger.Criticalf("critical message") //tag critical-location
s.logger.Errorf("error message") //tag error-location
s.logger.Warningf("warning message") //tag warning-location
s.logger.Infof("info message") //tag info-location
s.logger.Debugf("debug message") //tag debug-location
s.logger.Tracef("trace message") //tag trace-location
log := s.writer.Log()
tags := []string{
"critical-location",
"error-location",
"warning-location",
"info-location",
"debug-location",
"trace-location",
}
c.Assert(log, gc.HasLen, len(tags))
for x := range tags {
assertLocation(c, log[x], tags[x])
}
}
func (s *LoggingSuite) TestLogDoesntLogWeirdLevels(c *gc.C) {
s.logger.Logf(loggo.UNSPECIFIED, "message")
c.Assert(s.writer.Log(), gc.HasLen, 0)
s.logger.Logf(loggo.Level(42), "message")
c.Assert(s.writer.Log(), gc.HasLen, 0)
s.logger.Logf(loggo.CRITICAL+loggo.Level(1), "message")
c.Assert(s.writer.Log(), gc.HasLen, 0)
}

61
vendor/github.com/juju/loggo/module.go generated vendored Normal file
View File

@ -0,0 +1,61 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
// Do not change rootName: modules.resolve() will misbehave if it isn't "".
const (
rootString = "<root>"
defaultRootLevel = WARNING
defaultLevel = UNSPECIFIED
)
type module struct {
name string
level Level
parent *module
context *Context
}
// Name returns the module's name.
func (module *module) Name() string {
if module.name == "" {
return rootString
}
return module.name
}
func (m *module) willWrite(level Level) bool {
if level < TRACE || level > CRITICAL {
return false
}
return level >= m.getEffectiveLogLevel()
}
func (module *module) getEffectiveLogLevel() Level {
// Note: the root module is guaranteed to have a
// specified logging level, so acts as a suitable sentinel
// for this loop.
for {
if level := module.level.get(); level != UNSPECIFIED {
return level
}
module = module.parent
}
panic("unreachable")
}
// setLevel sets the severity level of the given module.
// The root module cannot be set to UNSPECIFIED level.
func (module *module) setLevel(level Level) {
// The root module can't be unspecified.
if module.name == "" && level == UNSPECIFIED {
level = WARNING
}
module.level.set(level)
}
func (m *module) write(entry Entry) {
entry.Module = m.name
m.context.write(entry)
}

14
vendor/github.com/juju/loggo/package_test.go generated vendored Normal file
View File

@ -0,0 +1,14 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"testing"
gc "gopkg.in/check.v1"
)
func Test(t *testing.T) {
gc.TestingT(t)
}

40
vendor/github.com/juju/loggo/testwriter.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"path"
"sync"
)
// TestWriter is a useful Writer for testing purposes. Each component of the
// logging message is stored in the Log array.
type TestWriter struct {
mu sync.Mutex
log []Entry
}
// Write saves the params as members in the TestLogValues struct appended to the Log array.
func (writer *TestWriter) Write(entry Entry) {
writer.mu.Lock()
defer writer.mu.Unlock()
entry.Filename = path.Base(entry.Filename)
writer.log = append(writer.log, entry)
}
// Clear removes any saved log messages.
func (writer *TestWriter) Clear() {
writer.mu.Lock()
defer writer.mu.Unlock()
writer.log = nil
}
// Log returns a copy of the current logged values.
func (writer *TestWriter) Log() []Entry {
writer.mu.Lock()
defer writer.mu.Unlock()
v := make([]Entry, len(writer.log))
copy(v, writer.log)
return v
}

68
vendor/github.com/juju/loggo/util_test.go generated vendored Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"fmt"
"io/ioutil"
"strings"
"github.com/juju/loggo"
gc "gopkg.in/check.v1"
)
func init() {
setLocationsForTags("logging_test.go")
setLocationsForTags("writer_test.go")
}
func assertLocation(c *gc.C, msg loggo.Entry, tag string) {
loc := location(tag)
c.Assert(msg.Filename, gc.Equals, loc.file)
c.Assert(msg.Line, gc.Equals, loc.line)
}
// All this location stuff is to avoid having hard coded line numbers
// in the tests. Any line where as a test writer you want to capture the
// file and line number, add a comment that has `//tag name` as the end of
// the line. The name must be unique across all the tests, and the test
// will panic if it is not. This name is then used to read the actual
// file and line numbers.
func location(tag string) Location {
loc, ok := tagToLocation[tag]
if !ok {
panic(fmt.Errorf("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 setLocationsForTags(filename string) {
data, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
lines := strings.Split(string(data), "\n")
for i, line := range lines {
if j := strings.Index(line, "//tag "); j >= 0 {
tag := line[j+len("//tag "):]
if _, found := tagToLocation[tag]; found {
panic(fmt.Errorf("tag %q already processed previously"))
}
tagToLocation[tag] = Location{file: filename, line: i + 1}
}
}
}

70
vendor/github.com/juju/loggo/writer.go generated vendored Normal file
View File

@ -0,0 +1,70 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"io"
"os"
)
// DefaultWriterName is the name of the default writer for
// a Context.
const DefaultWriterName = "default"
// Writer is implemented by any recipient of log messages.
type Writer interface {
// Write writes a message to the Writer with the given level and module
// name. The filename and line hold the file name and line number of the
// code that is generating the log message; the time stamp holds the time
// the log message was generated, and message holds the log message
// itself.
Write(entry Entry)
}
// NewMinLevelWriter returns a Writer that will only pass on the Write calls
// to the provided writer if the log level is at or above the specified
// minimum level.
func NewMinimumLevelWriter(writer Writer, minLevel Level) Writer {
return &minLevelWriter{
writer: writer,
level: minLevel,
}
}
type minLevelWriter struct {
writer Writer
level Level
}
// Write writes the log record.
func (w minLevelWriter) Write(entry Entry) {
if entry.Level < w.level {
return
}
w.writer.Write(entry)
}
type simpleWriter struct {
writer io.Writer
formatter func(entry Entry) string
}
// NewSimpleWriter returns a new writer that writes log messages to the given
// io.Writer formatting the messages with the given formatter.
func NewSimpleWriter(writer io.Writer, formatter func(entry Entry) string) Writer {
if formatter == nil {
formatter = DefaultFormatter
}
return &simpleWriter{writer, formatter}
}
func (simple *simpleWriter) Write(entry Entry) {
logLine := simple.formatter(entry)
fmt.Fprintln(simple.writer, logLine)
}
func defaultWriter() Writer {
return NewSimpleWriter(os.Stderr, DefaultFormatter)
}

37
vendor/github.com/juju/loggo/writer_test.go generated vendored Normal file
View File

@ -0,0 +1,37 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"bytes"
"time"
gc "gopkg.in/check.v1"
"github.com/juju/loggo"
)
type SimpleWriterSuite struct{}
var _ = gc.Suite(&SimpleWriterSuite{})
func (s *SimpleWriterSuite) TestNewSimpleWriter(c *gc.C) {
now := time.Now()
formatter := func(entry loggo.Entry) string {
return "<< " + entry.Message + " >>"
}
buf := &bytes.Buffer{}
writer := loggo.NewSimpleWriter(buf, formatter)
writer.Write(loggo.Entry{
Level: loggo.INFO,
Module: "test",
Filename: "somefile.go",
Line: 12,
Timestamp: now,
Message: "a message",
})
c.Check(buf.String(), gc.Equals, "<< a message >>\n")
}

27
vendor/github.com/juju/testing/checkers/LICENSE-golang generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

117
vendor/github.com/juju/testing/checkers/bool.go generated vendored Normal file
View File

@ -0,0 +1,117 @@
// Copyright 2011 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package checkers
import (
"fmt"
"reflect"
gc "gopkg.in/check.v1"
)
type isTrueChecker struct {
*gc.CheckerInfo
}
// IsTrue checks whether a value has an underlying
// boolean type and is true.
var IsTrue gc.Checker = &isTrueChecker{
&gc.CheckerInfo{Name: "IsTrue", Params: []string{"obtained"}},
}
// IsTrue checks whether a value has an underlying
// boolean type and is false.
var IsFalse gc.Checker = gc.Not(IsTrue)
func (checker *isTrueChecker) Check(params []interface{}, names []string) (result bool, error string) {
value := reflect.ValueOf(params[0])
if !value.IsValid() {
return false, fmt.Sprintf("expected type bool, received %s", value)
}
switch value.Kind() {
case reflect.Bool:
return value.Bool(), ""
}
return false, fmt.Sprintf("expected type bool, received type %s", value.Type())
}
type satisfiesChecker struct {
*gc.CheckerInfo
}
// Satisfies checks whether a value causes the argument
// function to return true. The function must be of
// type func(T) bool where the value being checked
// is assignable to T.
var Satisfies gc.Checker = &satisfiesChecker{
&gc.CheckerInfo{
Name: "Satisfies",
Params: []string{"obtained", "func(T) bool"},
},
}
func (checker *satisfiesChecker) Check(params []interface{}, names []string) (result bool, error string) {
f := reflect.ValueOf(params[1])
ft := f.Type()
if ft.Kind() != reflect.Func ||
ft.NumIn() != 1 ||
ft.NumOut() != 1 ||
ft.Out(0) != reflect.TypeOf(true) {
return false, fmt.Sprintf("expected func(T) bool, got %s", ft)
}
v := reflect.ValueOf(params[0])
if !v.IsValid() {
if !canBeNil(ft.In(0)) {
return false, fmt.Sprintf("cannot assign nil to argument %T", ft.In(0))
}
v = reflect.Zero(ft.In(0))
}
if !v.Type().AssignableTo(ft.In(0)) {
return false, fmt.Sprintf("wrong argument type %s for %s", v.Type(), ft)
}
return f.Call([]reflect.Value{v})[0].Interface().(bool), ""
}
func canBeNil(t reflect.Type) bool {
switch t.Kind() {
case reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.Map,
reflect.Ptr,
reflect.Slice:
return true
}
return false
}
type deepEqualsChecker struct {
*gc.CheckerInfo
}
// The DeepEquals checker verifies that the obtained value is deep-equal to
// the expected value. The check will work correctly even when facing
// slices, interfaces, and values of different types (which always fail
// the test).
//
// For example:
//
// c.Assert(value, DeepEquals, 42)
// c.Assert(array, DeepEquals, []string{"hi", "there"})
//
// This checker differs from gocheck.DeepEquals in that
// it will compare a nil slice equal to an empty slice,
// and a nil map equal to an empty map.
var DeepEquals gc.Checker = &deepEqualsChecker{
&gc.CheckerInfo{Name: "DeepEquals", Params: []string{"obtained", "expected"}},
}
func (checker *deepEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) {
if ok, err := DeepEqual(params[0], params[1]); !ok {
return false, err.Error()
}
return true, ""
}

125
vendor/github.com/juju/testing/checkers/bool_test.go generated vendored Normal file
View File

@ -0,0 +1,125 @@
// Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package checkers_test
import (
"errors"
"os"
gc "gopkg.in/check.v1"
jc "github.com/juju/testing/checkers"
)
type BoolSuite struct{}
var _ = gc.Suite(&BoolSuite{})
func (s *BoolSuite) TestIsTrue(c *gc.C) {
c.Assert(true, jc.IsTrue)
c.Assert(false, gc.Not(jc.IsTrue))
result, msg := jc.IsTrue.Check([]interface{}{false}, nil)
c.Assert(result, gc.Equals, false)
c.Assert(msg, gc.Equals, "")
result, msg = jc.IsTrue.Check([]interface{}{"foo"}, nil)
c.Assert(result, gc.Equals, false)
c.Check(msg, gc.Equals, `expected type bool, received type string`)
result, msg = jc.IsTrue.Check([]interface{}{42}, nil)
c.Assert(result, gc.Equals, false)
c.Assert(msg, gc.Equals, `expected type bool, received type int`)
result, msg = jc.IsTrue.Check([]interface{}{nil}, nil)
c.Assert(result, gc.Equals, false)
c.Assert(msg, gc.Matches, `expected type bool, received <invalid .*Value>`)
}
func (s *BoolSuite) TestIsFalse(c *gc.C) {
c.Check(false, jc.IsFalse)
c.Check(true, gc.Not(jc.IsFalse))
}
func is42(i int) bool {
return i == 42
}
var satisfiesTests = []struct {
f interface{}
arg interface{}
result bool
msg string
}{{
f: is42,
arg: 42,
result: true,
}, {
f: is42,
arg: 41,
result: false,
}, {
f: is42,
arg: "",
result: false,
msg: "wrong argument type string for func(int) bool",
}, {
f: os.IsNotExist,
arg: errors.New("foo"),
result: false,
}, {
f: os.IsNotExist,
arg: os.ErrNotExist,
result: true,
}, {
f: os.IsNotExist,
arg: nil,
result: false,
}, {
f: func(chan int) bool { return true },
arg: nil,
result: true,
}, {
f: func(func()) bool { return true },
arg: nil,
result: true,
}, {
f: func(interface{}) bool { return true },
arg: nil,
result: true,
}, {
f: func(map[string]bool) bool { return true },
arg: nil,
result: true,
}, {
f: func(*int) bool { return true },
arg: nil,
result: true,
}, {
f: func([]string) bool { return true },
arg: nil,
result: true,
}}
func (s *BoolSuite) TestSatisfies(c *gc.C) {
for i, test := range satisfiesTests {
c.Logf("test %d. %T %T", i, test.f, test.arg)
result, msg := jc.Satisfies.Check([]interface{}{test.arg, test.f}, nil)
c.Check(result, gc.Equals, test.result)
c.Check(msg, gc.Equals, test.msg)
}
}
func (s *BoolSuite) TestDeepEquals(c *gc.C) {
for i, test := range deepEqualTests {
c.Logf("test %d. %v == %v is %v", i, test.a, test.b, test.eq)
result, msg := jc.DeepEquals.Check([]interface{}{test.a, test.b}, nil)
c.Check(result, gc.Equals, test.eq)
if test.eq {
c.Check(msg, gc.Equals, "")
} else {
c.Check(msg, gc.Not(gc.Equals), "")
}
}
}

255
vendor/github.com/juju/testing/checkers/checker.go generated vendored Normal file
View File

@ -0,0 +1,255 @@
// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package checkers
import (
"fmt"
"reflect"
"strings"
"time"
gc "gopkg.in/check.v1"
)
func TimeBetween(start, end time.Time) gc.Checker {
if end.Before(start) {
return &timeBetweenChecker{end, start}
}
return &timeBetweenChecker{start, end}
}
type timeBetweenChecker struct {
start, end time.Time
}
func (checker *timeBetweenChecker) Info() *gc.CheckerInfo {
info := gc.CheckerInfo{
Name: "TimeBetween",
Params: []string{"obtained"},
}
return &info
}
func (checker *timeBetweenChecker) Check(params []interface{}, names []string) (result bool, error string) {
when, ok := params[0].(time.Time)
if !ok {
return false, "obtained value type must be time.Time"
}
if when.Before(checker.start) {
return false, fmt.Sprintf("obtained time %q is before start time %q", when, checker.start)
}
if when.After(checker.end) {
return false, fmt.Sprintf("obtained time %q is after end time %q", when, checker.end)
}
return true, ""
}
// DurationLessThan checker
type durationLessThanChecker struct {
*gc.CheckerInfo
}
var DurationLessThan gc.Checker = &durationLessThanChecker{
&gc.CheckerInfo{Name: "DurationLessThan", Params: []string{"obtained", "expected"}},
}
func (checker *durationLessThanChecker) Check(params []interface{}, names []string) (result bool, error string) {
obtained, ok := params[0].(time.Duration)
if !ok {
return false, "obtained value type must be time.Duration"
}
expected, ok := params[1].(time.Duration)
if !ok {
return false, "expected value type must be time.Duration"
}
return obtained.Nanoseconds() < expected.Nanoseconds(), ""
}
// HasPrefix checker for checking strings
func stringOrStringer(value interface{}) (string, bool) {
result, isString := value.(string)
if !isString {
if stringer, isStringer := value.(fmt.Stringer); isStringer {
result, isString = stringer.String(), true
}
}
return result, isString
}
type hasPrefixChecker struct {
*gc.CheckerInfo
}
var HasPrefix gc.Checker = &hasPrefixChecker{
&gc.CheckerInfo{Name: "HasPrefix", Params: []string{"obtained", "expected"}},
}
func (checker *hasPrefixChecker) Check(params []interface{}, names []string) (result bool, error string) {
expected, ok := params[1].(string)
if !ok {
return false, "expected must be a string"
}
obtained, isString := stringOrStringer(params[0])
if isString {
return strings.HasPrefix(obtained, expected), ""
}
return false, "Obtained value is not a string and has no .String()"
}
type hasSuffixChecker struct {
*gc.CheckerInfo
}
var HasSuffix gc.Checker = &hasSuffixChecker{
&gc.CheckerInfo{Name: "HasSuffix", Params: []string{"obtained", "expected"}},
}
func (checker *hasSuffixChecker) Check(params []interface{}, names []string) (result bool, error string) {
expected, ok := params[1].(string)
if !ok {
return false, "expected must be a string"
}
obtained, isString := stringOrStringer(params[0])
if isString {
return strings.HasSuffix(obtained, expected), ""
}
return false, "Obtained value is not a string and has no .String()"
}
type containsChecker struct {
*gc.CheckerInfo
}
var Contains gc.Checker = &containsChecker{
&gc.CheckerInfo{Name: "Contains", Params: []string{"obtained", "expected"}},
}
func (checker *containsChecker) Check(params []interface{}, names []string) (result bool, error string) {
expected, ok := params[1].(string)
if !ok {
return false, "expected must be a string"
}
obtained, isString := stringOrStringer(params[0])
if isString {
return strings.Contains(obtained, expected), ""
}
return false, "Obtained value is not a string and has no .String()"
}
type sameContents struct {
*gc.CheckerInfo
}
// SameContents checks that the obtained slice contains all the values (and
// same number of values) of the expected slice and vice versa, without respect
// to order or duplicates. Uses DeepEquals on mapped contents to compare.
var SameContents gc.Checker = &sameContents{
&gc.CheckerInfo{Name: "SameContents", Params: []string{"obtained", "expected"}},
}
func (checker *sameContents) Check(params []interface{}, names []string) (result bool, error string) {
if len(params) != 2 {
return false, "SameContents expects two slice arguments"
}
obtained := params[0]
expected := params[1]
tob := reflect.TypeOf(obtained)
if tob.Kind() != reflect.Slice {
return false, fmt.Sprintf("SameContents expects the obtained value to be a slice, got %q",
tob.Kind())
}
texp := reflect.TypeOf(expected)
if texp.Kind() != reflect.Slice {
return false, fmt.Sprintf("SameContents expects the expected value to be a slice, got %q",
texp.Kind())
}
if texp != tob {
return false, fmt.Sprintf(
"SameContents expects two slices of the same type, expected: %q, got: %q",
texp, tob)
}
vexp := reflect.ValueOf(expected)
vob := reflect.ValueOf(obtained)
length := vexp.Len()
if vob.Len() != length {
// Slice has incorrect number of elements
return false, ""
}
// spin up maps with the entries as keys and the counts as values
mob := make(map[interface{}]int, length)
mexp := make(map[interface{}]int, length)
for i := 0; i < length; i++ {
mexp[reflect.Indirect(vexp.Index(i)).Interface()]++
mob[reflect.Indirect(vob.Index(i)).Interface()]++
}
return reflect.DeepEqual(mob, mexp), ""
}
type errorIsNilChecker struct {
*gc.CheckerInfo
}
// The ErrorIsNil checker tests whether the obtained value is nil.
// Explicitly tests against only `nil`.
//
// For example:
//
// c.Assert(err, ErrorIsNil)
//
var ErrorIsNil gc.Checker = &errorIsNilChecker{
&gc.CheckerInfo{Name: "ErrorIsNil", Params: []string{"value"}},
}
type ErrorStacker interface {
error
StackTrace() []string
}
func (checker *errorIsNilChecker) Check(params []interface{}, names []string) (bool, string) {
result, message := errorIsNil(params[0])
if !result {
if stacker, ok := params[0].(ErrorStacker); ok && message == "" {
stack := stacker.StackTrace()
if stack != nil {
message = "error stack:\n\t" + strings.Join(stack, "\n\t")
}
}
}
return result, message
}
func errorIsNil(obtained interface{}) (result bool, message string) {
if obtained == nil {
return true, ""
}
if _, ok := obtained.(error); !ok {
return false, fmt.Sprintf("obtained type (%T) is not an error", obtained)
}
switch v := reflect.ValueOf(obtained); v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
if v.IsNil() {
return false, fmt.Sprintf("value of (%T) is nil, but a typed nil", obtained)
}
}
return false, ""
}

268
vendor/github.com/juju/testing/checkers/checker_test.go generated vendored Normal file
View File

@ -0,0 +1,268 @@
// Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package checkers_test
import (
"fmt"
"testing"
"time"
gc "gopkg.in/check.v1"
jc "github.com/juju/testing/checkers"
)
func Test(t *testing.T) { gc.TestingT(t) }
type CheckerSuite struct{}
var _ = gc.Suite(&CheckerSuite{})
func (s *CheckerSuite) TestHasPrefix(c *gc.C) {
c.Assert("foo bar", jc.HasPrefix, "foo")
c.Assert("foo bar", gc.Not(jc.HasPrefix), "omg")
}
func (s *CheckerSuite) TestHasSuffix(c *gc.C) {
c.Assert("foo bar", jc.HasSuffix, "bar")
c.Assert("foo bar", gc.Not(jc.HasSuffix), "omg")
}
func (s *CheckerSuite) TestContains(c *gc.C) {
c.Assert("foo bar baz", jc.Contains, "foo")
c.Assert("foo bar baz", jc.Contains, "bar")
c.Assert("foo bar baz", jc.Contains, "baz")
c.Assert("foo bar baz", gc.Not(jc.Contains), "omg")
}
func (s *CheckerSuite) TestTimeBetween(c *gc.C) {
now := time.Now()
earlier := now.Add(-1 * time.Second)
later := now.Add(time.Second)
checkOK := func(value interface{}, start, end time.Time) {
checker := jc.TimeBetween(start, end)
value, msg := checker.Check([]interface{}{value}, nil)
c.Check(value, jc.IsTrue)
c.Check(msg, gc.Equals, "")
}
checkFails := func(value interface{}, start, end time.Time, match string) {
checker := jc.TimeBetween(start, end)
value, msg := checker.Check([]interface{}{value}, nil)
c.Check(value, jc.IsFalse)
c.Check(msg, gc.Matches, match)
}
checkOK(now, earlier, later)
// Later can be before earlier...
checkOK(now, later, earlier)
// check at bounds
checkOK(earlier, earlier, later)
checkOK(later, earlier, later)
checkFails(earlier, now, later, `obtained time .* is before start time .*`)
checkFails(later, now, earlier, `obtained time .* is after end time .*`)
checkFails(42, now, earlier, `obtained value type must be time.Time`)
}
type someStruct struct {
a uint
}
func (s *CheckerSuite) TestSameContents(c *gc.C) {
//// positive cases ////
// same
c.Check(
[]int{1, 2, 3}, jc.SameContents,
[]int{1, 2, 3})
// empty
c.Check(
[]int{}, jc.SameContents,
[]int{})
// single
c.Check(
[]int{1}, jc.SameContents,
[]int{1})
// different order
c.Check(
[]int{1, 2, 3}, jc.SameContents,
[]int{3, 2, 1})
// multiple copies of same
c.Check(
[]int{1, 1, 2}, jc.SameContents,
[]int{2, 1, 1})
type test struct {
s string
i int
}
// test structs
c.Check(
[]test{{"a", 1}, {"b", 2}}, jc.SameContents,
[]test{{"b", 2}, {"a", 1}})
//// negative cases ////
// different contents
c.Check(
[]int{1, 3, 2, 5}, gc.Not(jc.SameContents),
[]int{5, 2, 3, 4})
// different size slices
c.Check(
[]int{1, 2, 3}, gc.Not(jc.SameContents),
[]int{1, 2})
// different counts of same items
c.Check(
[]int{1, 1, 2}, gc.Not(jc.SameContents),
[]int{1, 2, 2})
// Tests that check that we compare the contents of structs,
// that we point to, not just the pointers to them.
a1 := someStruct{1}
a2 := someStruct{2}
a3 := someStruct{3}
b1 := someStruct{1}
b2 := someStruct{2}
// Same order, same contents
c.Check(
[]*someStruct{&a1, &a2}, jc.SameContents,
[]*someStruct{&b1, &b2})
// Empty vs not
c.Check(
[]*someStruct{&a1, &a2}, gc.Not(jc.SameContents),
[]*someStruct{})
// Empty vs empty
// Same order, same contents
c.Check(
[]*someStruct{}, jc.SameContents,
[]*someStruct{})
// Different order, same contents
c.Check(
[]*someStruct{&a1, &a2}, jc.SameContents,
[]*someStruct{&b2, &b1})
// different contents
c.Check(
[]*someStruct{&a3, &a2}, gc.Not(jc.SameContents),
[]*someStruct{&b2, &b1})
// Different sizes, same contents (duplicate item)
c.Check(
[]*someStruct{&a1, &a2, &a1}, gc.Not(jc.SameContents),
[]*someStruct{&b2, &b1})
// Different sizes, same contents
c.Check(
[]*someStruct{&a1, &a1, &a2}, gc.Not(jc.SameContents),
[]*someStruct{&b2, &b1})
// Same sizes, same contents, different quantities
c.Check(
[]*someStruct{&a1, &a2, &a2}, gc.Not(jc.SameContents),
[]*someStruct{&b1, &b1, &b2})
/// Error cases ///
// note: for these tests, we can't use gc.Not, since Not passes the error value through
// and checks with a non-empty error always count as failed
// Oddly, there doesn't seem to actually be a way to check for an error from a Checker.
// different type
res, err := jc.SameContents.Check([]interface{}{
[]string{"1", "2"},
[]int{1, 2},
}, []string{})
c.Check(res, jc.IsFalse)
c.Check(err, gc.Not(gc.Equals), "")
// obtained not a slice
res, err = jc.SameContents.Check([]interface{}{
"test",
[]int{1},
}, []string{})
c.Check(res, jc.IsFalse)
c.Check(err, gc.Not(gc.Equals), "")
// expected not a slice
res, err = jc.SameContents.Check([]interface{}{
[]int{1},
"test",
}, []string{})
c.Check(res, jc.IsFalse)
c.Check(err, gc.Not(gc.Equals), "")
}
type stack_error struct {
message string
stack []string
}
type embedded struct {
typed *stack_error
err error
}
func (s *stack_error) Error() string {
return s.message
}
func (s *stack_error) StackTrace() []string {
return s.stack
}
type value_error string
func (e value_error) Error() string {
return string(e)
}
func (s *CheckerSuite) TestErrorIsNil(c *gc.C) {
checkOK := func(value interface{}) {
value, msg := jc.ErrorIsNil.Check([]interface{}{value}, nil)
c.Check(value, jc.IsTrue)
c.Check(msg, gc.Equals, "")
}
checkFails := func(value interface{}, match string) {
value, msg := jc.ErrorIsNil.Check([]interface{}{value}, nil)
c.Check(value, jc.IsFalse)
c.Check(msg, gc.Matches, match)
}
var typedNil *stack_error
var typedNilAsInterface error = typedNil
var nilError error
var value value_error
var emptyValueErrorAsInterface error = value
var embed embedded
checkOK(nil)
checkOK(nilError)
checkOK(embed.err)
checkFails([]string{}, `obtained type \(.*\) is not an error`)
checkFails("", `obtained type \(.*\) is not an error`)
checkFails(embed.typed, `value of \(.*\) is nil, but a typed nil`)
checkFails(typedNilAsInterface, `value of \(.*\) is nil, but a typed nil`)
checkFails(fmt.Errorf("an error"), "")
checkFails(value, "")
checkFails(emptyValueErrorAsInterface, "")
emptyStack := &stack_error{"message", nil}
checkFails(emptyStack, "")
withStack := &stack_error{"message", []string{
"filename:line", "filename2:line2"}}
checkFails(withStack, "error stack:\n\tfilename:line\n\tfilename2:line2")
}

87
vendor/github.com/juju/testing/checkers/codec.go generated vendored Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2012-2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package checkers
import (
"encoding/json"
"fmt"
gc "gopkg.in/check.v1"
"gopkg.in/mgo.v2/bson"
"gopkg.in/yaml.v2"
)
type codecEqualChecker struct {
name string
marshal func(interface{}) ([]byte, error)
unmarshal func([]byte, interface{}) error
}
// BSONEquals defines a checker that checks whether a byte slice, when
// unmarshaled as BSON, is equal to the given value. Rather than
// unmarshaling into something of the expected body type, we reform
// the expected body in BSON and back to interface{} so we can check
// the whole content. Otherwise we lose information when unmarshaling.
var BSONEquals = &codecEqualChecker{
name: "BSONEquals",
marshal: bson.Marshal,
unmarshal: bson.Unmarshal,
}
// JSONEquals defines a checker that checks whether a byte slice, when
// unmarshaled as JSON, is equal to the given value.
// Rather than unmarshaling into something of the expected
// body type, we reform the expected body in JSON and
// back to interface{}, so we can check the whole content.
// Otherwise we lose information when unmarshaling.
var JSONEquals = &codecEqualChecker{
name: "JSONEquals",
marshal: json.Marshal,
unmarshal: json.Unmarshal,
}
// YAMLEquals defines a checker that checks whether a byte slice, when
// unmarshaled as YAML, is equal to the given value.
// Rather than unmarshaling into something of the expected
// body type, we reform the expected body in YAML and
// back to interface{}, so we can check the whole content.
// Otherwise we lose information when unmarshaling.
var YAMLEquals = &codecEqualChecker{
name: "YAMLEquals",
marshal: yaml.Marshal,
unmarshal: yaml.Unmarshal,
}
func (checker *codecEqualChecker) Info() *gc.CheckerInfo {
return &gc.CheckerInfo{
Name: checker.name,
Params: []string{"obtained", "expected"},
}
}
func (checker *codecEqualChecker) Check(params []interface{}, names []string) (result bool, error string) {
gotContent, ok := params[0].(string)
if !ok {
return false, fmt.Sprintf("expected string, got %T", params[0])
}
expectContent := params[1]
expectContentBytes, err := checker.marshal(expectContent)
if err != nil {
return false, fmt.Sprintf("cannot marshal expected contents: %v", err)
}
var expectContentVal interface{}
if err := checker.unmarshal(expectContentBytes, &expectContentVal); err != nil {
return false, fmt.Sprintf("cannot unmarshal expected contents: %v", err)
}
var gotContentVal interface{}
if err := checker.unmarshal([]byte(gotContent), &gotContentVal); err != nil {
return false, fmt.Sprintf("cannot unmarshal obtained contents: %v; %q", err, gotContent)
}
if ok, err := DeepEqual(gotContentVal, expectContentVal); !ok {
return false, err.Error()
}
return true, ""
}

157
vendor/github.com/juju/testing/checkers/codec_test.go generated vendored Normal file
View File

@ -0,0 +1,157 @@
// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package checkers_test
import (
gc "gopkg.in/check.v1"
jc "github.com/juju/testing/checkers"
)
type Inner struct {
First string
Second int `json:",omitempty" yaml:",omitempty"`
Third map[string]bool `json:",omitempty" yaml:",omitempty"`
}
type Outer struct {
First float64
Second []*Inner `json:"Last,omitempty" yaml:"last,omitempty"`
}
func (s *CheckerSuite) TestJSONEquals(c *gc.C) {
tests := []struct {
descr string
obtained string
expected *Outer
result bool
msg string
}{
{
descr: "very simple",
obtained: `{"First": 47.11}`,
expected: &Outer{
First: 47.11,
},
result: true,
}, {
descr: "nested",
obtained: `{"First": 47.11, "Last": [{"First": "Hello", "Second": 42}]}`,
expected: &Outer{
First: 47.11,
Second: []*Inner{
{First: "Hello", Second: 42},
},
},
result: true,
}, {
descr: "nested with newline",
obtained: `{"First": 47.11, "Last": [{"First": "Hello", "Second": 42},
{"First": "World", "Third": {"T": true, "F": false}}]}`,
expected: &Outer{
First: 47.11,
Second: []*Inner{
{First: "Hello", Second: 42},
{First: "World", Third: map[string]bool{
"F": false,
"T": true,
}},
},
},
result: true,
}, {
descr: "illegal field",
obtained: `{"NotThere": 47.11}`,
expected: &Outer{
First: 47.11,
},
result: false,
msg: `mismatch at .*: validity mismatch; .*`,
}, {
descr: "illegal optained content",
obtained: `{"NotThere": `,
result: false,
msg: `cannot unmarshal obtained contents: unexpected end of JSON input; .*`,
},
}
for i, test := range tests {
c.Logf("test #%d) %s", i, test.descr)
result, msg := jc.JSONEquals.Check([]interface{}{test.obtained, test.expected}, nil)
c.Check(result, gc.Equals, test.result)
c.Check(msg, gc.Matches, test.msg)
}
// Test non-string input.
result, msg := jc.JSONEquals.Check([]interface{}{true, true}, nil)
c.Check(result, gc.Equals, false)
c.Check(msg, gc.Matches, "expected string, got bool")
}
func (s *CheckerSuite) TestYAMLEquals(c *gc.C) {
tests := []struct {
descr string
obtained string
expected *Outer
result bool
msg string
}{
{
descr: "very simple",
obtained: `first: 47.11`,
expected: &Outer{
First: 47.11,
},
result: true,
}, {
descr: "nested",
obtained: `{first: 47.11, last: [{first: 'Hello', second: 42}]}`,
expected: &Outer{
First: 47.11,
Second: []*Inner{
{First: "Hello", Second: 42},
},
},
result: true,
}, {
descr: "nested with newline",
obtained: `{first: 47.11, last: [{first: 'Hello', second: 42},
{first: 'World', third: {t: true, f: false}}]}`,
expected: &Outer{
First: 47.11,
Second: []*Inner{
{First: "Hello", Second: 42},
{First: "World", Third: map[string]bool{
"f": false,
"t": true,
}},
},
},
result: true,
}, {
descr: "illegal field",
obtained: `{"NotThere": 47.11}`,
expected: &Outer{
First: 47.11,
},
result: false,
msg: `mismatch at .*: validity mismatch; .*`,
}, {
descr: "illegal obtained content",
obtained: `{"NotThere": `,
result: false,
msg: `cannot unmarshal obtained contents: yaml: line 1: .*`,
},
}
for i, test := range tests {
c.Logf("test #%d) %s", i, test.descr)
result, msg := jc.YAMLEquals.Check([]interface{}{test.obtained, test.expected}, nil)
c.Check(result, gc.Equals, test.result)
c.Check(msg, gc.Matches, test.msg)
}
// Test non-string input.
result, msg := jc.YAMLEquals.Check([]interface{}{true, true}, nil)
c.Check(result, gc.Equals, false)
c.Check(msg, gc.Matches, "expected string, got bool")
}

341
vendor/github.com/juju/testing/checkers/deepequal.go generated vendored Normal file
View File

@ -0,0 +1,341 @@
// Copied with small adaptations from the reflect package in the
// Go source tree.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE-golang file.
package checkers
import (
"fmt"
"reflect"
"time"
"unsafe"
)
var timeType = reflect.TypeOf(time.Time{})
// During deepValueEqual, must keep track of checks that are
// in progress. The comparison algorithm assumes that all
// checks in progress are true when it reencounters them.
// Visited comparisons are stored in a map indexed by visit.
type visit struct {
a1 uintptr
a2 uintptr
typ reflect.Type
}
type mismatchError struct {
v1, v2 reflect.Value
path string
how string
}
func (err *mismatchError) Error() string {
path := err.path
if path == "" {
path = "top level"
}
return fmt.Sprintf("mismatch at %s: %s; obtained %#v; expected %#v", path, err.how, printable(err.v1), printable(err.v2))
}
func printable(v reflect.Value) interface{} {
vi := interfaceOf(v)
switch vi := vi.(type) {
case time.Time:
return vi.UTC().Format(time.RFC3339Nano)
default:
return vi
}
}
// Tests for deep equality using reflected types. The map argument tracks
// comparisons that have already been seen, which allows short circuiting on
// recursive types.
func deepValueEqual(path string, v1, v2 reflect.Value, visited map[visit]bool, depth int) (ok bool, err error) {
errorf := func(f string, a ...interface{}) error {
return &mismatchError{
v1: v1,
v2: v2,
path: path,
how: fmt.Sprintf(f, a...),
}
}
if !v1.IsValid() || !v2.IsValid() {
if v1.IsValid() == v2.IsValid() {
return true, nil
}
return false, errorf("validity mismatch")
}
if v1.Type() != v2.Type() {
return false, errorf("type mismatch %s vs %s", v1.Type(), v2.Type())
}
// if depth > 10 { panic("deepValueEqual") } // for debugging
hard := func(k reflect.Kind) bool {
switch k {
case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct:
return true
}
return false
}
if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) {
addr1 := v1.UnsafeAddr()
addr2 := v2.UnsafeAddr()
if addr1 > addr2 {
// Canonicalize order to reduce number of entries in visited.
addr1, addr2 = addr2, addr1
}
// Short circuit if references are identical ...
if addr1 == addr2 {
return true, nil
}
// ... or already seen
typ := v1.Type()
v := visit{addr1, addr2, typ}
if visited[v] {
return true, nil
}
// Remember for later.
visited[v] = true
}
switch v1.Kind() {
case reflect.Array:
if v1.Len() != v2.Len() {
// can't happen!
return false, errorf("length mismatch, %d vs %d", v1.Len(), v2.Len())
}
for i := 0; i < v1.Len(); i++ {
if ok, err := deepValueEqual(
fmt.Sprintf("%s[%d]", path, i),
v1.Index(i), v2.Index(i), visited, depth+1); !ok {
return false, err
}
}
return true, nil
case reflect.Slice:
// We treat a nil slice the same as an empty slice.
if v1.Len() != v2.Len() {
return false, errorf("length mismatch, %d vs %d", v1.Len(), v2.Len())
}
if v1.Pointer() == v2.Pointer() {
return true, nil
}
for i := 0; i < v1.Len(); i++ {
if ok, err := deepValueEqual(
fmt.Sprintf("%s[%d]", path, i),
v1.Index(i), v2.Index(i), visited, depth+1); !ok {
return false, err
}
}
return true, nil
case reflect.Interface:
if v1.IsNil() || v2.IsNil() {
if v1.IsNil() != v2.IsNil() {
return false, errorf("nil vs non-nil interface mismatch")
}
return true, nil
}
return deepValueEqual(path, v1.Elem(), v2.Elem(), visited, depth+1)
case reflect.Ptr:
return deepValueEqual("(*"+path+")", v1.Elem(), v2.Elem(), visited, depth+1)
case reflect.Struct:
if v1.Type() == timeType {
// Special case for time - we ignore the time zone.
t1 := interfaceOf(v1).(time.Time)
t2 := interfaceOf(v2).(time.Time)
if t1.Equal(t2) {
return true, nil
}
return false, errorf("unequal")
}
for i, n := 0, v1.NumField(); i < n; i++ {
path := path + "." + v1.Type().Field(i).Name
if ok, err := deepValueEqual(path, v1.Field(i), v2.Field(i), visited, depth+1); !ok {
return false, err
}
}
return true, nil
case reflect.Map:
if v1.IsNil() != v2.IsNil() {
return false, errorf("nil vs non-nil mismatch")
}
if v1.Len() != v2.Len() {
return false, errorf("length mismatch, %d vs %d", v1.Len(), v2.Len())
}
if v1.Pointer() == v2.Pointer() {
return true, nil
}
for _, k := range v1.MapKeys() {
var p string
if k.CanInterface() {
p = path + "[" + fmt.Sprintf("%#v", k.Interface()) + "]"
} else {
p = path + "[someKey]"
}
if ok, err := deepValueEqual(p, v1.MapIndex(k), v2.MapIndex(k), visited, depth+1); !ok {
return false, err
}
}
return true, nil
case reflect.Func:
if v1.IsNil() && v2.IsNil() {
return true, nil
}
// Can't do better than this:
return false, errorf("non-nil functions")
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if v1.Int() != v2.Int() {
return false, errorf("unequal")
}
return true, nil
case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if v1.Uint() != v2.Uint() {
return false, errorf("unequal")
}
return true, nil
case reflect.Float32, reflect.Float64:
if v1.Float() != v2.Float() {
return false, errorf("unequal")
}
return true, nil
case reflect.Complex64, reflect.Complex128:
if v1.Complex() != v2.Complex() {
return false, errorf("unequal")
}
return true, nil
case reflect.Bool:
if v1.Bool() != v2.Bool() {
return false, errorf("unequal")
}
return true, nil
case reflect.String:
if v1.String() != v2.String() {
return false, errorf("unequal")
}
return true, nil
case reflect.Chan, reflect.UnsafePointer:
if v1.Pointer() != v2.Pointer() {
return false, errorf("unequal")
}
return true, nil
default:
panic("unexpected type " + v1.Type().String())
}
}
// DeepEqual tests for deep equality. It uses normal == equality where
// possible but will scan elements of arrays, slices, maps, and fields
// of structs. In maps, keys are compared with == but elements use deep
// equality. DeepEqual correctly handles recursive types. Functions are
// equal only if they are both nil.
//
// DeepEqual differs from reflect.DeepEqual in two ways:
// - an empty slice is considered equal to a nil slice.
// - two time.Time values that represent the same instant
// but with different time zones are considered equal.
//
// If the two values compare unequal, the resulting error holds the
// first difference encountered.
func DeepEqual(a1, a2 interface{}) (bool, error) {
errorf := func(f string, a ...interface{}) error {
return &mismatchError{
v1: reflect.ValueOf(a1),
v2: reflect.ValueOf(a2),
path: "",
how: fmt.Sprintf(f, a...),
}
}
if a1 == nil || a2 == nil {
if a1 == a2 {
return true, nil
}
return false, errorf("nil vs non-nil mismatch")
}
v1 := reflect.ValueOf(a1)
v2 := reflect.ValueOf(a2)
if v1.Type() != v2.Type() {
return false, errorf("type mismatch %s vs %s", v1.Type(), v2.Type())
}
return deepValueEqual("", v1, v2, make(map[visit]bool), 0)
}
// interfaceOf returns v.Interface() even if v.CanInterface() == false.
// This enables us to call fmt.Printf on a value even if it's derived
// from inside an unexported field.
// See https://code.google.com/p/go/issues/detail?id=8965
// for a possible future alternative to this hack.
func interfaceOf(v reflect.Value) interface{} {
if !v.IsValid() {
return nil
}
return bypassCanInterface(v).Interface()
}
type flag uintptr
var flagRO flag
// constants copied from reflect/value.go
const (
// The value of flagRO up to and including Go 1.3.
flagRO1p3 = 1 << 0
// The value of flagRO from Go 1.4.
flagRO1p4 = 1 << 5
)
var flagValOffset = func() uintptr {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
return field.Offset
}()
func flagField(v *reflect.Value) *flag {
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
}
// bypassCanInterface returns a version of v that
// bypasses the CanInterface check.
func bypassCanInterface(v reflect.Value) reflect.Value {
if !v.IsValid() || v.CanInterface() {
return v
}
*flagField(&v) &^= flagRO
return v
}
// Sanity checks against future reflect package changes
// to the type or semantics of the Value.flag field.
func init() {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
panic("reflect.Value flag field has changed kind")
}
var t struct {
a int
A int
}
vA := reflect.ValueOf(t).FieldByName("A")
va := reflect.ValueOf(t).FieldByName("a")
flagA := *flagField(&vA)
flaga := *flagField(&va)
// Infer flagRO from the difference between the flags
// for the (otherwise identical) fields in t.
flagRO = flagA ^ flaga
if flagRO != flagRO1p3 && flagRO != flagRO1p4 {
panic("reflect.Value read-only flag has changed semantics")
}
}

View File

@ -0,0 +1,188 @@
// Copied with small adaptations from the reflect package in the
// Go source tree. We use testing rather than gocheck to preserve
// as much source equivalence as possible.
// TODO tests for error messages
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE-golang file.
package checkers_test
import (
"regexp"
"testing"
"time"
"github.com/juju/testing/checkers"
)
func deepEqual(a1, a2 interface{}) bool {
ok, _ := checkers.DeepEqual(a1, a2)
return ok
}
type Basic struct {
x int
y float32
}
type NotBasic Basic
type DeepEqualTest struct {
a, b interface{}
eq bool
msg string
}
// Simple functions for DeepEqual tests.
var (
fn1 func() // nil.
fn2 func() // nil.
fn3 = func() { fn1() } // Not nil.
)
var deepEqualTests = []DeepEqualTest{
// Equalities
{nil, nil, true, ""},
{1, 1, true, ""},
{int32(1), int32(1), true, ""},
{0.5, 0.5, true, ""},
{float32(0.5), float32(0.5), true, ""},
{"hello", "hello", true, ""},
{make([]int, 10), make([]int, 10), true, ""},
{&[3]int{1, 2, 3}, &[3]int{1, 2, 3}, true, ""},
{Basic{1, 0.5}, Basic{1, 0.5}, true, ""},
{error(nil), error(nil), true, ""},
{map[int]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, true, ""},
{fn1, fn2, true, ""},
{time.Unix(0, 0), time.Unix(0, 0), true, ""},
// Same time from different zones (difference from normal DeepEqual)
{time.Unix(0, 0).UTC(), time.Unix(0, 0).In(time.FixedZone("FOO", 60*60)), true, ""},
// Inequalities
{1, 2, false, `mismatch at top level: unequal; obtained 1; expected 2`},
{int32(1), int32(2), false, `mismatch at top level: unequal; obtained 1; expected 2`},
{0.5, 0.6, false, `mismatch at top level: unequal; obtained 0\.5; expected 0\.6`},
{float32(0.5), float32(0.6), false, `mismatch at top level: unequal; obtained 0\.5; expected 0\.6`},
{"hello", "hey", false, `mismatch at top level: unequal; obtained "hello"; expected "hey"`},
{make([]int, 10), make([]int, 11), false, `mismatch at top level: length mismatch, 10 vs 11; obtained \[\]int\{0, 0, 0, 0, 0, 0, 0, 0, 0, 0\}; expected \[\]int\{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\}`},
{&[3]int{1, 2, 3}, &[3]int{1, 2, 4}, false, `mismatch at \(\*\)\[2\]: unequal; obtained 3; expected 4`},
{Basic{1, 0.5}, Basic{1, 0.6}, false, `mismatch at \.y: unequal; obtained 0\.5; expected 0\.6`},
{Basic{1, 0}, Basic{2, 0}, false, `mismatch at \.x: unequal; obtained 1; expected 2`},
{map[int]string{1: "one", 3: "two"}, map[int]string{2: "two", 1: "one"}, false, `mismatch at \[3\]: validity mismatch; obtained "two"; expected <nil>`},
{map[int]string{1: "one", 2: "txo"}, map[int]string{2: "two", 1: "one"}, false, `mismatch at \[2\]: unequal; obtained "txo"; expected "two"`},
{map[int]string{1: "one"}, map[int]string{2: "two", 1: "one"}, false, `mismatch at top level: length mismatch, 1 vs 2; obtained map\[int\]string\{1:"one"\}; expected map\[int\]string\{.*\}`},
{map[int]string{2: "two", 1: "one"}, map[int]string{1: "one"}, false, `mismatch at top level: length mismatch, 2 vs 1; obtained map\[int\]string\{.*\}; expected map\[int\]string\{1:"one"\}`},
{nil, 1, false, `mismatch at top level: nil vs non-nil mismatch; obtained <nil>; expected 1`},
{1, nil, false, `mismatch at top level: nil vs non-nil mismatch; obtained 1; expected <nil>`},
{fn1, fn3, false, `mismatch at top level: non-nil functions; obtained \(func\(\)\)\(nil\); expected \(func\(\)\)\(0x[0-9a-f]+\)`},
{fn3, fn3, false, `mismatch at top level: non-nil functions; obtained \(func\(\)\)\(0x[0-9a-f]+\); expected \(func\(\)\)\(0x[0-9a-f]+\)`},
{[]interface{}{nil}, []interface{}{"a"}, false, `mismatch at \[0\]: nil vs non-nil interface mismatch`},
// Nil vs empty: they're the same (difference from normal DeepEqual)
{[]int{}, []int(nil), true, ""},
{[]int{}, []int{}, true, ""},
{[]int(nil), []int(nil), true, ""},
// Mismatched types
{1, 1.0, false, `mismatch at top level: type mismatch int vs float64; obtained 1; expected 1`},
{int32(1), int64(1), false, `mismatch at top level: type mismatch int32 vs int64; obtained 1; expected 1`},
{0.5, "hello", false, `mismatch at top level: type mismatch float64 vs string; obtained 0\.5; expected "hello"`},
{[]int{1, 2, 3}, [3]int{1, 2, 3}, false, `mismatch at top level: type mismatch \[\]int vs \[3\]int; obtained \[\]int\{1, 2, 3\}; expected \[3\]int\{1, 2, 3\}`},
{&[3]interface{}{1, 2, 4}, &[3]interface{}{1, 2, "s"}, false, `mismatch at \(\*\)\[2\]: type mismatch int vs string; obtained 4; expected "s"`},
{Basic{1, 0.5}, NotBasic{1, 0.5}, false, `mismatch at top level: type mismatch checkers_test\.Basic vs checkers_test\.NotBasic; obtained checkers_test\.Basic\{x:1, y:0\.5\}; expected checkers_test\.NotBasic\{x:1, y:0\.5\}`},
{time.Unix(0, 0).UTC(), time.Unix(0, 0).In(time.FixedZone("FOO", 60*60)).Add(1), false, `mismatch at top level: unequal; obtained "1970-01-01T00:00:00Z"; expected "1970-01-01T00:00:00.000000001Z"`},
{time.Unix(0, 0).UTC(), time.Unix(0, 0).Add(1), false, `mismatch at top level: unequal; obtained "1970-01-01T00:00:00Z"; expected "1970-01-01T00:00:00.000000001Z"`},
{
map[uint]string{1: "one", 2: "two"},
map[int]string{2: "two", 1: "one"},
false,
`mismatch at top level: type mismatch map\[uint\]string vs map\[int\]string; obtained map\[uint\]string\{.*\}; expected map\[int\]string\{.*\}`,
},
}
func TestDeepEqual(t *testing.T) {
for _, test := range deepEqualTests {
r, err := checkers.DeepEqual(test.a, test.b)
if r != test.eq {
t.Errorf("deepEqual(%v, %v) = %v, want %v", test.a, test.b, r, test.eq)
}
if test.eq {
if err != nil {
t.Errorf("deepEqual(%v, %v): unexpected error message %q when equal", test.a, test.b, err)
}
continue
}
if err == nil {
t.Errorf("deepEqual(%v, %v); mismatch but nil error", test.a, test.b)
continue
}
if ok, _ := regexp.MatchString(test.msg, err.Error()); !ok {
t.Errorf("deepEqual(%v, %v); unexpected error %q, want %q", test.a, test.b, err.Error(), test.msg)
}
}
}
type Recursive struct {
x int
r *Recursive
}
func TestDeepEqualRecursiveStruct(t *testing.T) {
a, b := new(Recursive), new(Recursive)
*a = Recursive{12, a}
*b = Recursive{12, b}
if !deepEqual(a, b) {
t.Error("deepEqual(recursive same) = false, want true")
}
}
type _Complex struct {
a int
b [3]*_Complex
c *string
d map[float64]float64
}
func TestDeepEqualComplexStruct(t *testing.T) {
m := make(map[float64]float64)
stra, strb := "hello", "hello"
a, b := new(_Complex), new(_Complex)
*a = _Complex{5, [3]*_Complex{a, b, a}, &stra, m}
*b = _Complex{5, [3]*_Complex{b, a, a}, &strb, m}
if !deepEqual(a, b) {
t.Error("deepEqual(complex same) = false, want true")
}
}
func TestDeepEqualComplexStructInequality(t *testing.T) {
m := make(map[float64]float64)
stra, strb := "hello", "helloo" // Difference is here
a, b := new(_Complex), new(_Complex)
*a = _Complex{5, [3]*_Complex{a, b, a}, &stra, m}
*b = _Complex{5, [3]*_Complex{b, a, a}, &strb, m}
if deepEqual(a, b) {
t.Error("deepEqual(complex different) = true, want false")
}
}
type UnexpT struct {
m map[int]int
}
func TestDeepEqualUnexportedMap(t *testing.T) {
// Check that DeepEqual can look at unexported fields.
x1 := UnexpT{map[int]int{1: 2}}
x2 := UnexpT{map[int]int{1: 2}}
if !deepEqual(&x1, &x2) {
t.Error("deepEqual(x1, x2) = false, want true")
}
y1 := UnexpT{map[int]int{2: 3}}
if deepEqual(&x1, &y1) {
t.Error("deepEqual(x1, y1) = true, want false")
}
}

224
vendor/github.com/juju/testing/checkers/file.go generated vendored Normal file
View File

@ -0,0 +1,224 @@
// Copyright 2013 Canonical Ltd.
// Copyright 2014 Cloudbase Solutions SRL
// Licensed under the LGPLv3, see LICENCE file for details.
package checkers
import (
"fmt"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
gc "gopkg.in/check.v1"
)
// IsNonEmptyFile checker
type isNonEmptyFileChecker struct {
*gc.CheckerInfo
}
var IsNonEmptyFile gc.Checker = &isNonEmptyFileChecker{
&gc.CheckerInfo{Name: "IsNonEmptyFile", Params: []string{"obtained"}},
}
func (checker *isNonEmptyFileChecker) Check(params []interface{}, names []string) (result bool, error string) {
filename, isString := stringOrStringer(params[0])
if isString {
fileInfo, err := os.Stat(filename)
if os.IsNotExist(err) {
return false, fmt.Sprintf("%s does not exist", filename)
} else if err != nil {
return false, fmt.Sprintf("other stat error: %v", err)
}
if fileInfo.Size() > 0 {
return true, ""
} else {
return false, fmt.Sprintf("%s is empty", filename)
}
}
value := reflect.ValueOf(params[0])
return false, fmt.Sprintf("obtained value is not a string and has no .String(), %s:%#v", value.Kind(), params[0])
}
// IsDirectory checker
type isDirectoryChecker struct {
*gc.CheckerInfo
}
var IsDirectory gc.Checker = &isDirectoryChecker{
&gc.CheckerInfo{Name: "IsDirectory", Params: []string{"obtained"}},
}
func (checker *isDirectoryChecker) Check(params []interface{}, names []string) (result bool, error string) {
path, isString := stringOrStringer(params[0])
if isString {
fileInfo, err := os.Stat(path)
if os.IsNotExist(err) {
return false, fmt.Sprintf("%s does not exist", path)
} else if err != nil {
return false, fmt.Sprintf("other stat error: %v", err)
}
if fileInfo.IsDir() {
return true, ""
} else {
return false, fmt.Sprintf("%s is not a directory", path)
}
}
value := reflect.ValueOf(params[0])
return false, fmt.Sprintf("obtained value is not a string and has no .String(), %s:%#v", value.Kind(), params[0])
}
// IsSymlink checker
type isSymlinkChecker struct {
*gc.CheckerInfo
}
var IsSymlink gc.Checker = &isSymlinkChecker{
&gc.CheckerInfo{Name: "IsSymlink", Params: []string{"obtained"}},
}
func (checker *isSymlinkChecker) Check(params []interface{}, names []string) (result bool, error string) {
path, isString := stringOrStringer(params[0])
if isString {
fileInfo, err := os.Lstat(path)
if os.IsNotExist(err) {
return false, fmt.Sprintf("%s does not exist", path)
} else if err != nil {
return false, fmt.Sprintf("other stat error: %v", err)
}
if fileInfo.Mode()&os.ModeSymlink != 0 {
return true, ""
} else {
return false, fmt.Sprintf("%s is not a symlink: %+v", path, fileInfo)
}
}
value := reflect.ValueOf(params[0])
return false, fmt.Sprintf("obtained value is not a string and has no .String(), %s:%#v", value.Kind(), params[0])
}
// DoesNotExist checker makes sure the path specified doesn't exist.
type doesNotExistChecker struct {
*gc.CheckerInfo
}
var DoesNotExist gc.Checker = &doesNotExistChecker{
&gc.CheckerInfo{Name: "DoesNotExist", Params: []string{"obtained"}},
}
func (checker *doesNotExistChecker) Check(params []interface{}, names []string) (result bool, error string) {
path, isString := stringOrStringer(params[0])
if isString {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return true, ""
} else if err != nil {
return false, fmt.Sprintf("other stat error: %v", err)
}
return false, fmt.Sprintf("%s exists", path)
}
value := reflect.ValueOf(params[0])
return false, fmt.Sprintf("obtained value is not a string and has no .String(), %s:%#v", value.Kind(), params[0])
}
// SymlinkDoesNotExist checker makes sure the path specified doesn't exist.
type symlinkDoesNotExistChecker struct {
*gc.CheckerInfo
}
var SymlinkDoesNotExist gc.Checker = &symlinkDoesNotExistChecker{
&gc.CheckerInfo{Name: "SymlinkDoesNotExist", Params: []string{"obtained"}},
}
func (checker *symlinkDoesNotExistChecker) Check(params []interface{}, names []string) (result bool, error string) {
path, isString := stringOrStringer(params[0])
if isString {
_, err := os.Lstat(path)
if os.IsNotExist(err) {
return true, ""
} else if err != nil {
return false, fmt.Sprintf("other stat error: %v", err)
}
return false, fmt.Sprintf("%s exists", path)
}
value := reflect.ValueOf(params[0])
return false, fmt.Sprintf("obtained value is not a string and has no .String(), %s:%#v", value.Kind(), params[0])
}
// Same path checker -- will check that paths are the same OS indepentent
type samePathChecker struct {
*gc.CheckerInfo
}
// SamePath checks paths to see whether they're the same, can follow symlinks and is OS independent
var SamePath gc.Checker = &samePathChecker{
&gc.CheckerInfo{Name: "SamePath", Params: []string{"obtained", "expected"}},
}
func (checker *samePathChecker) Check(params []interface{}, names []string) (result bool, error string) {
// Check for panics
defer func() {
if panicked := recover(); panicked != nil {
result = false
error = fmt.Sprint(panicked)
}
}()
// Convert input
obtained, isStr := stringOrStringer(params[0])
if !isStr {
return false, fmt.Sprintf("obtained value is not a string and has no .String(), %T:%#v", params[0], params[0])
}
expected, isStr := stringOrStringer(params[1])
if !isStr {
return false, fmt.Sprintf("obtained value is not a string and has no .String(), %T:%#v", params[1], params[1])
}
// Convert paths to proper format
obtained = filepath.FromSlash(obtained)
expected = filepath.FromSlash(expected)
// If running on Windows, paths will be case-insensitive and thus we
// normalize the inputs to a default of all upper-case
if runtime.GOOS == "windows" {
obtained = strings.ToUpper(obtained)
expected = strings.ToUpper(expected)
}
// Same path do not check further
if obtained == expected {
return true, ""
}
// If it's not the same path, check if it points to the same file.
// Thus, the cases with windows-shortened paths are accounted for
// This will throw an error if it's not a file
ob, err := os.Stat(obtained)
if err != nil {
return false, err.Error()
}
ex, err := os.Stat(expected)
if err != nil {
return false, err.Error()
}
res := os.SameFile(ob, ex)
if res {
return true, ""
}
return false, fmt.Sprintf("Not the same file")
}

288
vendor/github.com/juju/testing/checkers/file_test.go generated vendored Normal file
View File

@ -0,0 +1,288 @@
// Copyright 2013 Canonical Ltd.
// Copyright 2014 Cloudbase Solutions SRL
// Licensed under the LGPLv3, see LICENCE file for details.
package checkers_test
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
gc "gopkg.in/check.v1"
jc "github.com/juju/testing/checkers"
)
type FileSuite struct{}
var _ = gc.Suite(&FileSuite{})
func (s *FileSuite) TestIsNonEmptyFile(c *gc.C) {
file, err := ioutil.TempFile(c.MkDir(), "")
c.Assert(err, gc.IsNil)
fmt.Fprintf(file, "something")
file.Close()
c.Assert(file.Name(), jc.IsNonEmptyFile)
}
func (s *FileSuite) TestIsNonEmptyFileWithEmptyFile(c *gc.C) {
file, err := ioutil.TempFile(c.MkDir(), "")
c.Assert(err, gc.IsNil)
file.Close()
result, message := jc.IsNonEmptyFile.Check([]interface{}{file.Name()}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, file.Name()+" is empty")
}
func (s *FileSuite) TestIsNonEmptyFileWithMissingFile(c *gc.C) {
name := filepath.Join(c.MkDir(), "missing")
result, message := jc.IsNonEmptyFile.Check([]interface{}{name}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, name+" does not exist")
}
func (s *FileSuite) TestIsNonEmptyFileWithNumber(c *gc.C) {
result, message := jc.IsNonEmptyFile.Check([]interface{}{42}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, "obtained value is not a string and has no .String(), int:42")
}
func (s *FileSuite) TestIsDirectory(c *gc.C) {
dir := c.MkDir()
c.Assert(dir, jc.IsDirectory)
}
func (s *FileSuite) TestIsDirectoryMissing(c *gc.C) {
absentDir := filepath.Join(c.MkDir(), "foo")
result, message := jc.IsDirectory.Check([]interface{}{absentDir}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, absentDir+" does not exist")
}
func (s *FileSuite) TestIsDirectoryWithFile(c *gc.C) {
file, err := ioutil.TempFile(c.MkDir(), "")
c.Assert(err, gc.IsNil)
file.Close()
result, message := jc.IsDirectory.Check([]interface{}{file.Name()}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, file.Name()+" is not a directory")
}
func (s *FileSuite) TestIsDirectoryWithNumber(c *gc.C) {
result, message := jc.IsDirectory.Check([]interface{}{42}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, "obtained value is not a string and has no .String(), int:42")
}
func (s *FileSuite) TestDoesNotExist(c *gc.C) {
absentDir := filepath.Join(c.MkDir(), "foo")
c.Assert(absentDir, jc.DoesNotExist)
}
func (s *FileSuite) TestDoesNotExistWithPath(c *gc.C) {
dir := c.MkDir()
result, message := jc.DoesNotExist.Check([]interface{}{dir}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, dir+" exists")
}
func (s *FileSuite) TestDoesNotExistWithSymlink(c *gc.C) {
dir := c.MkDir()
deadPath := filepath.Join(dir, "dead")
symlinkPath := filepath.Join(dir, "a-symlink")
err := os.Symlink(deadPath, symlinkPath)
c.Assert(err, gc.IsNil)
// A valid symlink pointing to something that doesn't exist passes.
// Use SymlinkDoesNotExist to check for the non-existence of the link itself.
c.Assert(symlinkPath, jc.DoesNotExist)
}
func (s *FileSuite) TestDoesNotExistWithNumber(c *gc.C) {
result, message := jc.DoesNotExist.Check([]interface{}{42}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, "obtained value is not a string and has no .String(), int:42")
}
func (s *FileSuite) TestSymlinkDoesNotExist(c *gc.C) {
absentDir := filepath.Join(c.MkDir(), "foo")
c.Assert(absentDir, jc.SymlinkDoesNotExist)
}
func (s *FileSuite) TestSymlinkDoesNotExistWithPath(c *gc.C) {
dir := c.MkDir()
result, message := jc.SymlinkDoesNotExist.Check([]interface{}{dir}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, dir+" exists")
}
func (s *FileSuite) TestSymlinkDoesNotExistWithSymlink(c *gc.C) {
dir := c.MkDir()
deadPath := filepath.Join(dir, "dead")
symlinkPath := filepath.Join(dir, "a-symlink")
err := os.Symlink(deadPath, symlinkPath)
c.Assert(err, gc.IsNil)
result, message := jc.SymlinkDoesNotExist.Check([]interface{}{symlinkPath}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, symlinkPath+" exists")
}
func (s *FileSuite) TestSymlinkDoesNotExistWithNumber(c *gc.C) {
result, message := jc.SymlinkDoesNotExist.Check([]interface{}{42}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, "obtained value is not a string and has no .String(), int:42")
}
func (s *FileSuite) TestIsSymlink(c *gc.C) {
file, err := ioutil.TempFile(c.MkDir(), "")
c.Assert(err, gc.IsNil)
c.Log(file.Name())
c.Log(filepath.Dir(file.Name()))
symlinkPath := filepath.Join(filepath.Dir(file.Name()), "a-symlink")
err = os.Symlink(file.Name(), symlinkPath)
c.Assert(err, gc.IsNil)
c.Assert(symlinkPath, jc.IsSymlink)
}
func (s *FileSuite) TestIsSymlinkWithFile(c *gc.C) {
file, err := ioutil.TempFile(c.MkDir(), "")
c.Assert(err, gc.IsNil)
result, message := jc.IsSymlink.Check([]interface{}{file.Name()}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, jc.Contains, " is not a symlink")
}
func (s *FileSuite) TestIsSymlinkWithDir(c *gc.C) {
result, message := jc.IsSymlink.Check([]interface{}{c.MkDir()}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, jc.Contains, " is not a symlink")
}
func (s *FileSuite) TestSamePathWithNumber(c *gc.C) {
result, message := jc.SamePath.Check([]interface{}{42, 52}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, "obtained value is not a string and has no .String(), int:42")
}
func (s *FileSuite) TestSamePathBasic(c *gc.C) {
dir := c.MkDir()
result, message := jc.SamePath.Check([]interface{}{dir, dir}, nil)
c.Assert(result, jc.IsTrue)
c.Assert(message, gc.Equals, "")
}
type SamePathLinuxSuite struct{}
var _ = gc.Suite(&SamePathLinuxSuite{})
func (s *SamePathLinuxSuite) SetUpSuite(c *gc.C) {
if runtime.GOOS == "windows" {
c.Skip("Skipped Linux-intented SamePath tests on Windows.")
}
}
func (s *SamePathLinuxSuite) TestNotSamePathLinuxBasic(c *gc.C) {
dir := c.MkDir()
path1 := filepath.Join(dir, "Test")
path2 := filepath.Join(dir, "test")
result, message := jc.SamePath.Check([]interface{}{path1, path2}, nil)
c.Assert(result, jc.IsFalse)
c.Assert(message, gc.Equals, "stat "+path1+": no such file or directory")
}
func (s *SamePathLinuxSuite) TestSamePathLinuxSymlinks(c *gc.C) {
file, err := ioutil.TempFile(c.MkDir(), "")
c.Assert(err, gc.IsNil)
symlinkPath := filepath.Join(filepath.Dir(file.Name()), "a-symlink")
err = os.Symlink(file.Name(), symlinkPath)
result, message := jc.SamePath.Check([]interface{}{file.Name(), symlinkPath}, nil)
c.Assert(result, jc.IsTrue)
c.Assert(message, gc.Equals, "")
}
type SamePathWindowsSuite struct{}
var _ = gc.Suite(&SamePathWindowsSuite{})
func (s *SamePathWindowsSuite) SetUpSuite(c *gc.C) {
if runtime.GOOS != "windows" {
c.Skip("Skipped Windows-intented SamePath tests.")
}
}
func (s *SamePathWindowsSuite) TestNotSamePathBasic(c *gc.C) {
dir := c.MkDir()
path1 := filepath.Join(dir, "notTest")
path2 := filepath.Join(dir, "test")
result, message := jc.SamePath.Check([]interface{}{path1, path2}, nil)
c.Assert(result, jc.IsFalse)
path1 = strings.ToUpper(path1)
c.Assert(message, gc.Equals, "GetFileAttributesEx "+path1+": The system cannot find the file specified.")
}
func (s *SamePathWindowsSuite) TestSamePathWindowsCaseInsensitive(c *gc.C) {
dir := c.MkDir()
path1 := filepath.Join(dir, "Test")
path2 := filepath.Join(dir, "test")
result, message := jc.SamePath.Check([]interface{}{path1, path2}, nil)
c.Assert(result, jc.IsTrue)
c.Assert(message, gc.Equals, "")
}
func (s *SamePathWindowsSuite) TestSamePathWindowsFixSlashes(c *gc.C) {
result, message := jc.SamePath.Check([]interface{}{"C:/Users", "C:\\Users"}, nil)
c.Assert(result, jc.IsTrue)
c.Assert(message, gc.Equals, "")
}
func (s *SamePathWindowsSuite) TestSamePathShortenedPaths(c *gc.C) {
dir := c.MkDir()
dir1, err := ioutil.TempDir(dir, "Programming")
defer os.Remove(dir1)
c.Assert(err, gc.IsNil)
result, message := jc.SamePath.Check([]interface{}{dir + "\\PROGRA~1", dir1}, nil)
c.Assert(result, jc.IsTrue)
c.Assert(message, gc.Equals, "")
}
func (s *SamePathWindowsSuite) TestSamePathShortenedPathsConsistent(c *gc.C) {
dir := c.MkDir()
dir1, err := ioutil.TempDir(dir, "Programming")
defer os.Remove(dir1)
c.Assert(err, gc.IsNil)
dir2, err := ioutil.TempDir(dir, "Program Files")
defer os.Remove(dir2)
c.Assert(err, gc.IsNil)
result, message := jc.SamePath.Check([]interface{}{dir + "\\PROGRA~1", dir2}, nil)
c.Assert(result, gc.Not(jc.IsTrue))
c.Assert(message, gc.Equals, "Not the same file")
result, message = jc.SamePath.Check([]interface{}{"C:/PROGRA~2", "C:/Program Files (x86)"}, nil)
c.Assert(result, jc.IsTrue)
c.Assert(message, gc.Equals, "")
}

Some files were not shown because too many files have changed in this diff Show More