mirror of
https://github.com/Luzifer/promcertcheck.git
synced 2024-11-09 16:30:04 +00:00
Dockerized application
This commit is contained in:
parent
aea948973e
commit
b7bee41940
363 changed files with 48155 additions and 1 deletions
8
Dockerfile
Normal file
8
Dockerfile
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
ADD https://rootcastore.hub.luzifer.io/v1/store/latest /etc/ssl/ca-bundle.pem
|
||||||
|
ADD promcertcheck /promcertcheck
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
ENTRYPOINT ["/promcertcheck"]
|
||||||
|
CMD ["--probe=https://www.google.com/", "--probe=https://www.facebook.com/"]
|
73
Godeps/Godeps.json
generated
Normal file
73
Godeps/Godeps.json
generated
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Luzifer/promcertcheck",
|
||||||
|
"GoVersion": "go1.5",
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "bitbucket.org/ww/goautoneg",
|
||||||
|
"Comment": "null-5",
|
||||||
|
"Rev": "75cd24fc2f2c2a2088577d12123ddee5f54e0675"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Luzifer/rconfig",
|
||||||
|
"Rev": "0c78105a26af5663b6bb2c5be1fed4ed7d81d687"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/beorn7/perks/quantile",
|
||||||
|
"Rev": "b965b613227fddccbfffe13eae360ed3fa822f8d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/flosch/pongo2",
|
||||||
|
"Comment": "v1.0-rc1-174-gf5d79aa",
|
||||||
|
"Rev": "f5d79aa0a914c08eb7f51a96cd7b2dbbe46fca46"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/protobuf/proto",
|
||||||
|
"Rev": "aece6fb931241ad332956db4f62798dfbea944b3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/context",
|
||||||
|
"Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/mux",
|
||||||
|
"Rev": "8096f47503459bcc74d1f4c487b7e6e42e5746b5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil",
|
||||||
|
"Rev": "fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/prometheus/client_golang/model",
|
||||||
|
"Comment": "0.6.0-17-g3b16b46",
|
||||||
|
"Rev": "3b16b46a713f181888e5e9a1205ccc34d6917fb9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/prometheus/client_golang/prometheus",
|
||||||
|
"Comment": "0.6.0-17-g3b16b46",
|
||||||
|
"Rev": "3b16b46a713f181888e5e9a1205ccc34d6917fb9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/prometheus/client_golang/text",
|
||||||
|
"Comment": "0.6.0-17-g3b16b46",
|
||||||
|
"Rev": "3b16b46a713f181888e5e9a1205ccc34d6917fb9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/prometheus/client_model/go",
|
||||||
|
"Comment": "model-0.0.2-12-gfa8ad6f",
|
||||||
|
"Rev": "fa8ad6fec33561be4280a8f0514318c79d7f6cb6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/prometheus/procfs",
|
||||||
|
"Rev": "c91d8eefde16bd047416409eb56353ea84a186e4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/robfig/cron",
|
||||||
|
"Comment": "v1-2-g67823cd",
|
||||||
|
"Rev": "67823cd24dece1b04cced3a0a0b3ca2bc84d875e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/spf13/pflag",
|
||||||
|
"Rev": "8e7dc108ab3a1ab6ce6d922bbaff5657b88e8e49"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/pkg
|
||||||
|
/bin
|
13
Godeps/_workspace/src/bitbucket.org/ww/goautoneg/Makefile
generated
vendored
Normal file
13
Godeps/_workspace/src/bitbucket.org/ww/goautoneg/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
include $(GOROOT)/src/Make.inc
|
||||||
|
|
||||||
|
TARG=bitbucket.org/ww/goautoneg
|
||||||
|
GOFILES=autoneg.go
|
||||||
|
|
||||||
|
include $(GOROOT)/src/Make.pkg
|
||||||
|
|
||||||
|
format:
|
||||||
|
gofmt -w *.go
|
||||||
|
|
||||||
|
docs:
|
||||||
|
gomake clean
|
||||||
|
godoc ${TARG} > README.txt
|
67
Godeps/_workspace/src/bitbucket.org/ww/goautoneg/README.txt
generated
vendored
Normal file
67
Godeps/_workspace/src/bitbucket.org/ww/goautoneg/README.txt
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
PACKAGE
|
||||||
|
|
||||||
|
package goautoneg
|
||||||
|
import "bitbucket.org/ww/goautoneg"
|
||||||
|
|
||||||
|
HTTP Content-Type Autonegotiation.
|
||||||
|
|
||||||
|
The functions in this package implement the behaviour specified in
|
||||||
|
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||||
|
|
||||||
|
Copyright (c) 2011, Open Knowledge Foundation Ltd.
|
||||||
|
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 the Open Knowledge Foundation Ltd. 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
|
||||||
|
HOLDER 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.
|
||||||
|
|
||||||
|
|
||||||
|
FUNCTIONS
|
||||||
|
|
||||||
|
func Negotiate(header string, alternatives []string) (content_type string)
|
||||||
|
Negotiate the most appropriate content_type given the accept header
|
||||||
|
and a list of alternatives.
|
||||||
|
|
||||||
|
func ParseAccept(header string) (accept []Accept)
|
||||||
|
Parse an Accept Header string returning a sorted list
|
||||||
|
of clauses
|
||||||
|
|
||||||
|
|
||||||
|
TYPES
|
||||||
|
|
||||||
|
type Accept struct {
|
||||||
|
Type, SubType string
|
||||||
|
Q float32
|
||||||
|
Params map[string]string
|
||||||
|
}
|
||||||
|
Structure to represent a clause in an HTTP Accept Header
|
||||||
|
|
||||||
|
|
||||||
|
SUBDIRECTORIES
|
||||||
|
|
||||||
|
.hg
|
162
Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg.go
generated
vendored
Normal file
162
Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
HTTP Content-Type Autonegotiation.
|
||||||
|
|
||||||
|
The functions in this package implement the behaviour specified in
|
||||||
|
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||||
|
|
||||||
|
Copyright (c) 2011, Open Knowledge Foundation Ltd.
|
||||||
|
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 the Open Knowledge Foundation Ltd. 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
|
||||||
|
HOLDER 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.
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
package goautoneg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Structure to represent a clause in an HTTP Accept Header
|
||||||
|
type Accept struct {
|
||||||
|
Type, SubType string
|
||||||
|
Q float64
|
||||||
|
Params map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// For internal use, so that we can use the sort interface
|
||||||
|
type accept_slice []Accept
|
||||||
|
|
||||||
|
func (accept accept_slice) Len() int {
|
||||||
|
slice := []Accept(accept)
|
||||||
|
return len(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (accept accept_slice) Less(i, j int) bool {
|
||||||
|
slice := []Accept(accept)
|
||||||
|
ai, aj := slice[i], slice[j]
|
||||||
|
if ai.Q > aj.Q {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ai.Type != "*" && aj.Type == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ai.SubType != "*" && aj.SubType == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (accept accept_slice) Swap(i, j int) {
|
||||||
|
slice := []Accept(accept)
|
||||||
|
slice[i], slice[j] = slice[j], slice[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse an Accept Header string returning a sorted list
|
||||||
|
// of clauses
|
||||||
|
func ParseAccept(header string) (accept []Accept) {
|
||||||
|
parts := strings.Split(header, ",")
|
||||||
|
accept = make([]Accept, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
part := strings.Trim(part, " ")
|
||||||
|
|
||||||
|
a := Accept{}
|
||||||
|
a.Params = make(map[string]string)
|
||||||
|
a.Q = 1.0
|
||||||
|
|
||||||
|
mrp := strings.Split(part, ";")
|
||||||
|
|
||||||
|
media_range := mrp[0]
|
||||||
|
sp := strings.Split(media_range, "/")
|
||||||
|
a.Type = strings.Trim(sp[0], " ")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case len(sp) == 1 && a.Type == "*":
|
||||||
|
a.SubType = "*"
|
||||||
|
case len(sp) == 2:
|
||||||
|
a.SubType = strings.Trim(sp[1], " ")
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(mrp) == 1 {
|
||||||
|
accept = append(accept, a)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, param := range mrp[1:] {
|
||||||
|
sp := strings.SplitN(param, "=", 2)
|
||||||
|
if len(sp) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
token := strings.Trim(sp[0], " ")
|
||||||
|
if token == "q" {
|
||||||
|
a.Q, _ = strconv.ParseFloat(sp[1], 32)
|
||||||
|
} else {
|
||||||
|
a.Params[token] = strings.Trim(sp[1], " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accept = append(accept, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := accept_slice(accept)
|
||||||
|
sort.Sort(slice)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negotiate the most appropriate content_type given the accept header
|
||||||
|
// and a list of alternatives.
|
||||||
|
func Negotiate(header string, alternatives []string) (content_type string) {
|
||||||
|
asp := make([][]string, 0, len(alternatives))
|
||||||
|
for _, ctype := range alternatives {
|
||||||
|
asp = append(asp, strings.SplitN(ctype, "/", 2))
|
||||||
|
}
|
||||||
|
for _, clause := range ParseAccept(header) {
|
||||||
|
for i, ctsp := range asp {
|
||||||
|
if clause.Type == ctsp[0] && clause.SubType == ctsp[1] {
|
||||||
|
content_type = alternatives[i]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if clause.Type == ctsp[0] && clause.SubType == "*" {
|
||||||
|
content_type = alternatives[i]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if clause.Type == "*" && clause.SubType == "*" {
|
||||||
|
content_type = alternatives[i]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
33
Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg_test.go
generated
vendored
Normal file
33
Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg_test.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package goautoneg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var chrome = "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
|
||||||
|
|
||||||
|
func TestParseAccept(t *testing.T) {
|
||||||
|
alternatives := []string{"text/html", "image/png"}
|
||||||
|
content_type := Negotiate(chrome, alternatives)
|
||||||
|
if content_type != "image/png" {
|
||||||
|
t.Errorf("got %s expected image/png", content_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
alternatives = []string{"text/html", "text/plain", "text/n3"}
|
||||||
|
content_type = Negotiate(chrome, alternatives)
|
||||||
|
if content_type != "text/html" {
|
||||||
|
t.Errorf("got %s expected text/html", content_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
alternatives = []string{"text/n3", "text/plain"}
|
||||||
|
content_type = Negotiate(chrome, alternatives)
|
||||||
|
if content_type != "text/plain" {
|
||||||
|
t.Errorf("got %s expected text/plain", content_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
alternatives = []string{"text/n3", "application/rdf+xml"}
|
||||||
|
content_type = Negotiate(chrome, alternatives)
|
||||||
|
if content_type != "text/n3" {
|
||||||
|
t.Errorf("got %s expected text/n3", content_type)
|
||||||
|
}
|
||||||
|
}
|
13
Godeps/_workspace/src/github.com/Luzifer/rconfig/LICENSE
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/Luzifer/rconfig/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright 2015 Knut Ahlers <knut@ahlers.me>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
58
Godeps/_workspace/src/github.com/Luzifer/rconfig/README.md
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/Luzifer/rconfig/README.md
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
[![Circle CI](https://circleci.com/gh/Luzifer/rconfig.svg?style=svg)](https://circleci.com/gh/Luzifer/rconfig)
|
||||||
|
[![License: Apache v2.0](https://badge.luzifer.io/v1/badge?color=5d79b5&title=license&text=Apache+v2.0)](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
[![Documentation](https://badge.luzifer.io/v1/badge?title=godoc&text=reference)](https://godoc.org/github.com/Luzifer/rconfig)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
> Package rconfig implements a CLI configuration reader with struct-embedded defaults, environment variables and posix compatible flag parsing using the [pflag](https://github.com/spf13/pflag) library.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Install by running:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get -u github.com/Luzifer/rconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
Run tests by running:
|
||||||
|
|
||||||
|
```
|
||||||
|
go test -v -race -cover github.com/Luzifer/rconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
As a first step define a struct holding your configuration:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type config struct {
|
||||||
|
Username string `default:"unknown" flag:"user" description:"Your name"`
|
||||||
|
Details struct {
|
||||||
|
Age int `default:"25" flag:"age" env:"age" description:"Your age"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Next create an instance of that struct and let `rconfig` fill that config:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var cfg config
|
||||||
|
func init() {
|
||||||
|
cfg = config{}
|
||||||
|
rconfig.Parse(&cfg)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You're ready to access your configuration:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
|
||||||
|
cfg.Username,
|
||||||
|
cfg.Details.Age)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## More info
|
||||||
|
|
||||||
|
You can see the full reference documentation of the rconfig package [at godoc.org](https://godoc.org/github.com/Luzifer/rconfig), or through go's standard documentation system by running `godoc -http=:6060` and browsing to [http://localhost:6060/pkg/github.com/Luzifer/rconfig](http://localhost:6060/pkg/github.com/Luzifer/rconfig) after installation.
|
282
Godeps/_workspace/src/github.com/Luzifer/rconfig/config.go
generated
vendored
Normal file
282
Godeps/_workspace/src/github.com/Luzifer/rconfig/config.go
generated
vendored
Normal file
|
@ -0,0 +1,282 @@
|
||||||
|
// Package rconfig implements a CLI configuration reader with struct-embedded
|
||||||
|
// defaults, environment variables and posix compatible flag parsing using
|
||||||
|
// the pflag library.
|
||||||
|
package rconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fs *pflag.FlagSet
|
||||||
|
|
||||||
|
// Parse takes the pointer to a struct filled with variables which should be read
|
||||||
|
// from ENV, default or flag. The precedence in this is flag > ENV > default. So
|
||||||
|
// if a flag is specified on the CLI it will overwrite the ENV and otherwise ENV
|
||||||
|
// overwrites the default specified.
|
||||||
|
//
|
||||||
|
// For your configuration struct you can use the following struct-tags to control
|
||||||
|
// the behavior of rconfig:
|
||||||
|
//
|
||||||
|
// default: Set a default value
|
||||||
|
// env: Read the value from this environment variable
|
||||||
|
// flag: Flag to read in format "long,short" (for example "listen,l")
|
||||||
|
// description: A help text for Usage output to guide your users
|
||||||
|
//
|
||||||
|
// The format you need to specify those values you can see in the example to this
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
func Parse(config interface{}) error {
|
||||||
|
return parse(config, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage prints a basic usage with the corresponding defaults for the flags to
|
||||||
|
// os.Stdout. The defaults are derived from the `default` struct-tag and the ENV.
|
||||||
|
func Usage() {
|
||||||
|
if fs != nil && fs.Parsed() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||||
|
fs.PrintDefaults()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(in interface{}, args []string) error {
|
||||||
|
if args == nil {
|
||||||
|
args = os.Args
|
||||||
|
}
|
||||||
|
|
||||||
|
fs = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
|
||||||
|
if err := execTags(in, fs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.Parse(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func execTags(in interface{}, fs *pflag.FlagSet) error {
|
||||||
|
if reflect.TypeOf(in).Kind() != reflect.Ptr {
|
||||||
|
return errors.New("Calling parser with non-pointer")
|
||||||
|
}
|
||||||
|
|
||||||
|
if reflect.ValueOf(in).Elem().Kind() != reflect.Struct {
|
||||||
|
return errors.New("Calling parser with pointer to non-struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
st := reflect.ValueOf(in).Elem()
|
||||||
|
for i := 0; i < st.NumField(); i++ {
|
||||||
|
valField := st.Field(i)
|
||||||
|
typeField := st.Type().Field(i)
|
||||||
|
|
||||||
|
if typeField.Tag.Get("default") == "" && typeField.Tag.Get("env") == "" && typeField.Tag.Get("flag") == "" && typeField.Type.Kind() != reflect.Struct {
|
||||||
|
// None of our supported tags is present and it's not a sub-struct
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
value := envDefault(typeField.Tag.Get("env"), typeField.Tag.Get("default"))
|
||||||
|
parts := strings.Split(typeField.Tag.Get("flag"), ",")
|
||||||
|
|
||||||
|
switch typeField.Type.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.StringVar(valField.Addr().Interface().(*string), parts[0], value, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
fs.StringVarP(valField.Addr().Interface().(*string), parts[0], parts[1], value, typeField.Tag.Get("description"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valField.SetString(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
v := value == "true"
|
||||||
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.BoolVar(valField.Addr().Interface().(*bool), parts[0], v, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
fs.BoolVarP(valField.Addr().Interface().(*bool), parts[0], parts[1], v, typeField.Tag.Get("description"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valField.SetBool(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64:
|
||||||
|
vt, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
if value == "" {
|
||||||
|
vt = 0
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
registerFlagInt(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
valField.SetInt(vt)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
vt, err := strconv.ParseUint(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
if value == "" {
|
||||||
|
vt = 0
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
registerFlagUint(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
valField.SetUint(vt)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
vt, err := strconv.ParseFloat(value, 64)
|
||||||
|
if err != nil {
|
||||||
|
if value == "" {
|
||||||
|
vt = 0.0
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
registerFlagFloat(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
valField.SetFloat(vt)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
if err := execTags(valField.Addr().Interface(), fs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
switch typeField.Type.Elem().Kind() {
|
||||||
|
case reflect.Int:
|
||||||
|
def := []int{}
|
||||||
|
for _, v := range strings.Split(value, ",") {
|
||||||
|
it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
def = append(def, int(it))
|
||||||
|
}
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.IntSliceVar(valField.Addr().Interface().(*[]int), parts[0], def, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
fs.IntSliceVarP(valField.Addr().Interface().(*[]int), parts[0], parts[1], def, typeField.Tag.Get("description"))
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
del := typeField.Tag.Get("delimiter")
|
||||||
|
if len(del) == 0 {
|
||||||
|
del = ","
|
||||||
|
}
|
||||||
|
def := strings.Split(value, del)
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.StringSliceVar(valField.Addr().Interface().(*[]string), parts[0], def, typeField.Tag.Get("description"))
|
||||||
|
} else {
|
||||||
|
fs.StringSliceVarP(valField.Addr().Interface().(*[]string), parts[0], parts[1], def, typeField.Tag.Get("description"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerFlagFloat(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt float64, desc string) {
|
||||||
|
switch t {
|
||||||
|
case reflect.Float32:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Float32Var(field.(*float32), parts[0], float32(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Float32VarP(field.(*float32), parts[0], parts[1], float32(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Float64:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Float64Var(field.(*float64), parts[0], float64(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Float64VarP(field.(*float64), parts[0], parts[1], float64(vt), desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerFlagInt(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt int64, desc string) {
|
||||||
|
switch t {
|
||||||
|
case reflect.Int:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.IntVar(field.(*int), parts[0], int(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.IntVarP(field.(*int), parts[0], parts[1], int(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Int8:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Int8Var(field.(*int8), parts[0], int8(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Int8VarP(field.(*int8), parts[0], parts[1], int8(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Int32:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Int32Var(field.(*int32), parts[0], int32(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Int32VarP(field.(*int32), parts[0], parts[1], int32(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Int64:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Int64Var(field.(*int64), parts[0], int64(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Int64VarP(field.(*int64), parts[0], parts[1], int64(vt), desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerFlagUint(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt uint64, desc string) {
|
||||||
|
switch t {
|
||||||
|
case reflect.Uint:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.UintVar(field.(*uint), parts[0], uint(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.UintVarP(field.(*uint), parts[0], parts[1], uint(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Uint8:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Uint8Var(field.(*uint8), parts[0], uint8(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Uint8VarP(field.(*uint8), parts[0], parts[1], uint8(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Uint16:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Uint16Var(field.(*uint16), parts[0], uint16(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Uint16VarP(field.(*uint16), parts[0], parts[1], uint16(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Uint32:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Uint32Var(field.(*uint32), parts[0], uint32(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Uint32VarP(field.(*uint32), parts[0], parts[1], uint32(vt), desc)
|
||||||
|
}
|
||||||
|
case reflect.Uint64:
|
||||||
|
if len(parts) == 1 {
|
||||||
|
fs.Uint64Var(field.(*uint64), parts[0], uint64(vt), desc)
|
||||||
|
} else {
|
||||||
|
fs.Uint64VarP(field.(*uint64), parts[0], parts[1], uint64(vt), desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func envDefault(env, def string) string {
|
||||||
|
value := def
|
||||||
|
|
||||||
|
if env != "" {
|
||||||
|
if e := os.Getenv(env); e != "" {
|
||||||
|
value = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
274
Godeps/_workspace/src/github.com/Luzifer/rconfig/config_test.go
generated
vendored
Normal file
274
Godeps/_workspace/src/github.com/Luzifer/rconfig/config_test.go
generated
vendored
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
package rconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGeneralMechanics(t *testing.T) {
|
||||||
|
cfg := struct {
|
||||||
|
Test string `default:"foo" env:"shell" flag:"shell" description:"Test"`
|
||||||
|
Test2 string `default:"blub" env:"testvar" flag:"testvar,t" description:"Test"`
|
||||||
|
DefaultFlag string `default:"goo"`
|
||||||
|
SadFlag string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
parse(&cfg, []string{
|
||||||
|
"--shell=test23",
|
||||||
|
"-t", "bla",
|
||||||
|
})
|
||||||
|
|
||||||
|
if cfg.Test != "test23" {
|
||||||
|
t.Errorf("Test should be 'test23', is '%s'", cfg.Test)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Test2 != "bla" {
|
||||||
|
t.Errorf("Test2 should be 'bla', is '%s'", cfg.Test2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.SadFlag != "" {
|
||||||
|
t.Errorf("SadFlag should be '', is '%s'", cfg.SadFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.DefaultFlag != "goo" {
|
||||||
|
t.Errorf("DefaultFlag should be 'goo', is '%s'", cfg.DefaultFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(&cfg, []string{})
|
||||||
|
|
||||||
|
if cfg.Test != "foo" {
|
||||||
|
t.Errorf("Test should be 'foo', is '%s'", cfg.Test)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("shell", "test546")
|
||||||
|
parse(&cfg, []string{})
|
||||||
|
|
||||||
|
if cfg.Test != "test546" {
|
||||||
|
t.Errorf("Test should be 'test546', is '%s'", cfg.Test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBool(t *testing.T) {
|
||||||
|
cfg := struct {
|
||||||
|
Test1 bool `default:"true"`
|
||||||
|
Test2 bool `default:"false" flag:"test2"`
|
||||||
|
Test3 bool `default:"true" flag:"test3,t"`
|
||||||
|
Test4 bool `flag:"test4"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
parse(&cfg, []string{
|
||||||
|
"--test2",
|
||||||
|
"-t",
|
||||||
|
})
|
||||||
|
|
||||||
|
if !cfg.Test1 {
|
||||||
|
t.Errorf("Test1 should be 'true', is '%+v'", cfg.Test1)
|
||||||
|
}
|
||||||
|
if !cfg.Test2 {
|
||||||
|
t.Errorf("Test1 should be 'true', is '%+v'", cfg.Test2)
|
||||||
|
}
|
||||||
|
if !cfg.Test3 {
|
||||||
|
t.Errorf("Test1 should be 'true', is '%+v'", cfg.Test3)
|
||||||
|
}
|
||||||
|
if cfg.Test4 {
|
||||||
|
t.Errorf("Test1 should be 'false', is '%+v'", cfg.Test3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt(t *testing.T) {
|
||||||
|
cfg := struct {
|
||||||
|
Test int `flag:"int"`
|
||||||
|
TestP int `flag:"intp,i"`
|
||||||
|
Test8 int8 `flag:"int8"`
|
||||||
|
Test8P int8 `flag:"int8p,8"`
|
||||||
|
Test32 int32 `flag:"int32"`
|
||||||
|
Test32P int32 `flag:"int32p,3"`
|
||||||
|
Test64 int64 `flag:"int64"`
|
||||||
|
Test64P int64 `flag:"int64p,6"`
|
||||||
|
TestDef int8 `default:"66"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
parse(&cfg, []string{
|
||||||
|
"--int=1", "-i", "2",
|
||||||
|
"--int8=3", "-8", "4",
|
||||||
|
"--int32=5", "-3", "6",
|
||||||
|
"--int64=7", "-6", "8",
|
||||||
|
})
|
||||||
|
|
||||||
|
if cfg.Test != 1 || cfg.TestP != 2 || cfg.Test8 != 3 || cfg.Test8P != 4 || cfg.Test32 != 5 || cfg.Test32P != 6 || cfg.Test64 != 7 || cfg.Test64P != 8 {
|
||||||
|
t.Errorf("One of the int tests failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.TestDef != 66 {
|
||||||
|
t.Errorf("TestDef should be '66', is '%d'", cfg.TestDef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUint(t *testing.T) {
|
||||||
|
cfg := struct {
|
||||||
|
Test uint `flag:"int"`
|
||||||
|
TestP uint `flag:"intp,i"`
|
||||||
|
Test8 uint8 `flag:"int8"`
|
||||||
|
Test8P uint8 `flag:"int8p,8"`
|
||||||
|
Test16 uint16 `flag:"int16"`
|
||||||
|
Test16P uint16 `flag:"int16p,1"`
|
||||||
|
Test32 uint32 `flag:"int32"`
|
||||||
|
Test32P uint32 `flag:"int32p,3"`
|
||||||
|
Test64 uint64 `flag:"int64"`
|
||||||
|
Test64P uint64 `flag:"int64p,6"`
|
||||||
|
TestDef uint8 `default:"66"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
parse(&cfg, []string{
|
||||||
|
"--int=1", "-i", "2",
|
||||||
|
"--int8=3", "-8", "4",
|
||||||
|
"--int32=5", "-3", "6",
|
||||||
|
"--int64=7", "-6", "8",
|
||||||
|
"--int16=9", "-1", "10",
|
||||||
|
})
|
||||||
|
|
||||||
|
if cfg.Test != 1 || cfg.TestP != 2 || cfg.Test8 != 3 || cfg.Test8P != 4 || cfg.Test32 != 5 || cfg.Test32P != 6 || cfg.Test64 != 7 || cfg.Test64P != 8 || cfg.Test16 != 9 || cfg.Test16P != 10 {
|
||||||
|
t.Errorf("One of the uint tests failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.TestDef != 66 {
|
||||||
|
t.Errorf("TestDef should be '66', is '%d'", cfg.TestDef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat(t *testing.T) {
|
||||||
|
cfg := struct {
|
||||||
|
Test32 float32 `flag:"float32"`
|
||||||
|
Test32P float32 `flag:"float32p,3"`
|
||||||
|
Test64 float64 `flag:"float64"`
|
||||||
|
Test64P float64 `flag:"float64p,6"`
|
||||||
|
TestDef float32 `default:"66.256"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
parse(&cfg, []string{
|
||||||
|
"--float32=5.5", "-3", "6.6",
|
||||||
|
"--float64=7.7", "-6", "8.8",
|
||||||
|
})
|
||||||
|
|
||||||
|
if cfg.Test32 != 5.5 || cfg.Test32P != 6.6 || cfg.Test64 != 7.7 || cfg.Test64P != 8.8 {
|
||||||
|
t.Errorf("One of the int tests failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.TestDef != 66.256 {
|
||||||
|
t.Errorf("TestDef should be '66.256', is '%.3f'", cfg.TestDef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubStruct(t *testing.T) {
|
||||||
|
cfg := struct {
|
||||||
|
Test string `default:"blubb"`
|
||||||
|
Sub struct {
|
||||||
|
Test string `default:"Hallo"`
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := parse(&cfg, []string{}); err != nil {
|
||||||
|
t.Errorf("Test errored: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Test != "blubb" {
|
||||||
|
t.Errorf("Test should be 'blubb', is '%s'", cfg.Test)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Sub.Test != "Hallo" {
|
||||||
|
t.Errorf("Sub.Test should be 'Hallo', is '%s'", cfg.Sub.Test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlice(t *testing.T) {
|
||||||
|
cfg := struct {
|
||||||
|
Int []int `default:"1,2,3" flag:"int"`
|
||||||
|
String []string `default:"a,b,c" flag:"string"`
|
||||||
|
IntP []int `default:"1,2,3" flag:"intp,i"`
|
||||||
|
StringP []string `default:"a,b,c" flag:"stringp,s"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := parse(&cfg, []string{
|
||||||
|
"--int=4,5", "-s", "hallo,welt",
|
||||||
|
}); err != nil {
|
||||||
|
t.Errorf("Test errored: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Int) != 2 || cfg.Int[0] != 4 || cfg.Int[1] != 5 {
|
||||||
|
t.Errorf("Int should be '4,5', is '%+v'", cfg.Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.String) != 3 || cfg.String[0] != "a" || cfg.String[1] != "b" {
|
||||||
|
t.Errorf("String should be 'a,b,c', is '%+v'", cfg.String)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.StringP) != 2 || cfg.StringP[0] != "hallo" || cfg.StringP[1] != "welt" {
|
||||||
|
t.Errorf("StringP should be 'hallo,welt', is '%+v'", cfg.StringP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrors(t *testing.T) {
|
||||||
|
if err := parse(&struct {
|
||||||
|
A int `default:"a"`
|
||||||
|
}{}, []string{}); err == nil {
|
||||||
|
t.Errorf("Test should have errored")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parse(&struct {
|
||||||
|
A float32 `default:"a"`
|
||||||
|
}{}, []string{}); err == nil {
|
||||||
|
t.Errorf("Test should have errored")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parse(&struct {
|
||||||
|
A uint `default:"a"`
|
||||||
|
}{}, []string{}); err == nil {
|
||||||
|
t.Errorf("Test should have errored")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parse(&struct {
|
||||||
|
B struct {
|
||||||
|
A uint `default:"a"`
|
||||||
|
}
|
||||||
|
}{}, []string{}); err == nil {
|
||||||
|
t.Errorf("Test should have errored")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parse(&struct {
|
||||||
|
A []int `default:"a,bn"`
|
||||||
|
}{}, []string{}); err == nil {
|
||||||
|
t.Errorf("Test should have errored")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOSArgs(t *testing.T) {
|
||||||
|
os.Args = []string{"--a=bar"}
|
||||||
|
|
||||||
|
cfg := struct {
|
||||||
|
A string `default:"a" flag:"a"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
Parse(&cfg)
|
||||||
|
|
||||||
|
if cfg.A != "bar" {
|
||||||
|
t.Errorf("A should be 'bar', is '%s'", cfg.A)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNonPointer(t *testing.T) {
|
||||||
|
cfg := struct {
|
||||||
|
A string `default:"a"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := parse(cfg, []string{}); err == nil {
|
||||||
|
t.Errorf("Test should have errored")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOtherType(t *testing.T) {
|
||||||
|
cfg := "test"
|
||||||
|
|
||||||
|
if err := parse(&cfg, []string{}); err == nil {
|
||||||
|
t.Errorf("Test should have errored")
|
||||||
|
}
|
||||||
|
}
|
37
Godeps/_workspace/src/github.com/Luzifer/rconfig/example_test.go
generated
vendored
Normal file
37
Godeps/_workspace/src/github.com/Luzifer/rconfig/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package rconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleParse() {
|
||||||
|
// We're building an example configuration with a sub-struct to be filled
|
||||||
|
// by the Parse command.
|
||||||
|
config := struct {
|
||||||
|
Username string `default:"unknown" flag:"user,u" description:"Your name"`
|
||||||
|
Details struct {
|
||||||
|
Age int `default:"25" flag:"age" description:"Your age"`
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// To have more relieable results we're setting os.Args to a known value.
|
||||||
|
// In real-life use cases you wouldn't do this but parse the original
|
||||||
|
// commandline arguments.
|
||||||
|
os.Args = []string{
|
||||||
|
"example",
|
||||||
|
"--user=Luzifer",
|
||||||
|
}
|
||||||
|
|
||||||
|
Parse(&config)
|
||||||
|
|
||||||
|
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
|
||||||
|
config.Username,
|
||||||
|
config.Details.Age)
|
||||||
|
|
||||||
|
// You can also show an usage message for your user
|
||||||
|
Usage()
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Hello Luzifer, happy birthday for your 25th birthday.
|
||||||
|
}
|
63
Godeps/_workspace/src/github.com/beorn7/perks/quantile/bench_test.go
generated
vendored
Normal file
63
Godeps/_workspace/src/github.com/beorn7/perks/quantile/bench_test.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package quantile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkInsertTargeted(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
s := NewTargeted(Targets)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := float64(0); i < float64(b.N); i++ {
|
||||||
|
s.Insert(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkInsertTargetedSmallEpsilon(b *testing.B) {
|
||||||
|
s := NewTargeted(TargetsSmallEpsilon)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := float64(0); i < float64(b.N); i++ {
|
||||||
|
s.Insert(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkInsertBiased(b *testing.B) {
|
||||||
|
s := NewLowBiased(0.01)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := float64(0); i < float64(b.N); i++ {
|
||||||
|
s.Insert(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkInsertBiasedSmallEpsilon(b *testing.B) {
|
||||||
|
s := NewLowBiased(0.0001)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := float64(0); i < float64(b.N); i++ {
|
||||||
|
s.Insert(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkQuery(b *testing.B) {
|
||||||
|
s := NewTargeted(Targets)
|
||||||
|
for i := float64(0); i < 1e6; i++ {
|
||||||
|
s.Insert(i)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
n := float64(b.N)
|
||||||
|
for i := float64(0); i < n; i++ {
|
||||||
|
s.Query(i / n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkQuerySmallEpsilon(b *testing.B) {
|
||||||
|
s := NewTargeted(TargetsSmallEpsilon)
|
||||||
|
for i := float64(0); i < 1e6; i++ {
|
||||||
|
s.Insert(i)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
n := float64(b.N)
|
||||||
|
for i := float64(0); i < n; i++ {
|
||||||
|
s.Query(i / n)
|
||||||
|
}
|
||||||
|
}
|
121
Godeps/_workspace/src/github.com/beorn7/perks/quantile/example_test.go
generated
vendored
Normal file
121
Godeps/_workspace/src/github.com/beorn7/perks/quantile/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// +build go1.1
|
||||||
|
|
||||||
|
package quantile_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beorn7/perks/quantile"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_simple() {
|
||||||
|
ch := make(chan float64)
|
||||||
|
go sendFloats(ch)
|
||||||
|
|
||||||
|
// Compute the 50th, 90th, and 99th percentile.
|
||||||
|
q := quantile.NewTargeted(map[float64]float64{
|
||||||
|
0.50: 0.005,
|
||||||
|
0.90: 0.001,
|
||||||
|
0.99: 0.0001,
|
||||||
|
})
|
||||||
|
for v := range ch {
|
||||||
|
q.Insert(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("perc50:", q.Query(0.50))
|
||||||
|
fmt.Println("perc90:", q.Query(0.90))
|
||||||
|
fmt.Println("perc99:", q.Query(0.99))
|
||||||
|
fmt.Println("count:", q.Count())
|
||||||
|
// Output:
|
||||||
|
// perc50: 5
|
||||||
|
// perc90: 16
|
||||||
|
// perc99: 223
|
||||||
|
// count: 2388
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_mergeMultipleStreams() {
|
||||||
|
// Scenario:
|
||||||
|
// We have multiple database shards. On each shard, there is a process
|
||||||
|
// collecting query response times from the database logs and inserting
|
||||||
|
// them into a Stream (created via NewTargeted(0.90)), much like the
|
||||||
|
// Simple example. These processes expose a network interface for us to
|
||||||
|
// ask them to serialize and send us the results of their
|
||||||
|
// Stream.Samples so we may Merge and Query them.
|
||||||
|
//
|
||||||
|
// NOTES:
|
||||||
|
// * These sample sets are small, allowing us to get them
|
||||||
|
// across the network much faster than sending the entire list of data
|
||||||
|
// points.
|
||||||
|
//
|
||||||
|
// * For this to work correctly, we must supply the same quantiles
|
||||||
|
// a priori the process collecting the samples supplied to NewTargeted,
|
||||||
|
// even if we do not plan to query them all here.
|
||||||
|
ch := make(chan quantile.Samples)
|
||||||
|
getDBQuerySamples(ch)
|
||||||
|
q := quantile.NewTargeted(map[float64]float64{0.90: 0.001})
|
||||||
|
for samples := range ch {
|
||||||
|
q.Merge(samples)
|
||||||
|
}
|
||||||
|
fmt.Println("perc90:", q.Query(0.90))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_window() {
|
||||||
|
// Scenario: We want the 90th, 95th, and 99th percentiles for each
|
||||||
|
// minute.
|
||||||
|
|
||||||
|
ch := make(chan float64)
|
||||||
|
go sendStreamValues(ch)
|
||||||
|
|
||||||
|
tick := time.NewTicker(1 * time.Minute)
|
||||||
|
q := quantile.NewTargeted(map[float64]float64{
|
||||||
|
0.90: 0.001,
|
||||||
|
0.95: 0.0005,
|
||||||
|
0.99: 0.0001,
|
||||||
|
})
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case t := <-tick.C:
|
||||||
|
flushToDB(t, q.Samples())
|
||||||
|
q.Reset()
|
||||||
|
case v := <-ch:
|
||||||
|
q.Insert(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendStreamValues(ch chan float64) {
|
||||||
|
// Use your imagination
|
||||||
|
}
|
||||||
|
|
||||||
|
func flushToDB(t time.Time, samples quantile.Samples) {
|
||||||
|
// Use your imagination
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a stub for the above example. In reality this would hit the remote
|
||||||
|
// servers via http or something like it.
|
||||||
|
func getDBQuerySamples(ch chan quantile.Samples) {}
|
||||||
|
|
||||||
|
func sendFloats(ch chan<- float64) {
|
||||||
|
f, err := os.Open("exampledata.txt")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
sc := bufio.NewScanner(f)
|
||||||
|
for sc.Scan() {
|
||||||
|
b := sc.Bytes()
|
||||||
|
v, err := strconv.ParseFloat(string(b), 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
ch <- v
|
||||||
|
}
|
||||||
|
if sc.Err() != nil {
|
||||||
|
log.Fatal(sc.Err())
|
||||||
|
}
|
||||||
|
close(ch)
|
||||||
|
}
|
2388
Godeps/_workspace/src/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
Normal file
2388
Godeps/_workspace/src/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
292
Godeps/_workspace/src/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
292
Godeps/_workspace/src/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
// Package quantile computes approximate quantiles over an unbounded data
|
||||||
|
// stream within low memory and CPU bounds.
|
||||||
|
//
|
||||||
|
// A small amount of accuracy is traded to achieve the above properties.
|
||||||
|
//
|
||||||
|
// Multiple streams can be merged before calling Query to generate a single set
|
||||||
|
// of results. This is meaningful when the streams represent the same type of
|
||||||
|
// data. See Merge and Samples.
|
||||||
|
//
|
||||||
|
// For more detailed information about the algorithm used, see:
|
||||||
|
//
|
||||||
|
// Effective Computation of Biased Quantiles over Data Streams
|
||||||
|
//
|
||||||
|
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
|
||||||
|
package quantile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sample holds an observed value and meta information for compression. JSON
|
||||||
|
// tags have been added for convenience.
|
||||||
|
type Sample struct {
|
||||||
|
Value float64 `json:",string"`
|
||||||
|
Width float64 `json:",string"`
|
||||||
|
Delta float64 `json:",string"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Samples represents a slice of samples. It implements sort.Interface.
|
||||||
|
type Samples []Sample
|
||||||
|
|
||||||
|
func (a Samples) Len() int { return len(a) }
|
||||||
|
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||||
|
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
|
||||||
|
type invariant func(s *stream, r float64) float64
|
||||||
|
|
||||||
|
// NewLowBiased returns an initialized Stream for low-biased quantiles
|
||||||
|
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||||
|
// error guarantees can still be given even for the lower ranks of the data
|
||||||
|
// distribution.
|
||||||
|
//
|
||||||
|
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||||
|
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
|
||||||
|
//
|
||||||
|
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||||
|
// properties.
|
||||||
|
func NewLowBiased(epsilon float64) *Stream {
|
||||||
|
ƒ := func(s *stream, r float64) float64 {
|
||||||
|
return 2 * epsilon * r
|
||||||
|
}
|
||||||
|
return newStream(ƒ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHighBiased returns an initialized Stream for high-biased quantiles
|
||||||
|
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||||
|
// error guarantees can still be given even for the higher ranks of the data
|
||||||
|
// distribution.
|
||||||
|
//
|
||||||
|
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||||
|
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
|
||||||
|
//
|
||||||
|
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||||
|
// properties.
|
||||||
|
func NewHighBiased(epsilon float64) *Stream {
|
||||||
|
ƒ := func(s *stream, r float64) float64 {
|
||||||
|
return 2 * epsilon * (s.n - r)
|
||||||
|
}
|
||||||
|
return newStream(ƒ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTargeted returns an initialized Stream concerned with a particular set of
|
||||||
|
// quantile values that are supplied a priori. Knowing these a priori reduces
|
||||||
|
// space and computation time. The targets map maps the desired quantiles to
|
||||||
|
// their absolute errors, i.e. the true quantile of a value returned by a query
|
||||||
|
// is guaranteed to be within (Quantile±Epsilon).
|
||||||
|
//
|
||||||
|
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
|
||||||
|
func NewTargeted(targets map[float64]float64) *Stream {
|
||||||
|
ƒ := func(s *stream, r float64) float64 {
|
||||||
|
var m = math.MaxFloat64
|
||||||
|
var f float64
|
||||||
|
for quantile, epsilon := range targets {
|
||||||
|
if quantile*s.n <= r {
|
||||||
|
f = (2 * epsilon * r) / quantile
|
||||||
|
} else {
|
||||||
|
f = (2 * epsilon * (s.n - r)) / (1 - quantile)
|
||||||
|
}
|
||||||
|
if f < m {
|
||||||
|
m = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
return newStream(ƒ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
|
||||||
|
// design. Take care when using across multiple goroutines.
|
||||||
|
type Stream struct {
|
||||||
|
*stream
|
||||||
|
b Samples
|
||||||
|
sorted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStream(ƒ invariant) *Stream {
|
||||||
|
x := &stream{ƒ: ƒ}
|
||||||
|
return &Stream{x, make(Samples, 0, 500), true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert inserts v into the stream.
|
||||||
|
func (s *Stream) Insert(v float64) {
|
||||||
|
s.insert(Sample{Value: v, Width: 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stream) insert(sample Sample) {
|
||||||
|
s.b = append(s.b, sample)
|
||||||
|
s.sorted = false
|
||||||
|
if len(s.b) == cap(s.b) {
|
||||||
|
s.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query returns the computed qth percentiles value. If s was created with
|
||||||
|
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
|
||||||
|
// will return an unspecified result.
|
||||||
|
func (s *Stream) Query(q float64) float64 {
|
||||||
|
if !s.flushed() {
|
||||||
|
// Fast path when there hasn't been enough data for a flush;
|
||||||
|
// this also yields better accuracy for small sets of data.
|
||||||
|
l := len(s.b)
|
||||||
|
if l == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i := int(float64(l) * q)
|
||||||
|
if i > 0 {
|
||||||
|
i -= 1
|
||||||
|
}
|
||||||
|
s.maybeSort()
|
||||||
|
return s.b[i].Value
|
||||||
|
}
|
||||||
|
s.flush()
|
||||||
|
return s.stream.query(q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges samples into the underlying streams samples. This is handy when
|
||||||
|
// merging multiple streams from separate threads, database shards, etc.
|
||||||
|
//
|
||||||
|
// ATTENTION: This method is broken and does not yield correct results. The
|
||||||
|
// underlying algorithm is not capable of merging streams correctly.
|
||||||
|
func (s *Stream) Merge(samples Samples) {
|
||||||
|
sort.Sort(samples)
|
||||||
|
s.stream.merge(samples)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset reinitializes and clears the list reusing the samples buffer memory.
|
||||||
|
func (s *Stream) Reset() {
|
||||||
|
s.stream.reset()
|
||||||
|
s.b = s.b[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Samples returns stream samples held by s.
|
||||||
|
func (s *Stream) Samples() Samples {
|
||||||
|
if !s.flushed() {
|
||||||
|
return s.b
|
||||||
|
}
|
||||||
|
s.flush()
|
||||||
|
return s.stream.samples()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the total number of samples observed in the stream
|
||||||
|
// since initialization.
|
||||||
|
func (s *Stream) Count() int {
|
||||||
|
return len(s.b) + s.stream.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stream) flush() {
|
||||||
|
s.maybeSort()
|
||||||
|
s.stream.merge(s.b)
|
||||||
|
s.b = s.b[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stream) maybeSort() {
|
||||||
|
if !s.sorted {
|
||||||
|
s.sorted = true
|
||||||
|
sort.Sort(s.b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stream) flushed() bool {
|
||||||
|
return len(s.stream.l) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type stream struct {
|
||||||
|
n float64
|
||||||
|
l []Sample
|
||||||
|
ƒ invariant
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) reset() {
|
||||||
|
s.l = s.l[:0]
|
||||||
|
s.n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) insert(v float64) {
|
||||||
|
s.merge(Samples{{v, 1, 0}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) merge(samples Samples) {
|
||||||
|
// TODO(beorn7): This tries to merge not only individual samples, but
|
||||||
|
// whole summaries. The paper doesn't mention merging summaries at
|
||||||
|
// all. Unittests show that the merging is inaccurate. Find out how to
|
||||||
|
// do merges properly.
|
||||||
|
var r float64
|
||||||
|
i := 0
|
||||||
|
for _, sample := range samples {
|
||||||
|
for ; i < len(s.l); i++ {
|
||||||
|
c := s.l[i]
|
||||||
|
if c.Value > sample.Value {
|
||||||
|
// Insert at position i.
|
||||||
|
s.l = append(s.l, Sample{})
|
||||||
|
copy(s.l[i+1:], s.l[i:])
|
||||||
|
s.l[i] = Sample{
|
||||||
|
sample.Value,
|
||||||
|
sample.Width,
|
||||||
|
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
|
||||||
|
// TODO(beorn7): How to calculate delta correctly?
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
goto inserted
|
||||||
|
}
|
||||||
|
r += c.Width
|
||||||
|
}
|
||||||
|
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
|
||||||
|
i++
|
||||||
|
inserted:
|
||||||
|
s.n += sample.Width
|
||||||
|
r += sample.Width
|
||||||
|
}
|
||||||
|
s.compress()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) count() int {
|
||||||
|
return int(s.n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) query(q float64) float64 {
|
||||||
|
t := math.Ceil(q * s.n)
|
||||||
|
t += math.Ceil(s.ƒ(s, t) / 2)
|
||||||
|
p := s.l[0]
|
||||||
|
var r float64
|
||||||
|
for _, c := range s.l[1:] {
|
||||||
|
r += p.Width
|
||||||
|
if r+c.Width+c.Delta > t {
|
||||||
|
return p.Value
|
||||||
|
}
|
||||||
|
p = c
|
||||||
|
}
|
||||||
|
return p.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) compress() {
|
||||||
|
if len(s.l) < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x := s.l[len(s.l)-1]
|
||||||
|
xi := len(s.l) - 1
|
||||||
|
r := s.n - 1 - x.Width
|
||||||
|
|
||||||
|
for i := len(s.l) - 2; i >= 0; i-- {
|
||||||
|
c := s.l[i]
|
||||||
|
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
|
||||||
|
x.Width += c.Width
|
||||||
|
s.l[xi] = x
|
||||||
|
// Remove element at i.
|
||||||
|
copy(s.l[i:], s.l[i+1:])
|
||||||
|
s.l = s.l[:len(s.l)-1]
|
||||||
|
xi -= 1
|
||||||
|
} else {
|
||||||
|
x = c
|
||||||
|
xi = i
|
||||||
|
}
|
||||||
|
r -= c.Width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stream) samples() Samples {
|
||||||
|
samples := make(Samples, len(s.l))
|
||||||
|
copy(samples, s.l)
|
||||||
|
return samples
|
||||||
|
}
|
188
Godeps/_workspace/src/github.com/beorn7/perks/quantile/stream_test.go
generated
vendored
Normal file
188
Godeps/_workspace/src/github.com/beorn7/perks/quantile/stream_test.go
generated
vendored
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
package quantile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Targets = map[float64]float64{
|
||||||
|
0.01: 0.001,
|
||||||
|
0.10: 0.01,
|
||||||
|
0.50: 0.05,
|
||||||
|
0.90: 0.01,
|
||||||
|
0.99: 0.001,
|
||||||
|
}
|
||||||
|
TargetsSmallEpsilon = map[float64]float64{
|
||||||
|
0.01: 0.0001,
|
||||||
|
0.10: 0.001,
|
||||||
|
0.50: 0.005,
|
||||||
|
0.90: 0.001,
|
||||||
|
0.99: 0.0001,
|
||||||
|
}
|
||||||
|
LowQuantiles = []float64{0.01, 0.1, 0.5}
|
||||||
|
HighQuantiles = []float64{0.99, 0.9, 0.5}
|
||||||
|
)
|
||||||
|
|
||||||
|
const RelativeEpsilon = 0.01
|
||||||
|
|
||||||
|
func verifyPercsWithAbsoluteEpsilon(t *testing.T, a []float64, s *Stream) {
|
||||||
|
sort.Float64s(a)
|
||||||
|
for quantile, epsilon := range Targets {
|
||||||
|
n := float64(len(a))
|
||||||
|
k := int(quantile * n)
|
||||||
|
lower := int((quantile - epsilon) * n)
|
||||||
|
if lower < 1 {
|
||||||
|
lower = 1
|
||||||
|
}
|
||||||
|
upper := int(math.Ceil((quantile + epsilon) * n))
|
||||||
|
if upper > len(a) {
|
||||||
|
upper = len(a)
|
||||||
|
}
|
||||||
|
w, min, max := a[k-1], a[lower-1], a[upper-1]
|
||||||
|
if g := s.Query(quantile); g < min || g > max {
|
||||||
|
t.Errorf("q=%f: want %v [%f,%f], got %v", quantile, w, min, max, g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyLowPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
|
||||||
|
sort.Float64s(a)
|
||||||
|
for _, qu := range LowQuantiles {
|
||||||
|
n := float64(len(a))
|
||||||
|
k := int(qu * n)
|
||||||
|
|
||||||
|
lowerRank := int((1 - RelativeEpsilon) * qu * n)
|
||||||
|
upperRank := int(math.Ceil((1 + RelativeEpsilon) * qu * n))
|
||||||
|
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
|
||||||
|
if g := s.Query(qu); g < min || g > max {
|
||||||
|
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyHighPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
|
||||||
|
sort.Float64s(a)
|
||||||
|
for _, qu := range HighQuantiles {
|
||||||
|
n := float64(len(a))
|
||||||
|
k := int(qu * n)
|
||||||
|
|
||||||
|
lowerRank := int((1 - (1+RelativeEpsilon)*(1-qu)) * n)
|
||||||
|
upperRank := int(math.Ceil((1 - (1-RelativeEpsilon)*(1-qu)) * n))
|
||||||
|
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
|
||||||
|
if g := s.Query(qu); g < min || g > max {
|
||||||
|
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateStream(s *Stream) []float64 {
|
||||||
|
a := make([]float64, 0, 1e5+100)
|
||||||
|
for i := 0; i < cap(a); i++ {
|
||||||
|
v := rand.NormFloat64()
|
||||||
|
// Add 5% asymmetric outliers.
|
||||||
|
if i%20 == 0 {
|
||||||
|
v = v*v + 1
|
||||||
|
}
|
||||||
|
s.Insert(v)
|
||||||
|
a = append(a, v)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTargetedQuery(t *testing.T) {
|
||||||
|
rand.Seed(42)
|
||||||
|
s := NewTargeted(Targets)
|
||||||
|
a := populateStream(s)
|
||||||
|
verifyPercsWithAbsoluteEpsilon(t, a, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLowBiasedQuery(t *testing.T) {
|
||||||
|
rand.Seed(42)
|
||||||
|
s := NewLowBiased(RelativeEpsilon)
|
||||||
|
a := populateStream(s)
|
||||||
|
verifyLowPercsWithRelativeEpsilon(t, a, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHighBiasedQuery(t *testing.T) {
|
||||||
|
rand.Seed(42)
|
||||||
|
s := NewHighBiased(RelativeEpsilon)
|
||||||
|
a := populateStream(s)
|
||||||
|
verifyHighPercsWithRelativeEpsilon(t, a, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BrokenTestTargetedMerge is broken, see Merge doc comment.
|
||||||
|
func BrokenTestTargetedMerge(t *testing.T) {
|
||||||
|
rand.Seed(42)
|
||||||
|
s1 := NewTargeted(Targets)
|
||||||
|
s2 := NewTargeted(Targets)
|
||||||
|
a := populateStream(s1)
|
||||||
|
a = append(a, populateStream(s2)...)
|
||||||
|
s1.Merge(s2.Samples())
|
||||||
|
verifyPercsWithAbsoluteEpsilon(t, a, s1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BrokenTestLowBiasedMerge is broken, see Merge doc comment.
|
||||||
|
func BrokenTestLowBiasedMerge(t *testing.T) {
|
||||||
|
rand.Seed(42)
|
||||||
|
s1 := NewLowBiased(RelativeEpsilon)
|
||||||
|
s2 := NewLowBiased(RelativeEpsilon)
|
||||||
|
a := populateStream(s1)
|
||||||
|
a = append(a, populateStream(s2)...)
|
||||||
|
s1.Merge(s2.Samples())
|
||||||
|
verifyLowPercsWithRelativeEpsilon(t, a, s2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BrokenTestHighBiasedMerge is broken, see Merge doc comment.
|
||||||
|
func BrokenTestHighBiasedMerge(t *testing.T) {
|
||||||
|
rand.Seed(42)
|
||||||
|
s1 := NewHighBiased(RelativeEpsilon)
|
||||||
|
s2 := NewHighBiased(RelativeEpsilon)
|
||||||
|
a := populateStream(s1)
|
||||||
|
a = append(a, populateStream(s2)...)
|
||||||
|
s1.Merge(s2.Samples())
|
||||||
|
verifyHighPercsWithRelativeEpsilon(t, a, s2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUncompressed(t *testing.T) {
|
||||||
|
q := NewTargeted(Targets)
|
||||||
|
for i := 100; i > 0; i-- {
|
||||||
|
q.Insert(float64(i))
|
||||||
|
}
|
||||||
|
if g := q.Count(); g != 100 {
|
||||||
|
t.Errorf("want count 100, got %d", g)
|
||||||
|
}
|
||||||
|
// Before compression, Query should have 100% accuracy.
|
||||||
|
for quantile := range Targets {
|
||||||
|
w := quantile * 100
|
||||||
|
if g := q.Query(quantile); g != w {
|
||||||
|
t.Errorf("want %f, got %f", w, g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUncompressedSamples(t *testing.T) {
|
||||||
|
q := NewTargeted(map[float64]float64{0.99: 0.001})
|
||||||
|
for i := 1; i <= 100; i++ {
|
||||||
|
q.Insert(float64(i))
|
||||||
|
}
|
||||||
|
if g := q.Samples().Len(); g != 100 {
|
||||||
|
t.Errorf("want count 100, got %d", g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUncompressedOne(t *testing.T) {
|
||||||
|
q := NewTargeted(map[float64]float64{0.99: 0.01})
|
||||||
|
q.Insert(3.14)
|
||||||
|
if g := q.Query(0.90); g != 3.14 {
|
||||||
|
t.Error("want PI, got", g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaults(t *testing.T) {
|
||||||
|
if g := NewTargeted(map[float64]float64{0.99: 0.001}).Query(0.99); g != 0 {
|
||||||
|
t.Errorf("want 0, got %f", g)
|
||||||
|
}
|
||||||
|
}
|
40
Godeps/_workspace/src/github.com/flosch/pongo2/.gitignore
generated
vendored
Normal file
40
Godeps/_workspace/src/github.com/flosch/pongo2/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# 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
|
12
Godeps/_workspace/src/github.com/flosch/pongo2/.travis.yml
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/flosch/pongo2/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.4
|
||||||
|
- tip
|
||||||
|
install:
|
||||||
|
- go get golang.org/x/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'
|
10
Godeps/_workspace/src/github.com/flosch/pongo2/AUTHORS
generated
vendored
Normal file
10
Godeps/_workspace/src/github.com/flosch/pongo2/AUTHORS
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Main author and maintainer of pongo2:
|
||||||
|
|
||||||
|
* Florian Schlachter <flori@n-schlachter.de>
|
||||||
|
|
||||||
|
Contributors (in no specific order):
|
||||||
|
|
||||||
|
* @romanoaugusto88
|
||||||
|
* @vitalbh
|
||||||
|
|
||||||
|
Feel free to add yourself to the list or to modify your entry if you did a contribution.
|
20
Godeps/_workspace/src/github.com/flosch/pongo2/LICENSE
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/flosch/pongo2/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013-2014 Florian Schlachter
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
252
Godeps/_workspace/src/github.com/flosch/pongo2/README.md
generated
vendored
Normal file
252
Godeps/_workspace/src/github.com/flosch/pongo2/README.md
generated
vendored
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
# [pongo](https://en.wikipedia.org/wiki/Pongo_%28genus%29)2
|
||||||
|
|
||||||
|
[![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.png)](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)
|
||||||
|
[![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)
|
||||||
|
|
||||||
|
pongo2 is the successor of [pongo](https://github.com/flosch/pongo), a Django-syntax like templating-language.
|
||||||
|
|
||||||
|
Install/update using `go get` (no dependencies required by pongo2):
|
||||||
|
```
|
||||||
|
go get -u github.com/flosch/pongo2
|
||||||
|
```
|
||||||
|
|
||||||
|
Please use the [issue tracker](https://github.com/flosch/pongo2/issues) if you're encountering any problems with pongo2 or if you need help with implementing tags or filters ([create a ticket!](https://github.com/flosch/pongo2/issues/new)). If possible, please use [playground](https://www.florian-schlachter.de/pongo2/) to create a short test case on what's wrong and include the link to the snippet in your issue.
|
||||||
|
|
||||||
|
**New**: [Try pongo2 out in the pongo2 playground.](https://www.florian-schlachter.de/pongo2/)
|
||||||
|
|
||||||
|
## First impression of a template
|
||||||
|
|
||||||
|
```HTML+Django
|
||||||
|
<html><head><title>Our admins and users</title></head>
|
||||||
|
{# This is a short example to give you a quick overview of pongo2's syntax. #}
|
||||||
|
|
||||||
|
{% macro user_details(user, is_admin=false) %}
|
||||||
|
<div class="user_item">
|
||||||
|
<!-- Let's indicate a user's good karma -->
|
||||||
|
<h2 {% if (user.karma >= 40) || (user.karma > calc_avg_karma(userlist)+5) %}
|
||||||
|
class="karma-good"{% endif %}>
|
||||||
|
|
||||||
|
<!-- This will call user.String() automatically if available: -->
|
||||||
|
{{ user }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<!-- Will print a human-readable time duration like "3 weeks ago" -->
|
||||||
|
<p>This user registered {{ user.register_date|naturaltime }}.</p>
|
||||||
|
|
||||||
|
<!-- Let's allow the users to write down their biography using markdown;
|
||||||
|
we will only show the first 15 words as a preview -->
|
||||||
|
<p>The user's biography:</p>
|
||||||
|
<p>{{ user.biography|markdown|truncatewords_html:15 }}
|
||||||
|
<a href="/user/{{ user.id }}/">read more</a></p>
|
||||||
|
|
||||||
|
{% if is_admin %}<p>This user is an admin!</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Make use of the macro defined above to avoid repetitive HTML code
|
||||||
|
since we want to use the same code for admins AND members -->
|
||||||
|
|
||||||
|
<h1>Our admins</h1>
|
||||||
|
{% for admin in adminlist %}
|
||||||
|
{{ user_details(admin, true) }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<h1>Our members</h1>
|
||||||
|
{% for user in userlist %}
|
||||||
|
{{ user_details(user) }}
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development status
|
||||||
|
|
||||||
|
**Latest stable release**: v3.0 (`go get -u gopkg.in/flosch/pongo2.v3` / [`v3`](https://github.com/flosch/pongo2/tree/v3)-branch) [[read the announcement](https://www.florian-schlachter.de/post/pongo2-v3/)]
|
||||||
|
|
||||||
|
**Current development**: v4 (`master`-branch)
|
||||||
|
|
||||||
|
*Note*: With the release of pongo v4 the branch v2 will be deprecated.
|
||||||
|
|
||||||
|
**Deprecated versions** (not supported anymore): v1
|
||||||
|
|
||||||
|
| Topic | Status |
|
||||||
|
| ------------------------------------ | -------------------------------------------------------------------------------------- |
|
||||||
|
| Django version compatibility: | [1.7](https://docs.djangoproject.com/en/1.7/ref/templates/builtins/) |
|
||||||
|
| *Missing* (planned) **filters**: | none ([hints](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3)) |
|
||||||
|
| *Missing* (planned) **tags**: | none ([hints](https://github.com/flosch/pongo2/blob/master/tags.go#L3)) |
|
||||||
|
|
||||||
|
Please also have a look on the [caveats](https://github.com/flosch/pongo2#caveats) and on the [official add-ons](https://github.com/flosch/pongo2#official).
|
||||||
|
|
||||||
|
## Features (and new in pongo2)
|
||||||
|
|
||||||
|
* Entirely rewritten from the ground-up.
|
||||||
|
* [Advanced C-like expressions](https://github.com/flosch/pongo2/blob/master/template_tests/expressions.tpl).
|
||||||
|
* [Complex function calls within expressions](https://github.com/flosch/pongo2/blob/master/template_tests/function_calls_wrapper.tpl).
|
||||||
|
* [Easy API to create new filters and tags](http://godoc.org/github.com/flosch/pongo2#RegisterFilter) ([including parsing arguments](http://godoc.org/github.com/flosch/pongo2#Parser))
|
||||||
|
* Additional features:
|
||||||
|
* Macros including importing macros from other files (see [template_tests/macro.tpl](https://github.com/flosch/pongo2/blob/master/template_tests/macro.tpl))
|
||||||
|
* [Template sandboxing](https://godoc.org/github.com/flosch/pongo2#TemplateSet) ([directory patterns](http://golang.org/pkg/path/filepath/#Match), banned tags/filters)
|
||||||
|
|
||||||
|
## Recent API changes within pongo2
|
||||||
|
|
||||||
|
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).
|
||||||
|
* `Template.ExecuteRW()` is now [`Template.ExecuteWriter()`](https://godoc.org/github.com/flosch/pongo2#Template.ExecuteWriter)
|
||||||
|
* `Template.Execute*()` functions do now take a `pongo2.Context` directly (no pointer anymore).
|
||||||
|
|
||||||
|
## 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` 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. :-)
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
|
||||||
|
For a documentation on how the templating language works you can [head over to the Django documentation](https://docs.djangoproject.com/en/dev/topics/templates/). pongo2 aims to be compatible with it.
|
||||||
|
|
||||||
|
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]
|
||||||
|
* [Release of pongo2 1.0-rc1 + pongo2-addons](https://www.florian-schlachter.de/post/pongo2-10-rc1/) [July 30th 2014]
|
||||||
|
* [Introduction to pongo2 + migration- and "how to write tags/filters"-tutorial.](https://www.florian-schlachter.de/post/pongo2/) [June 29th 2014]
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
### Filters
|
||||||
|
|
||||||
|
* **date** / **time**: The `date` and `time` filter are taking the Golang specific time- and date-format (not Django's one) currently. [Take a look on the format here](http://golang.org/pkg/time/#Time.Format).
|
||||||
|
* **stringformat**: `stringformat` does **not** take Python's string format syntax as a parameter, instead it takes Go's. Essentially `{{ 3.14|stringformat:"pi is %.2f" }}` is `fmt.Sprintf("pi is %.2f", 3.14)`.
|
||||||
|
* **escape** / **force_escape**: Unlike Django's behaviour, the `escape`-filter is applied immediately. Therefore there is no need for a `force_escape`-filter yet.
|
||||||
|
|
||||||
|
### Tags
|
||||||
|
|
||||||
|
* **for**: All the `forloop` fields (like `forloop.counter`) are written with a capital letter at the beginning. For example, the `counter` can be accessed by `forloop.Counter` and the parentloop by `forloop.Parentloop`.
|
||||||
|
* **now**: takes Go's time format (see **date** and **time**-filter).
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
* **not in-operator**: You can check whether a map/struct/string contains a key/field/substring by using the in-operator (or the negation of it):
|
||||||
|
`{% if key in map %}Key is in map{% else %}Key not in map{% endif %}` or `{% if !(key in map) %}Key is NOT in map{% else %}Key is in map{% endif %}`.
|
||||||
|
|
||||||
|
# Add-ons, libraries and helpers
|
||||||
|
|
||||||
|
## Official
|
||||||
|
|
||||||
|
* [ponginae](https://github.com/flosch/ponginae) - A web-framework for Go (using pongo2).
|
||||||
|
* [pongo2-tools](https://github.com/flosch/pongo2-tools) - Official tools and helpers for pongo2
|
||||||
|
* [pongo2-addons](https://github.com/flosch/pongo2-addons) - Official additional filters/tags for pongo2 (for example a **markdown**-filter). They are in their own repository because they're relying on 3rd-party-libraries.
|
||||||
|
|
||||||
|
## 3rd-party
|
||||||
|
|
||||||
|
* [beego-pongo2](https://github.com/oal/beego-pongo2) - A tiny little helper for using Pongo2 with [Beego](https://github.com/astaxie/beego).
|
||||||
|
* [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
|
||||||
|
* [tpongo2](https://github.com/tango-contrib/tpongo2) - pongo2 support for [Tango](https://github.com/lunny/tango), a micro-kernel & pluggable web framework.
|
||||||
|
|
||||||
|
Please add your project to this list and send me a pull request when you've developed something nice for pongo2.
|
||||||
|
|
||||||
|
# API-usage examples
|
||||||
|
|
||||||
|
Please see the documentation for a full list of provided API methods.
|
||||||
|
|
||||||
|
## A tiny example (template string)
|
||||||
|
|
||||||
|
```Go
|
||||||
|
// Compile the template first (i. e. creating the AST)
|
||||||
|
tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// Now you can render the template with the given
|
||||||
|
// pongo2.Context how often you want to.
|
||||||
|
out, err := tpl.Execute(pongo2.Context{"name": "florian"})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(out) // Output: Hello Florian!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example server-usage (template file)
|
||||||
|
|
||||||
|
```Go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/flosch/pongo2"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pre-compiling the templates at application startup using the
|
||||||
|
// little Must()-helper function (Must() will panic if FromFile()
|
||||||
|
// or FromString() will return with an error - that's it).
|
||||||
|
// It's faster to pre-compile it anywhere at startup and only
|
||||||
|
// execute the template later.
|
||||||
|
var tplExample = pongo2.Must(pongo2.FromFile("example.html"))
|
||||||
|
|
||||||
|
func examplePage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Execute the template per HTTP request
|
||||||
|
err := tplExample.ExecuteWriter(pongo2.Context{"query": r.FormValue("query")}, w)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", examplePage)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Benchmark
|
||||||
|
|
||||||
|
The benchmarks have been run on the my machine (`Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz`) using the command:
|
||||||
|
|
||||||
|
go test -bench . -cpu 1,2,4,8
|
||||||
|
|
||||||
|
All benchmarks are compiling (depends on the benchmark) and executing the `template_tests/complex.tpl` template.
|
||||||
|
|
||||||
|
The results are:
|
||||||
|
|
||||||
|
BenchmarkExecuteComplexWithSandboxActive 50000 60450 ns/op
|
||||||
|
BenchmarkExecuteComplexWithSandboxActive-2 50000 56998 ns/op
|
||||||
|
BenchmarkExecuteComplexWithSandboxActive-4 50000 60343 ns/op
|
||||||
|
BenchmarkExecuteComplexWithSandboxActive-8 50000 64229 ns/op
|
||||||
|
BenchmarkCompileAndExecuteComplexWithSandboxActive 10000 164410 ns/op
|
||||||
|
BenchmarkCompileAndExecuteComplexWithSandboxActive-2 10000 156682 ns/op
|
||||||
|
BenchmarkCompileAndExecuteComplexWithSandboxActive-4 10000 164821 ns/op
|
||||||
|
BenchmarkCompileAndExecuteComplexWithSandboxActive-8 10000 171806 ns/op
|
||||||
|
BenchmarkParallelExecuteComplexWithSandboxActive 50000 60428 ns/op
|
||||||
|
BenchmarkParallelExecuteComplexWithSandboxActive-2 50000 31887 ns/op
|
||||||
|
BenchmarkParallelExecuteComplexWithSandboxActive-4 100000 22810 ns/op
|
||||||
|
BenchmarkParallelExecuteComplexWithSandboxActive-8 100000 18820 ns/op
|
||||||
|
BenchmarkExecuteComplexWithoutSandbox 50000 56942 ns/op
|
||||||
|
BenchmarkExecuteComplexWithoutSandbox-2 50000 56168 ns/op
|
||||||
|
BenchmarkExecuteComplexWithoutSandbox-4 50000 57838 ns/op
|
||||||
|
BenchmarkExecuteComplexWithoutSandbox-8 50000 60539 ns/op
|
||||||
|
BenchmarkCompileAndExecuteComplexWithoutSandbox 10000 162086 ns/op
|
||||||
|
BenchmarkCompileAndExecuteComplexWithoutSandbox-2 10000 159771 ns/op
|
||||||
|
BenchmarkCompileAndExecuteComplexWithoutSandbox-4 10000 163826 ns/op
|
||||||
|
BenchmarkCompileAndExecuteComplexWithoutSandbox-8 10000 169062 ns/op
|
||||||
|
BenchmarkParallelExecuteComplexWithoutSandbox 50000 57152 ns/op
|
||||||
|
BenchmarkParallelExecuteComplexWithoutSandbox-2 50000 30276 ns/op
|
||||||
|
BenchmarkParallelExecuteComplexWithoutSandbox-4 100000 22065 ns/op
|
||||||
|
BenchmarkParallelExecuteComplexWithoutSandbox-8 100000 18034 ns/op
|
||||||
|
|
||||||
|
Benchmarked on October 2nd 2014.
|
125
Godeps/_workspace/src/github.com/flosch/pongo2/context.go
generated
vendored
Normal file
125
Godeps/_workspace/src/github.com/flosch/pongo2/context.go
generated
vendored
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$")
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
// 1. version: returns the version string
|
||||||
|
//
|
||||||
|
// Template examples for accessing items from your context:
|
||||||
|
// {{ myconstant }}
|
||||||
|
// {{ myfunc("test", 42) }}
|
||||||
|
// {{ user.name }}
|
||||||
|
// {{ pongo2.version }}
|
||||||
|
type Context map[string]interface{}
|
||||||
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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
|
||||||
|
// the Context provided by the user (field Public).
|
||||||
|
// You can safely use the Private context to provide data to the user's
|
||||||
|
// template (like a 'forloop'-information). The Shared-context is used
|
||||||
|
// to share data between tags. All ExecutionContexts share this context.
|
||||||
|
//
|
||||||
|
// Please be careful when accessing the Public data.
|
||||||
|
// PLEASE DO NOT MODIFY THE PUBLIC CONTEXT (read-only).
|
||||||
|
//
|
||||||
|
// To create your own execution context within tags, use the
|
||||||
|
// NewChildExecutionContext(parent) function.
|
||||||
|
type ExecutionContext struct {
|
||||||
|
template *Template
|
||||||
|
|
||||||
|
Autoescape bool
|
||||||
|
Public Context
|
||||||
|
Private Context
|
||||||
|
Shared Context
|
||||||
|
}
|
||||||
|
|
||||||
|
var pongo2MetaContext = Context{
|
||||||
|
"version": Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExecutionContext(tpl *Template, ctx Context) *ExecutionContext {
|
||||||
|
privateCtx := make(Context)
|
||||||
|
|
||||||
|
// Make the pongo2-related funcs/vars available to the context
|
||||||
|
privateCtx["pongo2"] = pongo2MetaContext
|
||||||
|
|
||||||
|
return &ExecutionContext{
|
||||||
|
template: tpl,
|
||||||
|
|
||||||
|
Public: ctx,
|
||||||
|
Private: privateCtx,
|
||||||
|
Autoescape: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChildExecutionContext(parent *ExecutionContext) *ExecutionContext {
|
||||||
|
newctx := &ExecutionContext{
|
||||||
|
template: parent.template,
|
||||||
|
|
||||||
|
Public: parent.Public,
|
||||||
|
Private: make(Context),
|
||||||
|
Autoescape: parent.Autoescape,
|
||||||
|
}
|
||||||
|
newctx.Shared = parent.Shared
|
||||||
|
|
||||||
|
// Copy all existing private items
|
||||||
|
newctx.Private.Update(parent.Private)
|
||||||
|
|
||||||
|
return newctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ExecutionContext) Error(msg string, token *Token) *Error {
|
||||||
|
filename := ctx.template.name
|
||||||
|
var line, col int
|
||||||
|
if token != nil {
|
||||||
|
// No tokens available
|
||||||
|
// TODO: Add location (from where?)
|
||||||
|
filename = token.Filename
|
||||||
|
line = token.Line
|
||||||
|
col = token.Col
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
Template: ctx.template,
|
||||||
|
Filename: filename,
|
||||||
|
Line: line,
|
||||||
|
Column: col,
|
||||||
|
Token: token,
|
||||||
|
Sender: "execution",
|
||||||
|
ErrorMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ExecutionContext) Logf(format string, args ...interface{}) {
|
||||||
|
ctx.template.set.logf(format, args...)
|
||||||
|
}
|
31
Godeps/_workspace/src/github.com/flosch/pongo2/doc.go
generated
vendored
Normal file
31
Godeps/_workspace/src/github.com/flosch/pongo2/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// A Django-syntax like template-engine
|
||||||
|
//
|
||||||
|
// Blog posts about pongo2 (including introduction and migration):
|
||||||
|
// https://www.florian-schlachter.de/?tag=pongo2
|
||||||
|
//
|
||||||
|
// Complete documentation on the template language:
|
||||||
|
// https://docs.djangoproject.com/en/dev/topics/templates/
|
||||||
|
//
|
||||||
|
// Try out pongo2 live in the pongo2 playground:
|
||||||
|
// https://www.florian-schlachter.de/pongo2/
|
||||||
|
//
|
||||||
|
// Make sure to read README.md in the repository as well.
|
||||||
|
//
|
||||||
|
// A tiny example with template strings:
|
||||||
|
//
|
||||||
|
// (Snippet on playground: https://www.florian-schlachter.de/pongo2/?id=1206546277)
|
||||||
|
//
|
||||||
|
// // Compile the template first (i. e. creating the AST)
|
||||||
|
// tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// // Now you can render the template with the given
|
||||||
|
// // pongo2.Context how often you want to.
|
||||||
|
// out, err := tpl.Execute(pongo2.Context{"name": "fred"})
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// fmt.Println(out) // Output: Hello Fred!
|
||||||
|
//
|
||||||
|
package pongo2
|
1
Godeps/_workspace/src/github.com/flosch/pongo2/docs/examples.md
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/flosch/pongo2/docs/examples.md
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(Stub, TBA)
|
68
Godeps/_workspace/src/github.com/flosch/pongo2/docs/filters.md
generated
vendored
Normal file
68
Godeps/_workspace/src/github.com/flosch/pongo2/docs/filters.md
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
TODO:
|
||||||
|
|
||||||
|
* What are filters?
|
||||||
|
* List+explain all existing filters (pongo2 + pongo2-addons)
|
||||||
|
|
||||||
|
Implemented filters so far which needs documentation:
|
||||||
|
|
||||||
|
* escape
|
||||||
|
* safe
|
||||||
|
* escapejs
|
||||||
|
* add
|
||||||
|
* addslashes
|
||||||
|
* capfirst
|
||||||
|
* center
|
||||||
|
* cut
|
||||||
|
* date
|
||||||
|
* default
|
||||||
|
* default_if_none
|
||||||
|
* divisibleby
|
||||||
|
* first
|
||||||
|
* floatformat
|
||||||
|
* get_digit
|
||||||
|
* iriencode
|
||||||
|
* join
|
||||||
|
* last
|
||||||
|
* length
|
||||||
|
* length_is
|
||||||
|
* linebreaks
|
||||||
|
* linebreaksbr
|
||||||
|
* linenumbers
|
||||||
|
* ljust
|
||||||
|
* lower
|
||||||
|
* make_list
|
||||||
|
* phone2numeric
|
||||||
|
* pluralize
|
||||||
|
* random
|
||||||
|
* removetags
|
||||||
|
* rjust
|
||||||
|
* slice
|
||||||
|
* stringformat
|
||||||
|
* striptags
|
||||||
|
* time
|
||||||
|
* title
|
||||||
|
* truncatechars
|
||||||
|
* truncatechars_html
|
||||||
|
* truncatewords
|
||||||
|
* truncatewords_html
|
||||||
|
* upper
|
||||||
|
* urlencode
|
||||||
|
* urlize
|
||||||
|
* urlizetrunc
|
||||||
|
* wordcount
|
||||||
|
* wordwrap
|
||||||
|
* yesno
|
||||||
|
|
||||||
|
* filesizeformat*
|
||||||
|
* slugify*
|
||||||
|
* truncatesentences*
|
||||||
|
* truncatesentences_html*
|
||||||
|
* markdown*
|
||||||
|
* intcomma*
|
||||||
|
* ordinal*
|
||||||
|
* naturalday*
|
||||||
|
* timesince*
|
||||||
|
* timeuntil*
|
||||||
|
* naturaltime*
|
||||||
|
|
||||||
|
Filters marked with * are available through [pongo2-addons](https://github.com/flosch/pongo2-addons).
|
1
Godeps/_workspace/src/github.com/flosch/pongo2/docs/index.md
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/flosch/pongo2/docs/index.md
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(Stub, TBA)
|
1
Godeps/_workspace/src/github.com/flosch/pongo2/docs/macros.md
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/flosch/pongo2/docs/macros.md
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(Stub, TBA)
|
31
Godeps/_workspace/src/github.com/flosch/pongo2/docs/tags.md
generated
vendored
Normal file
31
Godeps/_workspace/src/github.com/flosch/pongo2/docs/tags.md
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
TODO:
|
||||||
|
|
||||||
|
* What are tags?
|
||||||
|
* List+explain all existing tags (pongo2 + pongo2-addons)
|
||||||
|
|
||||||
|
Implemented tags so far which needs documentation:
|
||||||
|
|
||||||
|
* autoescape
|
||||||
|
* block
|
||||||
|
* comment
|
||||||
|
* cycle
|
||||||
|
* extends
|
||||||
|
* filter
|
||||||
|
* firstof
|
||||||
|
* for
|
||||||
|
* if
|
||||||
|
* ifchanged
|
||||||
|
* ifequal
|
||||||
|
* ifnotequal
|
||||||
|
* import
|
||||||
|
* include
|
||||||
|
* lorem
|
||||||
|
* macro
|
||||||
|
* now
|
||||||
|
* set
|
||||||
|
* spaceless
|
||||||
|
* ssi
|
||||||
|
* templatetag
|
||||||
|
* verbatim
|
||||||
|
* widthratio
|
||||||
|
* with
|
1
Godeps/_workspace/src/github.com/flosch/pongo2/docs/template_sets.md
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/flosch/pongo2/docs/template_sets.md
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(Stub, TBA)
|
0
Godeps/_workspace/src/github.com/flosch/pongo2/docs/write_filters.md
generated
vendored
Normal file
0
Godeps/_workspace/src/github.com/flosch/pongo2/docs/write_filters.md
generated
vendored
Normal file
0
Godeps/_workspace/src/github.com/flosch/pongo2/docs/write_tags.md
generated
vendored
Normal file
0
Godeps/_workspace/src/github.com/flosch/pongo2/docs/write_tags.md
generated
vendored
Normal file
91
Godeps/_workspace/src/github.com/flosch/pongo2/error.go
generated
vendored
Normal file
91
Godeps/_workspace/src/github.com/flosch/pongo2/error.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) updateFromTokenIfNeeded(template *Template, t *Token) *Error {
|
||||||
|
if e.Template == nil {
|
||||||
|
e.Template = template
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Token == nil {
|
||||||
|
e.Token = t
|
||||||
|
if e.Line <= 0 {
|
||||||
|
e.Line = t.Line
|
||||||
|
e.Column = t.Col
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a nice formatted error string.
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
s := "[Error"
|
||||||
|
if e.Sender != "" {
|
||||||
|
s += " (where: " + e.Sender + ")"
|
||||||
|
}
|
||||||
|
if e.Filename != "" {
|
||||||
|
s += " in " + e.Filename
|
||||||
|
}
|
||||||
|
if e.Line > 0 {
|
||||||
|
s += fmt.Sprintf(" | Line %d Col %d", e.Line, e.Column)
|
||||||
|
if e.Token != nil {
|
||||||
|
s += fmt.Sprintf(" near '%s'", e.Token.Val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += "] "
|
||||||
|
s += e.ErrorMsg
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawLine returns the affected line from the original template, if available.
|
||||||
|
func (e *Error) RawLine() (line string, available bool) {
|
||||||
|
if e.Line <= 0 || e.Filename == "<string>" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := e.Filename
|
||||||
|
if e.Template != nil {
|
||||||
|
filename = e.Template.set.resolveFilename(e.Template, e.Filename)
|
||||||
|
}
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := file.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
l := 0
|
||||||
|
for scanner.Scan() {
|
||||||
|
l++
|
||||||
|
if l == e.Line {
|
||||||
|
return scanner.Text(), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
133
Godeps/_workspace/src/github.com/flosch/pongo2/filters.go
generated
vendored
Normal file
133
Godeps/_workspace/src/github.com/flosch/pongo2/filters.go
generated
vendored
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FilterFunction func(in *Value, param *Value) (out *Value, err *Error)
|
||||||
|
|
||||||
|
var filters map[string]FilterFunction
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
filters = make(map[string]FilterFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
|
filters[name] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
|
filters[name] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure param is a *Value
|
||||||
|
if param == nil {
|
||||||
|
param = AsValue(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(value, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
type filterCall struct {
|
||||||
|
token *Token
|
||||||
|
|
||||||
|
name string
|
||||||
|
parameter IEvaluator
|
||||||
|
|
||||||
|
filterFunc FilterFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fc *filterCall) Execute(v *Value, ctx *ExecutionContext) (*Value, *Error) {
|
||||||
|
var param *Value
|
||||||
|
var err *Error
|
||||||
|
|
||||||
|
if fc.parameter != nil {
|
||||||
|
param, err = fc.parameter.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
param = AsValue(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredValue, err := fc.filterFunc(v, param)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err.updateFromTokenIfNeeded(ctx.template, fc.token)
|
||||||
|
}
|
||||||
|
return filteredValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter = IDENT | IDENT ":" FilterArg | IDENT "|" Filter
|
||||||
|
func (p *Parser) parseFilter() (*filterCall, *Error) {
|
||||||
|
identToken := p.MatchType(TokenIdentifier)
|
||||||
|
|
||||||
|
// Check filter ident
|
||||||
|
if identToken == nil {
|
||||||
|
return nil, p.Error("Filter name must be an identifier.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &filterCall{
|
||||||
|
token: identToken,
|
||||||
|
name: identToken.Val,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the appropriate filter function and bind it
|
||||||
|
filterFn, exists := filters[identToken.Val]
|
||||||
|
if !exists {
|
||||||
|
return nil, p.Error(fmt.Sprintf("Filter '%s' does not exist.", identToken.Val), identToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.filterFunc = filterFn
|
||||||
|
|
||||||
|
// Check for filter-argument (2 tokens needed: ':' ARG)
|
||||||
|
if p.Match(TokenSymbol, ":") != nil {
|
||||||
|
if p.Peek(TokenSymbol, "}}") != nil {
|
||||||
|
return nil, p.Error("Filter parameter required after ':'.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get filter argument expression
|
||||||
|
v, err := p.parseVariableOrLiteral()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
filter.parameter = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter, nil
|
||||||
|
}
|
901
Godeps/_workspace/src/github.com/flosch/pongo2/filters_builtin.go
generated
vendored
Normal file
901
Godeps/_workspace/src/github.com/flosch/pongo2/filters_builtin.go
generated
vendored
Normal file
|
@ -0,0 +1,901 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
/* Filters that are provided through github.com/flosch/pongo2-addons:
|
||||||
|
------------------------------------------------------------------
|
||||||
|
|
||||||
|
filesizeformat
|
||||||
|
slugify
|
||||||
|
timesince
|
||||||
|
timeuntil
|
||||||
|
|
||||||
|
Filters that won't be added:
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
get_static_prefix (reason: web-framework specific)
|
||||||
|
pprint (reason: python-specific)
|
||||||
|
static (reason: web-framework specific)
|
||||||
|
|
||||||
|
Reconsideration (not implemented yet):
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
force_escape (reason: not yet needed since this is the behaviour of pongo2's escape filter)
|
||||||
|
safeseq (reason: same reason as `force_escape`)
|
||||||
|
unordered_list (python-specific; not sure whether needed or not)
|
||||||
|
dictsort (python-specific; maybe one could add a filter to sort a list of structs by a specific field name)
|
||||||
|
dictsortreversed (see dictsort)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().Unix())
|
||||||
|
|
||||||
|
RegisterFilter("escape", filterEscape)
|
||||||
|
RegisterFilter("safe", filterSafe)
|
||||||
|
RegisterFilter("escapejs", filterEscapejs)
|
||||||
|
|
||||||
|
RegisterFilter("add", filterAdd)
|
||||||
|
RegisterFilter("addslashes", filterAddslashes)
|
||||||
|
RegisterFilter("capfirst", filterCapfirst)
|
||||||
|
RegisterFilter("center", filterCenter)
|
||||||
|
RegisterFilter("cut", filterCut)
|
||||||
|
RegisterFilter("date", filterDate)
|
||||||
|
RegisterFilter("default", filterDefault)
|
||||||
|
RegisterFilter("default_if_none", filterDefaultIfNone)
|
||||||
|
RegisterFilter("divisibleby", filterDivisibleby)
|
||||||
|
RegisterFilter("first", filterFirst)
|
||||||
|
RegisterFilter("floatformat", filterFloatformat)
|
||||||
|
RegisterFilter("get_digit", filterGetdigit)
|
||||||
|
RegisterFilter("iriencode", filterIriencode)
|
||||||
|
RegisterFilter("join", filterJoin)
|
||||||
|
RegisterFilter("last", filterLast)
|
||||||
|
RegisterFilter("length", filterLength)
|
||||||
|
RegisterFilter("length_is", filterLengthis)
|
||||||
|
RegisterFilter("linebreaks", filterLinebreaks)
|
||||||
|
RegisterFilter("linebreaksbr", filterLinebreaksbr)
|
||||||
|
RegisterFilter("linenumbers", filterLinenumbers)
|
||||||
|
RegisterFilter("ljust", filterLjust)
|
||||||
|
RegisterFilter("lower", filterLower)
|
||||||
|
RegisterFilter("make_list", filterMakelist)
|
||||||
|
RegisterFilter("phone2numeric", filterPhone2numeric)
|
||||||
|
RegisterFilter("pluralize", filterPluralize)
|
||||||
|
RegisterFilter("random", filterRandom)
|
||||||
|
RegisterFilter("removetags", filterRemovetags)
|
||||||
|
RegisterFilter("rjust", filterRjust)
|
||||||
|
RegisterFilter("slice", filterSlice)
|
||||||
|
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("truncatewords", filterTruncatewords)
|
||||||
|
RegisterFilter("truncatewords_html", filterTruncatewordsHTML)
|
||||||
|
RegisterFilter("upper", filterUpper)
|
||||||
|
RegisterFilter("urlencode", filterUrlencode)
|
||||||
|
RegisterFilter("urlize", filterUrlize)
|
||||||
|
RegisterFilter("urlizetrunc", filterUrlizetrunc)
|
||||||
|
RegisterFilter("wordcount", filterWordcount)
|
||||||
|
RegisterFilter("wordwrap", filterWordwrap)
|
||||||
|
RegisterFilter("yesno", filterYesno)
|
||||||
|
|
||||||
|
RegisterFilter("float", filterFloat) // pongo-specific
|
||||||
|
RegisterFilter("integer", filterInteger) // pongo-specific
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterTruncatecharsHelper(s string, newLen int) string {
|
||||||
|
runes := []rune(s)
|
||||||
|
if newLen < len(runes) {
|
||||||
|
if newLen >= 3 {
|
||||||
|
return fmt.Sprintf("%s...", string(runes[:newLen-3]))
|
||||||
|
}
|
||||||
|
// Not enough space for the ellipsis
|
||||||
|
return string(runes[:newLen])
|
||||||
|
}
|
||||||
|
return string(runes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterTruncateHTMLHelper(value string, newOutput *bytes.Buffer, cond func() bool, fn func(c rune, s int, idx int) int, finalize func()) {
|
||||||
|
vLen := len(value)
|
||||||
|
var tagStack []string
|
||||||
|
idx := 0
|
||||||
|
|
||||||
|
for idx < vLen && !cond() {
|
||||||
|
c, s := utf8.DecodeRuneInString(value[idx:])
|
||||||
|
if c == utf8.RuneError {
|
||||||
|
idx += s
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == '<' {
|
||||||
|
newOutput.WriteRune(c)
|
||||||
|
idx += s // consume "<"
|
||||||
|
|
||||||
|
if idx+1 < vLen {
|
||||||
|
if value[idx] == '/' {
|
||||||
|
// Close tag
|
||||||
|
|
||||||
|
newOutput.WriteString("/")
|
||||||
|
|
||||||
|
tag := ""
|
||||||
|
idx++ // consume "/"
|
||||||
|
|
||||||
|
for idx < vLen {
|
||||||
|
c2, size2 := utf8.DecodeRuneInString(value[idx:])
|
||||||
|
if c2 == utf8.RuneError {
|
||||||
|
idx += size2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of tag found
|
||||||
|
if c2 == '>' {
|
||||||
|
idx++ // consume ">"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
tag += string(c2)
|
||||||
|
idx += size2
|
||||||
|
}
|
||||||
|
|
||||||
|
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(tagStack) - 1; i >= 0; i-- {
|
||||||
|
if tagStack[i] == tag {
|
||||||
|
// Found the tag
|
||||||
|
tagStack[i] = tagStack[len(tagStack)-1]
|
||||||
|
tagStack = tagStack[:len(tagStack)-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newOutput.WriteString(tag)
|
||||||
|
newOutput.WriteString(">")
|
||||||
|
} else {
|
||||||
|
// Open tag
|
||||||
|
|
||||||
|
tag := ""
|
||||||
|
|
||||||
|
params := false
|
||||||
|
for idx < vLen {
|
||||||
|
c2, size2 := utf8.DecodeRuneInString(value[idx:])
|
||||||
|
if c2 == utf8.RuneError {
|
||||||
|
idx += size2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newOutput.WriteRune(c2)
|
||||||
|
|
||||||
|
// End of tag found
|
||||||
|
if c2 == '>' {
|
||||||
|
idx++ // consume ">"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !params {
|
||||||
|
if c2 == ' ' {
|
||||||
|
params = true
|
||||||
|
} else {
|
||||||
|
tag += string(c2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += size2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tag to stack
|
||||||
|
tagStack = append(tagStack, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
idx = fn(c, s, idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize()
|
||||||
|
|
||||||
|
for i := len(tagStack) - 1; i >= 0; i-- {
|
||||||
|
tag := tagStack[i]
|
||||||
|
// Close everything from the regular tag stack
|
||||||
|
newOutput.WriteString(fmt.Sprintf("</%s>", tag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterTruncatechars(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
s := in.String()
|
||||||
|
newLen := param.Integer()
|
||||||
|
return AsValue(filterTruncatecharsHelper(s, newLen)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterTruncatecharsHTML(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
value := in.String()
|
||||||
|
newLen := max(param.Integer()-3, 0)
|
||||||
|
|
||||||
|
newOutput := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
textcounter := 0
|
||||||
|
|
||||||
|
filterTruncateHTMLHelper(value, newOutput, func() bool {
|
||||||
|
return textcounter >= newLen
|
||||||
|
}, func(c rune, s int, idx int) int {
|
||||||
|
textcounter++
|
||||||
|
newOutput.WriteRune(c)
|
||||||
|
|
||||||
|
return idx + s
|
||||||
|
}, func() {
|
||||||
|
if textcounter >= newLen && textcounter < len(value) {
|
||||||
|
newOutput.WriteString("...")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return AsSafeValue(newOutput.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterTruncatewords(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
words := strings.Fields(in.String())
|
||||||
|
n := param.Integer()
|
||||||
|
if n <= 0 {
|
||||||
|
return AsValue(""), nil
|
||||||
|
}
|
||||||
|
nlen := min(len(words), n)
|
||||||
|
out := make([]string, 0, nlen)
|
||||||
|
for i := 0; i < nlen; i++ {
|
||||||
|
out = append(out, words[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < len(words) {
|
||||||
|
out = append(out, "...")
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsValue(strings.Join(out, " ")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterTruncatewordsHTML(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
value := in.String()
|
||||||
|
newLen := max(param.Integer(), 0)
|
||||||
|
|
||||||
|
newOutput := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
wordcounter := 0
|
||||||
|
|
||||||
|
filterTruncateHTMLHelper(value, newOutput, func() bool {
|
||||||
|
return wordcounter >= newLen
|
||||||
|
}, func(_ rune, _ int, idx int) int {
|
||||||
|
// Get next word
|
||||||
|
wordFound := false
|
||||||
|
|
||||||
|
for idx < len(value) {
|
||||||
|
c2, size2 := utf8.DecodeRuneInString(value[idx:])
|
||||||
|
if c2 == utf8.RuneError {
|
||||||
|
idx += size2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c2 == '<' {
|
||||||
|
// HTML tag start, don't consume it
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
|
||||||
|
newOutput.WriteRune(c2)
|
||||||
|
idx += size2
|
||||||
|
|
||||||
|
if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' {
|
||||||
|
// Word ends here, stop capturing it now
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
wordFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if wordFound {
|
||||||
|
wordcounter++
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx
|
||||||
|
}, func() {
|
||||||
|
if wordcounter >= newLen {
|
||||||
|
newOutput.WriteString("...")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return AsSafeValue(newOutput.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterEscape(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
output := strings.Replace(in.String(), "&", "&", -1)
|
||||||
|
output = strings.Replace(output, ">", ">", -1)
|
||||||
|
output = strings.Replace(output, "<", "<", -1)
|
||||||
|
output = strings.Replace(output, "\"", """, -1)
|
||||||
|
output = strings.Replace(output, "'", "'", -1)
|
||||||
|
return AsValue(output), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterSafe(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return in, nil // nothing to do here, just to keep track of the safe application
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterEscapejs(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
sin := in.String()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
|
||||||
|
idx := 0
|
||||||
|
for idx < len(sin) {
|
||||||
|
c, size := utf8.DecodeRuneInString(sin[idx:])
|
||||||
|
if c == utf8.RuneError {
|
||||||
|
idx += size
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == '\\' {
|
||||||
|
// Escape seq?
|
||||||
|
if idx+1 < len(sin) {
|
||||||
|
switch sin[idx+1] {
|
||||||
|
case 'r':
|
||||||
|
b.WriteString(fmt.Sprintf(`\u%04X`, '\r'))
|
||||||
|
idx += 2
|
||||||
|
continue
|
||||||
|
case 'n':
|
||||||
|
b.WriteString(fmt.Sprintf(`\u%04X`, '\n'))
|
||||||
|
idx += 2
|
||||||
|
continue
|
||||||
|
/*case '\'':
|
||||||
|
b.WriteString(fmt.Sprintf(`\u%04X`, '\''))
|
||||||
|
idx += 2
|
||||||
|
continue
|
||||||
|
case '"':
|
||||||
|
b.WriteString(fmt.Sprintf(`\u%04X`, '"'))
|
||||||
|
idx += 2
|
||||||
|
continue*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == ' ' || c == '/' {
|
||||||
|
b.WriteRune(c)
|
||||||
|
} else {
|
||||||
|
b.WriteString(fmt.Sprintf(`\u%04X`, c))
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += size
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsValue(b.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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
|
||||||
|
return AsValue(in.String() + param.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterAddslashes(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
output := strings.Replace(in.String(), "\\", "\\\\", -1)
|
||||||
|
output = strings.Replace(output, "\"", "\\\"", -1)
|
||||||
|
output = strings.Replace(output, "'", "\\'", -1)
|
||||||
|
return AsValue(output), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterCut(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(strings.Replace(in.String(), param.String(), "", -1)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterLength(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(in.Len()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterLengthis(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(in.Len() == param.Integer()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterDefault(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
if !in.IsTrue() {
|
||||||
|
return param, nil
|
||||||
|
}
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterDefaultIfNone(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
if in.IsNil() {
|
||||||
|
return param, nil
|
||||||
|
}
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterDivisibleby(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
if param.Integer() == 0 {
|
||||||
|
return AsValue(false), nil
|
||||||
|
}
|
||||||
|
return AsValue(in.Integer()%param.Integer() == 0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterFirst(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
if in.CanSlice() && in.Len() > 0 {
|
||||||
|
return in.Index(0), nil
|
||||||
|
}
|
||||||
|
return AsValue(""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterFloatformat(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
val := in.Float()
|
||||||
|
|
||||||
|
decimals := -1
|
||||||
|
if !param.IsNil() {
|
||||||
|
// Any argument provided?
|
||||||
|
decimals = param.Integer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the argument is not a number (e. g. empty), the default
|
||||||
|
// behaviour is trim the result
|
||||||
|
trim := !param.IsNumber()
|
||||||
|
|
||||||
|
if decimals <= 0 {
|
||||||
|
// argument is negative or zero, so we
|
||||||
|
// want the output being trimmed
|
||||||
|
decimals = -decimals
|
||||||
|
trim = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if trim {
|
||||||
|
// Remove zeroes
|
||||||
|
if float64(int(val)) == val {
|
||||||
|
return AsValue(in.Integer()), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsValue(strconv.FormatFloat(val, 'f', decimals, 64)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterGetdigit(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
i := param.Integer()
|
||||||
|
l := len(in.String()) // do NOT use in.Len() here!
|
||||||
|
if i <= 0 || i > l {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
return AsValue(in.String()[l-i] - 48), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterIRIChars = "/#%[]=:;$&()+,!?*@'~"
|
||||||
|
|
||||||
|
func filterIriencode(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
|
||||||
|
sin := in.String()
|
||||||
|
for _, r := range sin {
|
||||||
|
if strings.IndexRune(filterIRIChars, r) >= 0 {
|
||||||
|
b.WriteRune(r)
|
||||||
|
} else {
|
||||||
|
b.WriteString(url.QueryEscape(string(r)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsValue(b.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterJoin(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
if !in.CanSlice() {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
sep := param.String()
|
||||||
|
sl := make([]string, 0, in.Len())
|
||||||
|
for i := 0; i < in.Len(); i++ {
|
||||||
|
sl = append(sl, in.Index(i).String())
|
||||||
|
}
|
||||||
|
return AsValue(strings.Join(sl, sep)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterLast(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
if in.CanSlice() && in.Len() > 0 {
|
||||||
|
return in.Index(in.Len() - 1), nil
|
||||||
|
}
|
||||||
|
return AsValue(""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterUpper(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(strings.ToUpper(in.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterLower(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(strings.ToLower(in.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterMakelist(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
s := in.String()
|
||||||
|
result := make([]string, 0, len(s))
|
||||||
|
for _, c := range s {
|
||||||
|
result = append(result, string(c))
|
||||||
|
}
|
||||||
|
return AsValue(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterCapfirst(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
if in.Len() <= 0 {
|
||||||
|
return AsValue(""), nil
|
||||||
|
}
|
||||||
|
t := in.String()
|
||||||
|
r, size := utf8.DecodeRuneInString(t)
|
||||||
|
return AsValue(strings.ToUpper(string(r)) + t[size:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterCenter(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
width := param.Integer()
|
||||||
|
slen := in.Len()
|
||||||
|
if width <= slen {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
spaces := width - slen
|
||||||
|
left := spaces/2 + spaces%2
|
||||||
|
right := spaces / 2
|
||||||
|
|
||||||
|
return AsValue(fmt.Sprintf("%s%s%s", strings.Repeat(" ", left),
|
||||||
|
in.String(), strings.Repeat(" ", right))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterDate(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
t, isTime := in.Interface().(time.Time)
|
||||||
|
if !isTime {
|
||||||
|
return nil, &Error{
|
||||||
|
Sender: "filter:date",
|
||||||
|
ErrorMsg: "Filter input argument must be of type 'time.Time'.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AsValue(t.Format(param.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterFloat(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(in.Float()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterInteger(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(in.Integer()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterLinebreaks(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
if in.Len() == 0 {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
|
||||||
|
// Newline = <br />
|
||||||
|
// Double newline = <p>...</p>
|
||||||
|
lines := strings.Split(in.String(), "\n")
|
||||||
|
lenlines := len(lines)
|
||||||
|
|
||||||
|
opened := false
|
||||||
|
|
||||||
|
for idx, line := range lines {
|
||||||
|
|
||||||
|
if !opened {
|
||||||
|
b.WriteString("<p>")
|
||||||
|
opened = true
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString(line)
|
||||||
|
|
||||||
|
if idx < lenlines-1 && strings.TrimSpace(lines[idx]) != "" {
|
||||||
|
// We've not reached the end
|
||||||
|
if strings.TrimSpace(lines[idx+1]) == "" {
|
||||||
|
// Next line is empty
|
||||||
|
if opened {
|
||||||
|
b.WriteString("</p>")
|
||||||
|
opened = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.WriteString("<br />")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opened {
|
||||||
|
b.WriteString("</p>")
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsValue(b.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterLinebreaksbr(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(strings.Replace(in.String(), "\n", "<br />", -1)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterLinenumbers(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
lines := strings.Split(in.String(), "\n")
|
||||||
|
output := make([]string, 0, len(lines))
|
||||||
|
for idx, line := range lines {
|
||||||
|
output = append(output, fmt.Sprintf("%d. %s", idx+1, line))
|
||||||
|
}
|
||||||
|
return AsValue(strings.Join(output, "\n")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterLjust(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
times := param.Integer() - in.Len()
|
||||||
|
if times < 0 {
|
||||||
|
times = 0
|
||||||
|
}
|
||||||
|
return AsValue(fmt.Sprintf("%s%s", in.String(), strings.Repeat(" ", times))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterUrlencode(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(url.QueryEscape(in.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This regexp could do some work
|
||||||
|
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 {
|
||||||
|
sout := filterUrlizeURLRegexp.ReplaceAllStringFunc(input, func(raw_url string) string {
|
||||||
|
var prefix string
|
||||||
|
var suffix string
|
||||||
|
if strings.HasPrefix(raw_url, " ") {
|
||||||
|
prefix = " "
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(raw_url, " ") {
|
||||||
|
suffix = " "
|
||||||
|
}
|
||||||
|
|
||||||
|
raw_url = strings.TrimSpace(raw_url)
|
||||||
|
|
||||||
|
t, err := ApplyFilter("iriencode", AsValue(raw_url), nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
url := t.String()
|
||||||
|
|
||||||
|
if !strings.HasPrefix(url, "http") {
|
||||||
|
url = fmt.Sprintf("http://%s", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
title := raw_url
|
||||||
|
|
||||||
|
if trunc > 3 && len(title) > trunc {
|
||||||
|
title = fmt.Sprintf("%s...", title[:trunc-3])
|
||||||
|
}
|
||||||
|
|
||||||
|
if autoescape {
|
||||||
|
t, err := ApplyFilter("escape", AsValue(title), nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
title = t.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`%s<a href="%s" rel="nofollow">%s</a>%s`, prefix, url, title, suffix)
|
||||||
|
})
|
||||||
|
|
||||||
|
sout = filterUrlizeEmailRegexp.ReplaceAllStringFunc(sout, func(mail string) string {
|
||||||
|
|
||||||
|
title := mail
|
||||||
|
|
||||||
|
if trunc > 3 && len(title) > trunc {
|
||||||
|
title = fmt.Sprintf("%s...", title[:trunc-3])
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`<a href="mailto:%s">%s</a>`, mail, title)
|
||||||
|
})
|
||||||
|
|
||||||
|
return sout
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterUrlize(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
autoescape := true
|
||||||
|
if param.IsBool() {
|
||||||
|
autoescape = param.Bool()
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsValue(filterUrlizeHelper(in.String(), autoescape, -1)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterUrlizetrunc(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(filterUrlizeHelper(in.String(), true, param.Integer())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterStringformat(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(fmt.Sprintf(param.String(), in.Interface())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var reStriptags = regexp.MustCompile("<[^>]*?>")
|
||||||
|
|
||||||
|
func filterStriptags(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
s := in.String()
|
||||||
|
|
||||||
|
// Strip all tags
|
||||||
|
s = reStriptags.ReplaceAllString(s, "")
|
||||||
|
|
||||||
|
return AsValue(strings.TrimSpace(s)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Phoneword
|
||||||
|
var filterPhone2numericMap = map[string]string{
|
||||||
|
"a": "2", "b": "2", "c": "2", "d": "3", "e": "3", "f": "3", "g": "4", "h": "4", "i": "4", "j": "5", "k": "5",
|
||||||
|
"l": "5", "m": "6", "n": "6", "o": "6", "p": "7", "q": "7", "r": "7", "s": "7", "t": "8", "u": "8", "v": "8",
|
||||||
|
"w": "9", "x": "9", "y": "9", "z": "9",
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterPhone2numeric(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
sin := in.String()
|
||||||
|
for k, v := range filterPhone2numericMap {
|
||||||
|
sin = strings.Replace(sin, k, v, -1)
|
||||||
|
sin = strings.Replace(sin, strings.ToUpper(k), v, -1)
|
||||||
|
}
|
||||||
|
return AsValue(sin), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterPluralize(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
if in.IsNumber() {
|
||||||
|
// Works only on numbers
|
||||||
|
if param.Len() > 0 {
|
||||||
|
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'.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(endings) == 1 {
|
||||||
|
// 1 argument
|
||||||
|
if in.Integer() != 1 {
|
||||||
|
return AsValue(endings[0]), nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if in.Integer() != 1 {
|
||||||
|
// 2 arguments
|
||||||
|
return AsValue(endings[1]), nil
|
||||||
|
}
|
||||||
|
return AsValue(endings[0]), nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if in.Integer() != 1 {
|
||||||
|
// return default 's'
|
||||||
|
return AsValue("s"), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsValue(""), nil
|
||||||
|
}
|
||||||
|
return nil, &Error{
|
||||||
|
Sender: "filter:pluralize",
|
||||||
|
ErrorMsg: "Filter 'pluralize' does only work on numbers.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterRandom(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
if !in.CanSlice() || in.Len() <= 0 {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
i := rand.Intn(in.Len())
|
||||||
|
return in.Index(i), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterRemovetags(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
s := in.String()
|
||||||
|
tags := strings.Split(param.String(), ",")
|
||||||
|
|
||||||
|
// Strip only specific tags
|
||||||
|
for _, tag := range tags {
|
||||||
|
re := regexp.MustCompile(fmt.Sprintf("</?%s/?>", tag))
|
||||||
|
s = re.ReplaceAllString(s, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsValue(strings.TrimSpace(s)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterRjust(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(fmt.Sprintf(fmt.Sprintf("%%%ds", param.Integer()), in.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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]",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !in.CanSlice() {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
from := AsValue(comp[0]).Integer()
|
||||||
|
to := in.Len()
|
||||||
|
|
||||||
|
if from > to {
|
||||||
|
from = to
|
||||||
|
}
|
||||||
|
|
||||||
|
vto := AsValue(comp[1]).Integer()
|
||||||
|
if vto >= from && vto <= in.Len() {
|
||||||
|
to = vto
|
||||||
|
}
|
||||||
|
|
||||||
|
return in.Slice(from, to), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterTitle(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
if !in.IsString() {
|
||||||
|
return AsValue(""), nil
|
||||||
|
}
|
||||||
|
return AsValue(strings.Title(strings.ToLower(in.String()))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterWordcount(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
return AsValue(len(strings.Fields(in.String()))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterWordwrap(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
words := strings.Fields(in.String())
|
||||||
|
wordsLen := len(words)
|
||||||
|
wrapAt := param.Integer()
|
||||||
|
if wrapAt <= 0 {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
linecount := wordsLen/wrapAt + wordsLen%wrapAt
|
||||||
|
lines := make([]string, 0, linecount)
|
||||||
|
for i := 0; i < linecount; i++ {
|
||||||
|
lines = append(lines, strings.Join(words[wrapAt*i:min(wrapAt*(i+1), wordsLen)], " "))
|
||||||
|
}
|
||||||
|
return AsValue(strings.Join(lines, "\n")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterYesno(in *Value, param *Value) (*Value, *Error) {
|
||||||
|
choices := map[int]string{
|
||||||
|
0: "yes",
|
||||||
|
1: "no",
|
||||||
|
2: "maybe",
|
||||||
|
}
|
||||||
|
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').", paramString),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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').", paramString),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map to the options now
|
||||||
|
choices[0] = customChoices[0]
|
||||||
|
choices[1] = customChoices[1]
|
||||||
|
if len(customChoices) == 3 {
|
||||||
|
choices[2] = customChoices[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybe
|
||||||
|
if in.IsNil() {
|
||||||
|
return AsValue(choices[2]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// yes
|
||||||
|
if in.IsTrue() {
|
||||||
|
return AsValue(choices[0]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// no
|
||||||
|
return AsValue(choices[1]), nil
|
||||||
|
}
|
15
Godeps/_workspace/src/github.com/flosch/pongo2/helpers.go
generated
vendored
Normal file
15
Godeps/_workspace/src/github.com/flosch/pongo2/helpers.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
func max(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
421
Godeps/_workspace/src/github.com/flosch/pongo2/lexer.go
generated
vendored
Normal file
421
Godeps/_workspace/src/github.com/flosch/pongo2/lexer.go
generated
vendored
Normal file
|
@ -0,0 +1,421 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenError = iota
|
||||||
|
EOF
|
||||||
|
|
||||||
|
TokenHTML
|
||||||
|
|
||||||
|
TokenKeyword
|
||||||
|
TokenIdentifier
|
||||||
|
TokenString
|
||||||
|
TokenNumber
|
||||||
|
TokenSymbol
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tokenSpaceChars = " \n\r\t"
|
||||||
|
tokenIdentifierChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
|
||||||
|
tokenIdentifierCharsWithDigits = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"
|
||||||
|
tokenDigits = "0123456789"
|
||||||
|
|
||||||
|
// Available symbols in pongo2 (within filters/tag)
|
||||||
|
TokenSymbols = []string{
|
||||||
|
// 3-Char symbols
|
||||||
|
|
||||||
|
// 2-Char symbols
|
||||||
|
"==", ">=", "<=", "&&", "||", "{{", "}}", "{%", "%}", "!=", "<>",
|
||||||
|
|
||||||
|
// 1-Char symbol
|
||||||
|
"(", ")", "+", "-", "*", "<", ">", "/", "^", ",", ".", "!", "|", ":", "=", "%",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Available keywords in pongo2
|
||||||
|
TokenKeywords = []string{"in", "and", "or", "not", "true", "false", "as", "export"}
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenType int
|
||||||
|
type Token struct {
|
||||||
|
Filename string
|
||||||
|
Typ TokenType
|
||||||
|
Val string
|
||||||
|
Line int
|
||||||
|
Col int
|
||||||
|
}
|
||||||
|
|
||||||
|
type lexerStateFn func() lexerStateFn
|
||||||
|
type lexer struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
start int // start pos of the item
|
||||||
|
pos int // current pos
|
||||||
|
width int // width of last rune
|
||||||
|
tokens []*Token
|
||||||
|
errored bool
|
||||||
|
startline int
|
||||||
|
startcol int
|
||||||
|
line int
|
||||||
|
col int
|
||||||
|
|
||||||
|
inVerbatim bool
|
||||||
|
verbatimName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) String() string {
|
||||||
|
val := t.Val
|
||||||
|
if len(val) > 1000 {
|
||||||
|
val = fmt.Sprintf("%s...%s", val[:10], val[len(val)-5:len(val)])
|
||||||
|
}
|
||||||
|
|
||||||
|
typ := ""
|
||||||
|
switch t.Typ {
|
||||||
|
case TokenHTML:
|
||||||
|
typ = "HTML"
|
||||||
|
case TokenError:
|
||||||
|
typ = "Error"
|
||||||
|
case TokenIdentifier:
|
||||||
|
typ = "Identifier"
|
||||||
|
case TokenKeyword:
|
||||||
|
typ = "Keyword"
|
||||||
|
case TokenNumber:
|
||||||
|
typ = "Number"
|
||||||
|
case TokenString:
|
||||||
|
typ = "String"
|
||||||
|
case TokenSymbol:
|
||||||
|
typ = "Symbol"
|
||||||
|
default:
|
||||||
|
typ = "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("<Token Typ=%s (%d) Val='%s' Line=%d Col=%d>",
|
||||||
|
typ, t.Typ, val, t.Line, t.Col)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lex(name string, input string) ([]*Token, *Error) {
|
||||||
|
l := &lexer{
|
||||||
|
name: name,
|
||||||
|
input: input,
|
||||||
|
tokens: make([]*Token, 0, 100),
|
||||||
|
line: 1,
|
||||||
|
col: 1,
|
||||||
|
startline: 1,
|
||||||
|
startcol: 1,
|
||||||
|
}
|
||||||
|
l.run()
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l.tokens, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) value() string {
|
||||||
|
return l.input[l.start:l.pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) length() int {
|
||||||
|
return l.pos - l.start
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) emit(t TokenType) {
|
||||||
|
tok := &Token{
|
||||||
|
Filename: l.name,
|
||||||
|
Typ: t,
|
||||||
|
Val: l.value(),
|
||||||
|
Line: l.startline,
|
||||||
|
Col: l.startcol,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t == TokenString {
|
||||||
|
// Escape sequence \" in strings
|
||||||
|
tok.Val = strings.Replace(tok.Val, `\"`, `"`, -1)
|
||||||
|
tok.Val = strings.Replace(tok.Val, `\\`, `\`, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.tokens = append(l.tokens, tok)
|
||||||
|
l.start = l.pos
|
||||||
|
l.startline = l.line
|
||||||
|
l.startcol = l.col
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) next() rune {
|
||||||
|
if l.pos >= len(l.input) {
|
||||||
|
l.width = 0
|
||||||
|
return EOF
|
||||||
|
}
|
||||||
|
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||||
|
l.width = w
|
||||||
|
l.pos += l.width
|
||||||
|
l.col += l.width
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) backup() {
|
||||||
|
l.pos -= l.width
|
||||||
|
l.col -= l.width
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) peek() rune {
|
||||||
|
r := l.next()
|
||||||
|
l.backup()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) ignore() {
|
||||||
|
l.start = l.pos
|
||||||
|
l.startline = l.line
|
||||||
|
l.startcol = l.col
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) accept(what string) bool {
|
||||||
|
if strings.IndexRune(what, l.next()) >= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) acceptRun(what string) {
|
||||||
|
for strings.IndexRune(what, l.next()) >= 0 {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) errorf(format string, args ...interface{}) lexerStateFn {
|
||||||
|
t := &Token{
|
||||||
|
Filename: l.name,
|
||||||
|
Typ: TokenError,
|
||||||
|
Val: fmt.Sprintf(format, args...),
|
||||||
|
Line: l.startline,
|
||||||
|
Col: l.startcol,
|
||||||
|
}
|
||||||
|
l.tokens = append(l.tokens, t)
|
||||||
|
l.errored = true
|
||||||
|
l.startline = l.line
|
||||||
|
l.startcol = l.col
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) eof() bool {
|
||||||
|
return l.start >= len(l.input)-1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) run() {
|
||||||
|
for {
|
||||||
|
// TODO: Support verbatim tag names
|
||||||
|
// https://docs.djangoproject.com/en/dev/ref/templates/builtins/#verbatim
|
||||||
|
if l.inVerbatim {
|
||||||
|
name := l.verbatimName
|
||||||
|
if name != "" {
|
||||||
|
name += " "
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], fmt.Sprintf("{%% endverbatim %s%%}", name)) { // end verbatim
|
||||||
|
if l.pos > l.start {
|
||||||
|
l.emit(TokenHTML)
|
||||||
|
}
|
||||||
|
w := len("{% endverbatim %}")
|
||||||
|
l.pos += w
|
||||||
|
l.col += w
|
||||||
|
l.ignore()
|
||||||
|
l.inVerbatim = false
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(l.input[l.pos:], "{% verbatim %}") { // tag
|
||||||
|
if l.pos > l.start {
|
||||||
|
l.emit(TokenHTML)
|
||||||
|
}
|
||||||
|
l.inVerbatim = true
|
||||||
|
w := len("{% verbatim %}")
|
||||||
|
l.pos += w
|
||||||
|
l.col += w
|
||||||
|
l.ignore()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !l.inVerbatim {
|
||||||
|
// Ignore single-line comments {# ... #}
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], "{#") {
|
||||||
|
if l.pos > l.start {
|
||||||
|
l.emit(TokenHTML)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.pos += 2 // pass '{#'
|
||||||
|
l.col += 2
|
||||||
|
|
||||||
|
for {
|
||||||
|
switch l.peek() {
|
||||||
|
case EOF:
|
||||||
|
l.errorf("Single-line comment not closed.")
|
||||||
|
return
|
||||||
|
case '\n':
|
||||||
|
l.errorf("Newline not permitted in a single-line comment.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], "#}") {
|
||||||
|
l.pos += 2 // pass '#}'
|
||||||
|
l.col += 2
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
l.next()
|
||||||
|
}
|
||||||
|
l.ignore() // ignore whole comment
|
||||||
|
|
||||||
|
// Comment skipped
|
||||||
|
continue // next token
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], "{{") || // variable
|
||||||
|
strings.HasPrefix(l.input[l.pos:], "{%") { // tag
|
||||||
|
if l.pos > l.start {
|
||||||
|
l.emit(TokenHTML)
|
||||||
|
}
|
||||||
|
l.tokenize()
|
||||||
|
if l.errored {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch l.peek() {
|
||||||
|
case '\n':
|
||||||
|
l.line++
|
||||||
|
l.col = 0
|
||||||
|
}
|
||||||
|
if l.next() == EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.pos > l.start {
|
||||||
|
l.emit(TokenHTML)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.inVerbatim {
|
||||||
|
l.errorf("verbatim-tag not closed, got EOF.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) tokenize() {
|
||||||
|
for state := l.stateCode; state != nil; {
|
||||||
|
state = state()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) stateCode() lexerStateFn {
|
||||||
|
outer_loop:
|
||||||
|
for {
|
||||||
|
switch {
|
||||||
|
case l.accept(tokenSpaceChars):
|
||||||
|
if l.value() == "\n" {
|
||||||
|
return l.errorf("Newline not allowed within tag/variable.")
|
||||||
|
}
|
||||||
|
l.ignore()
|
||||||
|
continue
|
||||||
|
case l.accept(tokenIdentifierChars):
|
||||||
|
return l.stateIdentifier
|
||||||
|
case l.accept(tokenDigits):
|
||||||
|
return l.stateNumber
|
||||||
|
case l.accept(`"`):
|
||||||
|
return l.stateString
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for symbol
|
||||||
|
for _, sym := range TokenSymbols {
|
||||||
|
if strings.HasPrefix(l.input[l.start:], sym) {
|
||||||
|
l.pos += len(sym)
|
||||||
|
l.col += l.length()
|
||||||
|
l.emit(TokenSymbol)
|
||||||
|
|
||||||
|
if sym == "%}" || sym == "}}" {
|
||||||
|
// Tag/variable end, return after emit
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
continue outer_loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.pos < len(l.input) {
|
||||||
|
return l.errorf("Unknown character: %q (%d)", l.peek(), l.peek())
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal shut down
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) stateIdentifier() lexerStateFn {
|
||||||
|
l.acceptRun(tokenIdentifierChars)
|
||||||
|
l.acceptRun(tokenIdentifierCharsWithDigits)
|
||||||
|
for _, kw := range TokenKeywords {
|
||||||
|
if kw == l.value() {
|
||||||
|
l.emit(TokenKeyword)
|
||||||
|
return l.stateCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(TokenIdentifier)
|
||||||
|
return l.stateCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) stateNumber() lexerStateFn {
|
||||||
|
l.acceptRun(tokenDigits)
|
||||||
|
/*
|
||||||
|
Maybe context-sensitive number lexing?
|
||||||
|
* comments.0.Text // first comment
|
||||||
|
* usercomments.1.0 // second user, first comment
|
||||||
|
* if (score >= 8.5) // 8.5 as a number
|
||||||
|
|
||||||
|
if l.peek() == '.' {
|
||||||
|
l.accept(".")
|
||||||
|
if !l.accept(tokenDigits) {
|
||||||
|
return l.errorf("Malformed number.")
|
||||||
|
}
|
||||||
|
l.acceptRun(tokenDigits)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
l.emit(TokenNumber)
|
||||||
|
return l.stateCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) stateString() lexerStateFn {
|
||||||
|
l.ignore()
|
||||||
|
l.startcol-- // we're starting the position at the first "
|
||||||
|
for !l.accept(`"`) {
|
||||||
|
switch l.next() {
|
||||||
|
case '\\':
|
||||||
|
// escape sequence
|
||||||
|
switch l.peek() {
|
||||||
|
case '"', '\\':
|
||||||
|
l.next()
|
||||||
|
default:
|
||||||
|
return l.errorf("Unknown escape sequence: \\%c", l.peek())
|
||||||
|
}
|
||||||
|
case EOF:
|
||||||
|
return l.errorf("Unexpected EOF, string not closed.")
|
||||||
|
case '\n':
|
||||||
|
return l.errorf("Newline in string is not allowed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
l.emit(TokenString)
|
||||||
|
|
||||||
|
l.next()
|
||||||
|
l.ignore()
|
||||||
|
|
||||||
|
return l.stateCode
|
||||||
|
}
|
16
Godeps/_workspace/src/github.com/flosch/pongo2/nodes.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/flosch/pongo2/nodes.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
// The root document
|
||||||
|
type nodeDocument struct {
|
||||||
|
Nodes []INode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (doc *nodeDocument) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
for _, n := range doc.Nodes {
|
||||||
|
err := n.Execute(ctx, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
10
Godeps/_workspace/src/github.com/flosch/pongo2/nodes_html.go
generated
vendored
Normal file
10
Godeps/_workspace/src/github.com/flosch/pongo2/nodes_html.go
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type nodeHTML struct {
|
||||||
|
token *Token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nodeHTML) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
writer.WriteString(n.token.Val)
|
||||||
|
return nil
|
||||||
|
}
|
16
Godeps/_workspace/src/github.com/flosch/pongo2/nodes_wrapper.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/flosch/pongo2/nodes_wrapper.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type NodeWrapper struct {
|
||||||
|
Endtag string
|
||||||
|
nodes []INode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wrapper *NodeWrapper) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
for _, n := range wrapper.nodes {
|
||||||
|
err := n.Execute(ctx, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
265
Godeps/_workspace/src/github.com/flosch/pongo2/parser.go
generated
vendored
Normal file
265
Godeps/_workspace/src/github.com/flosch/pongo2/parser.go
generated
vendored
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type INode interface {
|
||||||
|
Execute(*ExecutionContext, TemplateWriter) *Error
|
||||||
|
}
|
||||||
|
|
||||||
|
type IEvaluator interface {
|
||||||
|
INode
|
||||||
|
GetPositionToken() *Token
|
||||||
|
Evaluate(*ExecutionContext) (*Value, *Error)
|
||||||
|
FilterApplied(name string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// The parser provides you a comprehensive and easy tool to
|
||||||
|
// work with the template document and arguments provided by
|
||||||
|
// the user for your custom tag.
|
||||||
|
//
|
||||||
|
// The parser works on a token list which will be provided by pongo2.
|
||||||
|
// A token is a unit you can work with. Tokens are either of type identifier,
|
||||||
|
// string, number, keyword, HTML or symbol.
|
||||||
|
//
|
||||||
|
// (See Token's documentation for more about tokens)
|
||||||
|
type Parser struct {
|
||||||
|
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)
|
||||||
|
template *Template
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new parser to parse tokens.
|
||||||
|
// Used inside pongo2 to parse documents and to provide an easy-to-use
|
||||||
|
// parser for tag authors
|
||||||
|
func newParser(name string, tokens []*Token, template *Template) *Parser {
|
||||||
|
p := &Parser{
|
||||||
|
name: name,
|
||||||
|
tokens: tokens,
|
||||||
|
template: template,
|
||||||
|
}
|
||||||
|
if len(tokens) > 0 {
|
||||||
|
p.lastToken = tokens[len(tokens)-1]
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume one token. It will be gone forever.
|
||||||
|
func (p *Parser) Consume() {
|
||||||
|
p.ConsumeN(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume N tokens. They will be gone forever.
|
||||||
|
func (p *Parser) ConsumeN(count int) {
|
||||||
|
p.idx += count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the current token.
|
||||||
|
func (p *Parser) Current() *Token {
|
||||||
|
return p.Get(p.idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the CURRENT token if the given type matches.
|
||||||
|
// Consumes this token on success.
|
||||||
|
func (p *Parser) MatchType(typ TokenType) *Token {
|
||||||
|
if t := p.PeekType(typ); t != nil {
|
||||||
|
p.Consume()
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the CURRENT token if the given type AND value matches.
|
||||||
|
// Consumes this token on success.
|
||||||
|
func (p *Parser) Match(typ TokenType, val string) *Token {
|
||||||
|
if t := p.Peek(typ, val); t != nil {
|
||||||
|
p.Consume()
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the CURRENT token if the given type AND *one* of
|
||||||
|
// the given values matches.
|
||||||
|
// Consumes this token on success.
|
||||||
|
func (p *Parser) MatchOne(typ TokenType, vals ...string) *Token {
|
||||||
|
for _, val := range vals {
|
||||||
|
if t := p.Peek(typ, val); t != nil {
|
||||||
|
p.Consume()
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the CURRENT token if the given type matches.
|
||||||
|
// It DOES NOT consume the token.
|
||||||
|
func (p *Parser) PeekType(typ TokenType) *Token {
|
||||||
|
return p.PeekTypeN(0, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the CURRENT token if the given type AND value matches.
|
||||||
|
// It DOES NOT consume the token.
|
||||||
|
func (p *Parser) Peek(typ TokenType, val string) *Token {
|
||||||
|
return p.PeekN(0, typ, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the CURRENT token if the given type AND *one* of
|
||||||
|
// the given values matches.
|
||||||
|
// It DOES NOT consume the token.
|
||||||
|
func (p *Parser) PeekOne(typ TokenType, vals ...string) *Token {
|
||||||
|
for _, v := range vals {
|
||||||
|
t := p.PeekN(0, typ, v)
|
||||||
|
if t != nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the tokens[current position + shift] token if the
|
||||||
|
// given type AND value matches for that token.
|
||||||
|
// DOES NOT consume the token.
|
||||||
|
func (p *Parser) PeekN(shift int, typ TokenType, val string) *Token {
|
||||||
|
t := p.Get(p.idx + shift)
|
||||||
|
if t != nil {
|
||||||
|
if t.Typ == typ && t.Val == val {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the tokens[current position + shift] token if the given type matches.
|
||||||
|
// DOES NOT consume the token for that token.
|
||||||
|
func (p *Parser) PeekTypeN(shift int, typ TokenType) *Token {
|
||||||
|
t := p.Get(p.idx + shift)
|
||||||
|
if t != nil {
|
||||||
|
if t.Typ == typ {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the UNCONSUMED token count.
|
||||||
|
func (p *Parser) Remaining() int {
|
||||||
|
return len(p.tokens) - p.idx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the total token count.
|
||||||
|
func (p *Parser) Count() int {
|
||||||
|
return len(p.tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns tokens[i] or NIL (if i >= len(tokens))
|
||||||
|
func (p *Parser) Get(i int) *Token {
|
||||||
|
if i < len(p.tokens) {
|
||||||
|
return p.tokens[i]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns tokens[current-position + shift] or NIL
|
||||||
|
// (if (current-position + i) >= len(tokens))
|
||||||
|
func (p *Parser) GetR(shift int) *Token {
|
||||||
|
i := p.idx + shift
|
||||||
|
return p.Get(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (p *Parser) Error(msg string, token *Token) *Error {
|
||||||
|
if token == nil {
|
||||||
|
// Set current token
|
||||||
|
token = p.Current()
|
||||||
|
if token == nil {
|
||||||
|
// Set to last token
|
||||||
|
if len(p.tokens) > 0 {
|
||||||
|
token = p.tokens[len(p.tokens)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var line, col int
|
||||||
|
if token != nil {
|
||||||
|
line = token.Line
|
||||||
|
col = token.Col
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
Template: p.template,
|
||||||
|
Filename: p.name,
|
||||||
|
Sender: "parser",
|
||||||
|
Line: line,
|
||||||
|
Column: col,
|
||||||
|
Token: token,
|
||||||
|
ErrorMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wraps all nodes between starting tag and "{% endtag %}" and provides
|
||||||
|
// one simple interface to execute the wrapped nodes.
|
||||||
|
// It returns a parser to process provided arguments to the tag.
|
||||||
|
func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) {
|
||||||
|
wrapper := &NodeWrapper{}
|
||||||
|
|
||||||
|
var tagArgs []*Token
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// Okay, end the wrapping here
|
||||||
|
wrapper.Endtag = tagIdent.Val
|
||||||
|
return wrapper, newParser(p.template.name, tagArgs, p.template), nil
|
||||||
|
}
|
||||||
|
t := p.Current()
|
||||||
|
p.Consume()
|
||||||
|
if t == nil {
|
||||||
|
return nil, nil, p.Error("Unexpected EOF.", p.lastToken)
|
||||||
|
}
|
||||||
|
tagArgs = append(tagArgs, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise process next element to be wrapped
|
||||||
|
node, err := p.parseDocElement()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
wrapper.nodes = append(wrapper.nodes, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")),
|
||||||
|
p.lastToken)
|
||||||
|
}
|
54
Godeps/_workspace/src/github.com/flosch/pongo2/parser_document.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/flosch/pongo2/parser_document.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
// Doc = { ( Filter | Tag | HTML ) }
|
||||||
|
func (p *Parser) parseDocElement() (INode, *Error) {
|
||||||
|
t := p.Current()
|
||||||
|
|
||||||
|
switch t.Typ {
|
||||||
|
case TokenHTML:
|
||||||
|
p.Consume() // consume HTML element
|
||||||
|
return &nodeHTML{token: t}, nil
|
||||||
|
case TokenSymbol:
|
||||||
|
switch t.Val {
|
||||||
|
case "{{":
|
||||||
|
// parse variable
|
||||||
|
variable, err := p.parseVariableElement()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return variable, nil
|
||||||
|
case "{%":
|
||||||
|
// parse tag
|
||||||
|
tag, err := p.parseTagElement()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, p.Error("Unexpected token (only HTML/tags/filters in templates allowed)", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tpl *Template) parse() *Error {
|
||||||
|
tpl.parser = newParser(tpl.name, tpl.tokens, tpl)
|
||||||
|
doc, err := tpl.parser.parseDocument()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tpl.root = doc
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseDocument() (*nodeDocument, *Error) {
|
||||||
|
doc := &nodeDocument{}
|
||||||
|
|
||||||
|
for p.Remaining() > 0 {
|
||||||
|
node, err := p.parseDocElement()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
doc.Nodes = append(doc.Nodes, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc, nil
|
||||||
|
}
|
491
Godeps/_workspace/src/github.com/flosch/pongo2/parser_expression.go
generated
vendored
Normal file
491
Godeps/_workspace/src/github.com/flosch/pongo2/parser_expression.go
generated
vendored
Normal file
|
@ -0,0 +1,491 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Expression struct {
|
||||||
|
// TODO: Add location token?
|
||||||
|
expr1 IEvaluator
|
||||||
|
expr2 IEvaluator
|
||||||
|
opToken *Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type relationalExpression struct {
|
||||||
|
// TODO: Add location token?
|
||||||
|
expr1 IEvaluator
|
||||||
|
expr2 IEvaluator
|
||||||
|
opToken *Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleExpression struct {
|
||||||
|
negate bool
|
||||||
|
negativeSign bool
|
||||||
|
term1 IEvaluator
|
||||||
|
term2 IEvaluator
|
||||||
|
opToken *Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type term struct {
|
||||||
|
// TODO: Add location token?
|
||||||
|
factor1 IEvaluator
|
||||||
|
factor2 IEvaluator
|
||||||
|
opToken *Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type power struct {
|
||||||
|
// TODO: Add location token?
|
||||||
|
power1 IEvaluator
|
||||||
|
power2 IEvaluator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *Expression) FilterApplied(name string) bool {
|
||||||
|
return expr.expr1.FilterApplied(name) && (expr.expr2 == nil ||
|
||||||
|
(expr.expr2 != nil && expr.expr2.FilterApplied(name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *relationalExpression) FilterApplied(name string) bool {
|
||||||
|
return expr.expr1.FilterApplied(name) && (expr.expr2 == nil ||
|
||||||
|
(expr.expr2 != nil && expr.expr2.FilterApplied(name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *simpleExpression) FilterApplied(name string) bool {
|
||||||
|
return expr.term1.FilterApplied(name) && (expr.term2 == nil ||
|
||||||
|
(expr.term2 != nil && expr.term2.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 (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 {
|
||||||
|
return expr.expr1.GetPositionToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *relationalExpression) GetPositionToken() *Token {
|
||||||
|
return expr.expr1.GetPositionToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *simpleExpression) GetPositionToken() *Token {
|
||||||
|
return expr.term1.GetPositionToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *term) GetPositionToken() *Token {
|
||||||
|
return expr.factor1.GetPositionToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *power) GetPositionToken() *Token {
|
||||||
|
return expr.power1.GetPositionToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *Expression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
value, err := expr.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
writer.WriteString(value.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *relationalExpression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
value, err := expr.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
writer.WriteString(value.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *simpleExpression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
value, err := expr.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
writer.WriteString(value.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *term) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
value, err := expr.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
writer.WriteString(value.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *power) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
value, err := expr.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
writer.WriteString(value.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *Expression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
||||||
|
v1, err := expr.expr1.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if expr.expr2 != nil {
|
||||||
|
v2, err := expr.expr2.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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.opToken.Val))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return v1, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *relationalExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
||||||
|
v1, err := expr.expr1.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if expr.expr2 != nil {
|
||||||
|
v2, err := expr.expr2.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch expr.opToken.Val {
|
||||||
|
case "<=":
|
||||||
|
if v1.IsFloat() || v2.IsFloat() {
|
||||||
|
return AsValue(v1.Float() <= v2.Float()), nil
|
||||||
|
}
|
||||||
|
return AsValue(v1.Integer() <= v2.Integer()), nil
|
||||||
|
case ">=":
|
||||||
|
if v1.IsFloat() || v2.IsFloat() {
|
||||||
|
return AsValue(v1.Float() >= v2.Float()), 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
|
||||||
|
}
|
||||||
|
return AsValue(v1.Integer() > v2.Integer()), nil
|
||||||
|
case "<":
|
||||||
|
if v1.IsFloat() || v2.IsFloat() {
|
||||||
|
return AsValue(v1.Float() < v2.Float()), 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.opToken.Val))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return v1, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *simpleExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
||||||
|
t1, err := expr.term1.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := t1
|
||||||
|
|
||||||
|
if expr.negate {
|
||||||
|
result = result.Negate()
|
||||||
|
}
|
||||||
|
|
||||||
|
if expr.negativeSign {
|
||||||
|
if result.IsNumber() {
|
||||||
|
switch {
|
||||||
|
case result.IsFloat():
|
||||||
|
result = AsValue(-1 * result.Float())
|
||||||
|
case result.IsInteger():
|
||||||
|
result = AsValue(-1 * result.Integer())
|
||||||
|
default:
|
||||||
|
panic("not possible")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, ctx.Error("Negative sign on a non-number expression", expr.GetPositionToken())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expr.term2 != nil {
|
||||||
|
t2, err := expr.term2.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch expr.opToken.Val {
|
||||||
|
case "+":
|
||||||
|
if result.IsFloat() || t2.IsFloat() {
|
||||||
|
// Result will be a float
|
||||||
|
return AsValue(result.Float() + t2.Float()), 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
|
||||||
|
}
|
||||||
|
// Result will be an integer
|
||||||
|
return AsValue(result.Integer() - t2.Integer()), nil
|
||||||
|
default:
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
||||||
|
f1, err := expr.factor1.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if expr.factor2 != nil {
|
||||||
|
f2, err := expr.factor2.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch expr.opToken.Val {
|
||||||
|
case "*":
|
||||||
|
if f1.IsFloat() || f2.IsFloat() {
|
||||||
|
// Result will be float
|
||||||
|
return AsValue(f1.Float() * f2.Float()), nil
|
||||||
|
}
|
||||||
|
// Result will be int
|
||||||
|
return AsValue(f1.Integer() * f2.Integer()), nil
|
||||||
|
case "/":
|
||||||
|
if f1.IsFloat() || f2.IsFloat() {
|
||||||
|
// Result will be float
|
||||||
|
return AsValue(f1.Float() / f2.Float()), nil
|
||||||
|
}
|
||||||
|
// Result will be int
|
||||||
|
return AsValue(f1.Integer() / f2.Integer()), nil
|
||||||
|
case "%":
|
||||||
|
// Result will be int
|
||||||
|
return AsValue(f1.Integer() % f2.Integer()), nil
|
||||||
|
default:
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return f1, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *power) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
||||||
|
p1, err := expr.power1.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return p1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseFactor() (IEvaluator, *Error) {
|
||||||
|
if p.Match(TokenSymbol, "(") != nil {
|
||||||
|
expr, err := p.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if p.Match(TokenSymbol, ")") == nil {
|
||||||
|
return nil, p.Error("Closing bracket expected after expression", nil)
|
||||||
|
}
|
||||||
|
return expr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.parseVariableOrLiteralWithFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parsePower() (IEvaluator, *Error) {
|
||||||
|
pw := new(power)
|
||||||
|
|
||||||
|
power1, err := p.parseFactor()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pw.power1 = power1
|
||||||
|
|
||||||
|
if p.Match(TokenSymbol, "^") != nil {
|
||||||
|
power2, err := p.parsePower()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pw.power2 = power2
|
||||||
|
}
|
||||||
|
|
||||||
|
if pw.power2 == nil {
|
||||||
|
// Shortcut for faster evaluation
|
||||||
|
return pw.power1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return pw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseTerm() (IEvaluator, *Error) {
|
||||||
|
returnTerm := new(term)
|
||||||
|
|
||||||
|
factor1, err := p.parsePower()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
returnTerm.factor1 = factor1
|
||||||
|
|
||||||
|
for p.PeekOne(TokenSymbol, "*", "/", "%") != nil {
|
||||||
|
if returnTerm.opToken != nil {
|
||||||
|
// Create new sub-term
|
||||||
|
returnTerm = &term{
|
||||||
|
factor1: returnTerm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
op := p.Current()
|
||||||
|
p.Consume()
|
||||||
|
|
||||||
|
factor2, err := p.parsePower()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
returnTerm.opToken = op
|
||||||
|
returnTerm.factor2 = factor2
|
||||||
|
}
|
||||||
|
|
||||||
|
if returnTerm.opToken == nil {
|
||||||
|
// Shortcut for faster evaluation
|
||||||
|
return returnTerm.factor1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnTerm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) {
|
||||||
|
expr := new(simpleExpression)
|
||||||
|
|
||||||
|
if sign := p.MatchOne(TokenSymbol, "+", "-"); sign != nil {
|
||||||
|
if sign.Val == "-" {
|
||||||
|
expr.negativeSign = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Match(TokenSymbol, "!") != nil || p.Match(TokenKeyword, "not") != nil {
|
||||||
|
expr.negate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
term1, err := p.parseTerm()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
expr.term1 = term1
|
||||||
|
|
||||||
|
for p.PeekOne(TokenSymbol, "+", "-") != nil {
|
||||||
|
if expr.opToken != nil {
|
||||||
|
// New sub expr
|
||||||
|
expr = &simpleExpression{
|
||||||
|
term1: expr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
op := p.Current()
|
||||||
|
p.Consume()
|
||||||
|
|
||||||
|
term2, err := p.parseTerm()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
expr.term2 = term2
|
||||||
|
expr.opToken = op
|
||||||
|
}
|
||||||
|
|
||||||
|
if expr.negate == false && expr.negativeSign == false && expr.term2 == nil {
|
||||||
|
// Shortcut for faster evaluation
|
||||||
|
return expr.term1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseRelationalExpression() (IEvaluator, *Error) {
|
||||||
|
expr1, err := p.parseSimpleExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
expr := &relationalExpression{
|
||||||
|
expr1: expr1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t := p.MatchOne(TokenSymbol, "==", "<=", ">=", "!=", "<>", ">", "<"); t != nil {
|
||||||
|
expr2, err := p.parseRelationalExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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.opToken = t
|
||||||
|
expr.expr2 = expr2
|
||||||
|
}
|
||||||
|
|
||||||
|
if expr.expr2 == nil {
|
||||||
|
// Shortcut for faster evaluation
|
||||||
|
return expr.expr1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) ParseExpression() (IEvaluator, *Error) {
|
||||||
|
rexpr1, err := p.parseRelationalExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := &Expression{
|
||||||
|
expr1: rexpr1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.PeekOne(TokenSymbol, "&&", "||") != nil || p.PeekOne(TokenKeyword, "and", "or") != nil {
|
||||||
|
op := p.Current()
|
||||||
|
p.Consume()
|
||||||
|
expr2, err := p.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
exp.expr2 = expr2
|
||||||
|
exp.opToken = op
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp.expr2 == nil {
|
||||||
|
// Shortcut for faster evaluation
|
||||||
|
return exp.expr1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return exp, nil
|
||||||
|
}
|
14
Godeps/_workspace/src/github.com/flosch/pongo2/pongo2.go
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/flosch/pongo2/pongo2.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
// Version string
|
||||||
|
const Version = "dev"
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return tpl
|
||||||
|
}
|
528
Godeps/_workspace/src/github.com/flosch/pongo2/pongo2_template_test.go
generated
vendored
Normal file
528
Godeps/_workspace/src/github.com/flosch/pongo2/pongo2_template_test.go
generated
vendored
Normal file
|
@ -0,0 +1,528 @@
|
||||||
|
package pongo2_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/flosch/pongo2"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
type post struct {
|
||||||
|
Text string
|
||||||
|
Created time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type user struct {
|
||||||
|
Name string
|
||||||
|
Validated bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type comment struct {
|
||||||
|
Author *user
|
||||||
|
Date time.Time
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAdmin(u *user) bool {
|
||||||
|
for _, a := range adminList {
|
||||||
|
if a == u.Name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *user) Is_admin() *pongo2.Value {
|
||||||
|
return pongo2.AsValue(isAdmin(u))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *user) Is_admin2() bool {
|
||||||
|
return isAdmin(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *post) String() string {
|
||||||
|
return ":-)"
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start setup sandbox
|
||||||
|
*/
|
||||||
|
|
||||||
|
type tagSandboxDemoTag struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagSandboxDemoTag) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error {
|
||||||
|
writer.WriteString("hello")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagSandboxDemoTagParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) {
|
||||||
|
return &tagSandboxDemoTag{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BannedFilterFn(in *pongo2.Value, params *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pongo2.DefaultSet.Debug = true
|
||||||
|
|
||||||
|
pongo2.RegisterFilter("banned_filter", BannedFilterFn)
|
||||||
|
pongo2.RegisterFilter("unbanned_filter", BannedFilterFn)
|
||||||
|
pongo2.RegisterTag("banned_tag", tagSandboxDemoTagParser)
|
||||||
|
pongo2.RegisterTag("unbanned_tag", tagSandboxDemoTagParser)
|
||||||
|
|
||||||
|
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"))
|
||||||
|
pongo2.DefaultSet.Globals["temp_file"] = f.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* End setup sandbox
|
||||||
|
*/
|
||||||
|
|
||||||
|
var tplContext = pongo2.Context{
|
||||||
|
"number": 11,
|
||||||
|
"simple": map[string]interface{}{
|
||||||
|
"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,
|
||||||
|
"newline_text": `this is a text
|
||||||
|
with a new line in it`,
|
||||||
|
"long_text": `This is a simple text.
|
||||||
|
|
||||||
|
This too, as a paragraph.
|
||||||
|
Right?
|
||||||
|
|
||||||
|
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},
|
||||||
|
"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",
|
||||||
|
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
|
||||||
|
},
|
||||||
|
"func_add_iface": func(a, b interface{}) interface{} {
|
||||||
|
return a.(int) + b.(int)
|
||||||
|
},
|
||||||
|
"func_variadic": func(msg string, args ...interface{}) string {
|
||||||
|
return fmt.Sprintf(msg, args...)
|
||||||
|
},
|
||||||
|
"func_variadic_sum_int": func(args ...int) int {
|
||||||
|
// Create a sum
|
||||||
|
s := 0
|
||||||
|
for _, i := range args {
|
||||||
|
s += i
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
"func_variadic_sum_int2": func(args ...*pongo2.Value) *pongo2.Value {
|
||||||
|
// Create a sum
|
||||||
|
s := 0
|
||||||
|
for _, i := range args {
|
||||||
|
s += i.Integer()
|
||||||
|
}
|
||||||
|
return pongo2.AsValue(s)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"complex": map[string]interface{}{
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
"comments": []*comment{
|
||||||
|
&comment{
|
||||||
|
Author: &user{
|
||||||
|
Name: "user1",
|
||||||
|
Validated: true,
|
||||||
|
},
|
||||||
|
Date: time1,
|
||||||
|
Text: "\"pongo2 is nice!\"",
|
||||||
|
},
|
||||||
|
&comment{
|
||||||
|
Author: &user{
|
||||||
|
Name: "user2",
|
||||||
|
Validated: true,
|
||||||
|
},
|
||||||
|
Date: time2,
|
||||||
|
Text: "comment2 with <script>unsafe</script> tags in it",
|
||||||
|
},
|
||||||
|
&comment{
|
||||||
|
Author: &user{
|
||||||
|
Name: "user3",
|
||||||
|
Validated: false,
|
||||||
|
},
|
||||||
|
Date: time1,
|
||||||
|
Text: "<b>hello!</b> there",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"comments2": []*comment{
|
||||||
|
&comment{
|
||||||
|
Author: &user{
|
||||||
|
Name: "user1",
|
||||||
|
Validated: true,
|
||||||
|
},
|
||||||
|
Date: time2,
|
||||||
|
Text: "\"pongo2 is nice!\"",
|
||||||
|
},
|
||||||
|
&comment{
|
||||||
|
Author: &user{
|
||||||
|
Name: "user1",
|
||||||
|
Validated: true,
|
||||||
|
},
|
||||||
|
Date: time1,
|
||||||
|
Text: "comment2 with <script>unsafe</script> tags in it",
|
||||||
|
},
|
||||||
|
&comment{
|
||||||
|
Author: &user{
|
||||||
|
Name: "user3",
|
||||||
|
Validated: false,
|
||||||
|
},
|
||||||
|
Date: time1,
|
||||||
|
Text: "<b>hello!</b> there",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTemplates(t *testing.T) {
|
||||||
|
// Add a global to the default set
|
||||||
|
pongo2.Globals["this_is_a_global_variable"] = "this is a global text"
|
||||||
|
|
||||||
|
matches, err := filepath.Glob("./template_tests/*.tpl")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for idx, match := range matches {
|
||||||
|
t.Logf("[Template %3d] Testing '%s'", idx+1, match)
|
||||||
|
tpl, err := pongo2.FromFile(match)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error on FromFile('%s'): %s", match, err.Error())
|
||||||
|
}
|
||||||
|
testFilename := fmt.Sprintf("%s.out", match)
|
||||||
|
testOut, rerr := ioutil.ReadFile(testFilename)
|
||||||
|
if rerr != nil {
|
||||||
|
t.Fatalf("Error on ReadFile('%s'): %s", testFilename, rerr.Error())
|
||||||
|
}
|
||||||
|
tplOut, err := tpl.ExecuteBytes(tplContext)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error on Execute('%s'): %s", match, err.Error())
|
||||||
|
}
|
||||||
|
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'", testFilename, errFilename)
|
||||||
|
t.Errorf("Failed: test_out != tpl_out for %s", match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecutionErrors(t *testing.T) {
|
||||||
|
//debug = true
|
||||||
|
|
||||||
|
matches, err := filepath.Glob("./template_tests/*-execution.err")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for idx, match := range matches {
|
||||||
|
t.Logf("[Errors %3d] Testing '%s'", idx+1, match)
|
||||||
|
|
||||||
|
testData, err := ioutil.ReadFile(match)
|
||||||
|
tests := strings.Split(string(testData), "\n")
|
||||||
|
|
||||||
|
checkFilename := fmt.Sprintf("%s.out", match)
|
||||||
|
checkData, err := ioutil.ReadFile(checkFilename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error on ReadFile('%s'): %s", checkFilename, err.Error())
|
||||||
|
}
|
||||||
|
checks := strings.Split(string(checkData), "\n")
|
||||||
|
|
||||||
|
if len(checks) != len(tests) {
|
||||||
|
t.Fatal("Template lines != Checks lines")
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, test := range tests {
|
||||||
|
if strings.TrimSpace(test) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(checks[idx]) == "" {
|
||||||
|
t.Fatalf("[%s Line %d] Check is empty (must contain an regular expression).",
|
||||||
|
match, idx+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl, err := pongo2.FromString(test)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error on FromString('%s'): %s", test, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tpl.ExecuteBytes(tplContext)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("[%s Line %d] Expected error for (got none): %s",
|
||||||
|
match, idx+1, tests[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
re := regexp.MustCompile(fmt.Sprintf("^%s$", checks[idx]))
|
||||||
|
if !re.MatchString(err.Error()) {
|
||||||
|
t.Fatalf("[%s Line %d] Error for '%s' (err = '%s') does not match the (regexp-)check: %s",
|
||||||
|
match, idx+1, test, err.Error(), checks[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompilationErrors(t *testing.T) {
|
||||||
|
//debug = true
|
||||||
|
|
||||||
|
matches, err := filepath.Glob("./template_tests/*-compilation.err")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for idx, match := range matches {
|
||||||
|
t.Logf("[Errors %3d] Testing '%s'", idx+1, match)
|
||||||
|
|
||||||
|
testData, err := ioutil.ReadFile(match)
|
||||||
|
tests := strings.Split(string(testData), "\n")
|
||||||
|
|
||||||
|
checkFilename := fmt.Sprintf("%s.out", match)
|
||||||
|
checkData, err := ioutil.ReadFile(checkFilename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error on ReadFile('%s'): %s", checkFilename, err.Error())
|
||||||
|
}
|
||||||
|
checks := strings.Split(string(checkData), "\n")
|
||||||
|
|
||||||
|
if len(checks) != len(tests) {
|
||||||
|
t.Fatal("Template lines != Checks lines")
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, test := range tests {
|
||||||
|
if strings.TrimSpace(test) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(checks[idx]) == "" {
|
||||||
|
t.Fatalf("[%s Line %d] Check is empty (must contain an regular expression).",
|
||||||
|
match, idx+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = pongo2.FromString(test)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("[%s | Line %d] Expected error for (got none): %s", match, idx+1, tests[idx])
|
||||||
|
}
|
||||||
|
re := regexp.MustCompile(fmt.Sprintf("^%s$", checks[idx]))
|
||||||
|
if !re.MatchString(err.Error()) {
|
||||||
|
t.Fatalf("[%s | Line %d] Error for '%s' (err = '%s') does not match the (regexp-)check: %s",
|
||||||
|
match, idx+1, test, err.Error(), checks[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBaseDirectory(t *testing.T) {
|
||||||
|
mustStr := "Hello from template_tests/base_dir_test/"
|
||||||
|
|
||||||
|
fs := pongo2.MustNewLocalFileSystemLoader("")
|
||||||
|
s := pongo2.NewSet("test set with base directory", fs)
|
||||||
|
s.Globals["base_directory"] = "template_tests/base_dir_test/"
|
||||||
|
if err := fs.SetBaseDir(s.Globals["base_directory"].(string)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
matches, err := filepath.Glob("./template_tests/base_dir_test/subdir/*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, match := range matches {
|
||||||
|
match = strings.Replace(match, "template_tests/base_dir_test/", "", -1)
|
||||||
|
|
||||||
|
tpl, err := s.FromFile(match)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
out, err := tpl.Execute(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if out != mustStr {
|
||||||
|
t.Errorf("%s: out ('%s') != mustStr ('%s')", match, out, mustStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCache(b *testing.B) {
|
||||||
|
cacheSet := pongo2.NewSet("cache set", pongo2.MustNewLocalFileSystemLoader(""))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
tpl, err := cacheSet.FromCache("template_tests/complex.tpl")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCacheDebugOn(b *testing.B) {
|
||||||
|
cacheDebugSet := pongo2.NewSet("cache set", pongo2.MustNewLocalFileSystemLoader(""))
|
||||||
|
cacheDebugSet.Debug = true
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
tpl, err := cacheDebugSet.FromFile("template_tests/complex.tpl")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkExecuteComplexWithSandboxActive(b *testing.B) {
|
||||||
|
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.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCompileAndExecuteComplexWithSandboxActive(b *testing.B) {
|
||||||
|
buf, err := ioutil.ReadFile("template_tests/complex.tpl")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
preloadedTpl := string(buf)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
tpl, err := pongo2.FromString(preloadedTpl)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkParallelExecuteComplexWithSandboxActive(b *testing.B) {
|
||||||
|
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.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkExecuteComplexWithoutSandbox(b *testing.B) {
|
||||||
|
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.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCompileAndExecuteComplexWithoutSandbox(b *testing.B) {
|
||||||
|
buf, err := ioutil.ReadFile("template_tests/complex.tpl")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
preloadedTpl := string(buf)
|
||||||
|
|
||||||
|
s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader(""))
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
tpl, err := s.FromString(preloadedTpl)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkParallelExecuteComplexWithoutSandbox(b *testing.B) {
|
||||||
|
s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader(""))
|
||||||
|
tpl, err := s.FromFile("template_tests/complex.tpl")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
err := tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
70
Godeps/_workspace/src/github.com/flosch/pongo2/pongo2_test.go
generated
vendored
Normal file
70
Godeps/_workspace/src/github.com/flosch/pongo2/pongo2_test.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
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 *pongo2.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ = Suite(&TestSuite{})
|
||||||
|
testSuite2 = pongo2.NewSet("test suite 2", pongo2.MustNewLocalFileSystemLoader(""))
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseTemplate(s string, c pongo2.Context) string {
|
||||||
|
t, err := testSuite2.FromString(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
out, err := t.Execute(c)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTemplateFn(s string, c pongo2.Context) func() {
|
||||||
|
return func() {
|
||||||
|
parseTemplate(s, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TestSuite) TestMisc(c *C) {
|
||||||
|
// Must
|
||||||
|
// TODO: Add better error message (see issue #18)
|
||||||
|
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`,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context
|
||||||
|
c.Check(parseTemplateFn("", pongo2.Context{"'illegal": nil}), PanicMatches, ".*not a valid identifier.*")
|
||||||
|
|
||||||
|
// Registers
|
||||||
|
c.Check(func() { pongo2.RegisterFilter("escape", nil) }, PanicMatches, ".*is already registered.*")
|
||||||
|
c.Check(func() { pongo2.RegisterTag("for", nil) }, PanicMatches, ".*is already registered.*")
|
||||||
|
|
||||||
|
// ApplyFilter
|
||||||
|
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 := pongo2.ApplyFilter("doesnotexist", nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}, PanicMatches, `\[Error \(where: applyfilter\)\] Filter with name 'doesnotexist' not found.`)
|
||||||
|
}
|
132
Godeps/_workspace/src/github.com/flosch/pongo2/tags.go
generated
vendored
Normal file
132
Godeps/_workspace/src/github.com/flosch/pongo2/tags.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
/* Incomplete:
|
||||||
|
-----------
|
||||||
|
|
||||||
|
verbatim (only the "name" argument is missing for verbatim)
|
||||||
|
|
||||||
|
Reconsideration:
|
||||||
|
----------------
|
||||||
|
|
||||||
|
debug (reason: not sure what to output yet)
|
||||||
|
regroup / Grouping on other properties (reason: maybe too python-specific; not sure how useful this would be in Go)
|
||||||
|
|
||||||
|
Following built-in tags wont be added:
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
csrf_token (reason: web-framework specific)
|
||||||
|
load (reason: python-specific)
|
||||||
|
url (reason: web-framework specific)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type INodeTag interface {
|
||||||
|
INode
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the function signature of the tag's parser you will have
|
||||||
|
// to implement in order to create a new tag.
|
||||||
|
//
|
||||||
|
// 'doc' is providing access to the whole document while 'arguments'
|
||||||
|
// is providing access to the user's arguments to the tag:
|
||||||
|
//
|
||||||
|
// {% your_tag_name some "arguments" 123 %}
|
||||||
|
//
|
||||||
|
// start_token will be the *Token with the tag's name in it (here: your_tag_name).
|
||||||
|
//
|
||||||
|
// Please see the Parser documentation on how to use the parser.
|
||||||
|
// See RegisterTag()'s documentation for more information about
|
||||||
|
// writing a tag as well.
|
||||||
|
type TagParser func(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error)
|
||||||
|
|
||||||
|
type tag struct {
|
||||||
|
name string
|
||||||
|
parser TagParser
|
||||||
|
}
|
||||||
|
|
||||||
|
var tags map[string]*tag
|
||||||
|
|
||||||
|
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
|
||||||
|
// 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) {
|
||||||
|
_, existing := tags[name]
|
||||||
|
if existing {
|
||||||
|
panic(fmt.Sprintf("Tag with name '%s' is already registered.", name))
|
||||||
|
}
|
||||||
|
tags[name] = &tag{
|
||||||
|
name: name,
|
||||||
|
parser: parserFn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
_, existing := tags[name]
|
||||||
|
if !existing {
|
||||||
|
panic(fmt.Sprintf("Tag with name '%s' does not exist (therefore cannot be overridden).", name))
|
||||||
|
}
|
||||||
|
tags[name] = &tag{
|
||||||
|
name: name,
|
||||||
|
parser: parserFn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag = "{%" IDENT ARGS "%}"
|
||||||
|
func (p *Parser) parseTagElement() (INodeTag, *Error) {
|
||||||
|
p.Consume() // consume "{%"
|
||||||
|
tokenName := p.MatchType(TokenIdentifier)
|
||||||
|
|
||||||
|
// Check for identifier
|
||||||
|
if tokenName == nil {
|
||||||
|
return nil, p.Error("Tag name must be an identifier.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the existing tag
|
||||||
|
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)", tokenName.Val), tokenName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check sandbox tag restriction
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
var argsToken []*Token
|
||||||
|
for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 {
|
||||||
|
// Add token to args
|
||||||
|
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.lastToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Match(TokenSymbol, "%}")
|
||||||
|
|
||||||
|
argParser := newParser(p.name, argsToken, p.template)
|
||||||
|
if len(argsToken) == 0 {
|
||||||
|
// This is done to have nice EOF error messages
|
||||||
|
argParser.lastToken = tokenName
|
||||||
|
}
|
||||||
|
|
||||||
|
p.template.level++
|
||||||
|
defer func() { p.template.level-- }()
|
||||||
|
return tag.parser(p, tokenName, argParser)
|
||||||
|
}
|
52
Godeps/_workspace/src/github.com/flosch/pongo2/tags_autoescape.go
generated
vendored
Normal file
52
Godeps/_workspace/src/github.com/flosch/pongo2/tags_autoescape.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagAutoescapeNode struct {
|
||||||
|
wrapper *NodeWrapper
|
||||||
|
autoescape bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
old := ctx.Autoescape
|
||||||
|
ctx.Autoescape = node.autoescape
|
||||||
|
|
||||||
|
err := node.wrapper.Execute(ctx, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Autoescape = old
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagAutoescapeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
autoescapeNode := &tagAutoescapeNode{}
|
||||||
|
|
||||||
|
wrapper, _, err := doc.WrapUntilTag("endautoescape")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
autoescapeNode.wrapper = wrapper
|
||||||
|
|
||||||
|
modeToken := arguments.MatchType(TokenIdentifier)
|
||||||
|
if modeToken == nil {
|
||||||
|
return nil, arguments.Error("A mode is required for autoescape-tag.", nil)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed autoescape-tag arguments.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return autoescapeNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("autoescape", tagAutoescapeParser)
|
||||||
|
}
|
93
Godeps/_workspace/src/github.com/flosch/pongo2/tags_block.go
generated
vendored
Normal file
93
Godeps/_workspace/src/github.com/flosch/pongo2/tags_block.go
generated
vendored
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagBlockNode struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagBlockNode) getBlockWrapperByName(tpl *Template) *NodeWrapper {
|
||||||
|
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
|
||||||
|
t = tpl.blocks[node.name]
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
blockWrapper := node.getBlockWrapperByName(tpl)
|
||||||
|
if blockWrapper == nil {
|
||||||
|
// fmt.Printf("could not find: %s\n", node.name)
|
||||||
|
return ctx.Error("internal error: block_wrapper == nil in tagBlockNode.Execute()", nil)
|
||||||
|
}
|
||||||
|
err := blockWrapper.Execute(ctx, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add support for {{ block.super }}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
if arguments.Count() == 0 {
|
||||||
|
return nil, arguments.Error("Tag 'block' requires an identifier.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
nameToken := arguments.MatchType(TokenIdentifier)
|
||||||
|
if nameToken == nil {
|
||||||
|
return nil, arguments.Error("First argument for tag 'block' must be an identifier.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() != 0 {
|
||||||
|
return nil, arguments.Error("Tag 'block' takes exactly 1 argument (an identifier).", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper, endtagargs, err := doc.WrapUntilTag("endblock")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if endtagargs.Remaining() > 0 {
|
||||||
|
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').",
|
||||||
|
nameToken.Val, endtagnameToken.Val), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if endtagnameToken == nil || endtagargs.Remaining() > 0 {
|
||||||
|
return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endblock'.", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl := doc.template
|
||||||
|
if tpl == nil {
|
||||||
|
panic("internal error: tpl == nil")
|
||||||
|
}
|
||||||
|
_, hasBlock := tpl.blocks[nameToken.Val]
|
||||||
|
if !hasBlock {
|
||||||
|
tpl.blocks[nameToken.Val] = wrapper
|
||||||
|
} else {
|
||||||
|
return nil, arguments.Error(fmt.Sprintf("Block named '%s' already defined", nameToken.Val), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tagBlockNode{name: nameToken.Val}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("block", tagBlockParser)
|
||||||
|
}
|
27
Godeps/_workspace/src/github.com/flosch/pongo2/tags_comment.go
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/flosch/pongo2/tags_comment.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagCommentNode struct{}
|
||||||
|
|
||||||
|
func (node *tagCommentNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagCommentParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
commentNode := &tagCommentNode{}
|
||||||
|
|
||||||
|
// TODO: Process the endtag's arguments (see django 'comment'-tag documentation)
|
||||||
|
_, _, err := doc.WrapUntilTag("endcomment")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Count() != 0 {
|
||||||
|
return nil, arguments.Error("Tag 'comment' does not take any argument.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return commentNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("comment", tagCommentParser)
|
||||||
|
}
|
106
Godeps/_workspace/src/github.com/flosch/pongo2/tags_cycle.go
generated
vendored
Normal file
106
Godeps/_workspace/src/github.com/flosch/pongo2/tags_cycle.go
generated
vendored
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagCycleValue struct {
|
||||||
|
node *tagCycleNode
|
||||||
|
value *Value
|
||||||
|
}
|
||||||
|
|
||||||
|
type tagCycleNode struct {
|
||||||
|
position *Token
|
||||||
|
args []IEvaluator
|
||||||
|
idx int
|
||||||
|
asName string
|
||||||
|
silent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *tagCycleValue) String() string {
|
||||||
|
return cv.value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagCycleNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
item := node.args[node.idx%len(node.args)]
|
||||||
|
node.idx++
|
||||||
|
|
||||||
|
val, err := item.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, ok := val.Interface().(*tagCycleValue); ok {
|
||||||
|
// {% cycle "test1" "test2"
|
||||||
|
// {% cycle cycleitem %}
|
||||||
|
|
||||||
|
// Update the cycle value with next value
|
||||||
|
item := t.node.args[t.node.idx%len(t.node.args)]
|
||||||
|
t.node.idx++
|
||||||
|
|
||||||
|
val, err := item.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.value = val
|
||||||
|
|
||||||
|
if !t.node.silent {
|
||||||
|
writer.WriteString(val.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Regular call
|
||||||
|
|
||||||
|
cycleValue := &tagCycleValue{
|
||||||
|
node: node,
|
||||||
|
value: val,
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.asName != "" {
|
||||||
|
ctx.Private[node.asName] = cycleValue
|
||||||
|
}
|
||||||
|
if !node.silent {
|
||||||
|
writer.WriteString(val.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HINT: We're not supporting the old comma-seperated list of expresions argument-style
|
||||||
|
func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
cycleNode := &tagCycleNode{
|
||||||
|
position: start,
|
||||||
|
}
|
||||||
|
|
||||||
|
for arguments.Remaining() > 0 {
|
||||||
|
node, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cycleNode.args = append(cycleNode.args, node)
|
||||||
|
|
||||||
|
if arguments.MatchOne(TokenKeyword, "as") != nil {
|
||||||
|
// as
|
||||||
|
|
||||||
|
nameToken := arguments.MatchType(TokenIdentifier)
|
||||||
|
if nameToken == nil {
|
||||||
|
return nil, arguments.Error("Name (identifier) expected after 'as'.", nil)
|
||||||
|
}
|
||||||
|
cycleNode.asName = nameToken.Val
|
||||||
|
|
||||||
|
if arguments.MatchOne(TokenIdentifier, "silent") != nil {
|
||||||
|
cycleNode.silent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we're finished
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed cycle-tag.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cycleNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("cycle", tagCycleParser)
|
||||||
|
}
|
52
Godeps/_workspace/src/github.com/flosch/pongo2/tags_extends.go
generated
vendored
Normal file
52
Godeps/_workspace/src/github.com/flosch/pongo2/tags_extends.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagExtendsNode struct {
|
||||||
|
filename string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagExtendsNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
extendsNode := &tagExtendsNode{}
|
||||||
|
|
||||||
|
if doc.template.level > 1 {
|
||||||
|
return nil, arguments.Error("The 'extends' tag can only defined on root level.", start)
|
||||||
|
}
|
||||||
|
|
||||||
|
if doc.template.parent != nil {
|
||||||
|
// Already one parent
|
||||||
|
return nil, arguments.Error("This template has already one parent.", start)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filenameToken := arguments.MatchType(TokenString); filenameToken != nil {
|
||||||
|
// prepared, static template
|
||||||
|
|
||||||
|
// Get parent's filename
|
||||||
|
parentFilename := doc.template.set.resolveFilename(doc.template, filenameToken.Val)
|
||||||
|
|
||||||
|
// Parse the parent
|
||||||
|
parentTemplate, err := doc.template.set.FromFile(parentFilename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err.(*Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep track of things
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Tag 'extends' does only take 1 argument.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return extendsNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("extends", tagExtendsParser)
|
||||||
|
}
|
95
Godeps/_workspace/src/github.com/flosch/pongo2/tags_filter.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/flosch/pongo2/tags_filter.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type nodeFilterCall struct {
|
||||||
|
name string
|
||||||
|
paramExpr IEvaluator
|
||||||
|
}
|
||||||
|
|
||||||
|
type tagFilterNode struct {
|
||||||
|
position *Token
|
||||||
|
bodyWrapper *NodeWrapper
|
||||||
|
filterChain []*nodeFilterCall
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := AsValue(temp.String())
|
||||||
|
|
||||||
|
for _, call := range node.filterChain {
|
||||||
|
var param *Value
|
||||||
|
if call.paramExpr != nil {
|
||||||
|
param, err = call.paramExpr.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
param = AsValue(nil)
|
||||||
|
}
|
||||||
|
value, err = ApplyFilter(call.name, value, param)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Error(err.Error(), node.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteString(value.String())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
filterNode := &tagFilterNode{
|
||||||
|
position: start,
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper, _, err := doc.WrapUntilTag("endfilter")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
filterNode.bodyWrapper = wrapper
|
||||||
|
|
||||||
|
for arguments.Remaining() > 0 {
|
||||||
|
filterCall := &nodeFilterCall{}
|
||||||
|
|
||||||
|
nameToken := arguments.MatchType(TokenIdentifier)
|
||||||
|
if nameToken == nil {
|
||||||
|
return nil, arguments.Error("Expected a filter name (identifier).", nil)
|
||||||
|
}
|
||||||
|
filterCall.name = nameToken.Val
|
||||||
|
|
||||||
|
if arguments.MatchOne(TokenSymbol, ":") != nil {
|
||||||
|
// Filter parameter
|
||||||
|
// NOTICE: we can't use ParseExpression() here, because it would parse the next filter "|..." as well in the argument list
|
||||||
|
expr, err := arguments.parseVariableOrLiteral()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
filterCall.paramExpr = expr
|
||||||
|
}
|
||||||
|
|
||||||
|
filterNode.filterChain = append(filterNode.filterChain, filterCall)
|
||||||
|
|
||||||
|
if arguments.MatchOne(TokenSymbol, "|") == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed filter-tag arguments.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("filter", tagFilterParser)
|
||||||
|
}
|
49
Godeps/_workspace/src/github.com/flosch/pongo2/tags_firstof.go
generated
vendored
Normal file
49
Godeps/_workspace/src/github.com/flosch/pongo2/tags_firstof.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagFirstofNode struct {
|
||||||
|
position *Token
|
||||||
|
args []IEvaluator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagFirstofNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
for _, arg := range node.args {
|
||||||
|
val, err := arg.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if val.IsTrue() {
|
||||||
|
if ctx.Autoescape && !arg.FilterApplied("safe") {
|
||||||
|
val, err = ApplyFilter("escape", val, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteString(val.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
firstofNode := &tagFirstofNode{
|
||||||
|
position: start,
|
||||||
|
}
|
||||||
|
|
||||||
|
for arguments.Remaining() > 0 {
|
||||||
|
node, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
firstofNode.args = append(firstofNode.args, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstofNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("firstof", tagFirstofParser)
|
||||||
|
}
|
159
Godeps/_workspace/src/github.com/flosch/pongo2/tags_for.go
generated
vendored
Normal file
159
Godeps/_workspace/src/github.com/flosch/pongo2/tags_for.go
generated
vendored
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagForNode struct {
|
||||||
|
key string
|
||||||
|
value string // only for maps: for key, value in map
|
||||||
|
objectEvaluator IEvaluator
|
||||||
|
reversed bool
|
||||||
|
sorted bool
|
||||||
|
|
||||||
|
bodyWrapper *NodeWrapper
|
||||||
|
emptyWrapper *NodeWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
type tagForLoopInformation struct {
|
||||||
|
Counter int
|
||||||
|
Counter0 int
|
||||||
|
Revcounter int
|
||||||
|
Revcounter0 int
|
||||||
|
First bool
|
||||||
|
Last bool
|
||||||
|
Parentloop *tagForLoopInformation
|
||||||
|
}
|
||||||
|
|
||||||
|
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"]
|
||||||
|
|
||||||
|
// Create loop struct
|
||||||
|
loopInfo := &tagForLoopInformation{
|
||||||
|
First: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it a loop in a loop?
|
||||||
|
if parentloop != nil {
|
||||||
|
loopInfo.Parentloop = parentloop.(*tagForLoopInformation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register loopInfo in public context
|
||||||
|
forCtx.Private["forloop"] = loopInfo
|
||||||
|
|
||||||
|
obj, err := node.objectEvaluator.Evaluate(forCtx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.IterateOrder(func(idx, count int, key, value *Value) bool {
|
||||||
|
// There's something to iterate over (correct type and at least 1 item)
|
||||||
|
|
||||||
|
// Update loop infos and public context
|
||||||
|
forCtx.Private[node.key] = key
|
||||||
|
if value != nil {
|
||||||
|
forCtx.Private[node.value] = value
|
||||||
|
}
|
||||||
|
loopInfo.Counter = idx + 1
|
||||||
|
loopInfo.Counter0 = idx
|
||||||
|
if idx == 1 {
|
||||||
|
loopInfo.First = false
|
||||||
|
}
|
||||||
|
if idx+1 == count {
|
||||||
|
loopInfo.Last = true
|
||||||
|
}
|
||||||
|
loopInfo.Revcounter = count - idx // TODO: Not sure about this, have to look it up
|
||||||
|
loopInfo.Revcounter0 = count - (idx + 1) // TODO: Not sure about this, have to look it up
|
||||||
|
|
||||||
|
// Render elements with updated context
|
||||||
|
err := node.bodyWrapper.Execute(forCtx, writer)
|
||||||
|
if err != nil {
|
||||||
|
forError = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, func() {
|
||||||
|
// Nothing to iterate over (maybe wrong type or no items)
|
||||||
|
if node.emptyWrapper != nil {
|
||||||
|
err := node.emptyWrapper.Execute(forCtx, writer)
|
||||||
|
if err != nil {
|
||||||
|
forError = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, node.reversed, node.sorted)
|
||||||
|
|
||||||
|
return forError
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
forNode := &tagForNode{}
|
||||||
|
|
||||||
|
// Arguments parsing
|
||||||
|
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
|
||||||
|
valueToken = arguments.MatchType(TokenIdentifier)
|
||||||
|
if valueToken == nil {
|
||||||
|
return nil, arguments.Error("Value name must be an identifier.", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Match(TokenKeyword, "in") == nil {
|
||||||
|
return nil, arguments.Error("Expected keyword 'in'.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
objectEvaluator, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
forNode.objectEvaluator = objectEvaluator
|
||||||
|
forNode.key = keyToken.Val
|
||||||
|
if valueToken != nil {
|
||||||
|
forNode.value = valueToken.Val
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.MatchOne(TokenIdentifier, "reversed") != nil {
|
||||||
|
forNode.reversed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.MatchOne(TokenIdentifier, "sorted") != nil {
|
||||||
|
forNode.sorted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed for-loop arguments.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body wrapping
|
||||||
|
wrapper, endargs, err := doc.WrapUntilTag("empty", "endfor")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
forNode.bodyWrapper = wrapper
|
||||||
|
|
||||||
|
if endargs.Count() > 0 {
|
||||||
|
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wrapper.Endtag == "empty" {
|
||||||
|
// if there's an else in the if-statement, we need the else-Block as well
|
||||||
|
wrapper, endargs, err = doc.WrapUntilTag("endfor")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
forNode.emptyWrapper = wrapper
|
||||||
|
|
||||||
|
if endargs.Count() > 0 {
|
||||||
|
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return forNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("for", tagForParser)
|
||||||
|
}
|
76
Godeps/_workspace/src/github.com/flosch/pongo2/tags_if.go
generated
vendored
Normal file
76
Godeps/_workspace/src/github.com/flosch/pongo2/tags_if.go
generated
vendored
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagIfNode struct {
|
||||||
|
conditions []IEvaluator
|
||||||
|
wrappers []*NodeWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagIfNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
for i, condition := range node.conditions {
|
||||||
|
result, err := condition.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.IsTrue() {
|
||||||
|
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) {
|
||||||
|
ifNode := &tagIfNode{}
|
||||||
|
|
||||||
|
// Parse first and main IF condition
|
||||||
|
condition, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ifNode.conditions = append(ifNode.conditions, condition)
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("If-condition is malformed.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the rest
|
||||||
|
for {
|
||||||
|
wrapper, tagArgs, err := doc.WrapUntilTag("elif", "else", "endif")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ifNode.wrappers = append(ifNode.wrappers, wrapper)
|
||||||
|
|
||||||
|
if wrapper.Endtag == "elif" {
|
||||||
|
// elif can take a condition
|
||||||
|
condition, err = tagArgs.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ifNode.conditions = append(ifNode.conditions, condition)
|
||||||
|
|
||||||
|
if tagArgs.Remaining() > 0 {
|
||||||
|
return nil, tagArgs.Error("Elif-condition is malformed.", nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if tagArgs.Count() > 0 {
|
||||||
|
// else/endif can't take any conditions
|
||||||
|
return nil, tagArgs.Error("Arguments not allowed here.", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if wrapper.Endtag == "endif" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ifNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("if", tagIfParser)
|
||||||
|
}
|
116
Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifchanged.go
generated
vendored
Normal file
116
Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifchanged.go
generated
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagIfchangedNode struct {
|
||||||
|
watchedExpr []IEvaluator
|
||||||
|
lastValues []*Value
|
||||||
|
lastContent []byte
|
||||||
|
thenWrapper *NodeWrapper
|
||||||
|
elseWrapper *NodeWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
err := node.thenWrapper.Execute(ctx, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bufBytes := buf.Bytes()
|
||||||
|
if !bytes.Equal(node.lastContent, bufBytes) {
|
||||||
|
// Rendered content changed, output it
|
||||||
|
writer.Write(bufBytes)
|
||||||
|
node.lastContent = bufBytes
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nowValues := make([]*Value, 0, len(node.watchedExpr))
|
||||||
|
for _, expr := range node.watchedExpr {
|
||||||
|
val, err := expr.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nowValues = append(nowValues, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare old to new values now
|
||||||
|
changed := len(node.lastValues) == 0
|
||||||
|
|
||||||
|
for idx, oldVal := range node.lastValues {
|
||||||
|
if !oldVal.EqualValueTo(nowValues[idx]) {
|
||||||
|
changed = true
|
||||||
|
break // we can stop here because ONE value changed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.lastValues = nowValues
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
// Render thenWrapper
|
||||||
|
err := node.thenWrapper.Execute(ctx, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Render elseWrapper
|
||||||
|
err := node.elseWrapper.Execute(ctx, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
ifchangedNode := &tagIfchangedNode{}
|
||||||
|
|
||||||
|
for arguments.Remaining() > 0 {
|
||||||
|
// Parse condition
|
||||||
|
expr, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ifchangedNode.watchedExpr = append(ifchangedNode.watchedExpr, expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Ifchanged-arguments are malformed.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap then/else-blocks
|
||||||
|
wrapper, endargs, err := doc.WrapUntilTag("else", "endifchanged")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ifchangedNode.thenWrapper = wrapper
|
||||||
|
|
||||||
|
if endargs.Count() > 0 {
|
||||||
|
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
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("endifchanged")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ifchangedNode.elseWrapper = wrapper
|
||||||
|
|
||||||
|
if endargs.Count() > 0 {
|
||||||
|
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ifchangedNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("ifchanged", tagIfchangedParser)
|
||||||
|
}
|
78
Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifequal.go
generated
vendored
Normal file
78
Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifequal.go
generated
vendored
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagIfEqualNode struct {
|
||||||
|
var1, var2 IEvaluator
|
||||||
|
thenWrapper *NodeWrapper
|
||||||
|
elseWrapper *NodeWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagIfEqualNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
r1, err := node.var1.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r2, err := node.var2.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := r1.EqualValueTo(r2)
|
||||||
|
|
||||||
|
if result {
|
||||||
|
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) {
|
||||||
|
ifequalNode := &tagIfEqualNode{}
|
||||||
|
|
||||||
|
// Parse two expressions
|
||||||
|
var1, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var2, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ifequalNode.var1 = var1
|
||||||
|
ifequalNode.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")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ifequalNode.thenWrapper = wrapper
|
||||||
|
|
||||||
|
if endargs.Count() > 0 {
|
||||||
|
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ifequalNode.elseWrapper = wrapper
|
||||||
|
|
||||||
|
if endargs.Count() > 0 {
|
||||||
|
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ifequalNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("ifequal", tagIfEqualParser)
|
||||||
|
}
|
78
Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifnotequal.go
generated
vendored
Normal file
78
Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifnotequal.go
generated
vendored
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagIfNotEqualNode struct {
|
||||||
|
var1, var2 IEvaluator
|
||||||
|
thenWrapper *NodeWrapper
|
||||||
|
elseWrapper *NodeWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagIfNotEqualNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
r1, err := node.var1.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r2, err := node.var2.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := !r1.EqualValueTo(r2)
|
||||||
|
|
||||||
|
if result {
|
||||||
|
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) {
|
||||||
|
ifnotequalNode := &tagIfNotEqualNode{}
|
||||||
|
|
||||||
|
// Parse two expressions
|
||||||
|
var1, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var2, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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", "endifnotequal")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ifnotequalNode.thenWrapper = wrapper
|
||||||
|
|
||||||
|
if endargs.Count() > 0 {
|
||||||
|
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
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("endifnotequal")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ifnotequalNode.elseWrapper = wrapper
|
||||||
|
|
||||||
|
if endargs.Count() > 0 {
|
||||||
|
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ifnotequalNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("ifnotequal", tagIfNotEqualParser)
|
||||||
|
}
|
84
Godeps/_workspace/src/github.com/flosch/pongo2/tags_import.go
generated
vendored
Normal file
84
Godeps/_workspace/src/github.com/flosch/pongo2/tags_import.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagImportNode struct {
|
||||||
|
position *Token
|
||||||
|
filename string
|
||||||
|
macros map[string]*tagMacroNode // alias/name -> macro instance
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return macro.call(ctx, args...)
|
||||||
|
}
|
||||||
|
}(name, macro)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagImportParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
importNode := &tagImportNode{
|
||||||
|
position: start,
|
||||||
|
macros: make(map[string]*tagMacroNode),
|
||||||
|
}
|
||||||
|
|
||||||
|
filenameToken := arguments.MatchType(TokenString)
|
||||||
|
if filenameToken == nil {
|
||||||
|
return nil, arguments.Error("Import-tag needs a filename as string.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(importNode.filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, start)
|
||||||
|
}
|
||||||
|
|
||||||
|
for arguments.Remaining() > 0 {
|
||||||
|
macroNameToken := arguments.MatchType(TokenIdentifier)
|
||||||
|
if macroNameToken == nil {
|
||||||
|
return nil, arguments.Error("Expected macro name (identifier).", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
asName := macroNameToken.Val
|
||||||
|
if arguments.Match(TokenKeyword, "as") != nil {
|
||||||
|
aliasToken := arguments.MatchType(TokenIdentifier)
|
||||||
|
if aliasToken == nil {
|
||||||
|
return nil, arguments.Error("Expected macro alias name (identifier).", nil)
|
||||||
|
}
|
||||||
|
asName = aliasToken.Val
|
||||||
|
}
|
||||||
|
|
||||||
|
macroInstance, has := tpl.exportedMacros[macroNameToken.Val]
|
||||||
|
if !has {
|
||||||
|
return nil, arguments.Error(fmt.Sprintf("Macro '%s' not found (or not exported) in '%s'.", macroNameToken.Val,
|
||||||
|
importNode.filename), macroNameToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
importNode.macros[asName] = macroInstance
|
||||||
|
|
||||||
|
if arguments.Remaining() == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Match(TokenSymbol, ",") == nil {
|
||||||
|
return nil, arguments.Error("Expected ','.", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return importNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("import", tagImportParser)
|
||||||
|
}
|
146
Godeps/_workspace/src/github.com/flosch/pongo2/tags_include.go
generated
vendored
Normal file
146
Godeps/_workspace/src/github.com/flosch/pongo2/tags_include.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagIncludeNode struct {
|
||||||
|
tpl *Template
|
||||||
|
filenameEvaluator IEvaluator
|
||||||
|
lazy bool
|
||||||
|
only bool
|
||||||
|
filename string
|
||||||
|
withPairs map[string]IEvaluator
|
||||||
|
ifExists bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagIncludeNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
// Building the context for the template
|
||||||
|
includeCtx := make(Context)
|
||||||
|
|
||||||
|
// Fill the context with all data from the parent
|
||||||
|
if !node.only {
|
||||||
|
includeCtx.Update(ctx.Public)
|
||||||
|
includeCtx.Update(ctx.Private)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put all custom with-pairs into the context
|
||||||
|
for key, value := range node.withPairs {
|
||||||
|
val, err := value.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
includeCtx[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the template
|
||||||
|
if node.lazy {
|
||||||
|
// Evaluate the filename
|
||||||
|
filename, err := node.filenameEvaluator.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if filename.String() == "" {
|
||||||
|
return ctx.Error("Filename for 'include'-tag evaluated to an empty string.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get include-filename
|
||||||
|
includedFilename := ctx.template.set.resolveFilename(ctx.template, filename.String())
|
||||||
|
|
||||||
|
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 = includedTpl.ExecuteWriter(includeCtx, writer)
|
||||||
|
if err2 != nil {
|
||||||
|
return err2.(*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) {
|
||||||
|
includeNode := &tagIncludeNode{
|
||||||
|
withPairs: make(map[string]IEvaluator),
|
||||||
|
}
|
||||||
|
|
||||||
|
if filenameToken := arguments.MatchType(TokenString); filenameToken != nil {
|
||||||
|
// prepared, static template
|
||||||
|
|
||||||
|
// "if_exists" flag
|
||||||
|
ifExists := arguments.Match(TokenIdentifier, "if_exists") != nil
|
||||||
|
|
||||||
|
// Get include-filename
|
||||||
|
includedFilename := doc.template.set.resolveFilename(doc.template, filenameToken.Val)
|
||||||
|
|
||||||
|
// Parse the parent
|
||||||
|
includeNode.filename = includedFilename
|
||||||
|
includedTpl, err := doc.template.set.FromFile(includedFilename)
|
||||||
|
if err != nil {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
includeNode.tpl = includedTpl
|
||||||
|
} else {
|
||||||
|
// No String, then the user wants to use lazy-evaluation (slower, but possible)
|
||||||
|
filenameEvaluator, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err.updateFromTokenIfNeeded(doc.template, filenameToken)
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
valueExpr, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err.updateFromTokenIfNeeded(doc.template, keyToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
includeNode.withPairs[keyToken.Val] = valueExpr
|
||||||
|
|
||||||
|
// Only?
|
||||||
|
if arguments.Match(TokenIdentifier, "only") != nil {
|
||||||
|
includeNode.only = true
|
||||||
|
break // stop parsing arguments because it's the last option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed 'include'-tag arguments.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return includeNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("include", tagIncludeParser)
|
||||||
|
}
|
131
Godeps/_workspace/src/github.com/flosch/pongo2/tags_lorem.go
generated
vendored
Normal file
131
Godeps/_workspace/src/github.com/flosch/pongo2/tags_lorem.go
generated
vendored
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
tagLoremParagraphs = strings.Split(tagLoremText, "\n")
|
||||||
|
tagLoremWords = strings.Fields(tagLoremText)
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagLoremNode struct {
|
||||||
|
position *Token
|
||||||
|
count int // number of paragraphs
|
||||||
|
method string // w = words, p = HTML paragraphs, b = plain-text (default is b)
|
||||||
|
random bool // does not use the default paragraph "Lorem ipsum dolor sit amet, ..."
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
writer.WriteString("\n")
|
||||||
|
}
|
||||||
|
par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))]
|
||||||
|
writer.WriteString(par)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < node.count; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
writer.WriteString("\n")
|
||||||
|
}
|
||||||
|
par := tagLoremParagraphs[i%len(tagLoremParagraphs)]
|
||||||
|
writer.WriteString(par)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "w":
|
||||||
|
if node.random {
|
||||||
|
for i := 0; i < node.count; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
writer.WriteString(" ")
|
||||||
|
}
|
||||||
|
word := tagLoremWords[rand.Intn(len(tagLoremWords))]
|
||||||
|
writer.WriteString(word)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < node.count; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
writer.WriteString(" ")
|
||||||
|
}
|
||||||
|
word := tagLoremWords[i%len(tagLoremWords)]
|
||||||
|
writer.WriteString(word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "p":
|
||||||
|
if node.random {
|
||||||
|
for i := 0; i < node.count; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
writer.WriteString("\n")
|
||||||
|
}
|
||||||
|
writer.WriteString("<p>")
|
||||||
|
par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))]
|
||||||
|
writer.WriteString(par)
|
||||||
|
writer.WriteString("</p>")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < node.count; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
writer.WriteString("\n")
|
||||||
|
}
|
||||||
|
writer.WriteString("<p>")
|
||||||
|
par := tagLoremParagraphs[i%len(tagLoremParagraphs)]
|
||||||
|
writer.WriteString(par)
|
||||||
|
writer.WriteString("</p>")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unsupported method")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagLoremParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
loremNode := &tagLoremNode{
|
||||||
|
position: start,
|
||||||
|
count: 1,
|
||||||
|
method: "b",
|
||||||
|
}
|
||||||
|
|
||||||
|
if countToken := arguments.MatchType(TokenNumber); countToken != nil {
|
||||||
|
loremNode.count = AsValue(countToken.Val).Integer()
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
loremNode.method = methodToken.Val
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.MatchOne(TokenIdentifier, "random") != nil {
|
||||||
|
loremNode.random = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed lorem-tag arguments.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return loremNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().Unix())
|
||||||
|
|
||||||
|
RegisterTag("lorem", tagLoremParser)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagLoremText = `Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||||
|
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
|
||||||
|
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
|
||||||
|
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
|
||||||
|
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.
|
||||||
|
At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.
|
||||||
|
Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.`
|
149
Godeps/_workspace/src/github.com/flosch/pongo2/tags_macro.go
generated
vendored
Normal file
149
Godeps/_workspace/src/github.com/flosch/pongo2/tags_macro.go
generated
vendored
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagMacroNode struct {
|
||||||
|
position *Token
|
||||||
|
name string
|
||||||
|
argsOrder []string
|
||||||
|
args map[string]IEvaluator
|
||||||
|
exported bool
|
||||||
|
|
||||||
|
wrapper *NodeWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagMacroNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
ctx.Private[node.name] = func(args ...*Value) *Value {
|
||||||
|
return node.call(ctx, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value {
|
||||||
|
argsCtx := make(Context)
|
||||||
|
|
||||||
|
for k, v := range node.args {
|
||||||
|
if v == nil {
|
||||||
|
// User did not provided a default value
|
||||||
|
argsCtx[k] = nil
|
||||||
|
} else {
|
||||||
|
// Evaluate the default value
|
||||||
|
valueExpr, err := v.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logf(err.Error())
|
||||||
|
return AsSafeValue(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
argsCtx[k] = valueExpr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a context for the macro execution
|
||||||
|
macroCtx := NewChildExecutionContext(ctx)
|
||||||
|
|
||||||
|
// Register all arguments in the private context
|
||||||
|
macroCtx.Private.Update(argsCtx)
|
||||||
|
|
||||||
|
for idx, argValue := range args {
|
||||||
|
macroCtx.Private[node.argsOrder[idx]] = argValue.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
err := node.wrapper.Execute(macroCtx, &b)
|
||||||
|
if err != nil {
|
||||||
|
return AsSafeValue(err.updateFromTokenIfNeeded(ctx.template, node.position).Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsSafeValue(b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
macroNode := &tagMacroNode{
|
||||||
|
position: start,
|
||||||
|
args: make(map[string]IEvaluator),
|
||||||
|
}
|
||||||
|
|
||||||
|
nameToken := arguments.MatchType(TokenIdentifier)
|
||||||
|
if nameToken == nil {
|
||||||
|
return nil, arguments.Error("Macro-tag needs at least an identifier as name.", nil)
|
||||||
|
}
|
||||||
|
macroNode.name = nameToken.Val
|
||||||
|
|
||||||
|
if arguments.MatchOne(TokenSymbol, "(") == nil {
|
||||||
|
return nil, arguments.Error("Expected '('.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
for arguments.Match(TokenSymbol, ")") == nil {
|
||||||
|
argNameToken := arguments.MatchType(TokenIdentifier)
|
||||||
|
if argNameToken == nil {
|
||||||
|
return nil, arguments.Error("Expected argument name as identifier.", nil)
|
||||||
|
}
|
||||||
|
macroNode.argsOrder = append(macroNode.argsOrder, argNameToken.Val)
|
||||||
|
|
||||||
|
if arguments.Match(TokenSymbol, "=") != nil {
|
||||||
|
// Default expression follows
|
||||||
|
argDefaultExpr, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
macroNode.args[argNameToken.Val] = argDefaultExpr
|
||||||
|
} else {
|
||||||
|
// No default expression
|
||||||
|
macroNode.args[argNameToken.Val] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Match(TokenSymbol, ")") != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if arguments.Match(TokenSymbol, ",") == nil {
|
||||||
|
return nil, arguments.Error("Expected ',' or ')'.", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Match(TokenKeyword, "export") != nil {
|
||||||
|
macroNode.exported = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed macro-tag.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body wrapping
|
||||||
|
wrapper, endargs, err := doc.WrapUntilTag("endmacro")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
macroNode.wrapper = wrapper
|
||||||
|
|
||||||
|
if endargs.Count() > 0 {
|
||||||
|
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if macroNode.exported {
|
||||||
|
// Now register the macro if it wants to be exported
|
||||||
|
_, has := doc.template.exportedMacros[macroNode.name]
|
||||||
|
if has {
|
||||||
|
return nil, doc.Error(fmt.Sprintf("Another macro with name '%s' already exported.", macroNode.name), start)
|
||||||
|
}
|
||||||
|
doc.template.exportedMacros[macroNode.name] = macroNode
|
||||||
|
}
|
||||||
|
|
||||||
|
return macroNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("macro", tagMacroParser)
|
||||||
|
}
|
50
Godeps/_workspace/src/github.com/flosch/pongo2/tags_now.go
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/flosch/pongo2/tags_now.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagNowNode struct {
|
||||||
|
position *Token
|
||||||
|
format string
|
||||||
|
fake bool
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
t = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteString(t.Format(node.format))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagNowParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
nowNode := &tagNowNode{
|
||||||
|
position: start,
|
||||||
|
}
|
||||||
|
|
||||||
|
formatToken := arguments.MatchType(TokenString)
|
||||||
|
if formatToken == nil {
|
||||||
|
return nil, arguments.Error("Expected a format string.", nil)
|
||||||
|
}
|
||||||
|
nowNode.format = formatToken.Val
|
||||||
|
|
||||||
|
if arguments.MatchOne(TokenIdentifier, "fake") != nil {
|
||||||
|
nowNode.fake = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed now-tag arguments.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nowNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("now", tagNowParser)
|
||||||
|
}
|
50
Godeps/_workspace/src/github.com/flosch/pongo2/tags_set.go
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/flosch/pongo2/tags_set.go
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagSetNode struct {
|
||||||
|
name string
|
||||||
|
expression IEvaluator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagSetNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
// Evaluate expression
|
||||||
|
value, err := node.expression.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Private[node.name] = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagSetParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
node := &tagSetNode{}
|
||||||
|
|
||||||
|
// Parse variable name
|
||||||
|
typeToken := arguments.MatchType(TokenIdentifier)
|
||||||
|
if typeToken == nil {
|
||||||
|
return nil, arguments.Error("Expected an identifier.", nil)
|
||||||
|
}
|
||||||
|
node.name = typeToken.Val
|
||||||
|
|
||||||
|
if arguments.Match(TokenSymbol, "=") == nil {
|
||||||
|
return nil, arguments.Error("Expected '='.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable expression
|
||||||
|
keyExpression, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
node.expression = keyExpression
|
||||||
|
|
||||||
|
// Remaining arguments
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed 'set'-tag arguments.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("set", tagSetParser)
|
||||||
|
}
|
54
Godeps/_workspace/src/github.com/flosch/pongo2/tags_spaceless.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/flosch/pongo2/tags_spaceless.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagSpacelessNode struct {
|
||||||
|
wrapper *NodeWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
var tagSpacelessRegexp = regexp.MustCompile(`(?U:(<.*>))([\t\n\v\f\r ]+)(?U:(<.*>))`)
|
||||||
|
|
||||||
|
func (node *tagSpacelessNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
b := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB
|
||||||
|
|
||||||
|
err := node.wrapper.Execute(ctx, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := b.String()
|
||||||
|
// Repeat this recursively
|
||||||
|
changed := true
|
||||||
|
for changed {
|
||||||
|
s2 := tagSpacelessRegexp.ReplaceAllString(s, "$1$3")
|
||||||
|
changed = s != s2
|
||||||
|
s = s2
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteString(s)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagSpacelessParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
spacelessNode := &tagSpacelessNode{}
|
||||||
|
|
||||||
|
wrapper, _, err := doc.WrapUntilTag("endspaceless")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
spacelessNode.wrapper = wrapper
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed spaceless-tag arguments.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return spacelessNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("spaceless", tagSpacelessParser)
|
||||||
|
}
|
68
Godeps/_workspace/src/github.com/flosch/pongo2/tags_ssi.go
generated
vendored
Normal file
68
Godeps/_workspace/src/github.com/flosch/pongo2/tags_ssi.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagSSINode struct {
|
||||||
|
filename string
|
||||||
|
content string
|
||||||
|
template *Template
|
||||||
|
}
|
||||||
|
|
||||||
|
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.execute(includeCtx, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err.(*Error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Just print out the content
|
||||||
|
writer.WriteString(node.content)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagSSIParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
SSINode := &tagSSINode{}
|
||||||
|
|
||||||
|
if fileToken := arguments.MatchType(TokenString); fileToken != nil {
|
||||||
|
SSINode.filename = fileToken.Val
|
||||||
|
|
||||||
|
if arguments.Match(TokenIdentifier, "parsed") != nil {
|
||||||
|
// parsed
|
||||||
|
temporaryTpl, err := doc.template.set.FromFile(doc.template.set.resolveFilename(doc.template, fileToken.Val))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, fileToken)
|
||||||
|
}
|
||||||
|
SSINode.template = temporaryTpl
|
||||||
|
} else {
|
||||||
|
// plaintext
|
||||||
|
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, fileToken)
|
||||||
|
}
|
||||||
|
SSINode.content = string(buf)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, arguments.Error("First argument must be a string.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed SSI-tag argument.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return SSINode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("ssi", tagSSIParser)
|
||||||
|
}
|
45
Godeps/_workspace/src/github.com/flosch/pongo2/tags_templatetag.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/flosch/pongo2/tags_templatetag.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagTemplateTagNode struct {
|
||||||
|
content string
|
||||||
|
}
|
||||||
|
|
||||||
|
var templateTagMapping = map[string]string{
|
||||||
|
"openblock": "{%",
|
||||||
|
"closeblock": "%}",
|
||||||
|
"openvariable": "{{",
|
||||||
|
"closevariable": "}}",
|
||||||
|
"openbrace": "{",
|
||||||
|
"closebrace": "}",
|
||||||
|
"opencomment": "{#",
|
||||||
|
"closecomment": "#}",
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
ttNode := &tagTemplateTagNode{}
|
||||||
|
|
||||||
|
if argToken := arguments.MatchType(TokenIdentifier); argToken != nil {
|
||||||
|
output, found := templateTagMapping[argToken.Val]
|
||||||
|
if !found {
|
||||||
|
return nil, arguments.Error("Argument not found", argToken)
|
||||||
|
}
|
||||||
|
ttNode.content = output
|
||||||
|
} else {
|
||||||
|
return nil, arguments.Error("Identifier expected.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed templatetag-tag argument.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ttNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("templatetag", tagTemplateTagParser)
|
||||||
|
}
|
83
Godeps/_workspace/src/github.com/flosch/pongo2/tags_widthratio.go
generated
vendored
Normal file
83
Godeps/_workspace/src/github.com/flosch/pongo2/tags_widthratio.go
generated
vendored
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagWidthratioNode struct {
|
||||||
|
position *Token
|
||||||
|
current, max IEvaluator
|
||||||
|
width IEvaluator
|
||||||
|
ctxName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *tagWidthratioNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||||
|
current, err := node.current.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
max, err := node.max.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
width, err := node.width.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := int(math.Ceil(current.Float()/max.Float()*width.Float() + 0.5))
|
||||||
|
|
||||||
|
if node.ctxName == "" {
|
||||||
|
writer.WriteString(fmt.Sprintf("%d", value))
|
||||||
|
} else {
|
||||||
|
ctx.Private[node.ctxName] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagWidthratioParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
widthratioNode := &tagWidthratioNode{
|
||||||
|
position: start,
|
||||||
|
}
|
||||||
|
|
||||||
|
current, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
widthratioNode.current = current
|
||||||
|
|
||||||
|
max, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
widthratioNode.max = max
|
||||||
|
|
||||||
|
width, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
widthratioNode.width = width
|
||||||
|
|
||||||
|
if arguments.MatchOne(TokenKeyword, "as") != nil {
|
||||||
|
// Name follows
|
||||||
|
nameToken := arguments.MatchType(TokenIdentifier)
|
||||||
|
if nameToken == nil {
|
||||||
|
return nil, arguments.Error("Expected name (identifier).", nil)
|
||||||
|
}
|
||||||
|
widthratioNode.ctxName = nameToken.Val
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Remaining() > 0 {
|
||||||
|
return nil, arguments.Error("Malformed widthratio-tag arguments.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return widthratioNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("widthratio", tagWidthratioParser)
|
||||||
|
}
|
88
Godeps/_workspace/src/github.com/flosch/pongo2/tags_with.go
generated
vendored
Normal file
88
Godeps/_workspace/src/github.com/flosch/pongo2/tags_with.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
type tagWithNode struct {
|
||||||
|
withPairs map[string]IEvaluator
|
||||||
|
wrapper *NodeWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
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.withPairs {
|
||||||
|
val, err := value.Evaluate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
withctx.Private[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.wrapper.Execute(withctx, writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||||
|
withNode := &tagWithNode{
|
||||||
|
withPairs: make(map[string]IEvaluator),
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.Count() == 0 {
|
||||||
|
return nil, arguments.Error("Tag 'with' requires at least one argument.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper, endargs, err := doc.WrapUntilTag("endwith")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withNode.wrapper = wrapper
|
||||||
|
|
||||||
|
if endargs.Count() > 0 {
|
||||||
|
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
oldStyle := false // by default we're using the new_style
|
||||||
|
for i := 0; i < arguments.Count(); i++ {
|
||||||
|
if arguments.PeekN(i, TokenKeyword, "as") != nil {
|
||||||
|
oldStyle = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for arguments.Remaining() > 0 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
keyToken := arguments.MatchType(TokenIdentifier)
|
||||||
|
if keyToken == nil {
|
||||||
|
return nil, arguments.Error("Expected an identifier", nil)
|
||||||
|
}
|
||||||
|
withNode.withPairs[keyToken.Val] = valueExpr
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
valueExpr, err := arguments.ParseExpression()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
withNode.withPairs[keyToken.Val] = valueExpr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return withNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterTag("with", tagWithParser)
|
||||||
|
}
|
193
Godeps/_workspace/src/github.com/flosch/pongo2/template.go
generated
vendored
Normal file
193
Godeps/_workspace/src/github.com/flosch/pongo2/template.go
generated
vendored
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
exportedMacros map[string]*tagMacroNode
|
||||||
|
|
||||||
|
// Output
|
||||||
|
root *nodeDocument
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTemplateString(set *TemplateSet, tpl []byte) (*Template, error) {
|
||||||
|
return newTemplate(set, "<string>", true, tpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTemplate(set *TemplateSet, name string, isTplString bool, tpl []byte) (*Template, error) {
|
||||||
|
strTpl := string(tpl)
|
||||||
|
|
||||||
|
// Create the template
|
||||||
|
t := &Template{
|
||||||
|
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, strTpl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.tokens = tokens
|
||||||
|
|
||||||
|
// For debugging purposes, show all tokens:
|
||||||
|
/*for i, t := range tokens {
|
||||||
|
fmt.Printf("%3d. %s\n", i, t)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Parse it
|
||||||
|
err = t.parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tpl *Template) execute(context Context, writer TemplateWriter) error {
|
||||||
|
// Determine the parent to be executed (for template inheritance)
|
||||||
|
parent := tpl
|
||||||
|
for parent.parent != nil {
|
||||||
|
parent = parent.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create context if none is given
|
||||||
|
newContext := make(Context)
|
||||||
|
newContext.Update(tpl.set.Globals)
|
||||||
|
|
||||||
|
if context != nil {
|
||||||
|
newContext.Update(context)
|
||||||
|
|
||||||
|
if len(newContext) > 0 {
|
||||||
|
// Check for context name syntax
|
||||||
|
err := newContext.checkForValidIdentifiers()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for clashes with macro names
|
||||||
|
for k := range newContext {
|
||||||
|
_, has := tpl.exportedMacros[k]
|
||||||
|
if has {
|
||||||
|
return &Error{
|
||||||
|
Filename: tpl.name,
|
||||||
|
Sender: "execution",
|
||||||
|
ErrorMsg: fmt.Sprintf("Context key name '%s' clashes with macro '%s'.", k, k),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create operational context
|
||||||
|
ctx := newExecutionContext(parent, newContext)
|
||||||
|
|
||||||
|
// Run the selected document
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes the template with the given context and writes to writer (io.Writer)
|
||||||
|
// 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 {
|
||||||
|
buf, err := tpl.newBufferAndExecute(context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, 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.newBufferAndExecute(context)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes the template and returns the rendered template as a string
|
||||||
|
func (tpl *Template) Execute(context Context) (string, error) {
|
||||||
|
// Execute template
|
||||||
|
buffer, err := tpl.newBufferAndExecute(context)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.String(), nil
|
||||||
|
|
||||||
|
}
|
156
Godeps/_workspace/src/github.com/flosch/pongo2/template_loader.go
generated
vendored
Normal file
156
Godeps/_workspace/src/github.com/flosch/pongo2/template_loader.go
generated
vendored
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 fmt.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 = ""
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
*/
|
239
Godeps/_workspace/src/github.com/flosch/pongo2/template_sets.go
generated
vendored
Normal file
239
Godeps/_workspace/src/github.com/flosch/pongo2/template_sets.go
generated
vendored
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
package pongo2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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
|
||||||
|
// variable during program execution (and template compilation/execution).
|
||||||
|
Debug bool
|
||||||
|
|
||||||
|
// Sandbox features
|
||||||
|
// - Disallow access to specific tags and/or filters (using BanTag() and BanFilter())
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// Template cache (for FromCache())
|
||||||
|
templateCache map[string]*Template
|
||||||
|
templateCacheMutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
templateCache: make(map[string]*Template),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (set *TemplateSet) resolveFilename(tpl *Template, path string) string {
|
||||||
|
name := ""
|
||||||
|
if tpl != nil && tpl.isTplString {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if tpl != nil {
|
||||||
|
name = tpl.name
|
||||||
|
}
|
||||||
|
return set.loader.Abs(name, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return fmt.Errorf("Tag '%s' not found.", name)
|
||||||
|
}
|
||||||
|
if set.firstTemplateCreated {
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("Tag '%s' is already banned.", name)
|
||||||
|
}
|
||||||
|
set.bannedTags[name] = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return fmt.Errorf("Filter '%s' not found.", name)
|
||||||
|
}
|
||||||
|
if set.firstTemplateCreated {
|
||||||
|
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 {
|
||||||
|
return fmt.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
|
||||||
|
// 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).
|
||||||
|
func (set *TemplateSet) FromCache(filename string) (*Template, error) {
|
||||||
|
if set.Debug {
|
||||||
|
// Recompile on any request
|
||||||
|
return set.FromFile(filename)
|
||||||
|
}
|
||||||
|
// Cache the template
|
||||||
|
cleanedFilename := set.resolveFilename(nil, filename)
|
||||||
|
|
||||||
|
set.templateCacheMutex.Lock()
|
||||||
|
defer set.templateCacheMutex.Unlock()
|
||||||
|
|
||||||
|
tpl, has := set.templateCache[cleanedFilename]
|
||||||
|
|
||||||
|
// Cache miss
|
||||||
|
if !has {
|
||||||
|
tpl, err := set.FromFile(cleanedFilename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
set.templateCache[cleanedFilename] = tpl
|
||||||
|
return tpl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache hit
|
||||||
|
return tpl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromFile loads a template from a filename and returns a Template instance.
|
||||||
|
func (set *TemplateSet) FromFile(filename string) (*Template, error) {
|
||||||
|
set.firstTemplateCreated = true
|
||||||
|
|
||||||
|
fd, err := set.loader.Get(set.resolveFilename(nil, filename))
|
||||||
|
if err != nil {
|
||||||
|
return nil, &Error{
|
||||||
|
Filename: filename,
|
||||||
|
Sender: "fromfile",
|
||||||
|
ErrorMsg: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf, err := ioutil.ReadAll(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &Error{
|
||||||
|
Filename: filename,
|
||||||
|
Sender: "fromfile",
|
||||||
|
ErrorMsg: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTemplate(set, filename, false, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderTemplateString is a shortcut and 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 {
|
||||||
|
set.firstTemplateCreated = true
|
||||||
|
|
||||||
|
tpl := Must(set.FromString(s))
|
||||||
|
result, err := tpl.Execute(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderTemplateFile is a shortcut and 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 {
|
||||||
|
set.firstTemplateCreated = true
|
||||||
|
|
||||||
|
tpl := Must(set.FromFile(fn))
|
||||||
|
result, err := tpl.Execute(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (set *TemplateSet) logf(format string, args ...interface{}) {
|
||||||
|
if set.Debug {
|
||||||
|
logger.Printf(fmt.Sprintf("[template set: %s] %s", set.name, format), args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging function (internally used)
|
||||||
|
func logf(format string, items ...interface{}) {
|
||||||
|
if debug {
|
||||||
|
logger.Printf(format, items...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
debug bool // internal debugging
|
||||||
|
logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags|log.Lshortfile)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
FromFile = DefaultSet.FromFile
|
||||||
|
FromCache = DefaultSet.FromCache
|
||||||
|
RenderTemplateString = DefaultSet.RenderTemplateString
|
||||||
|
RenderTemplateFile = DefaultSet.RenderTemplateFile
|
||||||
|
|
||||||
|
// Globals for the default set
|
||||||
|
Globals = DefaultSet.Globals
|
||||||
|
)
|
10
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/autoescape.tpl
generated
vendored
Normal file
10
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/autoescape.tpl
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{{ "<script>alert('xss');</script>" }}
|
||||||
|
{% autoescape off %}
|
||||||
|
{{ "<script>alert('xss');</script>" }}
|
||||||
|
{% endautoescape %}
|
||||||
|
{% autoescape on %}
|
||||||
|
{{ "<script>alert('xss');</script>" }}
|
||||||
|
{% endautoescape %}
|
||||||
|
{% autoescape off %}
|
||||||
|
{{ "<script>alert('xss');</script>"|escape }}
|
||||||
|
{% endautoescape %}
|
9
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/autoescape.tpl.out
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/autoescape.tpl.out
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<script>alert('xss');</script>
|
||||||
|
|
||||||
|
<script>alert('xss');</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>alert('xss');</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>alert('xss');</script>
|
1
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/base_dir_test/base.html
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/base_dir_test/base.html
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Hello from {{ base_directory }}
|
1
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/base_dir_test/subdir/include.html
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/base_dir_test/subdir/include.html
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{% include "base.html" %}
|
1
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/base_dir_test/subdir/index.html
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/base_dir_test/subdir/index.html
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{% extends "base.html" %}
|
1
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/base_dir_test/subdir/ssi.html
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/base_dir_test/subdir/ssi.html
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{% ssi "base.html" parsed %}
|
32
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/complex.tpl
generated
vendored
Normal file
32
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/complex.tpl
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{# A more complex template using pongo2 (fully django-compatible template) #}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>My blog page</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Blogpost</h1>
|
||||||
|
<div id="content">
|
||||||
|
{{ complex.post.Text|safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>Comments</h1>
|
||||||
|
|
||||||
|
{% for comment in complex.comments %}
|
||||||
|
<h2>{{ forloop.Counter }}. Comment ({{ forloop.Revcounter}} comment{{ forloop.Revcounter|pluralize:"s" }} left)</h2>
|
||||||
|
<p>From: {{ comment.Author.Name }} ({{ comment.Author.Validated|yesno:"validated,not validated,unknown validation status" }})</p>
|
||||||
|
|
||||||
|
{% if complex.is_admin(comment.Author) %}
|
||||||
|
<p>This user is an admin (verify: {{ comment.Author.Is_admin }})!</p>
|
||||||
|
{% else %}
|
||||||
|
<p>This user is not admin!</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<p>Written {{ comment.Date }}</p>
|
||||||
|
<p>{{ comment.Text|striptags }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
50
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/complex.tpl.out
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/complex.tpl.out
generated
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>My blog page</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Blogpost</h1>
|
||||||
|
<div id="content">
|
||||||
|
<h2>Hello!</h2><p>Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>Comments</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<h2>1. Comment (3 comments left)</h2>
|
||||||
|
<p>From: user1 (validated)</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>This user is not admin!</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>Written 2014-06-10 15:30:15 +0000 UTC</p>
|
||||||
|
<p>"pongo2 is nice!"</p>
|
||||||
|
|
||||||
|
<h2>2. Comment (2 comments left)</h2>
|
||||||
|
<p>From: user2 (validated)</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>This user is an admin (verify: True)!</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>Written 2011-03-21 08:37:56.000000012 +0000 UTC</p>
|
||||||
|
<p>comment2 with unsafe tags in it</p>
|
||||||
|
|
||||||
|
<h2>3. Comment (1 comment left)</h2>
|
||||||
|
<p>From: user3 (not validated)</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>This user is not admin!</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>Written 2014-06-10 15:30:15 +0000 UTC</p>
|
||||||
|
<p>hello! there</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
22
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/cycle.tpl
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/cycle.tpl
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{% for item in simple.multiple_item_list %}
|
||||||
|
'{% cycle "item1" simple.name simple.number %}'
|
||||||
|
{% endfor %}
|
||||||
|
{% for item in simple.multiple_item_list %}
|
||||||
|
'{% cycle "item1" simple.name simple.number as cycleitem %}'
|
||||||
|
May I present the cycle item again: '{{ cycleitem }}'
|
||||||
|
{% endfor %}
|
||||||
|
{% for item in simple.multiple_item_list %}
|
||||||
|
'{% cycle "item1" simple.name simple.number as cycleitem silent %}'
|
||||||
|
May I present the cycle item: '{{ cycleitem }}'
|
||||||
|
{% endfor %}
|
||||||
|
{% for item in simple.multiple_item_list %}
|
||||||
|
'{% cycle "item1" simple.name simple.number as cycleitem silent %}'
|
||||||
|
May I present the cycle item: '{{ cycleitem }}'
|
||||||
|
{% include "inheritance/cycle_include.tpl" %}
|
||||||
|
{% endfor %}
|
||||||
|
'{% cycle "item1" simple.name simple.number as cycleitem %}'
|
||||||
|
'{% cycle cycleitem %}'
|
||||||
|
'{% cycle "item1" simple.name simple.number as cycleitem silent %}'
|
||||||
|
'{{ cycleitem }}'
|
||||||
|
'{% cycle cycleitem %}'
|
||||||
|
'{{ cycleitem }}'
|
130
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/cycle.tpl.out
generated
vendored
Normal file
130
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/cycle.tpl.out
generated
vendored
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
|
||||||
|
'item1'
|
||||||
|
|
||||||
|
'john doe'
|
||||||
|
|
||||||
|
'42'
|
||||||
|
|
||||||
|
'item1'
|
||||||
|
|
||||||
|
'john doe'
|
||||||
|
|
||||||
|
'42'
|
||||||
|
|
||||||
|
'item1'
|
||||||
|
|
||||||
|
'john doe'
|
||||||
|
|
||||||
|
'42'
|
||||||
|
|
||||||
|
'item1'
|
||||||
|
|
||||||
|
|
||||||
|
'item1'
|
||||||
|
May I present the cycle item again: 'item1'
|
||||||
|
|
||||||
|
'john doe'
|
||||||
|
May I present the cycle item again: 'john doe'
|
||||||
|
|
||||||
|
'42'
|
||||||
|
May I present the cycle item again: '42'
|
||||||
|
|
||||||
|
'item1'
|
||||||
|
May I present the cycle item again: 'item1'
|
||||||
|
|
||||||
|
'john doe'
|
||||||
|
May I present the cycle item again: 'john doe'
|
||||||
|
|
||||||
|
'42'
|
||||||
|
May I present the cycle item again: '42'
|
||||||
|
|
||||||
|
'item1'
|
||||||
|
May I present the cycle item again: 'item1'
|
||||||
|
|
||||||
|
'john doe'
|
||||||
|
May I present the cycle item again: 'john doe'
|
||||||
|
|
||||||
|
'42'
|
||||||
|
May I present the cycle item again: '42'
|
||||||
|
|
||||||
|
'item1'
|
||||||
|
May I present the cycle item again: 'item1'
|
||||||
|
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'item1'
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'john doe'
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: '42'
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'item1'
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'john doe'
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: '42'
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'item1'
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'john doe'
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: '42'
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'item1'
|
||||||
|
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'item1'
|
||||||
|
Included 'item1'.
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'john doe'
|
||||||
|
Included 'john doe'.
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: '42'
|
||||||
|
Included '42'.
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'item1'
|
||||||
|
Included 'item1'.
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'john doe'
|
||||||
|
Included 'john doe'.
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: '42'
|
||||||
|
Included '42'.
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'item1'
|
||||||
|
Included 'item1'.
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'john doe'
|
||||||
|
Included 'john doe'.
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: '42'
|
||||||
|
Included '42'.
|
||||||
|
|
||||||
|
''
|
||||||
|
May I present the cycle item: 'item1'
|
||||||
|
Included 'item1'.
|
||||||
|
|
||||||
|
'item1'
|
||||||
|
'john doe'
|
||||||
|
''
|
||||||
|
'item1'
|
||||||
|
''
|
||||||
|
'john doe'
|
0
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/empty.tpl
generated
vendored
Normal file
0
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/empty.tpl
generated
vendored
Normal file
0
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/empty.tpl.out
generated
vendored
Normal file
0
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/empty.tpl.out
generated
vendored
Normal file
69
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/expressions.tpl
generated
vendored
Normal file
69
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/expressions.tpl
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
integers and complex expressions
|
||||||
|
{{ 10-100 }}
|
||||||
|
{{ -(10-100) }}
|
||||||
|
{{ -(-(10-100)) }}
|
||||||
|
{{ -1 * (-(-(10-100))) }}
|
||||||
|
{{ -1 * (-(-(10-100)) ^ 2) ^ 3 + 3 * (5 - 17) + 1 + 2 }}
|
||||||
|
|
||||||
|
floats
|
||||||
|
{{ 5.5 }}
|
||||||
|
{{ 5.172841 }}
|
||||||
|
{{ 5.5 - 1.5 == 4 }}
|
||||||
|
{{ 5.5 - 1.5 == 4.0 }}
|
||||||
|
|
||||||
|
mul/div
|
||||||
|
{{ 2 * 5 }}
|
||||||
|
{{ 2 * 5.0 }}
|
||||||
|
{{ 2 * 0 }}
|
||||||
|
{{ 2.5 * 5.3 }}
|
||||||
|
{{ 1/2 }}
|
||||||
|
{{ 1/2.0 }}
|
||||||
|
{{ 1/0.000001 }}
|
||||||
|
|
||||||
|
logic expressions
|
||||||
|
{{ !true }}
|
||||||
|
{{ !(true || false) }}
|
||||||
|
{{ true || false }}
|
||||||
|
{{ true or false }}
|
||||||
|
{{ false or false }}
|
||||||
|
{{ false || false }}
|
||||||
|
{{ true && (true && (true && (true && (1 == 1 || false)))) }}
|
||||||
|
|
||||||
|
float comparison
|
||||||
|
{{ 5.5 <= 5.5 }}
|
||||||
|
{{ 5.5 < 5.5 }}
|
||||||
|
{{ 5.5 > 5.5 }}
|
||||||
|
{{ 5.5 >= 5.5 }}
|
||||||
|
|
||||||
|
remainders
|
||||||
|
{{ (simple.number+7)%7 }}
|
||||||
|
{{ (simple.number+7)%7 == 0 }}
|
||||||
|
{{ (simple.number+7)%6 }}
|
||||||
|
|
||||||
|
in/not in
|
||||||
|
{{ 5 in simple.intmap }}
|
||||||
|
{{ 2 in simple.intmap }}
|
||||||
|
{{ 7 in simple.intmap }}
|
||||||
|
{{ !(5 in simple.intmap) }}
|
||||||
|
{{ not(7 in simple.intmap) }}
|
||||||
|
{{ 1 in simple.multiple_item_list }}
|
||||||
|
{{ 4 in simple.multiple_item_list }}
|
||||||
|
{{ !(4 in simple.multiple_item_list) }}
|
||||||
|
{{ "Hello" in simple.misc_list }}
|
||||||
|
{{ "Hello2" in simple.misc_list }}
|
||||||
|
{{ 99 in simple.misc_list }}
|
||||||
|
{{ False in simple.misc_list }}
|
||||||
|
|
||||||
|
issue #48 (associativity for infix operators)
|
||||||
|
{{ 34/3*3 }}
|
||||||
|
{{ 10 + 24 / 6 / 2 }}
|
||||||
|
{{ 6 - 4 - 2 }}
|
||||||
|
|
||||||
|
issue #64 (uint comparison with int const)
|
||||||
|
{{ simple.uint }}
|
||||||
|
{{ simple.uint == 8 }}
|
||||||
|
{{ simple.uint == 9 }}
|
||||||
|
{{ simple.uint >= 8 }}
|
||||||
|
{{ simple.uint <= 8 }}
|
||||||
|
{{ simple.uint < 8 }}
|
||||||
|
{{ simple.uint > 8 }}
|
69
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/expressions.tpl.out
generated
vendored
Normal file
69
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/expressions.tpl.out
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
integers and complex expressions
|
||||||
|
-90
|
||||||
|
90
|
||||||
|
-90
|
||||||
|
90
|
||||||
|
531440999967.000000
|
||||||
|
|
||||||
|
floats
|
||||||
|
5.500000
|
||||||
|
5.172841
|
||||||
|
False
|
||||||
|
True
|
||||||
|
|
||||||
|
mul/div
|
||||||
|
10
|
||||||
|
10.000000
|
||||||
|
0
|
||||||
|
13.250000
|
||||||
|
0
|
||||||
|
0.500000
|
||||||
|
1000000.000000
|
||||||
|
|
||||||
|
logic expressions
|
||||||
|
False
|
||||||
|
False
|
||||||
|
True
|
||||||
|
True
|
||||||
|
False
|
||||||
|
False
|
||||||
|
True
|
||||||
|
|
||||||
|
float comparison
|
||||||
|
True
|
||||||
|
False
|
||||||
|
False
|
||||||
|
True
|
||||||
|
|
||||||
|
remainders
|
||||||
|
0
|
||||||
|
True
|
||||||
|
1
|
||||||
|
|
||||||
|
in/not in
|
||||||
|
True
|
||||||
|
True
|
||||||
|
False
|
||||||
|
False
|
||||||
|
True
|
||||||
|
True
|
||||||
|
False
|
||||||
|
True
|
||||||
|
True
|
||||||
|
False
|
||||||
|
True
|
||||||
|
False
|
||||||
|
|
||||||
|
issue #48 (associativity for infix operators)
|
||||||
|
33
|
||||||
|
12
|
||||||
|
0
|
||||||
|
|
||||||
|
issue #64 (uint comparison with int const)
|
||||||
|
8
|
||||||
|
True
|
||||||
|
False
|
||||||
|
True
|
||||||
|
True
|
||||||
|
False
|
||||||
|
False
|
3
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/extends.tpl
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/extends.tpl
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{% extends "inheritance/base.tpl" %}
|
||||||
|
|
||||||
|
{% block content %}Extends' content{% endblock %}
|
1
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/extends.tpl.out
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/extends.tpl.out
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Start#This is base's bodyExtends' content#End
|
5
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-compilation.err
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-compilation.err
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{{ }}
|
||||||
|
{{ (1 - 1 }}
|
||||||
|
{{ 1|float: }}
|
||||||
|
{{ "test"|non_existent_filter }}
|
||||||
|
{{ "test"|"test" }}
|
5
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-compilation.err.out
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-compilation.err.out
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.*Expected either a number, string, keyword or identifier\.
|
||||||
|
.*Closing bracket expected after expression
|
||||||
|
.*Filter parameter required after ':'.*
|
||||||
|
.*Filter 'non_existent_filter' does not exist\.
|
||||||
|
.*Filter name must be an identifier\.
|
4
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-execution.err
generated
vendored
Normal file
4
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-execution.err
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{{ -(true || false) }}
|
||||||
|
{{ simple.func_add("test", 5) }}
|
||||||
|
{% for item in simple.multiple_item_list %} {{ simple.func_add("test", 5) }} {% endfor %}
|
||||||
|
{{ simple.func_variadic_sum_int("foo") }}
|
4
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-execution.err.out
generated
vendored
Normal file
4
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-execution.err.out
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.*where: execution.*Negative sign on a non\-number expression
|
||||||
|
.*Function input argument 0 of 'simple.func_add' must be of type int or \*pongo2.Value \(not string\).
|
||||||
|
.*Function input argument 0 of 'simple.func_add' must be of type int or \*pongo2.Value \(not string\).
|
||||||
|
.*Function variadic input argument of 'simple.func_variadic_sum_int' must be of type int or \*pongo2.Value \(not string\).
|
304
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters.tpl
generated
vendored
Normal file
304
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters.tpl
generated
vendored
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
add
|
||||||
|
{{ 5|add:2 }}
|
||||||
|
{{ 5|add:simple.number }}
|
||||||
|
{{ 5|add:nothing }}
|
||||||
|
{{ 5|add:"test" }}
|
||||||
|
{{ "hello "|add:"john doe" }}
|
||||||
|
{{ "hello "|add:simple.name }}
|
||||||
|
|
||||||
|
addslashes
|
||||||
|
{{ "plain text"|addslashes|safe }}
|
||||||
|
{{ simple.escape_text|addslashes|safe }}
|
||||||
|
|
||||||
|
capfirst
|
||||||
|
{{ ""|capfirst }}
|
||||||
|
{{ 5|capfirst }}
|
||||||
|
{{ "h"|capfirst }}
|
||||||
|
{{ "hello there!"|capfirst }}
|
||||||
|
{{ simple.chinese_hello_world|capfirst }}
|
||||||
|
|
||||||
|
cut
|
||||||
|
{{ 15|cut:"5" }}
|
||||||
|
{{ "Hello world"|cut: " " }}
|
||||||
|
|
||||||
|
default
|
||||||
|
{{ simple.nothing|default:"n/a" }}
|
||||||
|
{{ nothing|default:simple.number }}
|
||||||
|
{{ simple.number|default:"n/a" }}
|
||||||
|
{{ 5|default:"n/a" }}
|
||||||
|
|
||||||
|
default_if_none
|
||||||
|
{{ simple.nothing|default_if_none:"n/a" }}
|
||||||
|
{{ ""|default_if_none:"n/a" }}
|
||||||
|
{{ nil|default_if_none:"n/a" }}
|
||||||
|
|
||||||
|
get_digit
|
||||||
|
{{ 1234567890|get_digit:0 }}
|
||||||
|
{{ 1234567890|get_digit }}
|
||||||
|
{{ 1234567890|get_digit:2 }}
|
||||||
|
{{ 1234567890|get_digit:"4" }}
|
||||||
|
{{ 1234567890|get_digit:10 }}
|
||||||
|
{{ 1234567890|get_digit:15 }}
|
||||||
|
|
||||||
|
safe
|
||||||
|
{{ "<script>" }}
|
||||||
|
{{ "<script>"|safe }}
|
||||||
|
|
||||||
|
escape
|
||||||
|
{{ "<script>"|safe|escape }}
|
||||||
|
|
||||||
|
title
|
||||||
|
{{ ""|title }}
|
||||||
|
{{ 5|title }}
|
||||||
|
{{ "h"|title }}
|
||||||
|
{{ "hello there!"|title }}
|
||||||
|
{{ "HELLO THERE!"|title }}
|
||||||
|
{{ "hELLO tHERE!"|title }}
|
||||||
|
{{ simple.chinese_hello_world|title }}
|
||||||
|
|
||||||
|
truncatechars
|
||||||
|
{{ "Joel is a slug"|truncatechars:9 }}
|
||||||
|
{{ "Joel is a slug"|truncatechars:13 }}
|
||||||
|
{{ "Joel is a slug"|truncatechars:14 }}
|
||||||
|
{{ simple.chinese_hello_world|truncatechars:1 }}
|
||||||
|
{{ simple.chinese_hello_world|truncatechars:2 }}
|
||||||
|
|
||||||
|
divisibleby
|
||||||
|
{{ 21|divisibleby:3 }}
|
||||||
|
{{ 21|divisibleby:"3" }}
|
||||||
|
{{ 21|float|divisibleby:"3" }}
|
||||||
|
{{ 22|divisibleby:"3" }}
|
||||||
|
{{ 85|divisibleby:simple.number }}
|
||||||
|
{{ 84|divisibleby:simple.number }}
|
||||||
|
|
||||||
|
striptags
|
||||||
|
{{ "<strong><i>Hello!</i></strong>"|striptags|safe }}
|
||||||
|
|
||||||
|
removetags
|
||||||
|
{{ "<strong><i>Hello!</i></strong>"|removetags:"i"|safe }}
|
||||||
|
|
||||||
|
yesno
|
||||||
|
{{ simple.bool_true|yesno }}
|
||||||
|
{{ simple.bool_false|yesno }}
|
||||||
|
{{ simple.nil|yesno }}
|
||||||
|
{{ simple.nothing|yesno }}
|
||||||
|
{{ simple.bool_true|yesno:"ja,nein,vielleicht" }}
|
||||||
|
{{ simple.bool_false|yesno:"ja,nein,vielleicht" }}
|
||||||
|
{{ simple.nothing|yesno:"ja,nein" }}
|
||||||
|
|
||||||
|
pluralize
|
||||||
|
customer{{ 0|pluralize }}
|
||||||
|
customer{{ 1|pluralize }}
|
||||||
|
customer{{ 2|pluralize }}
|
||||||
|
cherr{{ 0|pluralize:"y,ies" }}
|
||||||
|
cherr{{ 1|pluralize:"y,ies" }}
|
||||||
|
cherr{{ 2|pluralize:"y,ies" }}
|
||||||
|
walrus{{ 0|pluralize:"es" }}
|
||||||
|
walrus{{ 1|pluralize:"es" }}
|
||||||
|
walrus{{ simple.number|pluralize:"es" }}
|
||||||
|
|
||||||
|
random
|
||||||
|
{{ 5|random }}
|
||||||
|
{{ ""|random }}
|
||||||
|
{{ "h"|random }}
|
||||||
|
{{ simple.one_item_list|random }}
|
||||||
|
|
||||||
|
first
|
||||||
|
{{ "Test"|first }}
|
||||||
|
{{ complex.comments|first }}
|
||||||
|
{{ 5|first }}
|
||||||
|
{{ true|first }}
|
||||||
|
{{ nothing|first }}
|
||||||
|
{{ simple.chinese_hello_world|first }}
|
||||||
|
|
||||||
|
last
|
||||||
|
{{ "Test"|last }}
|
||||||
|
{{ complex.comments|last }}
|
||||||
|
{{ 5|last }}
|
||||||
|
{{ true|last }}
|
||||||
|
{{ nothing|last }}
|
||||||
|
{{ simple.chinese_hello_world|last }}
|
||||||
|
|
||||||
|
urlencode
|
||||||
|
{{ "http://www.example.org/foo?a=b&c=d"|urlencode }}
|
||||||
|
|
||||||
|
linebreaksbr
|
||||||
|
{{ simple.newline_text|linebreaksbr }}
|
||||||
|
{{ ""|linebreaksbr }}
|
||||||
|
{{ "hallo"|linebreaksbr }}
|
||||||
|
|
||||||
|
length_is
|
||||||
|
{{ simple.name|length_is:8 }}
|
||||||
|
{{ simple.name|length_is:10 }}
|
||||||
|
{{ simple.name|length_is:"8" }}
|
||||||
|
{{ simple.name|length_is:"10" }}
|
||||||
|
{{ 5|length_is:1 }}
|
||||||
|
{{ simple.chinese_hello_world|length_is:4 }}
|
||||||
|
{{ simple.chinese_hello_world|length_is:3 }}
|
||||||
|
{{ simple.chinese_hello_world|length_is:5 }}
|
||||||
|
|
||||||
|
integer
|
||||||
|
{{ "foobar"|integer }}
|
||||||
|
{{ nothing|integer }}
|
||||||
|
{{ "5.4"|float|integer }}
|
||||||
|
{{ "5.5"|float|integer }}
|
||||||
|
{{ "5.6"|float|integer }}
|
||||||
|
{{ 6|float|integer }}
|
||||||
|
{{ -100|integer }}
|
||||||
|
|
||||||
|
float
|
||||||
|
{{ "foobar"|float }}
|
||||||
|
{{ nil|float }}
|
||||||
|
{{ "5.5"|float }}
|
||||||
|
{{ 5|float }}
|
||||||
|
{{ "5.6"|integer|float }}
|
||||||
|
{{ -100|float }}
|
||||||
|
{% if 5.5 == 5.500000 %}5.5 is 5.500000{% endif %}
|
||||||
|
{% if 5.5 != 5.500001 %}5.5 is not 5.500001{% endif %}
|
||||||
|
|
||||||
|
floatformat
|
||||||
|
{{ 34.23234|floatformat }}
|
||||||
|
{{ 34.00000|floatformat }}
|
||||||
|
{{ 34.26000|floatformat }}
|
||||||
|
{{ "34.23234"|floatformat }}
|
||||||
|
{{ "34.00000"|floatformat }}
|
||||||
|
{{ "34.26000"|floatformat }}
|
||||||
|
{{ 34.23234|floatformat:3 }}
|
||||||
|
{{ 34.00000|floatformat:3 }}
|
||||||
|
{{ 34.26000|floatformat:3 }}
|
||||||
|
{{ 34.23234|floatformat:"0" }}
|
||||||
|
{{ 34.00000|floatformat:"0" }}
|
||||||
|
{{ 39.56000|floatformat:"0" }}
|
||||||
|
{{ 34.23234|floatformat:"-3" }}
|
||||||
|
{{ 34.00000|floatformat:"-3" }}
|
||||||
|
{{ 34.26000|floatformat:"-3" }}
|
||||||
|
|
||||||
|
join
|
||||||
|
{{ simple.misc_list|join:", " }}
|
||||||
|
|
||||||
|
stringformat
|
||||||
|
{{ simple.float|stringformat:"%.2f" }}
|
||||||
|
{{ simple.uint|stringformat:"Test: %d" }}
|
||||||
|
{{ simple.chinese_hello_world|stringformat:"Chinese: %s" }}
|
||||||
|
|
||||||
|
make_list
|
||||||
|
{{ simple.name|make_list|join:", " }}
|
||||||
|
{% for char in simple.name|make_list %}{{ char }}{% endfor %}
|
||||||
|
|
||||||
|
center
|
||||||
|
'{{ "test"|center:3 }}'
|
||||||
|
'{{ "test"|center:19 }}'
|
||||||
|
'{{ "test"|center:20 }}'
|
||||||
|
{{ "test"|center:20|length }}
|
||||||
|
'{{ "test2"|center:19 }}'
|
||||||
|
'{{ "test2"|center:20 }}'
|
||||||
|
{{ "test2"|center:20|length }}
|
||||||
|
'{{ simple.chinese_hello_world|center:20 }}'
|
||||||
|
|
||||||
|
ljust
|
||||||
|
'{{ "test"|ljust:"2" }}'
|
||||||
|
'{{ "test"|ljust:"20" }}'
|
||||||
|
{{ "test"|ljust:"20"|length }}
|
||||||
|
'{{ simple.chinese_hello_world|ljust:10 }}'
|
||||||
|
|
||||||
|
rjust
|
||||||
|
'{{ "test"|rjust:"2" }}'
|
||||||
|
'{{ "test"|rjust:"20" }}'
|
||||||
|
{{ "test"|rjust:"20"|length }}
|
||||||
|
'{{ simple.chinese_hello_world|rjust:10 }}'
|
||||||
|
|
||||||
|
wordcount
|
||||||
|
{{ ""|wordcount }}
|
||||||
|
{% filter wordcount %}{% lorem 25 w %}{% endfilter %}
|
||||||
|
|
||||||
|
wordwrap
|
||||||
|
{{ ""|wordwrap:2 }}
|
||||||
|
{% filter wordwrap:5 %}{% lorem 26 w %}{% endfilter %}
|
||||||
|
|
||||||
|
iriencode
|
||||||
|
{{ "?foo=123&bar=yes"|iriencode }}
|
||||||
|
|
||||||
|
linebreaks
|
||||||
|
{{ ""|linebreaks|safe }}
|
||||||
|
{{ simple.newline_text|linebreaks|safe }}
|
||||||
|
{{ simple.long_text|linebreaks|safe }}
|
||||||
|
{{ simple.name|linebreaks|safe }}
|
||||||
|
|
||||||
|
linenumbers
|
||||||
|
{% filter linenumbers %}{% lorem 10 %}{% endfilter %}
|
||||||
|
|
||||||
|
phone2numeric
|
||||||
|
{{ "999-PONGO2"|phone2numeric }}
|
||||||
|
|
||||||
|
truncatewords
|
||||||
|
{% filter truncatewords:9 %}{% lorem 25 w %}{% endfilter %}
|
||||||
|
{% filter wordcount %}{% filter truncatewords:9 %}{% lorem 25 w %}{% endfilter %}{% endfilter %}
|
||||||
|
{{ simple.chinese_hello_world|truncatewords:0 }}
|
||||||
|
{{ simple.chinese_hello_world|truncatewords:1 }}
|
||||||
|
{{ simple.chinese_hello_world|truncatewords:2 }}
|
||||||
|
|
||||||
|
urlize
|
||||||
|
{{ "http://www.florian-schlachter.de"|urlize|safe }}
|
||||||
|
{{ "www.florian-schlachter.de"|urlize|safe }}
|
||||||
|
{{ "florian-schlachter.de"|urlize|safe }}
|
||||||
|
{% filter urlize:true|safe %}
|
||||||
|
Please mail me at demo@example.com or visit mit on:
|
||||||
|
- lorem ipsum github.com/flosch/pongo2 lorem ipsum
|
||||||
|
- lorem ipsum http://www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum www.florian-schlachter.de/test="test" lorem ipsum
|
||||||
|
{% endfilter %}
|
||||||
|
--
|
||||||
|
{% filter urlize:false|safe %}
|
||||||
|
Please mail me at demo@example.com or visit mit on:
|
||||||
|
- lorem ipsum github.com/flosch/pongo2 lorem ipsum
|
||||||
|
- lorem ipsum http://www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum www.florian-schlachter.de/test="test" lorem ipsum
|
||||||
|
{% endfilter %}
|
||||||
|
|
||||||
|
urlizetrunc
|
||||||
|
{% filter urlizetrunc:15|safe %}
|
||||||
|
Please mail me at demo@example.com or visit mit on:
|
||||||
|
- lorem ipsum github.com/flosch/pongo2 lorem ipsum
|
||||||
|
- lorem ipsum http://www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum https://www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum www.florian-schlachter.de lorem ipsum
|
||||||
|
- lorem ipsum www.florian-schlachter.de/test="test" lorem ipsum
|
||||||
|
{% endfilter %}
|
||||||
|
|
||||||
|
escapejs
|
||||||
|
{{ simple.escape_js_test|escapejs|safe }}
|
||||||
|
|
||||||
|
slice
|
||||||
|
{{ simple.multiple_item_list|slice:":99"|join:"," }}
|
||||||
|
{{ simple.multiple_item_list|slice:"99:"|join:"," }}
|
||||||
|
{{ simple.multiple_item_list|slice:":3"|join:"," }}
|
||||||
|
{{ simple.multiple_item_list|slice:"3:5"|join:"," }}
|
||||||
|
{{ simple.multiple_item_list|slice:"2:"|join:"," }}
|
||||||
|
{{ simple.multiple_item_list|slice:"2:3"|join:"," }}
|
||||||
|
{{ simple.multiple_item_list|slice:"2:1"|join:"," }}
|
||||||
|
{{ "Test"|slice:"1:3" }}
|
||||||
|
{{ simple.chinese_hello_world|slice:"1:3" }}
|
||||||
|
|
||||||
|
truncatechars_html
|
||||||
|
{{ "This is a long test which will be cutted after some chars."|truncatechars_html:25 }}
|
||||||
|
{{ "<div class=\"foo\"><ul class=\"foo\"><li class=\"foo\"><p class=\"foo\">This is a long test which will be cutted after some chars.</p></li></ul></div>"|truncatechars_html:25 }}
|
||||||
|
{{ "<p class='test' id='foo'>This is a long test which will be cutted after some chars.</p>"|truncatechars_html:25 }}
|
||||||
|
{{ "<a name='link'><p>This </a>is a long test which will be cutted after some chars.</p>"|truncatechars_html:25 }}
|
||||||
|
{{ "<p>This </a>is a long test which will be cutted after some chars.</p>"|truncatechars_html:25 }}
|
||||||
|
{{ "<p>This is a long test which will be cutted after some chars.</p>"|truncatechars_html:7 }}
|
||||||
|
|
||||||
|
truncatewords_html
|
||||||
|
{{ "This is a long test which will be cutted after some words."|truncatewords_html:25|safe }}
|
||||||
|
{{ "<div class=\"foo\"><ul class=\"foo\"><li class=\"foo\"><p class=\"foo\">This is a long test which will be cutted after some chars.</p></li></ul></div>"|truncatewords_html:5 }}
|
||||||
|
{{ "<p>This. is. a. long test. Test test, test.</p>"|truncatewords_html:8 }}
|
||||||
|
{{ "<a name='link' href=\"https://....\"><p class=\"foo\">This </a>is a long test, which will be cutted after some words.</p>"|truncatewords_html:5 }}
|
||||||
|
{{ "<p>This </a>is a long test, which will be cutted after some words.</p>"|truncatewords_html:5 }}
|
||||||
|
{{ "<p>This is a long test which will be cutted after some words.</p>"|truncatewords_html:2 }}
|
||||||
|
{{ "<p>This is a long test which will be cutted after some words.</p>"|truncatewords_html:0 }}
|
318
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters.tpl.out
generated
vendored
Normal file
318
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters.tpl.out
generated
vendored
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
add
|
||||||
|
7
|
||||||
|
47
|
||||||
|
5
|
||||||
|
5test
|
||||||
|
hello john doe
|
||||||
|
hello john doe
|
||||||
|
|
||||||
|
addslashes
|
||||||
|
plain text
|
||||||
|
This is \\a Test. \"Yep\". \'Yep\'.
|
||||||
|
|
||||||
|
capfirst
|
||||||
|
|
||||||
|
|
||||||
|
H
|
||||||
|
Hello there!
|
||||||
|
你好世界
|
||||||
|
|
||||||
|
cut
|
||||||
|
1
|
||||||
|
Helloworld
|
||||||
|
|
||||||
|
default
|
||||||
|
n/a
|
||||||
|
42
|
||||||
|
42
|
||||||
|
5
|
||||||
|
|
||||||
|
default_if_none
|
||||||
|
n/a
|
||||||
|
|
||||||
|
n/a
|
||||||
|
|
||||||
|
get_digit
|
||||||
|
1234567890
|
||||||
|
1234567890
|
||||||
|
9
|
||||||
|
7
|
||||||
|
1
|
||||||
|
1234567890
|
||||||
|
|
||||||
|
safe
|
||||||
|
<script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
escape
|
||||||
|
<script>
|
||||||
|
|
||||||
|
title
|
||||||
|
|
||||||
|
|
||||||
|
H
|
||||||
|
Hello There!
|
||||||
|
Hello There!
|
||||||
|
Hello There!
|
||||||
|
你好世界
|
||||||
|
|
||||||
|
truncatechars
|
||||||
|
Joel i...
|
||||||
|
Joel is a ...
|
||||||
|
Joel is a slug
|
||||||
|
你
|
||||||
|
你好
|
||||||
|
|
||||||
|
divisibleby
|
||||||
|
True
|
||||||
|
True
|
||||||
|
True
|
||||||
|
False
|
||||||
|
False
|
||||||
|
True
|
||||||
|
|
||||||
|
striptags
|
||||||
|
Hello!
|
||||||
|
|
||||||
|
removetags
|
||||||
|
<strong>Hello!</strong>
|
||||||
|
|
||||||
|
yesno
|
||||||
|
yes
|
||||||
|
no
|
||||||
|
maybe
|
||||||
|
maybe
|
||||||
|
ja
|
||||||
|
nein
|
||||||
|
maybe
|
||||||
|
|
||||||
|
pluralize
|
||||||
|
customers
|
||||||
|
customer
|
||||||
|
customers
|
||||||
|
cherries
|
||||||
|
cherry
|
||||||
|
cherries
|
||||||
|
walruses
|
||||||
|
walrus
|
||||||
|
walruses
|
||||||
|
|
||||||
|
random
|
||||||
|
5
|
||||||
|
|
||||||
|
h
|
||||||
|
99
|
||||||
|
|
||||||
|
first
|
||||||
|
T
|
||||||
|
<pongo2_test.comment Value>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
你
|
||||||
|
|
||||||
|
last
|
||||||
|
t
|
||||||
|
<pongo2_test.comment Value>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
界
|
||||||
|
|
||||||
|
urlencode
|
||||||
|
http%3A%2F%2Fwww.example.org%2Ffoo%3Fa%3Db%26c%3Dd
|
||||||
|
|
||||||
|
linebreaksbr
|
||||||
|
this is a text<br />with a new line in it
|
||||||
|
|
||||||
|
hallo
|
||||||
|
|
||||||
|
length_is
|
||||||
|
True
|
||||||
|
False
|
||||||
|
True
|
||||||
|
False
|
||||||
|
False
|
||||||
|
True
|
||||||
|
False
|
||||||
|
False
|
||||||
|
|
||||||
|
integer
|
||||||
|
0
|
||||||
|
0
|
||||||
|
5
|
||||||
|
5
|
||||||
|
5
|
||||||
|
6
|
||||||
|
-100
|
||||||
|
|
||||||
|
float
|
||||||
|
0.000000
|
||||||
|
0.000000
|
||||||
|
5.500000
|
||||||
|
5.000000
|
||||||
|
5.000000
|
||||||
|
-100.000000
|
||||||
|
5.5 is 5.500000
|
||||||
|
5.5 is not 5.500001
|
||||||
|
|
||||||
|
floatformat
|
||||||
|
34.2
|
||||||
|
34
|
||||||
|
34.3
|
||||||
|
34.2
|
||||||
|
34
|
||||||
|
34.3
|
||||||
|
34.232
|
||||||
|
34.000
|
||||||
|
34.260
|
||||||
|
34
|
||||||
|
34
|
||||||
|
40
|
||||||
|
34.232
|
||||||
|
34
|
||||||
|
34.260
|
||||||
|
|
||||||
|
join
|
||||||
|
Hello, 99, 3.140000, good
|
||||||
|
|
||||||
|
stringformat
|
||||||
|
3.14
|
||||||
|
Test: 8
|
||||||
|
Chinese: 你好世界
|
||||||
|
|
||||||
|
make_list
|
||||||
|
j, o, h, n, , d, o, e
|
||||||
|
john doe
|
||||||
|
|
||||||
|
center
|
||||||
|
'test'
|
||||||
|
' test '
|
||||||
|
' test '
|
||||||
|
20
|
||||||
|
' test2 '
|
||||||
|
' test2 '
|
||||||
|
20
|
||||||
|
' 你好世界 '
|
||||||
|
|
||||||
|
ljust
|
||||||
|
'test'
|
||||||
|
'test '
|
||||||
|
20
|
||||||
|
'你好世界 '
|
||||||
|
|
||||||
|
rjust
|
||||||
|
'test'
|
||||||
|
' test'
|
||||||
|
20
|
||||||
|
' 你好世界'
|
||||||
|
|
||||||
|
wordcount
|
||||||
|
0
|
||||||
|
25
|
||||||
|
|
||||||
|
wordwrap
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
|
consectetur adipisici elit, sed eiusmod
|
||||||
|
tempor incidunt ut labore et
|
||||||
|
dolore magna aliqua. Ut enim
|
||||||
|
ad minim veniam, quis nostrud
|
||||||
|
exercitation
|
||||||
|
|
||||||
|
iriencode
|
||||||
|
?foo=123&bar=yes
|
||||||
|
|
||||||
|
linebreaks
|
||||||
|
|
||||||
|
<p>this is a text<br />with a new line in it</p>
|
||||||
|
<p>This is a simple text.</p><p>This too, as a paragraph.<br />Right?</p><p>Yep!</p>
|
||||||
|
<p>john doe</p>
|
||||||
|
|
||||||
|
linenumbers
|
||||||
|
1. Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||||
|
2. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
|
||||||
|
3. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
|
||||||
|
4. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
|
||||||
|
5. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.
|
||||||
|
6. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.
|
||||||
|
7. Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||||
|
8. Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||||
|
9. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
|
||||||
|
10. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
|
||||||
|
|
||||||
|
phone2numeric
|
||||||
|
999-766462
|
||||||
|
|
||||||
|
truncatewords
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipisici elit, sed ...
|
||||||
|
10
|
||||||
|
|
||||||
|
你好世界
|
||||||
|
你好世界
|
||||||
|
|
||||||
|
urlize
|
||||||
|
<a href="http://www.florian-schlachter.de" rel="nofollow">http://www.florian-schlachter.de</a>
|
||||||
|
<a href="http://www.florian-schlachter.de" rel="nofollow">www.florian-schlachter.de</a>
|
||||||
|
<a href="http://florian-schlachter.de" rel="nofollow">florian-schlachter.de</a>
|
||||||
|
|
||||||
|
Please mail me at <a href="mailto:demo@example.com">demo@example.com</a> or visit mit on:
|
||||||
|
- lorem ipsum <a href="http://github.com/flosch/pongo2" rel="nofollow">github.com/flosch/pongo2</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">http://www.florian-schlachter.de</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www.florian-schlachter.de</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www.florian-schlachter.de</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">www.florian-schlachter.de</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="http://www.florian-schlachter.de/test=%22test%22" rel="nofollow">www.florian-schlachter.de/test="test"</a> lorem ipsum
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
Please mail me at <a href="mailto:demo@example.com">demo@example.com</a> or visit mit on:
|
||||||
|
- lorem ipsum <a href="http://github.com/flosch/pongo2" rel="nofollow">github.com/flosch/pongo2</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">http://www.florian-schlachter.de</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www.florian-schlachter.de</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www.florian-schlachter.de</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">www.florian-schlachter.de</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="http://www.florian-schlachter.de/test=%22test%22" rel="nofollow">www.florian-schlachter.de/test="test"</a> lorem ipsum
|
||||||
|
|
||||||
|
|
||||||
|
urlizetrunc
|
||||||
|
|
||||||
|
Please mail me at <a href="mailto:demo@example.com">demo@example...</a> or visit mit on:
|
||||||
|
- lorem ipsum <a href="http://github.com/flosch/pongo2" rel="nofollow">github.com/f...</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">http://www.f...</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www....</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="https://www.florian-schlachter.de" rel="nofollow">https://www....</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="http://www.florian-schlachter.de" rel="nofollow">www.florian-...</a> lorem ipsum
|
||||||
|
- lorem ipsum <a href="http://www.florian-schlachter.de/test=%22test%22" rel="nofollow">www.florian-...</a> lorem ipsum
|
||||||
|
|
||||||
|
|
||||||
|
escapejs
|
||||||
|
escape sequences \u000D\u000A\u005C\u0027\u005C\u0022 special chars \u0022\u003F\u0021\u003D\u0024\u003C\u003E
|
||||||
|
|
||||||
|
slice
|
||||||
|
1,1,2,3,5,8,13,21,34,55
|
||||||
|
|
||||||
|
1,1,2
|
||||||
|
3,5
|
||||||
|
2,3,5,8,13,21,34,55
|
||||||
|
2
|
||||||
|
2,3,5,8,13,21,34,55
|
||||||
|
es
|
||||||
|
好世
|
||||||
|
|
||||||
|
truncatechars_html
|
||||||
|
This is a long test wh...
|
||||||
|
<div class="foo"><ul class="foo"><li class="foo"><p class="foo">This is a long test wh...</p></li></ul></div>
|
||||||
|
<p class='test' id='foo'>This is a long test wh...</p>
|
||||||
|
<a name='link'><p>This </a>is a long test wh...</p>
|
||||||
|
<p>This </a>is a long test wh...</p>
|
||||||
|
<p>This...</p>
|
||||||
|
|
||||||
|
truncatewords_html
|
||||||
|
This is a long test which will be cutted after some words.
|
||||||
|
<div class="foo"><ul class="foo"><li class="foo"><p class="foo">This is a long test ...</p></li></ul></div>
|
||||||
|
<p>This. is. a. long test. Test test, test....</p>
|
||||||
|
<a name='link' href="https://...."><p class="foo">This </a>is a long test,...</p>
|
||||||
|
<p>This </a>is a long test,...</p>
|
||||||
|
<p>This is ...</p>
|
||||||
|
...
|
7
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/firstof.tpl
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/firstof.tpl
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{% firstof doesnotexist 42 %}
|
||||||
|
{% firstof doesnotexist "<script>alert('xss');</script>" %}
|
||||||
|
{% firstof doesnotexist "<script>alert('xss');</script>"|safe %}
|
||||||
|
{% firstof doesnotexist simple.uint 42 %}
|
||||||
|
{% firstof doesnotexist "test" simple.number 42 %}
|
||||||
|
{% firstof %}
|
||||||
|
{% firstof "test" "test2" %}
|
7
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/firstof.tpl.out
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/firstof.tpl.out
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
42
|
||||||
|
<script>alert('xss');</script>
|
||||||
|
<script>alert('xss');</script>
|
||||||
|
8
|
||||||
|
test
|
||||||
|
|
||||||
|
test
|
27
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/for.tpl
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/for.tpl
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{% for comment in complex.comments %}[{{ forloop.Counter }} {{ forloop.Counter0 }} {{ forloop.First }} {{ forloop.Last }} {{ forloop.Revcounter }} {{ forloop.Revcounter0 }}] {{ comment.Author.Name }}
|
||||||
|
|
||||||
|
{# nested loop #}
|
||||||
|
{% for char in comment.Text %}{{forloop.Parentloop.Counter0}}.{{forloop.Counter0}}:{{ char|safe }} {% endfor %}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
reversed
|
||||||
|
'{% for item in simple.multiple_item_list reversed %}{{ item }} {% endfor %}'
|
||||||
|
|
||||||
|
sorted string map
|
||||||
|
'{% for key in simple.strmap sorted %}{{ key }} {% endfor %}'
|
||||||
|
|
||||||
|
sorted int map
|
||||||
|
'{% for key in simple.intmap sorted %}{{ key }} {% endfor %}'
|
||||||
|
|
||||||
|
sorted int list
|
||||||
|
'{% for key in simple.unsorted_int_list sorted %}{{ key }} {% endfor %}'
|
||||||
|
|
||||||
|
reversed sorted int list
|
||||||
|
'{% for key in simple.unsorted_int_list reversed sorted %}{{ key }} {% endfor %}'
|
||||||
|
|
||||||
|
reversed sorted string map
|
||||||
|
'{% for key in simple.strmap reversed sorted %}{{ key }} {% endfor %}'
|
||||||
|
|
||||||
|
reversed sorted int map
|
||||||
|
'{% for key in simple.intmap reversed sorted %}{{ key }} {% endfor %}'
|
37
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/for.tpl.out
generated
vendored
Normal file
37
Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/for.tpl.out
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
[1 0 True False 3 2] user1
|
||||||
|
|
||||||
|
|
||||||
|
0.0:" 0.1:p 0.2:o 0.3:n 0.4:g 0.5:o 0.6:2 0.7: 0.8:i 0.9:s 0.10: 0.11:n 0.12:i 0.13:c 0.14:e 0.15:! 0.16:"
|
||||||
|
|
||||||
|
[2 1 False False 2 1] user2
|
||||||
|
|
||||||
|
|
||||||
|
1.0:c 1.1:o 1.2:m 1.3:m 1.4:e 1.5:n 1.6:t 1.7:2 1.8: 1.9:w 1.10:i 1.11:t 1.12:h 1.13: 1.14:< 1.15:s 1.16:c 1.17:r 1.18:i 1.19:p 1.20:t 1.21:> 1.22:u 1.23:n 1.24:s 1.25:a 1.26:f 1.27:e 1.28:< 1.29:/ 1.30:s 1.31:c 1.32:r 1.33:i 1.34:p 1.35:t 1.36:> 1.37: 1.38:t 1.39:a 1.40:g 1.41:s 1.42: 1.43:i 1.44:n 1.45: 1.46:i 1.47:t
|
||||||
|
|
||||||
|
[3 2 False True 1 0] user3
|
||||||
|
|
||||||
|
|
||||||
|
2.0:< 2.1:b 2.2:> 2.3:h 2.4:e 2.5:l 2.6:l 2.7:o 2.8:! 2.9:< 2.10:/ 2.11:b 2.12:> 2.13: 2.14:t 2.15:h 2.16:e 2.17:r 2.18:e
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
reversed
|
||||||
|
'55 34 21 13 8 5 3 2 1 1 '
|
||||||
|
|
||||||
|
sorted string map
|
||||||
|
'aab abc bcd gh ukq zab '
|
||||||
|
|
||||||
|
sorted int map
|
||||||
|
'1 2 5 '
|
||||||
|
|
||||||
|
sorted int list
|
||||||
|
'1 22 192 249 581 8271 9999 1828591 '
|
||||||
|
|
||||||
|
reversed sorted int list
|
||||||
|
'1828591 9999 8271 581 249 192 22 1 '
|
||||||
|
|
||||||
|
reversed sorted string map
|
||||||
|
'zab ukq gh bcd abc aab '
|
||||||
|
|
||||||
|
reversed sorted int map
|
||||||
|
'5 2 1 '
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue