1
0
Fork 0
mirror of https://github.com/Luzifer/repo-template.git synced 2024-12-22 20:21:19 +00:00

Migrate to pongo2 as template engine

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2018-02-07 22:20:28 +01:00
parent dbe4253ba9
commit a0b3b414c5
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
146 changed files with 8792 additions and 16 deletions

10
Gopkg.lock generated
View file

@ -7,6 +7,12 @@
revision = "7aef1d393c1e2d0758901853b59981c7adc67c7e" revision = "7aef1d393c1e2d0758901853b59981c7adc67c7e"
version = "v1.2.0" version = "v1.2.0"
[[projects]]
name = "github.com/flosch/pongo2"
packages = ["."]
revision = "5e81b817a0c48c1c57cdf1a9056cf76bdee02ca9"
version = "v3.0"
[[projects]] [[projects]]
name = "github.com/golang/protobuf" name = "github.com/golang/protobuf"
packages = ["proto"] packages = ["proto"]
@ -71,7 +77,7 @@
".", ".",
"internal" "internal"
] ]
revision = "a032972e28060ca4f5644acffae3dfc268cc09db" revision = "543e37812f10c46c622c9575afd7ad22f22a12ba"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -111,6 +117,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "5c8716b27c705f289d6f7f32bb73a18ed53b7a0028f30fcd1d7f3906f5087aaa" inputs-digest = "8231595c752fdf2d3c5a412ea10826718df940e6afb2c9e501aa79ee84704dba"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View file

@ -24,6 +24,10 @@
name = "github.com/Luzifer/rconfig" name = "github.com/Luzifer/rconfig"
version = "1.2.0" version = "1.2.0"
[[constraint]]
name = "github.com/flosch/pongo2"
version = "3.0.0"
[[constraint]] [[constraint]]
name = "github.com/google/go-github" name = "github.com/google/go-github"
version = "15.0.0" version = "15.0.0"

23
main.go
View file

@ -9,13 +9,12 @@ import (
"os" "os"
"regexp" "regexp"
"strings" "strings"
"text/template"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"github.com/Luzifer/rconfig" "github.com/Luzifer/rconfig"
"github.com/flosch/pongo2"
"github.com/google/go-github/github" "github.com/google/go-github/github"
"github.com/gosimple/slug"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -170,33 +169,29 @@ func render(repo *github.Repository) error {
return err return err
} }
tpl, err := template.New("output").Funcs(tplFuncs()).Parse(string(tplRaw)) tpl, err := pongo2.FromString(string(tplRaw))
if err != nil { if err != nil {
return err return err
} }
return tpl.Execute(outFile, repo) return tpl.ExecuteWriter(pongo2.Context{
"repo": repo,
}, outFile)
} }
func getOutfile(repo *github.Repository) (string, error) { func getOutfile(repo *github.Repository) (string, error) {
tpl, err := template.New("outfile").Funcs(tplFuncs()).Parse(cfg.Output) tpl, err := pongo2.FromString(cfg.Output)
if err != nil { if err != nil {
return "", err return "", err
} }
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
err = tpl.Execute(buf, repo) err = tpl.ExecuteWriter(pongo2.Context{
"repo": repo,
}, buf)
return buf.String(), err return buf.String(), err
} }
func simpleReplace(s, old, new string) string { func simpleReplace(s, old, new string) string {
return strings.Replace(s, old, new, -1) return strings.Replace(s, old, new, -1)
} }
func tplFuncs() template.FuncMap {
return template.FuncMap{
"chkPtrBool": func(i *bool) bool { return i != nil && *i },
"slugify": slug.Make,
"replace": simpleReplace,
}
}

21
template.go Normal file
View file

@ -0,0 +1,21 @@
package main
import (
"strings"
"github.com/flosch/pongo2"
"github.com/gosimple/slug"
)
func init() {
pongo2.RegisterFilter("groovy_save", tplGroovyFileSave)
pongo2.RegisterFilter("slugify", tplSlugify)
}
func tplSlugify(in, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
return pongo2.AsValue(slug.Make(in.String())), nil
}
func tplGroovyFileSave(in, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
return pongo2.AsValue(strings.Replace(in.String(), "-", "_", -1)), nil
}

40
vendor/github.com/flosch/pongo2/.gitignore generated vendored Normal file
View 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
vendor/github.com/flosch/pongo2/.travis.yml generated vendored Normal file
View file

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

10
vendor/github.com/flosch/pongo2/AUTHORS generated vendored Normal file
View 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
vendor/github.com/flosch/pongo2/LICENSE generated vendored Normal file
View 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.

248
vendor/github.com/flosch/pongo2/README.md generated vendored Normal file
View file

@ -0,0 +1,248 @@
# [pongo](https://en.wikipedia.org/wiki/Pongo_%28genus%29)2
[![GoDoc](https://godoc.org/github.com/flosch/pongo2?status.png)](https://godoc.org/github.com/flosch/pongo2)
[![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 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`)
* 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 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
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.

122
vendor/github.com/flosch/pongo2/context.go generated vendored Normal file
View file

@ -0,0 +1,122 @@
package pongo2
import (
"fmt"
"regexp"
)
var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$")
// Use this Context type to provide constants, variables, instances or functions to your 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
}
func (c Context) Update(other Context) Context {
for k, v := range other {
c[k] = v
}
return c
}
// 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
vendor/github.com/flosch/pongo2/doc.go generated vendored Normal file
View 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
vendor/github.com/flosch/pongo2/docs/examples.md generated vendored Normal file
View file

@ -0,0 +1 @@
(Stub, TBA)

68
vendor/github.com/flosch/pongo2/docs/filters.md generated vendored Normal file
View 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
vendor/github.com/flosch/pongo2/docs/index.md generated vendored Normal file
View file

@ -0,0 +1 @@
(Stub, TBA)

1
vendor/github.com/flosch/pongo2/docs/macros.md generated vendored Normal file
View file

@ -0,0 +1 @@
(Stub, TBA)

31
vendor/github.com/flosch/pongo2/docs/tags.md generated vendored Normal file
View 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

View file

@ -0,0 +1 @@
(Stub, TBA)

View file

0
vendor/github.com/flosch/pongo2/docs/write_tags.md generated vendored Normal file
View file

86
vendor/github.com/flosch/pongo2/error.go generated vendored Normal file
View file

@ -0,0 +1,86 @@
package pongo2
import (
"bufio"
"fmt"
"os"
)
// This 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
}
// 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 file.Close()
scanner := bufio.NewScanner(file)
l := 0
for scanner.Scan() {
l++
if l == e.Line {
return scanner.Text(), true
}
}
return "", false
}

133
vendor/github.com/flosch/pongo2/filters.go generated vendored Normal file
View 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)
}
filtered_value, err := fc.filterFunc(v, param)
if err != nil {
return nil, err.updateFromTokenIfNeeded(ctx.template, fc.token)
}
return filtered_value, nil
}
// Filter = IDENT | IDENT ":" FilterArg | IDENT "|" Filter
func (p *Parser) parseFilter() (*filterCall, *Error) {
ident_token := p.MatchType(TokenIdentifier)
// Check filter ident
if ident_token == nil {
return nil, p.Error("Filter name must be an identifier.", nil)
}
filter := &filterCall{
token: ident_token,
name: ident_token.Val,
}
// Get the appropriate filter function and bind it
filterFn, exists := filters[ident_token.Val]
if !exists {
return nil, p.Error(fmt.Sprintf("Filter '%s' does not exist.", ident_token.Val), ident_token)
}
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
}

903
vendor/github.com/flosch/pongo2/filters_builtin.go generated vendored Normal file
View file

@ -0,0 +1,903 @@
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, new_output *bytes.Buffer, cond func() bool, fn func(c rune, s int, idx int) int, finalize func()) {
vLen := len(value)
tag_stack := make([]string, 0)
idx := 0
for idx < vLen && !cond() {
c, s := utf8.DecodeRuneInString(value[idx:])
if c == utf8.RuneError {
idx += s
continue
}
if c == '<' {
new_output.WriteRune(c)
idx += s // consume "<"
if idx+1 < vLen {
if value[idx] == '/' {
// Close tag
new_output.WriteString("/")
tag := ""
idx += 1 // 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(tag_stack) > 0 {
// Ideally, the close tag is TOP of tag stack
// In malformed HTML, it must not be, so iterate through the stack and remove the tag
for i := len(tag_stack) - 1; i >= 0; i-- {
if tag_stack[i] == tag {
// Found the tag
tag_stack[i] = tag_stack[len(tag_stack)-1]
tag_stack = tag_stack[:len(tag_stack)-1]
break
}
}
}
new_output.WriteString(tag)
new_output.WriteString(">")
} else {
// Open tag
tag := ""
params := false
for idx < vLen {
c2, size2 := utf8.DecodeRuneInString(value[idx:])
if c2 == utf8.RuneError {
idx += size2
continue
}
new_output.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
tag_stack = append(tag_stack, tag)
}
}
} else {
idx = fn(c, s, idx)
}
}
finalize()
for i := len(tag_stack) - 1; i >= 0; i-- {
tag := tag_stack[i]
// Close everything from the regular tag stack
new_output.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)
new_output := bytes.NewBuffer(nil)
textcounter := 0
filterTruncateHtmlHelper(value, new_output, func() bool {
return textcounter >= newLen
}, func(c rune, s int, idx int) int {
textcounter++
new_output.WriteRune(c)
return idx + s
}, func() {
if textcounter >= newLen && textcounter < len(value) {
new_output.WriteString("...")
}
})
return AsSafeValue(new_output.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)
new_output := bytes.NewBuffer(nil)
wordcounter := 0
filterTruncateHtmlHelper(value, new_output, func() bool {
return wordcounter >= newLen
}, func(_ rune, _ int, idx int) int {
// Get next word
word_found := 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
}
new_output.WriteRune(c2)
idx += size2
if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' {
// Word ends here, stop capturing it now
break
} else {
word_found = true
}
}
if word_found {
wordcounter++
}
return idx
}, func() {
if wordcounter >= newLen {
new_output.WriteString("...")
}
})
return AsSafeValue(new_output.String()), nil
}
func filterEscape(in *Value, param *Value) (*Value, *Error) {
output := strings.Replace(in.String(), "&", "&amp;", -1)
output = strings.Replace(output, ">", "&gt;", -1)
output = strings.Replace(output, "<", "&lt;", -1)
output = strings.Replace(output, "\"", "&quot;", -1)
output = strings.Replace(output, "'", "&#39;", -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
} else {
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, is_time := in.Interface().(time.Time)
if !is_time {
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 re_striptags = regexp.MustCompile("<[^>]*?>")
func filterStriptags(in *Value, param *Value) (*Value, *Error) {
s := in.String()
// Strip all tags
s = re_striptags.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
} else {
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())
words_len := len(words)
wrap_at := param.Integer()
if wrap_at <= 0 {
return in, nil
}
linecount := words_len/wrap_at + words_len%wrap_at
lines := make([]string, 0, linecount)
for i := 0; i < linecount; i++ {
lines = append(lines, strings.Join(words[wrap_at*i:min(wrap_at*(i+1), words_len)], " "))
}
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",
}
param_string := param.String()
custom_choices := strings.Split(param_string, ",")
if len(param_string) > 0 {
if len(custom_choices) > 3 {
return nil, &Error{
Sender: "filter:yesno",
ErrorMsg: fmt.Sprintf("You cannot pass more than 3 options to the 'yesno'-filter (got: '%s').", param_string),
}
}
if len(custom_choices) < 2 {
return nil, &Error{
Sender: "filter:yesno",
ErrorMsg: fmt.Sprintf("You must pass either no or at least 2 arguments to the 'yesno'-filter (got: '%s').", param_string),
}
}
// Map to the options now
choices[0] = custom_choices[0]
choices[1] = custom_choices[1]
if len(custom_choices) == 3 {
choices[2] = custom_choices[2]
}
}
// 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
vendor/github.com/flosch/pongo2/helpers.go generated vendored Normal file
View 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
vendor/github.com/flosch/pongo2/lexer.go generated vendored Normal file
View 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
in_verbatim bool
verbatim_name 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.in_verbatim {
name := l.verbatim_name
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.in_verbatim = false
}
} else if strings.HasPrefix(l.input[l.pos:], "{% verbatim %}") { // tag
if l.pos > l.start {
l.emit(TokenHTML)
}
l.in_verbatim = true
w := len("{% verbatim %}")
l.pos += w
l.col += w
l.ignore()
}
if !l.in_verbatim {
// 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.in_verbatim {
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 -= 1 // 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
}

20
vendor/github.com/flosch/pongo2/nodes.go generated vendored Normal file
View file

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

14
vendor/github.com/flosch/pongo2/nodes_html.go generated vendored Normal file
View file

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

20
vendor/github.com/flosch/pongo2/nodes_wrapper.go generated vendored Normal file
View file

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

267
vendor/github.com/flosch/pongo2/parser.go generated vendored Normal file
View file

@ -0,0 +1,267 @@
package pongo2
import (
"bytes"
"fmt"
"strings"
)
type INode interface {
Execute(*ExecutionContext, *bytes.Buffer) *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
last_token *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.last_token = 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{}
tagArgs := make([]*Token, 0)
for p.Remaining() > 0 {
// New tag, check whether we have to stop wrapping here
if p.Peek(TokenSymbol, "{%") != nil {
tag_ident := p.PeekTypeN(1, TokenIdentifier)
if tag_ident != nil {
// We've found a (!) end-tag
found := false
for _, n := range names {
if tag_ident.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 = tag_ident.Val
return wrapper, newParser(p.template.name, tagArgs, p.template), nil
} else {
t := p.Current()
p.Consume()
if t == nil {
return nil, nil, p.Error("Unexpected EOF.", p.last_token)
}
tagArgs = append(tagArgs, t)
}
}
}
}
}
// 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.last_token)
}

54
vendor/github.com/flosch/pongo2/parser_document.go generated vendored Normal file
View 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
}

499
vendor/github.com/flosch/pongo2/parser_expression.go generated vendored Normal file
View file

@ -0,0 +1,499 @@
package pongo2
import (
"bytes"
"fmt"
"math"
)
type Expression struct {
// TODO: Add location token?
expr1 IEvaluator
expr2 IEvaluator
op_token *Token
}
type relationalExpression struct {
// TODO: Add location token?
expr1 IEvaluator
expr2 IEvaluator
op_token *Token
}
type simpleExpression struct {
negate bool
negative_sign bool
term1 IEvaluator
term2 IEvaluator
op_token *Token
}
type term struct {
// TODO: Add location token?
factor1 IEvaluator
factor2 IEvaluator
op_token *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 (t *term) FilterApplied(name string) bool {
return t.factor1.FilterApplied(name) && (t.factor2 == nil ||
(t.factor2 != nil && t.factor2.FilterApplied(name)))
}
func (p *power) FilterApplied(name string) bool {
return p.power1.FilterApplied(name) && (p.power2 == nil ||
(p.power2 != nil && p.power2.FilterApplied(name)))
}
func (expr *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, buffer *bytes.Buffer) *Error {
value, err := expr.Evaluate(ctx)
if err != nil {
return err
}
buffer.WriteString(value.String())
return nil
}
func (expr *relationalExpression) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
value, err := expr.Evaluate(ctx)
if err != nil {
return err
}
buffer.WriteString(value.String())
return nil
}
func (expr *simpleExpression) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
value, err := expr.Evaluate(ctx)
if err != nil {
return err
}
buffer.WriteString(value.String())
return nil
}
func (expr *term) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
value, err := expr.Evaluate(ctx)
if err != nil {
return err
}
buffer.WriteString(value.String())
return nil
}
func (expr *power) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
value, err := expr.Evaluate(ctx)
if err != nil {
return err
}
buffer.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.op_token.Val {
case "and", "&&":
return AsValue(v1.IsTrue() && v2.IsTrue()), nil
case "or", "||":
return AsValue(v1.IsTrue() || v2.IsTrue()), nil
default:
panic(fmt.Sprintf("unimplemented: %s", expr.op_token.Val))
}
} 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.op_token.Val {
case "<=":
if v1.IsFloat() || v2.IsFloat() {
return AsValue(v1.Float() <= v2.Float()), nil
} else {
return AsValue(v1.Integer() <= v2.Integer()), nil
}
case ">=":
if v1.IsFloat() || v2.IsFloat() {
return AsValue(v1.Float() >= v2.Float()), nil
} else {
return AsValue(v1.Integer() >= v2.Integer()), nil
}
case "==":
return AsValue(v1.EqualValueTo(v2)), nil
case ">":
if v1.IsFloat() || v2.IsFloat() {
return AsValue(v1.Float() > v2.Float()), nil
} else {
return AsValue(v1.Integer() > v2.Integer()), nil
}
case "<":
if v1.IsFloat() || v2.IsFloat() {
return AsValue(v1.Float() < v2.Float()), nil
} else {
return AsValue(v1.Integer() < v2.Integer()), nil
}
case "!=", "<>":
return AsValue(!v1.EqualValueTo(v2)), nil
case "in":
return AsValue(v2.Contains(v1)), nil
default:
panic(fmt.Sprintf("unimplemented: %s", expr.op_token.Val))
}
} 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.negative_sign {
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.op_token.Val {
case "+":
if result.IsFloat() || t2.IsFloat() {
// Result will be a float
return AsValue(result.Float() + t2.Float()), nil
} else {
// Result will be an integer
return AsValue(result.Integer() + t2.Integer()), nil
}
case "-":
if result.IsFloat() || t2.IsFloat() {
// Result will be a float
return AsValue(result.Float() - t2.Float()), nil
} else {
// Result will be an integer
return AsValue(result.Integer() - t2.Integer()), nil
}
default:
panic("unimplemented")
}
}
return result, nil
}
func (t *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
f1, err := t.factor1.Evaluate(ctx)
if err != nil {
return nil, err
}
if t.factor2 != nil {
f2, err := t.factor2.Evaluate(ctx)
if err != nil {
return nil, err
}
switch t.op_token.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 (pw *power) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
p1, err := pw.power1.Evaluate(ctx)
if err != nil {
return nil, err
}
if pw.power2 != nil {
p2, err := pw.power2.Evaluate(ctx)
if err != nil {
return nil, err
}
return AsValue(math.Pow(p1.Float(), p2.Float())), nil
} else {
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) {
return_term := new(term)
factor1, err := p.parsePower()
if err != nil {
return nil, err
}
return_term.factor1 = factor1
for p.PeekOne(TokenSymbol, "*", "/", "%") != nil {
if return_term.op_token != nil {
// Create new sub-term
return_term = &term{
factor1: return_term,
}
}
op := p.Current()
p.Consume()
factor2, err := p.parsePower()
if err != nil {
return nil, err
}
return_term.op_token = op
return_term.factor2 = factor2
}
if return_term.op_token == nil {
// Shortcut for faster evaluation
return return_term.factor1, nil
}
return return_term, nil
}
func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) {
expr := new(simpleExpression)
if sign := p.MatchOne(TokenSymbol, "+", "-"); sign != nil {
if sign.Val == "-" {
expr.negative_sign = 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.op_token != 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.op_token = op
}
if expr.negate == false && expr.negative_sign == 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.op_token = t
expr.expr2 = expr2
} else if t := p.MatchOne(TokenKeyword, "in"); t != nil {
expr2, err := p.parseSimpleExpression()
if err != nil {
return nil, err
}
expr.op_token = t
expr.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.op_token = op
}
if exp.expr2 == nil {
// Shortcut for faster evaluation
return exp.expr1, nil
}
return exp, nil
}

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

@ -0,0 +1,14 @@
package pongo2
// Version string
const Version = "v3"
// Helper function which 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
}

20
vendor/github.com/flosch/pongo2/pongo2_issues_test.go generated vendored Normal file
View file

@ -0,0 +1,20 @@
package pongo2
import (
"testing"
. "gopkg.in/check.v1"
)
// Hook up gocheck into the "go test" runner.
func TestIssues(t *testing.T) { TestingT(t) }
type IssueTestSuite struct{}
var _ = Suite(&IssueTestSuite{})
func (s *TestSuite) TestIssues(c *C) {
// Add a test for any issue
c.Check(42, Equals, 42)
}

539
vendor/github.com/flosch/pongo2/pongo2_template_test.go generated vendored Normal file
View file

@ -0,0 +1,539 @@
package pongo2
import (
"bytes"
"fmt"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
)
var admin_list = []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 is_admin(u *user) bool {
for _, a := range admin_list {
if a == u.Name {
return true
}
}
return false
}
func (u *user) Is_admin() *Value {
return AsValue(is_admin(u))
}
func (u *user) Is_admin2() bool {
return is_admin(u)
}
func (p *post) String() string {
return ":-)"
}
/*
* Start setup sandbox
*/
type tagSandboxDemoTag struct {
}
func (node *tagSandboxDemoTag) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
buffer.WriteString("hello")
return nil
}
func tagSandboxDemoTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
return &tagSandboxDemoTag{}, nil
}
func BannedFilterFn(in *Value, params *Value) (*Value, *Error) {
return in, nil
}
func init() {
DefaultSet.Debug = true
RegisterFilter("banned_filter", BannedFilterFn)
RegisterFilter("unbanned_filter", BannedFilterFn)
RegisterTag("banned_tag", tagSandboxDemoTagParser)
RegisterTag("unbanned_tag", tagSandboxDemoTagParser)
DefaultSet.BanFilter("banned_filter")
DefaultSet.BanTag("banned_tag")
// Allow different kind of levels inside template_tests/
abs_path, err := filepath.Abs("./template_tests/*")
if err != nil {
panic(err)
}
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, abs_path)
abs_path, err = filepath.Abs("./template_tests/*/*")
if err != nil {
panic(err)
}
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, abs_path)
abs_path, err = filepath.Abs("./template_tests/*/*/*")
if err != nil {
panic(err)
}
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, abs_path)
// Allow pongo2 temp files
DefaultSet.SandboxDirectories = append(DefaultSet.SandboxDirectories, "/tmp/pongo2_*")
f, err := ioutil.TempFile("/tmp/", "pongo2_")
if err != nil {
panic("cannot write to /tmp/")
}
f.Write([]byte("Hello from pongo2"))
DefaultSet.Globals["temp_file"] = f.Name()
}
/*
* End setup sandbox
*/
var tplContext = Context{
"number": 11,
"simple": map[string]interface{}{
"number": 42,
"name": "john doe",
"included_file": "INCLUDES.helper",
"nil": nil,
"uint": uint(8),
"float": float64(3.1415),
"str": "string",
"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},
"misc_list": []interface{}{"Hello", 99, 3.14, "good"},
"escape_text": "This is \\a Test. \"Yep\". 'Yep'.",
"xss": "<script>alert(\"uh oh\");</script>",
"intmap": map[int]string{
1: "one",
2: "two",
5: "five",
},
"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 ...*Value) *Value {
// Create a sum
s := 0
for _, i := range args {
s += i.Integer()
}
return AsValue(s)
},
},
"complex": map[string]interface{}{
"is_admin": is_admin,
"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) {
debug = true
// Add a global to the default set
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 := FromFile(match)
if err != nil {
t.Fatalf("Error on FromFile('%s'): %s", match, err.Error())
}
test_filename := fmt.Sprintf("%s.out", match)
test_out, rerr := ioutil.ReadFile(test_filename)
if rerr != nil {
t.Fatalf("Error on ReadFile('%s'): %s", test_filename, rerr.Error())
}
tpl_out, err := tpl.ExecuteBytes(tplContext)
if err != nil {
t.Fatalf("Error on Execute('%s'): %s", match, err.Error())
}
if bytes.Compare(test_out, tpl_out) != 0 {
t.Logf("Template (rendered) '%s': '%s'", match, tpl_out)
err_filename := filepath.Base(fmt.Sprintf("%s.error", match))
err := ioutil.WriteFile(err_filename, []byte(tpl_out), 0600)
if err != nil {
t.Fatalf(err.Error())
}
t.Logf("get a complete diff with command: 'diff -ya %s %s'", test_filename, err_filename)
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)
test_data, err := ioutil.ReadFile(match)
tests := strings.Split(string(test_data), "\n")
check_filename := fmt.Sprintf("%s.out", match)
check_data, err := ioutil.ReadFile(check_filename)
if err != nil {
t.Fatalf("Error on ReadFile('%s'): %s", check_filename, err.Error())
}
checks := strings.Split(string(check_data), "\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 := 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)
test_data, err := ioutil.ReadFile(match)
tests := strings.Split(string(test_data), "\n")
check_filename := fmt.Sprintf("%s.out", match)
check_data, err := ioutil.ReadFile(check_filename)
if err != nil {
t.Fatalf("Error on ReadFile('%s'): %s", check_filename, err.Error())
}
checks := strings.Split(string(check_data), "\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 = 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/"
s := NewSet("test set with base directory")
s.Globals["base_directory"] = "template_tests/base_dir_test/"
if err := s.SetBaseDirectory(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) {
cache_set := NewSet("cache set")
for i := 0; i < b.N; i++ {
tpl, err := cache_set.FromCache("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkCacheDebugOn(b *testing.B) {
cache_debug_set := NewSet("cache set")
cache_debug_set.Debug = true
for i := 0; i < b.N; i++ {
tpl, err := cache_debug_set.FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkExecuteComplexWithSandboxActive(b *testing.B) {
tpl, err := FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err = tpl.ExecuteBytes(tplContext)
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 := FromString(preloadedTpl)
if err != nil {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkParallelExecuteComplexWithSandboxActive(b *testing.B) {
tpl, err := FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, err := tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
})
}
func BenchmarkExecuteComplexWithoutSandbox(b *testing.B) {
s := NewSet("set without sandbox")
tpl, err := s.FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err = tpl.ExecuteBytes(tplContext)
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 := NewSet("set without sandbox")
b.ResetTimer()
for i := 0; i < b.N; i++ {
tpl, err := s.FromString(preloadedTpl)
if err != nil {
b.Fatal(err)
}
_, err = tpl.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkParallelExecuteComplexWithoutSandbox(b *testing.B) {
s := NewSet("set without sandbox")
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.ExecuteBytes(tplContext)
if err != nil {
b.Fatal(err)
}
}
})
}

66
vendor/github.com/flosch/pongo2/pongo2_test.go generated vendored Normal file
View file

@ -0,0 +1,66 @@
package pongo2
import (
"testing"
. "gopkg.in/check.v1"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct {
tpl *Template
}
var (
_ = Suite(&TestSuite{})
test_suite2 = NewSet("test suite 2")
)
func parseTemplate(s string, c Context) string {
t, err := test_suite2.FromString(s)
if err != nil {
panic(err)
}
out, err := t.Execute(c)
if err != nil {
panic(err)
}
return out
}
func parseTemplateFn(s string, c 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() { Must(test_suite2.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("", Context{"'illegal": nil}), PanicMatches, ".*not a valid identifier.*")
// Registers
c.Check(func() { RegisterFilter("escape", nil) }, PanicMatches, ".*is already registered.*")
c.Check(func() { RegisterTag("for", nil) }, PanicMatches, ".*is already registered.*")
// ApplyFilter
v, err := ApplyFilter("title", AsValue("this is a title"), nil)
if err != nil {
c.Fatal(err)
}
c.Check(v.String(), Equals, "This Is A Title")
c.Check(func() {
_, err := ApplyFilter("doesnotexist", nil, nil)
if err != nil {
panic(err)
}
}, PanicMatches, `\[Error \(where: applyfilter\)\] Filter with name 'doesnotexist' not found.`)
}

132
vendor/github.com/flosch/pongo2/tags.go generated vendored Normal file
View 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 "{%"
token_name := p.MatchType(TokenIdentifier)
// Check for identifier
if token_name == nil {
return nil, p.Error("Tag name must be an identifier.", nil)
}
// Check for the existing tag
tag, exists := tags[token_name.Val]
if !exists {
// Does not exists
return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", token_name.Val), token_name)
}
// Check sandbox tag restriction
if _, is_banned := p.template.set.bannedTags[token_name.Val]; is_banned {
return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", token_name.Val), token_name)
}
args_token := make([]*Token, 0)
for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 {
// Add token to args
args_token = append(args_token, p.Current())
p.Consume() // next token
}
// EOF?
if p.Remaining() == 0 {
return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.last_token)
}
p.Match(TokenSymbol, "%}")
arg_parser := newParser(p.name, args_token, p.template)
if len(args_token) == 0 {
// This is done to have nice EOF error messages
arg_parser.last_token = token_name
}
p.template.level++
defer func() { p.template.level-- }()
return tag.parser(p, token_name, arg_parser)
}

56
vendor/github.com/flosch/pongo2/tags_autoescape.go generated vendored Normal file
View file

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

94
vendor/github.com/flosch/pongo2/tags_block.go generated vendored Normal file
View file

@ -0,0 +1,94 @@
package pongo2
import (
"bytes"
"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, buffer *bytes.Buffer) *Error {
tpl := ctx.template
if tpl == nil {
panic("internal error: tpl == nil")
}
// Determine the block to execute
block_wrapper := node.getBlockWrapperByName(tpl)
if block_wrapper == nil {
// fmt.Printf("could not find: %s\n", node.name)
return ctx.Error("internal error: block_wrapper == nil in tagBlockNode.Execute()", nil)
}
err := block_wrapper.Execute(ctx, buffer)
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)
}
name_token := arguments.MatchType(TokenIdentifier)
if name_token == 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 {
endtagname_token := endtagargs.MatchType(TokenIdentifier)
if endtagname_token != nil {
if endtagname_token.Val != name_token.Val {
return nil, endtagargs.Error(fmt.Sprintf("Name for 'endblock' must equal to 'block'-tag's name ('%s' != '%s').",
name_token.Val, endtagname_token.Val), nil)
}
}
if endtagname_token == 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")
}
_, has_block := tpl.blocks[name_token.Val]
if !has_block {
tpl.blocks[name_token.Val] = wrapper
} else {
return nil, arguments.Error(fmt.Sprintf("Block named '%s' already defined", name_token.Val), nil)
}
return &tagBlockNode{name: name_token.Val}, nil
}
func init() {
RegisterTag("block", tagBlockParser)
}

31
vendor/github.com/flosch/pongo2/tags_comment.go generated vendored Normal file
View file

@ -0,0 +1,31 @@
package pongo2
import (
"bytes"
)
type tagCommentNode struct{}
func (node *tagCommentNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
return nil
}
func tagCommentParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
comment_node := &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 comment_node, nil
}
func init() {
RegisterTag("comment", tagCommentParser)
}

110
vendor/github.com/flosch/pongo2/tags_cycle.go generated vendored Normal file
View file

@ -0,0 +1,110 @@
package pongo2
import (
"bytes"
)
type tagCycleValue struct {
node *tagCycleNode
value *Value
}
type tagCycleNode struct {
position *Token
args []IEvaluator
idx int
as_name string
silent bool
}
func (cv *tagCycleValue) String() string {
return cv.value.String()
}
func (node *tagCycleNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *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 {
buffer.WriteString(val.String())
}
} else {
// Regular call
cycle_value := &tagCycleValue{
node: node,
value: val,
}
if node.as_name != "" {
ctx.Private[node.as_name] = cycle_value
}
if !node.silent {
buffer.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) {
cycle_node := &tagCycleNode{
position: start,
}
for arguments.Remaining() > 0 {
node, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
cycle_node.args = append(cycle_node.args, node)
if arguments.MatchOne(TokenKeyword, "as") != nil {
// as
name_token := arguments.MatchType(TokenIdentifier)
if name_token == nil {
return nil, arguments.Error("Name (identifier) expected after 'as'.", nil)
}
cycle_node.as_name = name_token.Val
if arguments.MatchOne(TokenIdentifier, "silent") != nil {
cycle_node.silent = true
}
// Now we're finished
break
}
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed cycle-tag.", nil)
}
return cycle_node, nil
}
func init() {
RegisterTag("cycle", tagCycleParser)
}

56
vendor/github.com/flosch/pongo2/tags_extends.go generated vendored Normal file
View file

@ -0,0 +1,56 @@
package pongo2
import (
"bytes"
)
type tagExtendsNode struct {
filename string
}
func (node *tagExtendsNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
return nil
}
func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
extends_node := &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 filename_token := arguments.MatchType(TokenString); filename_token != nil {
// prepared, static template
// Get parent's filename
parent_filename := doc.template.set.resolveFilename(doc.template, filename_token.Val)
// Parse the parent
parent_template, err := doc.template.set.FromFile(parent_filename)
if err != nil {
return nil, err.(*Error)
}
// Keep track of things
parent_template.child = doc.template
doc.template.parent = parent_template
extends_node.filename = parent_filename
} 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 extends_node, nil
}
func init() {
RegisterTag("extends", tagExtendsParser)
}

95
vendor/github.com/flosch/pongo2/tags_filter.go generated vendored Normal file
View file

@ -0,0 +1,95 @@
package pongo2
import (
"bytes"
)
type nodeFilterCall struct {
name string
param_expr IEvaluator
}
type tagFilterNode struct {
position *Token
bodyWrapper *NodeWrapper
filterChain []*nodeFilterCall
}
func (node *tagFilterNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *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.param_expr != nil {
param, err = call.param_expr.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)
}
}
buffer.WriteString(value.String())
return nil
}
func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
filter_node := &tagFilterNode{
position: start,
}
wrapper, _, err := doc.WrapUntilTag("endfilter")
if err != nil {
return nil, err
}
filter_node.bodyWrapper = wrapper
for arguments.Remaining() > 0 {
filterCall := &nodeFilterCall{}
name_token := arguments.MatchType(TokenIdentifier)
if name_token == nil {
return nil, arguments.Error("Expected a filter name (identifier).", nil)
}
filterCall.name = name_token.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.param_expr = expr
}
filter_node.filterChain = append(filter_node.filterChain, filterCall)
if arguments.MatchOne(TokenSymbol, "|") == nil {
break
}
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed filter-tag arguments.", nil)
}
return filter_node, nil
}
func init() {
RegisterTag("filter", tagFilterParser)
}

53
vendor/github.com/flosch/pongo2/tags_firstof.go generated vendored Normal file
View file

@ -0,0 +1,53 @@
package pongo2
import (
"bytes"
)
type tagFirstofNode struct {
position *Token
args []IEvaluator
}
func (node *tagFirstofNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *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
}
}
buffer.WriteString(val.String())
return nil
}
}
return nil
}
func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
firstof_node := &tagFirstofNode{
position: start,
}
for arguments.Remaining() > 0 {
node, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
firstof_node.args = append(firstof_node.args, node)
}
return firstof_node, nil
}
func init() {
RegisterTag("firstof", tagFirstofParser)
}

158
vendor/github.com/flosch/pongo2/tags_for.go generated vendored Normal file
View file

@ -0,0 +1,158 @@
package pongo2
import (
"bytes"
)
type tagForNode struct {
key string
value string // only for maps: for key, value in map
object_evaluator IEvaluator
reversed 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, buffer *bytes.Buffer) (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.object_evaluator.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, buffer)
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, buffer)
if err != nil {
forError = err
}
}
}, node.reversed)
return nil
}
func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
for_node := &tagForNode{}
// Arguments parsing
var value_token *Token
key_token := arguments.MatchType(TokenIdentifier)
if key_token == nil {
return nil, arguments.Error("Expected an key identifier as first argument for 'for'-tag", nil)
}
if arguments.Match(TokenSymbol, ",") != nil {
// Value name is provided
value_token = arguments.MatchType(TokenIdentifier)
if value_token == nil {
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)
}
object_evaluator, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
for_node.object_evaluator = object_evaluator
for_node.key = key_token.Val
if value_token != nil {
for_node.value = value_token.Val
}
if arguments.MatchOne(TokenIdentifier, "reversed") != nil {
for_node.reversed = 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
}
for_node.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
}
for_node.emptyWrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
}
return for_node, nil
}
func init() {
RegisterTag("for", tagForParser)
}

81
vendor/github.com/flosch/pongo2/tags_if.go generated vendored Normal file
View file

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

117
vendor/github.com/flosch/pongo2/tags_ifchanged.go generated vendored Normal file
View file

@ -0,0 +1,117 @@
package pongo2
import (
"bytes"
)
type tagIfchangedNode struct {
watched_expr []IEvaluator
last_values []*Value
last_content []byte
thenWrapper *NodeWrapper
elseWrapper *NodeWrapper
}
func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
if len(node.watched_expr) == 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
}
buf_bytes := buf.Bytes()
if !bytes.Equal(node.last_content, buf_bytes) {
// Rendered content changed, output it
buffer.Write(buf_bytes)
node.last_content = buf_bytes
}
} else {
now_values := make([]*Value, 0, len(node.watched_expr))
for _, expr := range node.watched_expr {
val, err := expr.Evaluate(ctx)
if err != nil {
return err
}
now_values = append(now_values, val)
}
// Compare old to new values now
changed := len(node.last_values) == 0
for idx, old_val := range node.last_values {
if !old_val.EqualValueTo(now_values[idx]) {
changed = true
break // we can stop here because ONE value changed
}
}
node.last_values = now_values
if changed {
// Render thenWrapper
err := node.thenWrapper.Execute(ctx, buffer)
if err != nil {
return err
}
} else {
// Render elseWrapper
err := node.elseWrapper.Execute(ctx, buffer)
if err != nil {
return err
}
}
}
return nil
}
func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ifchanged_node := &tagIfchangedNode{}
for arguments.Remaining() > 0 {
// Parse condition
expr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
ifchanged_node.watched_expr = append(ifchanged_node.watched_expr, 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
}
ifchanged_node.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
}
ifchanged_node.elseWrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
}
return ifchanged_node, nil
}
func init() {
RegisterTag("ifchanged", tagIfchangedParser)
}

83
vendor/github.com/flosch/pongo2/tags_ifequal.go generated vendored Normal file
View file

@ -0,0 +1,83 @@
package pongo2
import (
"bytes"
)
type tagIfEqualNode struct {
var1, var2 IEvaluator
thenWrapper *NodeWrapper
elseWrapper *NodeWrapper
}
func (node *tagIfEqualNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *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, buffer)
} else {
if node.elseWrapper != nil {
return node.elseWrapper.Execute(ctx, buffer)
}
}
return nil
}
func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ifequal_node := &tagIfEqualNode{}
// Parse two expressions
var1, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
var2, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
ifequal_node.var1 = var1
ifequal_node.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
}
ifequal_node.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
}
ifequal_node.elseWrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
}
return ifequal_node, nil
}
func init() {
RegisterTag("ifequal", tagIfEqualParser)
}

83
vendor/github.com/flosch/pongo2/tags_ifnotequal.go generated vendored Normal file
View file

@ -0,0 +1,83 @@
package pongo2
import (
"bytes"
)
type tagIfNotEqualNode struct {
var1, var2 IEvaluator
thenWrapper *NodeWrapper
elseWrapper *NodeWrapper
}
func (node *tagIfNotEqualNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *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, buffer)
} else {
if node.elseWrapper != nil {
return node.elseWrapper.Execute(ctx, buffer)
}
}
return nil
}
func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ifnotequal_node := &tagIfNotEqualNode{}
// Parse two expressions
var1, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
var2, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
ifnotequal_node.var1 = var1
ifnotequal_node.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
}
ifnotequal_node.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
}
ifnotequal_node.elseWrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
}
return ifnotequal_node, nil
}
func init() {
RegisterTag("ifnotequal", tagIfNotEqualParser)
}

86
vendor/github.com/flosch/pongo2/tags_import.go generated vendored Normal file
View file

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

132
vendor/github.com/flosch/pongo2/tags_include.go generated vendored Normal file
View file

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

132
vendor/github.com/flosch/pongo2/tags_lorem.go generated vendored Normal file
View file

@ -0,0 +1,132 @@
package pongo2
import (
"bytes"
"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, buffer *bytes.Buffer) *Error {
switch node.method {
case "b":
if node.random {
for i := 0; i < node.count; i++ {
if i > 0 {
buffer.WriteString("\n")
}
par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))]
buffer.WriteString(par)
}
} else {
for i := 0; i < node.count; i++ {
if i > 0 {
buffer.WriteString("\n")
}
par := tagLoremParagraphs[i%len(tagLoremParagraphs)]
buffer.WriteString(par)
}
}
case "w":
if node.random {
for i := 0; i < node.count; i++ {
if i > 0 {
buffer.WriteString(" ")
}
word := tagLoremWords[rand.Intn(len(tagLoremWords))]
buffer.WriteString(word)
}
} else {
for i := 0; i < node.count; i++ {
if i > 0 {
buffer.WriteString(" ")
}
word := tagLoremWords[i%len(tagLoremWords)]
buffer.WriteString(word)
}
}
case "p":
if node.random {
for i := 0; i < node.count; i++ {
if i > 0 {
buffer.WriteString("\n")
}
buffer.WriteString("<p>")
par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))]
buffer.WriteString(par)
buffer.WriteString("</p>")
}
} else {
for i := 0; i < node.count; i++ {
if i > 0 {
buffer.WriteString("\n")
}
buffer.WriteString("<p>")
par := tagLoremParagraphs[i%len(tagLoremParagraphs)]
buffer.WriteString(par)
buffer.WriteString("</p>")
}
}
default:
panic("unsupported method")
}
return nil
}
func tagLoremParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
lorem_node := &tagLoremNode{
position: start,
count: 1,
method: "b",
}
if count_token := arguments.MatchType(TokenNumber); count_token != nil {
lorem_node.count = AsValue(count_token.Val).Integer()
}
if method_token := arguments.MatchType(TokenIdentifier); method_token != nil {
if method_token.Val != "w" && method_token.Val != "p" && method_token.Val != "b" {
return nil, arguments.Error("lorem-method must be either 'w', 'p' or 'b'.", nil)
}
lorem_node.method = method_token.Val
}
if arguments.MatchOne(TokenIdentifier, "random") != nil {
lorem_node.random = true
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed lorem-tag arguments.", nil)
}
return lorem_node, 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
vendor/github.com/flosch/pongo2/tags_macro.go generated vendored Normal file
View file

@ -0,0 +1,149 @@
package pongo2
import (
"bytes"
"fmt"
)
type tagMacroNode struct {
position *Token
name string
args_order []string
args map[string]IEvaluator
exported bool
wrapper *NodeWrapper
}
func (node *tagMacroNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *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 {
args_ctx := make(Context)
for k, v := range node.args {
if v == nil {
// User did not provided a default value
args_ctx[k] = nil
} else {
// Evaluate the default value
value_expr, err := v.Evaluate(ctx)
if err != nil {
ctx.Logf(err.Error())
return AsSafeValue(err.Error())
}
args_ctx[k] = value_expr
}
}
if len(args) > len(node.args_order) {
// Too many arguments, we're ignoring them and just logging into debug mode.
err := ctx.Error(fmt.Sprintf("Macro '%s' called with too many arguments (%d instead of %d).",
node.name, len(args), len(node.args_order)), nil).updateFromTokenIfNeeded(ctx.template, node.position)
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(args_ctx)
for idx, arg_value := range args {
macroCtx.Private[node.args_order[idx]] = arg_value.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) {
macro_node := &tagMacroNode{
position: start,
args: make(map[string]IEvaluator),
}
name_token := arguments.MatchType(TokenIdentifier)
if name_token == nil {
return nil, arguments.Error("Macro-tag needs at least an identifier as name.", nil)
}
macro_node.name = name_token.Val
if arguments.MatchOne(TokenSymbol, "(") == nil {
return nil, arguments.Error("Expected '('.", nil)
}
for arguments.Match(TokenSymbol, ")") == nil {
arg_name_token := arguments.MatchType(TokenIdentifier)
if arg_name_token == nil {
return nil, arguments.Error("Expected argument name as identifier.", nil)
}
macro_node.args_order = append(macro_node.args_order, arg_name_token.Val)
if arguments.Match(TokenSymbol, "=") != nil {
// Default expression follows
arg_default_expr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
macro_node.args[arg_name_token.Val] = arg_default_expr
} else {
// No default expression
macro_node.args[arg_name_token.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 {
macro_node.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
}
macro_node.wrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
if macro_node.exported {
// Now register the macro if it wants to be exported
_, has := doc.template.exported_macros[macro_node.name]
if has {
return nil, doc.Error(fmt.Sprintf("Another macro with name '%s' already exported.", macro_node.name), start)
}
doc.template.exported_macros[macro_node.name] = macro_node
}
return macro_node, nil
}
func init() {
RegisterTag("macro", tagMacroParser)
}

51
vendor/github.com/flosch/pongo2/tags_now.go generated vendored Normal file
View file

@ -0,0 +1,51 @@
package pongo2
import (
"bytes"
"time"
)
type tagNowNode struct {
position *Token
format string
fake bool
}
func (node *tagNowNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *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()
}
buffer.WriteString(t.Format(node.format))
return nil
}
func tagNowParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
now_node := &tagNowNode{
position: start,
}
format_token := arguments.MatchType(TokenString)
if format_token == nil {
return nil, arguments.Error("Expected a format string.", nil)
}
now_node.format = format_token.Val
if arguments.MatchOne(TokenIdentifier, "fake") != nil {
now_node.fake = true
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed now-tag arguments.", nil)
}
return now_node, nil
}
func init() {
RegisterTag("now", tagNowParser)
}

52
vendor/github.com/flosch/pongo2/tags_set.go generated vendored Normal file
View file

@ -0,0 +1,52 @@
package pongo2
import "bytes"
type tagSetNode struct {
name string
expression IEvaluator
}
func (node *tagSetNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *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
vendor/github.com/flosch/pongo2/tags_spaceless.go generated vendored Normal file
View 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, buffer *bytes.Buffer) *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
}
buffer.WriteString(s)
return nil
}
func tagSpacelessParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
spaceless_node := &tagSpacelessNode{}
wrapper, _, err := doc.WrapUntilTag("endspaceless")
if err != nil {
return nil, err
}
spaceless_node.wrapper = wrapper
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed spaceless-tag arguments.", nil)
}
return spaceless_node, nil
}
func init() {
RegisterTag("spaceless", tagSpacelessParser)
}

69
vendor/github.com/flosch/pongo2/tags_ssi.go generated vendored Normal file
View file

@ -0,0 +1,69 @@
package pongo2
import (
"bytes"
"io/ioutil"
)
type tagSSINode struct {
filename string
content string
template *Template
}
func (node *tagSSINode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
if node.template != nil {
// Execute the template within the current context
includeCtx := make(Context)
includeCtx.Update(ctx.Public)
includeCtx.Update(ctx.Private)
err := node.template.ExecuteWriter(includeCtx, buffer)
if err != nil {
return err.(*Error)
}
} else {
// Just print out the content
buffer.WriteString(node.content)
}
return nil
}
func tagSSIParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ssi_node := &tagSSINode{}
if file_token := arguments.MatchType(TokenString); file_token != nil {
ssi_node.filename = file_token.Val
if arguments.Match(TokenIdentifier, "parsed") != nil {
// parsed
temporary_tpl, err := doc.template.set.FromFile(doc.template.set.resolveFilename(doc.template, file_token.Val))
if err != nil {
return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, file_token)
}
ssi_node.template = temporary_tpl
} else {
// plaintext
buf, err := ioutil.ReadFile(doc.template.set.resolveFilename(doc.template, file_token.Val))
if err != nil {
return nil, (&Error{
Sender: "tag:ssi",
ErrorMsg: err.Error(),
}).updateFromTokenIfNeeded(doc.template, file_token)
}
ssi_node.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 ssi_node, nil
}
func init() {
RegisterTag("ssi", tagSSIParser)
}

49
vendor/github.com/flosch/pongo2/tags_templatetag.go generated vendored Normal file
View file

@ -0,0 +1,49 @@
package pongo2
import (
"bytes"
)
type tagTemplateTagNode struct {
content string
}
var templateTagMapping = map[string]string{
"openblock": "{%",
"closeblock": "%}",
"openvariable": "{{",
"closevariable": "}}",
"openbrace": "{",
"closebrace": "}",
"opencomment": "{#",
"closecomment": "#}",
}
func (node *tagTemplateTagNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
buffer.WriteString(node.content)
return nil
}
func tagTemplateTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
tt_node := &tagTemplateTagNode{}
if arg_token := arguments.MatchType(TokenIdentifier); arg_token != nil {
output, found := templateTagMapping[arg_token.Val]
if !found {
return nil, arguments.Error("Argument not found", arg_token)
}
tt_node.content = output
} else {
return nil, arguments.Error("Identifier expected.", nil)
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed templatetag-tag argument.", nil)
}
return tt_node, nil
}
func init() {
RegisterTag("templatetag", tagTemplateTagParser)
}

84
vendor/github.com/flosch/pongo2/tags_widthratio.go generated vendored Normal file
View file

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

92
vendor/github.com/flosch/pongo2/tags_with.go generated vendored Normal file
View file

@ -0,0 +1,92 @@
package pongo2
import (
"bytes"
)
type tagWithNode struct {
with_pairs map[string]IEvaluator
wrapper *NodeWrapper
}
func (node *tagWithNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
//new context for block
withctx := NewChildExecutionContext(ctx)
// Put all custom with-pairs into the context
for key, value := range node.with_pairs {
val, err := value.Evaluate(ctx)
if err != nil {
return err
}
withctx.Private[key] = val
}
return node.wrapper.Execute(withctx, buffer)
}
func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
with_node := &tagWithNode{
with_pairs: 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
}
with_node.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.
old_style := false // by default we're using the new_style
for i := 0; i < arguments.Count(); i++ {
if arguments.PeekN(i, TokenKeyword, "as") != nil {
old_style = true
break
}
}
for arguments.Remaining() > 0 {
if old_style {
value_expr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
if arguments.Match(TokenKeyword, "as") == nil {
return nil, arguments.Error("Expected 'as' keyword.", nil)
}
key_token := arguments.MatchType(TokenIdentifier)
if key_token == nil {
return nil, arguments.Error("Expected an identifier", nil)
}
with_node.with_pairs[key_token.Val] = value_expr
} else {
key_token := arguments.MatchType(TokenIdentifier)
if key_token == nil {
return nil, arguments.Error("Expected an identifier", nil)
}
if arguments.Match(TokenSymbol, "=") == nil {
return nil, arguments.Error("Expected '='.", nil)
}
value_expr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
with_node.with_pairs[key_token.Val] = value_expr
}
}
return with_node, nil
}
func init() {
RegisterTag("with", tagWithParser)
}

164
vendor/github.com/flosch/pongo2/template.go generated vendored Normal file
View file

@ -0,0 +1,164 @@
package pongo2
import (
"bytes"
"fmt"
"io"
)
type Template struct {
set *TemplateSet
// Input
is_tpl_string bool
name string
tpl string
size int
// Calculation
tokens []*Token
parser *Parser
// first come, first serve (it's important to not override existing entries in here)
level int
parent *Template
child *Template
blocks map[string]*NodeWrapper
exported_macros map[string]*tagMacroNode
// Output
root *nodeDocument
}
func newTemplateString(set *TemplateSet, tpl string) (*Template, error) {
return newTemplate(set, "<string>", true, tpl)
}
func newTemplate(set *TemplateSet, name string, is_tpl_string bool, tpl string) (*Template, error) {
// Create the template
t := &Template{
set: set,
is_tpl_string: is_tpl_string,
name: name,
tpl: tpl,
size: len(tpl),
blocks: make(map[string]*NodeWrapper),
exported_macros: make(map[string]*tagMacroNode),
}
// Tokenize it
tokens, err := lex(name, tpl)
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) (*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)))
// 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 nil, err
}
// Check for clashes with macro names
for k, _ := range newContext {
_, has := tpl.exported_macros[k]
if has {
return nil, &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
err := parent.root.Execute(ctx, buffer)
if 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 {
buffer, err := tpl.execute(context)
if err != nil {
return err
}
l := buffer.Len()
n, werr := buffer.WriteTo(writer)
if int(n) != l {
panic(fmt.Sprintf("error on writing template: n(%d) != buffer.Len(%d)", n, l))
}
if werr != nil {
return &Error{
Filename: tpl.name,
Sender: "execution",
ErrorMsg: werr.Error(),
}
}
return nil
}
// Executes the template and returns the rendered template as a []byte
func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) {
// Execute template
buffer, err := tpl.execute(context)
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.execute(context)
if err != nil {
return "", err
}
return buffer.String(), nil
}

296
vendor/github.com/flosch/pongo2/template_sets.go generated vendored Normal file
View file

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

View 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 %}

View file

@ -0,0 +1,9 @@
&lt;script&gt;alert(&#39;xss&#39;);&lt;/script&gt;
<script>alert('xss');</script>
&lt;script&gt;alert(&#39;xss&#39;);&lt;/script&gt;
&lt;script&gt;alert(&#39;xss&#39;);&lt;/script&gt;

View file

@ -0,0 +1 @@
Hello from {{ base_directory }}

View file

@ -0,0 +1 @@
{% include "base.html" %}

View file

@ -0,0 +1 @@
{% extends "base.html" %}

View file

@ -0,0 +1 @@
{% ssi "base.html" parsed %}

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

View 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>&quot;pongo2 is nice!&quot;</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>

View 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 }}'

View 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'

View file

View file

View file

@ -0,0 +1,53 @@
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) }}
issue #48 (associativity for infix operators)
{{ 34/3*3 }}
{{ 10 + 24 / 6 / 2 }}
{{ 6 - 4 - 2 }}

View file

@ -0,0 +1,53 @@
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
issue #48 (associativity for infix operators)
33
12
0

View file

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

View file

@ -0,0 +1 @@
Start#This is base's bodyExtends' content#End

View file

@ -0,0 +1,5 @@
{{ }}
{{ (1 - 1 }}
{{ 1|float: }}
{{ "test"|non_existent_filter }}
{{ "test"|"test" }}

View 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\.

View file

@ -0,0 +1,3 @@
{{ -(true || false) }}
{{ simple.func_add("test", 5) }}
{{ simple.func_variadic_sum_int("foo") }}

View file

@ -0,0 +1,3 @@
.*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 variadic input argument of 'simple.func_variadic_sum_int' must be of type int or \*pongo2.Value \(not string\).

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

View 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
&lt;script&gt;
<script>
escape
&lt;script&gt;
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.comment Value>
last
t
<pongo2.comment Value>
urlencode
http%3A%2F%2Fwww.example.org%2Ffoo%3Fa%3Db%26c%3Dd
linebreaksbr
this is a text&lt;br /&gt;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&amp;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=&quot;test&quot;</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>
...

View 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" %}

View file

@ -0,0 +1,7 @@
42
&lt;script&gt;alert(&#39;xss&#39;);&lt;/script&gt;
<script>alert('xss');</script>
8
test
test

View file

@ -0,0 +1,9 @@
{% 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 %}'

View file

@ -0,0 +1,19 @@
[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 '

View file

@ -0,0 +1,11 @@
{{ simple.func_add(simple.func_add(5, 15), simple.number) + 17 }}
{{ simple.func_add_iface(simple.func_add_iface(5, 15), simple.number) + 17 }}
{{ simple.func_variadic("hello") }}
{{ simple.func_variadic("hello, %s", simple.name) }}
{{ simple.func_variadic("%d + %d %s %d", 5, simple.number, "is", 49) }}
{{ simple.func_variadic_sum_int() }}
{{ simple.func_variadic_sum_int(1) }}
{{ simple.func_variadic_sum_int(1, 19, 185) }}
{{ simple.func_variadic_sum_int2() }}
{{ simple.func_variadic_sum_int2(2) }}
{{ simple.func_variadic_sum_int2(1, 7, 100) }}

View file

@ -0,0 +1,11 @@
79
79
hello
hello, john doe
5 + 42 is 49
0
1
205
0
2
108

17
vendor/github.com/flosch/pongo2/template_tests/if.tpl generated vendored Normal file
View file

@ -0,0 +1,17 @@
{% if nothing %}false{% else %}true{% endif %}
{% if simple %}simple != nil{% endif %}
{% if simple.uint %}uint != 0{% endif %}
{% if simple.float %}float != 0.0{% endif %}
{% if !simple %}false{% else %}!simple{% endif %}
{% if !simple.uint %}false{% else %}!simple.uint{% endif %}
{% if !simple.float %}false{% else %}!simple.float{% endif %}
{% if "Text" in complex.post %}text field in complex.post{% endif %}
{% if 5 in simple.intmap %}5 in simple.intmap{% endif %}
{% if !0.0 %}!0.0{% endif %}
{% if !0 %}!0{% endif %}
{% if simple.number == 43 %}no{% else %}42{% endif %}
{% if simple.number < 42 %}false{% elif simple.number > 42 %}no{% elif simple.number >= 42 %}yes{% else %}no{% endif %}
{% if simple.number < 42 %}false{% elif simple.number > 42 %}no{% elif simple.number != 42 %}no{% else %}yes{% endif %}
{% if 0 %}!0{% elif nothing %}nothing{% else %}true{% endif %}
{% if 0 %}!0{% elif simple.float %}simple.float{% else %}false{% endif %}
{% if 0 %}!0{% elif !simple.float %}false{% elif "Text" in complex.post%}Elseif with no else{% endif %}

View file

@ -0,0 +1,17 @@
true
simple != nil
uint != 0
float != 0.0
!simple
!simple.uint
!simple.float
text field in complex.post
5 in simple.intmap
!0.0
!0
42
yes
yes
true
simple.float
Elseif with no else

View file

@ -0,0 +1,9 @@
{% for comment in complex.comments2 %}
{% ifchanged %}New comment from another user {{ comment.Author.Name }}{% endifchanged %}
{% ifchanged comment.Author.Validated %}
Validated changed to {{ comment.Author.Validated }}
{% else %}
Validated value not changed
{% endifchanged %}
{% ifchanged comment.Author.Name comment.Date %}Comment's author name or date changed{% endifchanged %}
{% endfor %}

View file

@ -0,0 +1,18 @@
New comment from another user user1
Validated changed to True
Comment's author name or date changed
Validated value not changed
Comment's author name or date changed
New comment from another user user3
Validated changed to False
Comment's author name or date changed

View file

@ -0,0 +1 @@
I'm {{ what_am_i }}{{ number }}

View file

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

View file

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

View file

@ -0,0 +1,3 @@
{% extends "inheritance2/skeleton.tpl" %}
{% block body %}This is base's body{% block content %}Default content{% endblock %}{% endblock %}

View file

@ -0,0 +1 @@
{% include "doesnotexist.tpl" %}

View file

@ -0,0 +1 @@
Included '{{ cycleitem }}'.

View file

@ -0,0 +1 @@
Start#{% block body %}Default body{% endblock %}#End

View file

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