diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..4bf14f9
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,8 @@
+FROM scratch
+
+ADD https://rootcastore.hub.luzifer.io/v1/store/latest /etc/ssl/ca-bundle.pem
+ADD promcertcheck /promcertcheck
+
+EXPOSE 3000
+ENTRYPOINT ["/promcertcheck"]
+CMD ["--probe=https://www.google.com/", "--probe=https://www.facebook.com/"]
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
new file mode 100644
index 0000000..d5614cc
--- /dev/null
+++ b/Godeps/Godeps.json
@@ -0,0 +1,73 @@
+{
+ "ImportPath": "github.com/Luzifer/promcertcheck",
+ "GoVersion": "go1.5",
+ "Deps": [
+ {
+ "ImportPath": "bitbucket.org/ww/goautoneg",
+ "Comment": "null-5",
+ "Rev": "75cd24fc2f2c2a2088577d12123ddee5f54e0675"
+ },
+ {
+ "ImportPath": "github.com/Luzifer/rconfig",
+ "Rev": "0c78105a26af5663b6bb2c5be1fed4ed7d81d687"
+ },
+ {
+ "ImportPath": "github.com/beorn7/perks/quantile",
+ "Rev": "b965b613227fddccbfffe13eae360ed3fa822f8d"
+ },
+ {
+ "ImportPath": "github.com/flosch/pongo2",
+ "Comment": "v1.0-rc1-174-gf5d79aa",
+ "Rev": "f5d79aa0a914c08eb7f51a96cd7b2dbbe46fca46"
+ },
+ {
+ "ImportPath": "github.com/golang/protobuf/proto",
+ "Rev": "aece6fb931241ad332956db4f62798dfbea944b3"
+ },
+ {
+ "ImportPath": "github.com/gorilla/context",
+ "Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
+ },
+ {
+ "ImportPath": "github.com/gorilla/mux",
+ "Rev": "8096f47503459bcc74d1f4c487b7e6e42e5746b5"
+ },
+ {
+ "ImportPath": "github.com/matttproud/golang_protobuf_extensions/pbutil",
+ "Rev": "fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a"
+ },
+ {
+ "ImportPath": "github.com/prometheus/client_golang/model",
+ "Comment": "0.6.0-17-g3b16b46",
+ "Rev": "3b16b46a713f181888e5e9a1205ccc34d6917fb9"
+ },
+ {
+ "ImportPath": "github.com/prometheus/client_golang/prometheus",
+ "Comment": "0.6.0-17-g3b16b46",
+ "Rev": "3b16b46a713f181888e5e9a1205ccc34d6917fb9"
+ },
+ {
+ "ImportPath": "github.com/prometheus/client_golang/text",
+ "Comment": "0.6.0-17-g3b16b46",
+ "Rev": "3b16b46a713f181888e5e9a1205ccc34d6917fb9"
+ },
+ {
+ "ImportPath": "github.com/prometheus/client_model/go",
+ "Comment": "model-0.0.2-12-gfa8ad6f",
+ "Rev": "fa8ad6fec33561be4280a8f0514318c79d7f6cb6"
+ },
+ {
+ "ImportPath": "github.com/prometheus/procfs",
+ "Rev": "c91d8eefde16bd047416409eb56353ea84a186e4"
+ },
+ {
+ "ImportPath": "github.com/robfig/cron",
+ "Comment": "v1-2-g67823cd",
+ "Rev": "67823cd24dece1b04cced3a0a0b3ca2bc84d875e"
+ },
+ {
+ "ImportPath": "github.com/spf13/pflag",
+ "Rev": "8e7dc108ab3a1ab6ce6d922bbaff5657b88e8e49"
+ }
+ ]
+}
diff --git a/Godeps/Readme b/Godeps/Readme
new file mode 100644
index 0000000..4cdaa53
--- /dev/null
+++ b/Godeps/Readme
@@ -0,0 +1,5 @@
+This directory tree is generated automatically by godep.
+
+Please do not edit.
+
+See https://github.com/tools/godep for more information.
diff --git a/Godeps/_workspace/.gitignore b/Godeps/_workspace/.gitignore
new file mode 100644
index 0000000..f037d68
--- /dev/null
+++ b/Godeps/_workspace/.gitignore
@@ -0,0 +1,2 @@
+/pkg
+/bin
diff --git a/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/Makefile b/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/Makefile
new file mode 100644
index 0000000..e33ee17
--- /dev/null
+++ b/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/Makefile
@@ -0,0 +1,13 @@
+include $(GOROOT)/src/Make.inc
+
+TARG=bitbucket.org/ww/goautoneg
+GOFILES=autoneg.go
+
+include $(GOROOT)/src/Make.pkg
+
+format:
+ gofmt -w *.go
+
+docs:
+ gomake clean
+ godoc ${TARG} > README.txt
diff --git a/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/README.txt b/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/README.txt
new file mode 100644
index 0000000..7723656
--- /dev/null
+++ b/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/README.txt
@@ -0,0 +1,67 @@
+PACKAGE
+
+package goautoneg
+import "bitbucket.org/ww/goautoneg"
+
+HTTP Content-Type Autonegotiation.
+
+The functions in this package implement the behaviour specified in
+http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+
+Copyright (c) 2011, Open Knowledge Foundation Ltd.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ Neither the name of the Open Knowledge Foundation Ltd. nor the
+ names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+FUNCTIONS
+
+func Negotiate(header string, alternatives []string) (content_type string)
+Negotiate the most appropriate content_type given the accept header
+and a list of alternatives.
+
+func ParseAccept(header string) (accept []Accept)
+Parse an Accept Header string returning a sorted list
+of clauses
+
+
+TYPES
+
+type Accept struct {
+ Type, SubType string
+ Q float32
+ Params map[string]string
+}
+Structure to represent a clause in an HTTP Accept Header
+
+
+SUBDIRECTORIES
+
+ .hg
diff --git a/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg.go b/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg.go
new file mode 100644
index 0000000..648b38c
--- /dev/null
+++ b/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg.go
@@ -0,0 +1,162 @@
+/*
+HTTP Content-Type Autonegotiation.
+
+The functions in this package implement the behaviour specified in
+http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+
+Copyright (c) 2011, Open Knowledge Foundation Ltd.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ Neither the name of the Open Knowledge Foundation Ltd. nor the
+ names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+*/
+package goautoneg
+
+import (
+ "sort"
+ "strconv"
+ "strings"
+)
+
+// Structure to represent a clause in an HTTP Accept Header
+type Accept struct {
+ Type, SubType string
+ Q float64
+ Params map[string]string
+}
+
+// For internal use, so that we can use the sort interface
+type accept_slice []Accept
+
+func (accept accept_slice) Len() int {
+ slice := []Accept(accept)
+ return len(slice)
+}
+
+func (accept accept_slice) Less(i, j int) bool {
+ slice := []Accept(accept)
+ ai, aj := slice[i], slice[j]
+ if ai.Q > aj.Q {
+ return true
+ }
+ if ai.Type != "*" && aj.Type == "*" {
+ return true
+ }
+ if ai.SubType != "*" && aj.SubType == "*" {
+ return true
+ }
+ return false
+}
+
+func (accept accept_slice) Swap(i, j int) {
+ slice := []Accept(accept)
+ slice[i], slice[j] = slice[j], slice[i]
+}
+
+// Parse an Accept Header string returning a sorted list
+// of clauses
+func ParseAccept(header string) (accept []Accept) {
+ parts := strings.Split(header, ",")
+ accept = make([]Accept, 0, len(parts))
+ for _, part := range parts {
+ part := strings.Trim(part, " ")
+
+ a := Accept{}
+ a.Params = make(map[string]string)
+ a.Q = 1.0
+
+ mrp := strings.Split(part, ";")
+
+ media_range := mrp[0]
+ sp := strings.Split(media_range, "/")
+ a.Type = strings.Trim(sp[0], " ")
+
+ switch {
+ case len(sp) == 1 && a.Type == "*":
+ a.SubType = "*"
+ case len(sp) == 2:
+ a.SubType = strings.Trim(sp[1], " ")
+ default:
+ continue
+ }
+
+ if len(mrp) == 1 {
+ accept = append(accept, a)
+ continue
+ }
+
+ for _, param := range mrp[1:] {
+ sp := strings.SplitN(param, "=", 2)
+ if len(sp) != 2 {
+ continue
+ }
+ token := strings.Trim(sp[0], " ")
+ if token == "q" {
+ a.Q, _ = strconv.ParseFloat(sp[1], 32)
+ } else {
+ a.Params[token] = strings.Trim(sp[1], " ")
+ }
+ }
+
+ accept = append(accept, a)
+ }
+
+ slice := accept_slice(accept)
+ sort.Sort(slice)
+
+ return
+}
+
+// Negotiate the most appropriate content_type given the accept header
+// and a list of alternatives.
+func Negotiate(header string, alternatives []string) (content_type string) {
+ asp := make([][]string, 0, len(alternatives))
+ for _, ctype := range alternatives {
+ asp = append(asp, strings.SplitN(ctype, "/", 2))
+ }
+ for _, clause := range ParseAccept(header) {
+ for i, ctsp := range asp {
+ if clause.Type == ctsp[0] && clause.SubType == ctsp[1] {
+ content_type = alternatives[i]
+ return
+ }
+ if clause.Type == ctsp[0] && clause.SubType == "*" {
+ content_type = alternatives[i]
+ return
+ }
+ if clause.Type == "*" && clause.SubType == "*" {
+ content_type = alternatives[i]
+ return
+ }
+ }
+ }
+ return
+}
diff --git a/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg_test.go b/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg_test.go
new file mode 100644
index 0000000..41d328f
--- /dev/null
+++ b/Godeps/_workspace/src/bitbucket.org/ww/goautoneg/autoneg_test.go
@@ -0,0 +1,33 @@
+package goautoneg
+
+import (
+ "testing"
+)
+
+var chrome = "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
+
+func TestParseAccept(t *testing.T) {
+ alternatives := []string{"text/html", "image/png"}
+ content_type := Negotiate(chrome, alternatives)
+ if content_type != "image/png" {
+ t.Errorf("got %s expected image/png", content_type)
+ }
+
+ alternatives = []string{"text/html", "text/plain", "text/n3"}
+ content_type = Negotiate(chrome, alternatives)
+ if content_type != "text/html" {
+ t.Errorf("got %s expected text/html", content_type)
+ }
+
+ alternatives = []string{"text/n3", "text/plain"}
+ content_type = Negotiate(chrome, alternatives)
+ if content_type != "text/plain" {
+ t.Errorf("got %s expected text/plain", content_type)
+ }
+
+ alternatives = []string{"text/n3", "application/rdf+xml"}
+ content_type = Negotiate(chrome, alternatives)
+ if content_type != "text/n3" {
+ t.Errorf("got %s expected text/n3", content_type)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/Luzifer/rconfig/LICENSE b/Godeps/_workspace/src/github.com/Luzifer/rconfig/LICENSE
new file mode 100644
index 0000000..4fde5d2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/Luzifer/rconfig/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2015 Knut Ahlers This user registered {{ user.register_date|naturaltime }}. The user's biography: {{ user.biography|markdown|truncatewords_html:15 }}
+ read more This user is an admin! ... ")
+ 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("= 40) || (user.karma > calc_avg_karma(userlist)+5) %}
+ class="karma-good"{% endif %}>
+
+
+ {{ user }}
+
+
+
+ Our admins
+ {% for admin in adminlist %}
+ {{ user_details(admin, true) }}
+ {% endfor %}
+
+ Our members
+ {% for user in userlist %}
+ {{ user_details(user) }}
+ {% endfor %}
+
+
+```
+
+## Development status
+
+**Latest stable release**: v3.0 (`go get -u gopkg.in/flosch/pongo2.v3` / [`v3`](https://github.com/flosch/pongo2/tree/v3)-branch) [[read the announcement](https://www.florian-schlachter.de/post/pongo2-v3/)]
+
+**Current development**: v4 (`master`-branch)
+
+*Note*: With the release of pongo v4 the branch v2 will be deprecated.
+
+**Deprecated versions** (not supported anymore): v1
+
+| Topic | Status |
+| ------------------------------------ | -------------------------------------------------------------------------------------- |
+| Django version compatibility: | [1.7](https://docs.djangoproject.com/en/1.7/ref/templates/builtins/) |
+| *Missing* (planned) **filters**: | none ([hints](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3)) |
+| *Missing* (planned) **tags**: | none ([hints](https://github.com/flosch/pongo2/blob/master/tags.go#L3)) |
+
+Please also have a look on the [caveats](https://github.com/flosch/pongo2#caveats) and on the [official add-ons](https://github.com/flosch/pongo2#official).
+
+## Features (and new in pongo2)
+
+ * Entirely rewritten from the ground-up.
+ * [Advanced C-like expressions](https://github.com/flosch/pongo2/blob/master/template_tests/expressions.tpl).
+ * [Complex function calls within expressions](https://github.com/flosch/pongo2/blob/master/template_tests/function_calls_wrapper.tpl).
+ * [Easy API to create new filters and tags](http://godoc.org/github.com/flosch/pongo2#RegisterFilter) ([including parsing arguments](http://godoc.org/github.com/flosch/pongo2#Parser))
+ * Additional features:
+ * Macros including importing macros from other files (see [template_tests/macro.tpl](https://github.com/flosch/pongo2/blob/master/template_tests/macro.tpl))
+ * [Template sandboxing](https://godoc.org/github.com/flosch/pongo2#TemplateSet) ([directory patterns](http://golang.org/pkg/path/filepath/#Match), banned tags/filters)
+
+## Recent API changes within pongo2
+
+If you're using the `master`-branch of pongo2, you might be interested in this section. Since pongo2 is still in development (even though there is a first stable release!), there could be (backwards-incompatible) API changes over time. To keep track of these and therefore make it painless for you to adapt your codebase, I'll list them here.
+
+ * Function signature for tag execution changed: not taking a `bytes.Buffer` anymore; instead `Execute()`-functions are now taking a `TemplateWriter` interface.
+ * Function signature for tag and filter parsing/execution changed (`error` return type changed to `*Error`).
+ * `INodeEvaluator` has been removed and got replaced by `IEvaluator`. You can change your existing tags/filters by simply replacing the interface.
+ * Two new helper functions: [`RenderTemplateFile()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateFile) and [`RenderTemplateString()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateString).
+ * `Template.ExecuteRW()` is now [`Template.ExecuteWriter()`](https://godoc.org/github.com/flosch/pongo2#Template.ExecuteWriter)
+ * `Template.Execute*()` functions do now take a `pongo2.Context` directly (no pointer anymore).
+
+## How you can help
+
+ * Write [filters](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3) / [tags](https://github.com/flosch/pongo2/blob/master/tags.go#L4) (see [tutorial](https://www.florian-schlachter.de/post/pongo2/)) by forking pongo2 and sending pull requests
+ * Write/improve code tests (use the following command to see what tests are missing: `go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out` or have a look on [gocover.io/github.com/flosch/pongo2](http://gocover.io/github.com/flosch/pongo2))
+ * Write/improve template tests (see the `template_tests/` directory)
+ * Write middleware, libraries and websites using pongo2. :-)
+
+# Documentation
+
+For a documentation on how the templating language works you can [head over to the Django documentation](https://docs.djangoproject.com/en/dev/topics/templates/). pongo2 aims to be compatible with it.
+
+You can access pongo2's API documentation on [godoc](https://godoc.org/github.com/flosch/pongo2).
+
+## Blog post series
+
+ * [pongo2 v3 released](https://www.florian-schlachter.de/post/pongo2-v3/)
+ * [pongo2 v2 released](https://www.florian-schlachter.de/post/pongo2-v2/)
+ * [pongo2 1.0 released](https://www.florian-schlachter.de/post/pongo2-10/) [August 8th 2014]
+ * [pongo2 playground](https://www.florian-schlachter.de/post/pongo2-playground/) [August 1st 2014]
+ * [Release of pongo2 1.0-rc1 + pongo2-addons](https://www.florian-schlachter.de/post/pongo2-10-rc1/) [July 30th 2014]
+ * [Introduction to pongo2 + migration- and "how to write tags/filters"-tutorial.](https://www.florian-schlachter.de/post/pongo2/) [June 29th 2014]
+
+## Caveats
+
+### Filters
+
+ * **date** / **time**: The `date` and `time` filter are taking the Golang specific time- and date-format (not Django's one) currently. [Take a look on the format here](http://golang.org/pkg/time/#Time.Format).
+ * **stringformat**: `stringformat` does **not** take Python's string format syntax as a parameter, instead it takes Go's. Essentially `{{ 3.14|stringformat:"pi is %.2f" }}` is `fmt.Sprintf("pi is %.2f", 3.14)`.
+ * **escape** / **force_escape**: Unlike Django's behaviour, the `escape`-filter is applied immediately. Therefore there is no need for a `force_escape`-filter yet.
+
+### Tags
+
+ * **for**: All the `forloop` fields (like `forloop.counter`) are written with a capital letter at the beginning. For example, the `counter` can be accessed by `forloop.Counter` and the parentloop by `forloop.Parentloop`.
+ * **now**: takes Go's time format (see **date** and **time**-filter).
+
+### Misc
+
+ * **not in-operator**: You can check whether a map/struct/string contains a key/field/substring by using the in-operator (or the negation of it):
+ `{% if key in map %}Key is in map{% else %}Key not in map{% endif %}` or `{% if !(key in map) %}Key is NOT in map{% else %}Key is in map{% endif %}`.
+
+# Add-ons, libraries and helpers
+
+## Official
+
+ * [ponginae](https://github.com/flosch/ponginae) - A web-framework for Go (using pongo2).
+ * [pongo2-tools](https://github.com/flosch/pongo2-tools) - Official tools and helpers for pongo2
+ * [pongo2-addons](https://github.com/flosch/pongo2-addons) - Official additional filters/tags for pongo2 (for example a **markdown**-filter). They are in their own repository because they're relying on 3rd-party-libraries.
+
+## 3rd-party
+
+ * [beego-pongo2](https://github.com/oal/beego-pongo2) - A tiny little helper for using Pongo2 with [Beego](https://github.com/astaxie/beego).
+ * [beego-pongo2.v2](https://github.com/ipfans/beego-pongo2.v2) - Same as `beego-pongo2`, but for pongo2 v2.
+ * [macaron-pongo2](https://github.com/macaron-contrib/pongo2) - pongo2 support for [Macaron](https://github.com/Unknwon/macaron), a modular web framework.
+ * [ginpongo2](https://github.com/ngerakines/ginpongo2) - middleware for [gin](github.com/gin-gonic/gin) to use pongo2 templates
+ * [pongo2-trans](https://github.com/fromYukki/pongo2trans) - `trans`-tag implementation for internationalization
+ * [tpongo2](https://github.com/tango-contrib/tpongo2) - pongo2 support for [Tango](https://github.com/lunny/tango), a micro-kernel & pluggable web framework.
+
+Please add your project to this list and send me a pull request when you've developed something nice for pongo2.
+
+# API-usage examples
+
+Please see the documentation for a full list of provided API methods.
+
+## A tiny example (template string)
+
+```Go
+// Compile the template first (i. e. creating the AST)
+tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
+if err != nil {
+ panic(err)
+}
+// Now you can render the template with the given
+// pongo2.Context how often you want to.
+out, err := tpl.Execute(pongo2.Context{"name": "florian"})
+if err != nil {
+ panic(err)
+}
+fmt.Println(out) // Output: Hello Florian!
+```
+
+## Example server-usage (template file)
+
+```Go
+package main
+
+import (
+ "github.com/flosch/pongo2"
+ "net/http"
+)
+
+// Pre-compiling the templates at application startup using the
+// little Must()-helper function (Must() will panic if FromFile()
+// or FromString() will return with an error - that's it).
+// It's faster to pre-compile it anywhere at startup and only
+// execute the template later.
+var tplExample = pongo2.Must(pongo2.FromFile("example.html"))
+
+func examplePage(w http.ResponseWriter, r *http.Request) {
+ // Execute the template per HTTP request
+ err := tplExample.ExecuteWriter(pongo2.Context{"query": r.FormValue("query")}, w)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
+
+func main() {
+ http.HandleFunc("/", examplePage)
+ http.ListenAndServe(":8080", nil)
+}
+```
+
+# Benchmark
+
+The benchmarks have been run on the my machine (`Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz`) using the command:
+
+ go test -bench . -cpu 1,2,4,8
+
+All benchmarks are compiling (depends on the benchmark) and executing the `template_tests/complex.tpl` template.
+
+The results are:
+
+ BenchmarkExecuteComplexWithSandboxActive 50000 60450 ns/op
+ BenchmarkExecuteComplexWithSandboxActive-2 50000 56998 ns/op
+ BenchmarkExecuteComplexWithSandboxActive-4 50000 60343 ns/op
+ BenchmarkExecuteComplexWithSandboxActive-8 50000 64229 ns/op
+ BenchmarkCompileAndExecuteComplexWithSandboxActive 10000 164410 ns/op
+ BenchmarkCompileAndExecuteComplexWithSandboxActive-2 10000 156682 ns/op
+ BenchmarkCompileAndExecuteComplexWithSandboxActive-4 10000 164821 ns/op
+ BenchmarkCompileAndExecuteComplexWithSandboxActive-8 10000 171806 ns/op
+ BenchmarkParallelExecuteComplexWithSandboxActive 50000 60428 ns/op
+ BenchmarkParallelExecuteComplexWithSandboxActive-2 50000 31887 ns/op
+ BenchmarkParallelExecuteComplexWithSandboxActive-4 100000 22810 ns/op
+ BenchmarkParallelExecuteComplexWithSandboxActive-8 100000 18820 ns/op
+ BenchmarkExecuteComplexWithoutSandbox 50000 56942 ns/op
+ BenchmarkExecuteComplexWithoutSandbox-2 50000 56168 ns/op
+ BenchmarkExecuteComplexWithoutSandbox-4 50000 57838 ns/op
+ BenchmarkExecuteComplexWithoutSandbox-8 50000 60539 ns/op
+ BenchmarkCompileAndExecuteComplexWithoutSandbox 10000 162086 ns/op
+ BenchmarkCompileAndExecuteComplexWithoutSandbox-2 10000 159771 ns/op
+ BenchmarkCompileAndExecuteComplexWithoutSandbox-4 10000 163826 ns/op
+ BenchmarkCompileAndExecuteComplexWithoutSandbox-8 10000 169062 ns/op
+ BenchmarkParallelExecuteComplexWithoutSandbox 50000 57152 ns/op
+ BenchmarkParallelExecuteComplexWithoutSandbox-2 50000 30276 ns/op
+ BenchmarkParallelExecuteComplexWithoutSandbox-4 100000 22065 ns/op
+ BenchmarkParallelExecuteComplexWithoutSandbox-8 100000 18034 ns/op
+
+Benchmarked on October 2nd 2014.
diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/context.go b/Godeps/_workspace/src/github.com/flosch/pongo2/context.go
new file mode 100644
index 0000000..7b728ec
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/flosch/pongo2/context.go
@@ -0,0 +1,125 @@
+package pongo2
+
+import (
+ "fmt"
+ "regexp"
+)
+
+var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$")
+
+// A Context type provides constants, variables, instances or functions to a template.
+//
+// pongo2 automatically provides meta-information or functions through the "pongo2"-key.
+// Currently, context["pongo2"] contains the following keys:
+// 1. version: returns the version string
+//
+// Template examples for accessing items from your context:
+// {{ myconstant }}
+// {{ myfunc("test", 42) }}
+// {{ user.name }}
+// {{ pongo2.version }}
+type Context map[string]interface{}
+
+func (c Context) checkForValidIdentifiers() *Error {
+ for k, v := range c {
+ if !reIdentifiers.MatchString(k) {
+ return &Error{
+ Sender: "checkForValidIdentifiers",
+ ErrorMsg: fmt.Sprintf("Context-key '%s' (value: '%+v') is not a valid identifier.", k, v),
+ }
+ }
+ }
+ return nil
+}
+
+// Update updates this context with the key/value-pairs from another context.
+func (c Context) Update(other Context) Context {
+ for k, v := range other {
+ c[k] = v
+ }
+ return c
+}
+
+// ExecutionContext contains all data important for the current rendering state.
+//
+// If you're writing a custom tag, your tag's Execute()-function will
+// have access to the ExecutionContext. This struct stores anything
+// about the current rendering process's Context including
+// the Context provided by the user (field Public).
+// You can safely use the Private context to provide data to the user's
+// template (like a 'forloop'-information). The Shared-context is used
+// to share data between tags. All ExecutionContexts share this context.
+//
+// Please be careful when accessing the Public data.
+// PLEASE DO NOT MODIFY THE PUBLIC CONTEXT (read-only).
+//
+// To create your own execution context within tags, use the
+// NewChildExecutionContext(parent) function.
+type ExecutionContext struct {
+ template *Template
+
+ Autoescape bool
+ Public Context
+ Private Context
+ Shared Context
+}
+
+var pongo2MetaContext = Context{
+ "version": Version,
+}
+
+func newExecutionContext(tpl *Template, ctx Context) *ExecutionContext {
+ privateCtx := make(Context)
+
+ // Make the pongo2-related funcs/vars available to the context
+ privateCtx["pongo2"] = pongo2MetaContext
+
+ return &ExecutionContext{
+ template: tpl,
+
+ Public: ctx,
+ Private: privateCtx,
+ Autoescape: true,
+ }
+}
+
+func NewChildExecutionContext(parent *ExecutionContext) *ExecutionContext {
+ newctx := &ExecutionContext{
+ template: parent.template,
+
+ Public: parent.Public,
+ Private: make(Context),
+ Autoescape: parent.Autoescape,
+ }
+ newctx.Shared = parent.Shared
+
+ // Copy all existing private items
+ newctx.Private.Update(parent.Private)
+
+ return newctx
+}
+
+func (ctx *ExecutionContext) Error(msg string, token *Token) *Error {
+ filename := ctx.template.name
+ var line, col int
+ if token != nil {
+ // No tokens available
+ // TODO: Add location (from where?)
+ filename = token.Filename
+ line = token.Line
+ col = token.Col
+ }
+ return &Error{
+ Template: ctx.template,
+ Filename: filename,
+ Line: line,
+ Column: col,
+ Token: token,
+ Sender: "execution",
+ ErrorMsg: msg,
+ }
+}
+
+func (ctx *ExecutionContext) Logf(format string, args ...interface{}) {
+ ctx.template.set.logf(format, args...)
+}
diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/doc.go b/Godeps/_workspace/src/github.com/flosch/pongo2/doc.go
new file mode 100644
index 0000000..5a23e2b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/flosch/pongo2/doc.go
@@ -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
diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/docs/examples.md b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/examples.md
new file mode 100644
index 0000000..a98bb3a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/examples.md
@@ -0,0 +1 @@
+(Stub, TBA)
\ No newline at end of file
diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/docs/filters.md b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/filters.md
new file mode 100644
index 0000000..40a3253
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/filters.md
@@ -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).
\ No newline at end of file
diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/docs/index.md b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/index.md
new file mode 100644
index 0000000..a98bb3a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/index.md
@@ -0,0 +1 @@
+(Stub, TBA)
\ No newline at end of file
diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/docs/macros.md b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/macros.md
new file mode 100644
index 0000000..2b27069
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/macros.md
@@ -0,0 +1 @@
+(Stub, TBA)
diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/docs/tags.md b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/tags.md
new file mode 100644
index 0000000..dae4566
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/tags.md
@@ -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
\ No newline at end of file
diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/docs/template_sets.md b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/template_sets.md
new file mode 100644
index 0000000..a98bb3a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/template_sets.md
@@ -0,0 +1 @@
+(Stub, TBA)
\ No newline at end of file
diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/docs/write_filters.md b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/write_filters.md
new file mode 100644
index 0000000..e69de29
diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/docs/write_tags.md b/Godeps/_workspace/src/github.com/flosch/pongo2/docs/write_tags.md
new file mode 100644
index 0000000..e69de29
diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/error.go b/Godeps/_workspace/src/github.com/flosch/pongo2/error.go
new file mode 100644
index 0000000..80d1147
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/flosch/pongo2/error.go
@@ -0,0 +1,91 @@
+package pongo2
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+)
+
+// The Error type is being used to address an error during lexing, parsing or
+// execution. If you want to return an error object (for example in your own
+// tag or filter) fill this object with as much information as you have.
+// Make sure "Sender" is always given (if you're returning an error within
+// a filter, make Sender equals 'filter:yourfilter'; same goes for tags: 'tag:mytag').
+// It's okay if you only fill in ErrorMsg if you don't have any other details at hand.
+type Error struct {
+ Template *Template
+ Filename string
+ Line int
+ Column int
+ Token *Token
+ Sender string
+ ErrorMsg string
+}
+
+func (e *Error) updateFromTokenIfNeeded(template *Template, t *Token) *Error {
+ if e.Template == nil {
+ e.Template = template
+ }
+
+ if e.Token == nil {
+ e.Token = t
+ if e.Line <= 0 {
+ e.Line = t.Line
+ e.Column = t.Col
+ }
+ }
+
+ return e
+}
+
+// Returns a nice formatted error string.
+func (e *Error) Error() string {
+ s := "[Error"
+ if e.Sender != "" {
+ s += " (where: " + e.Sender + ")"
+ }
+ if e.Filename != "" {
+ s += " in " + e.Filename
+ }
+ if e.Line > 0 {
+ s += fmt.Sprintf(" | Line %d Col %d", e.Line, e.Column)
+ if e.Token != nil {
+ s += fmt.Sprintf(" near '%s'", e.Token.Val)
+ }
+ }
+ s += "] "
+ s += e.ErrorMsg
+ return s
+}
+
+// RawLine returns the affected line from the original template, if available.
+func (e *Error) RawLine() (line string, available bool) {
+ if e.Line <= 0 || e.Filename == "
+ // Double newline =
")
+ }
+ }
+ }
+
+ if opened {
+ b.WriteString("
Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.
", + 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 tags in it", + }, + &comment{ + Author: &user{ + Name: "user3", + Validated: false, + }, + Date: time1, + Text: "hello! 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 tags in it", + }, + &comment{ + Author: &user{ + Name: "user3", + Validated: false, + }, + Date: time1, + Text: "hello! there", + }, + }, + }, +} + +func TestTemplates(t *testing.T) { + // Add a global to the default set + pongo2.Globals["this_is_a_global_variable"] = "this is a global text" + + matches, err := filepath.Glob("./template_tests/*.tpl") + if err != nil { + t.Fatal(err) + } + for idx, match := range matches { + t.Logf("[Template %3d] Testing '%s'", idx+1, match) + tpl, err := pongo2.FromFile(match) + if err != nil { + t.Fatalf("Error on FromFile('%s'): %s", match, err.Error()) + } + testFilename := fmt.Sprintf("%s.out", match) + testOut, rerr := ioutil.ReadFile(testFilename) + if rerr != nil { + t.Fatalf("Error on ReadFile('%s'): %s", testFilename, rerr.Error()) + } + tplOut, err := tpl.ExecuteBytes(tplContext) + if err != nil { + t.Fatalf("Error on Execute('%s'): %s", match, err.Error()) + } + if bytes.Compare(testOut, tplOut) != 0 { + t.Logf("Template (rendered) '%s': '%s'", match, tplOut) + errFilename := filepath.Base(fmt.Sprintf("%s.error", match)) + err := ioutil.WriteFile(errFilename, []byte(tplOut), 0600) + if err != nil { + t.Fatalf(err.Error()) + } + t.Logf("get a complete diff with command: 'diff -ya %s %s'", testFilename, errFilename) + t.Errorf("Failed: test_out != tpl_out for %s", match) + } + } +} + +func TestExecutionErrors(t *testing.T) { + //debug = true + + matches, err := filepath.Glob("./template_tests/*-execution.err") + if err != nil { + t.Fatal(err) + } + for idx, match := range matches { + t.Logf("[Errors %3d] Testing '%s'", idx+1, match) + + testData, err := ioutil.ReadFile(match) + tests := strings.Split(string(testData), "\n") + + checkFilename := fmt.Sprintf("%s.out", match) + checkData, err := ioutil.ReadFile(checkFilename) + if err != nil { + t.Fatalf("Error on ReadFile('%s'): %s", checkFilename, err.Error()) + } + checks := strings.Split(string(checkData), "\n") + + if len(checks) != len(tests) { + t.Fatal("Template lines != Checks lines") + } + + for idx, test := range tests { + if strings.TrimSpace(test) == "" { + continue + } + if strings.TrimSpace(checks[idx]) == "" { + t.Fatalf("[%s Line %d] Check is empty (must contain an regular expression).", + match, idx+1) + } + + tpl, err := pongo2.FromString(test) + if err != nil { + t.Fatalf("Error on FromString('%s'): %s", test, err.Error()) + } + + _, err = tpl.ExecuteBytes(tplContext) + if err == nil { + t.Fatalf("[%s Line %d] Expected error for (got none): %s", + match, idx+1, tests[idx]) + } + + re := regexp.MustCompile(fmt.Sprintf("^%s$", checks[idx])) + if !re.MatchString(err.Error()) { + t.Fatalf("[%s Line %d] Error for '%s' (err = '%s') does not match the (regexp-)check: %s", + match, idx+1, test, err.Error(), checks[idx]) + } + } + } +} + +func TestCompilationErrors(t *testing.T) { + //debug = true + + matches, err := filepath.Glob("./template_tests/*-compilation.err") + if err != nil { + t.Fatal(err) + } + for idx, match := range matches { + t.Logf("[Errors %3d] Testing '%s'", idx+1, match) + + testData, err := ioutil.ReadFile(match) + tests := strings.Split(string(testData), "\n") + + checkFilename := fmt.Sprintf("%s.out", match) + checkData, err := ioutil.ReadFile(checkFilename) + if err != nil { + t.Fatalf("Error on ReadFile('%s'): %s", checkFilename, err.Error()) + } + checks := strings.Split(string(checkData), "\n") + + if len(checks) != len(tests) { + t.Fatal("Template lines != Checks lines") + } + + for idx, test := range tests { + if strings.TrimSpace(test) == "" { + continue + } + if strings.TrimSpace(checks[idx]) == "" { + t.Fatalf("[%s Line %d] Check is empty (must contain an regular expression).", + match, idx+1) + } + + _, err = pongo2.FromString(test) + if err == nil { + t.Fatalf("[%s | Line %d] Expected error for (got none): %s", match, idx+1, tests[idx]) + } + re := regexp.MustCompile(fmt.Sprintf("^%s$", checks[idx])) + if !re.MatchString(err.Error()) { + t.Fatalf("[%s | Line %d] Error for '%s' (err = '%s') does not match the (regexp-)check: %s", + match, idx+1, test, err.Error(), checks[idx]) + } + } + } +} + +func TestBaseDirectory(t *testing.T) { + mustStr := "Hello from template_tests/base_dir_test/" + + fs := pongo2.MustNewLocalFileSystemLoader("") + s := pongo2.NewSet("test set with base directory", fs) + s.Globals["base_directory"] = "template_tests/base_dir_test/" + if err := fs.SetBaseDir(s.Globals["base_directory"].(string)); err != nil { + t.Fatal(err) + } + + matches, err := filepath.Glob("./template_tests/base_dir_test/subdir/*") + if err != nil { + t.Fatal(err) + } + for _, match := range matches { + match = strings.Replace(match, "template_tests/base_dir_test/", "", -1) + + tpl, err := s.FromFile(match) + if err != nil { + t.Fatal(err) + } + out, err := tpl.Execute(nil) + if err != nil { + t.Fatal(err) + } + if out != mustStr { + t.Errorf("%s: out ('%s') != mustStr ('%s')", match, out, mustStr) + } + } +} + +func BenchmarkCache(b *testing.B) { + cacheSet := pongo2.NewSet("cache set", pongo2.MustNewLocalFileSystemLoader("")) + for i := 0; i < b.N; i++ { + tpl, err := cacheSet.FromCache("template_tests/complex.tpl") + if err != nil { + b.Fatal(err) + } + err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkCacheDebugOn(b *testing.B) { + cacheDebugSet := pongo2.NewSet("cache set", pongo2.MustNewLocalFileSystemLoader("")) + cacheDebugSet.Debug = true + for i := 0; i < b.N; i++ { + tpl, err := cacheDebugSet.FromFile("template_tests/complex.tpl") + if err != nil { + b.Fatal(err) + } + err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkExecuteComplexWithSandboxActive(b *testing.B) { + tpl, err := pongo2.FromFile("template_tests/complex.tpl") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkCompileAndExecuteComplexWithSandboxActive(b *testing.B) { + buf, err := ioutil.ReadFile("template_tests/complex.tpl") + if err != nil { + b.Fatal(err) + } + preloadedTpl := string(buf) + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl, err := pongo2.FromString(preloadedTpl) + if err != nil { + b.Fatal(err) + } + + err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkParallelExecuteComplexWithSandboxActive(b *testing.B) { + tpl, err := pongo2.FromFile("template_tests/complex.tpl") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + err := tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard) + if err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkExecuteComplexWithoutSandbox(b *testing.B) { + s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader("")) + tpl, err := s.FromFile("template_tests/complex.tpl") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkCompileAndExecuteComplexWithoutSandbox(b *testing.B) { + buf, err := ioutil.ReadFile("template_tests/complex.tpl") + if err != nil { + b.Fatal(err) + } + preloadedTpl := string(buf) + + s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader("")) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl, err := s.FromString(preloadedTpl) + if err != nil { + b.Fatal(err) + } + + err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkParallelExecuteComplexWithoutSandbox(b *testing.B) { + s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader("")) + tpl, err := s.FromFile("template_tests/complex.tpl") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + err := tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard) + if err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/pongo2_test.go b/Godeps/_workspace/src/github.com/flosch/pongo2/pongo2_test.go new file mode 100644 index 0000000..ea56f0b --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/pongo2_test.go @@ -0,0 +1,70 @@ +package pongo2_test + +import ( + "testing" + + "github.com/flosch/pongo2" + + . "gopkg.in/check.v1" +) + +// Hook up gocheck into the "go test" runner. + +func Test(t *testing.T) { TestingT(t) } + +type TestSuite struct { + tpl *pongo2.Template +} + +var ( + _ = Suite(&TestSuite{}) + testSuite2 = pongo2.NewSet("test suite 2", pongo2.MustNewLocalFileSystemLoader("")) +) + +func parseTemplate(s string, c pongo2.Context) string { + t, err := testSuite2.FromString(s) + if err != nil { + panic(err) + } + out, err := t.Execute(c) + if err != nil { + panic(err) + } + return out +} + +func parseTemplateFn(s string, c pongo2.Context) func() { + return func() { + parseTemplate(s, c) + } +} + +func (s *TestSuite) TestMisc(c *C) { + // Must + // TODO: Add better error message (see issue #18) + c.Check( + func() { pongo2.Must(testSuite2.FromFile("template_tests/inheritance/base2.tpl")) }, + PanicMatches, + `\[Error \(where: fromfile\) in .*template_tests/inheritance/doesnotexist.tpl | Line 1 Col 12 near 'doesnotexist.tpl'\] open .*template_tests/inheritance/doesnotexist.tpl: no such file or directory`, + ) + + // Context + c.Check(parseTemplateFn("", pongo2.Context{"'illegal": nil}), PanicMatches, ".*not a valid identifier.*") + + // Registers + c.Check(func() { pongo2.RegisterFilter("escape", nil) }, PanicMatches, ".*is already registered.*") + c.Check(func() { pongo2.RegisterTag("for", nil) }, PanicMatches, ".*is already registered.*") + + // ApplyFilter + v, err := pongo2.ApplyFilter("title", pongo2.AsValue("this is a title"), nil) + if err != nil { + c.Fatal(err) + } + c.Check(v.String(), Equals, "This Is A Title") + c.Check(func() { + _, err := pongo2.ApplyFilter("doesnotexist", nil, nil) + if err != nil { + panic(err) + } + }, PanicMatches, `\[Error \(where: applyfilter\)\] Filter with name 'doesnotexist' not found.`) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags.go new file mode 100644 index 0000000..5168d17 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags.go @@ -0,0 +1,132 @@ +package pongo2 + +/* Incomplete: + ----------- + + verbatim (only the "name" argument is missing for verbatim) + + Reconsideration: + ---------------- + + debug (reason: not sure what to output yet) + regroup / Grouping on other properties (reason: maybe too python-specific; not sure how useful this would be in Go) + + Following built-in tags wont be added: + -------------------------------------- + + csrf_token (reason: web-framework specific) + load (reason: python-specific) + url (reason: web-framework specific) +*/ + +import ( + "fmt" +) + +type INodeTag interface { + INode +} + +// This is the function signature of the tag's parser you will have +// to implement in order to create a new tag. +// +// 'doc' is providing access to the whole document while 'arguments' +// is providing access to the user's arguments to the tag: +// +// {% your_tag_name some "arguments" 123 %} +// +// start_token will be the *Token with the tag's name in it (here: your_tag_name). +// +// Please see the Parser documentation on how to use the parser. +// See RegisterTag()'s documentation for more information about +// writing a tag as well. +type TagParser func(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) + +type tag struct { + name string + parser TagParser +} + +var tags map[string]*tag + +func init() { + tags = make(map[string]*tag) +} + +// Registers a new tag. If there's already a tag with the same +// name, RegisterTag will panic. You usually want to call this +// function in the tag's init() function: +// http://golang.org/doc/effective_go.html#init +// +// See http://www.florian-schlachter.de/post/pongo2/ for more about +// writing filters and tags. +func RegisterTag(name string, parserFn TagParser) { + _, existing := tags[name] + if existing { + panic(fmt.Sprintf("Tag with name '%s' is already registered.", name)) + } + tags[name] = &tag{ + name: name, + parser: parserFn, + } +} + +// Replaces an already registered tag with a new implementation. Use this +// function with caution since it allows you to change existing tag behaviour. +func ReplaceTag(name string, parserFn TagParser) { + _, existing := tags[name] + if !existing { + panic(fmt.Sprintf("Tag with name '%s' does not exist (therefore cannot be overridden).", name)) + } + tags[name] = &tag{ + name: name, + parser: parserFn, + } +} + +// Tag = "{%" IDENT ARGS "%}" +func (p *Parser) parseTagElement() (INodeTag, *Error) { + p.Consume() // consume "{%" + tokenName := p.MatchType(TokenIdentifier) + + // Check for identifier + if tokenName == nil { + return nil, p.Error("Tag name must be an identifier.", nil) + } + + // Check for the existing tag + tag, exists := tags[tokenName.Val] + if !exists { + // Does not exists + return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", tokenName.Val), tokenName) + } + + // Check sandbox tag restriction + if _, isBanned := p.template.set.bannedTags[tokenName.Val]; isBanned { + return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", tokenName.Val), tokenName) + } + + var argsToken []*Token + for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 { + // Add token to args + argsToken = append(argsToken, p.Current()) + p.Consume() // next token + } + + // EOF? + if p.Remaining() == 0 { + return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.lastToken) + } + + p.Match(TokenSymbol, "%}") + + argParser := newParser(p.name, argsToken, p.template) + if len(argsToken) == 0 { + // This is done to have nice EOF error messages + argParser.lastToken = tokenName + } + + p.template.level++ + defer func() { p.template.level-- }() + return tag.parser(p, tokenName, argParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_autoescape.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_autoescape.go new file mode 100644 index 0000000..590a1db --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_autoescape.go @@ -0,0 +1,52 @@ +package pongo2 + +type tagAutoescapeNode struct { + wrapper *NodeWrapper + autoescape bool +} + +func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + old := ctx.Autoescape + ctx.Autoescape = node.autoescape + + err := node.wrapper.Execute(ctx, writer) + if err != nil { + return err + } + + ctx.Autoescape = old + + return nil +} + +func tagAutoescapeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + autoescapeNode := &tagAutoescapeNode{} + + wrapper, _, err := doc.WrapUntilTag("endautoescape") + if err != nil { + return nil, err + } + autoescapeNode.wrapper = wrapper + + modeToken := arguments.MatchType(TokenIdentifier) + if modeToken == nil { + return nil, arguments.Error("A mode is required for autoescape-tag.", nil) + } + if modeToken.Val == "on" { + autoescapeNode.autoescape = true + } else if modeToken.Val == "off" { + autoescapeNode.autoescape = false + } else { + return nil, arguments.Error("Only 'on' or 'off' is valid as an autoescape-mode.", nil) + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed autoescape-tag arguments.", nil) + } + + return autoescapeNode, nil +} + +func init() { + RegisterTag("autoescape", tagAutoescapeParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_block.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_block.go new file mode 100644 index 0000000..b558930 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_block.go @@ -0,0 +1,93 @@ +package pongo2 + +import ( + "fmt" +) + +type tagBlockNode struct { + name string +} + +func (node *tagBlockNode) getBlockWrapperByName(tpl *Template) *NodeWrapper { + var t *NodeWrapper + if tpl.child != nil { + // First ask the child for the block + t = node.getBlockWrapperByName(tpl.child) + } + if t == nil { + // Child has no block, lets look up here at parent + t = tpl.blocks[node.name] + } + return t +} + +func (node *tagBlockNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + tpl := ctx.template + if tpl == nil { + panic("internal error: tpl == nil") + } + // Determine the block to execute + blockWrapper := node.getBlockWrapperByName(tpl) + if blockWrapper == nil { + // fmt.Printf("could not find: %s\n", node.name) + return ctx.Error("internal error: block_wrapper == nil in tagBlockNode.Execute()", nil) + } + err := blockWrapper.Execute(ctx, writer) + if err != nil { + return err + } + + // TODO: Add support for {{ block.super }} + + return nil +} + +func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + if arguments.Count() == 0 { + return nil, arguments.Error("Tag 'block' requires an identifier.", nil) + } + + nameToken := arguments.MatchType(TokenIdentifier) + if nameToken == nil { + return nil, arguments.Error("First argument for tag 'block' must be an identifier.", nil) + } + + if arguments.Remaining() != 0 { + return nil, arguments.Error("Tag 'block' takes exactly 1 argument (an identifier).", nil) + } + + wrapper, endtagargs, err := doc.WrapUntilTag("endblock") + if err != nil { + return nil, err + } + if endtagargs.Remaining() > 0 { + endtagnameToken := endtagargs.MatchType(TokenIdentifier) + if endtagnameToken != nil { + if endtagnameToken.Val != nameToken.Val { + return nil, endtagargs.Error(fmt.Sprintf("Name for 'endblock' must equal to 'block'-tag's name ('%s' != '%s').", + nameToken.Val, endtagnameToken.Val), nil) + } + } + + if endtagnameToken == nil || endtagargs.Remaining() > 0 { + return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endblock'.", nil) + } + } + + tpl := doc.template + if tpl == nil { + panic("internal error: tpl == nil") + } + _, hasBlock := tpl.blocks[nameToken.Val] + if !hasBlock { + tpl.blocks[nameToken.Val] = wrapper + } else { + return nil, arguments.Error(fmt.Sprintf("Block named '%s' already defined", nameToken.Val), nil) + } + + return &tagBlockNode{name: nameToken.Val}, nil +} + +func init() { + RegisterTag("block", tagBlockParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_comment.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_comment.go new file mode 100644 index 0000000..a66a973 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_comment.go @@ -0,0 +1,27 @@ +package pongo2 + +type tagCommentNode struct{} + +func (node *tagCommentNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + return nil +} + +func tagCommentParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + commentNode := &tagCommentNode{} + + // TODO: Process the endtag's arguments (see django 'comment'-tag documentation) + _, _, err := doc.WrapUntilTag("endcomment") + if err != nil { + return nil, err + } + + if arguments.Count() != 0 { + return nil, arguments.Error("Tag 'comment' does not take any argument.", nil) + } + + return commentNode, nil +} + +func init() { + RegisterTag("comment", tagCommentParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_cycle.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_cycle.go new file mode 100644 index 0000000..9b83b9b --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_cycle.go @@ -0,0 +1,106 @@ +package pongo2 + +type tagCycleValue struct { + node *tagCycleNode + value *Value +} + +type tagCycleNode struct { + position *Token + args []IEvaluator + idx int + asName string + silent bool +} + +func (cv *tagCycleValue) String() string { + return cv.value.String() +} + +func (node *tagCycleNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + item := node.args[node.idx%len(node.args)] + node.idx++ + + val, err := item.Evaluate(ctx) + if err != nil { + return err + } + + if t, ok := val.Interface().(*tagCycleValue); ok { + // {% cycle "test1" "test2" + // {% cycle cycleitem %} + + // Update the cycle value with next value + item := t.node.args[t.node.idx%len(t.node.args)] + t.node.idx++ + + val, err := item.Evaluate(ctx) + if err != nil { + return err + } + + t.value = val + + if !t.node.silent { + writer.WriteString(val.String()) + } + } else { + // Regular call + + cycleValue := &tagCycleValue{ + node: node, + value: val, + } + + if node.asName != "" { + ctx.Private[node.asName] = cycleValue + } + if !node.silent { + writer.WriteString(val.String()) + } + } + + return nil +} + +// HINT: We're not supporting the old comma-seperated list of expresions argument-style +func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + cycleNode := &tagCycleNode{ + position: start, + } + + for arguments.Remaining() > 0 { + node, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + cycleNode.args = append(cycleNode.args, node) + + if arguments.MatchOne(TokenKeyword, "as") != nil { + // as + + nameToken := arguments.MatchType(TokenIdentifier) + if nameToken == nil { + return nil, arguments.Error("Name (identifier) expected after 'as'.", nil) + } + cycleNode.asName = nameToken.Val + + if arguments.MatchOne(TokenIdentifier, "silent") != nil { + cycleNode.silent = true + } + + // Now we're finished + break + } + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed cycle-tag.", nil) + } + + return cycleNode, nil +} + +func init() { + RegisterTag("cycle", tagCycleParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_extends.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_extends.go new file mode 100644 index 0000000..5771020 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_extends.go @@ -0,0 +1,52 @@ +package pongo2 + +type tagExtendsNode struct { + filename string +} + +func (node *tagExtendsNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + return nil +} + +func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + extendsNode := &tagExtendsNode{} + + if doc.template.level > 1 { + return nil, arguments.Error("The 'extends' tag can only defined on root level.", start) + } + + if doc.template.parent != nil { + // Already one parent + return nil, arguments.Error("This template has already one parent.", start) + } + + if filenameToken := arguments.MatchType(TokenString); filenameToken != nil { + // prepared, static template + + // Get parent's filename + parentFilename := doc.template.set.resolveFilename(doc.template, filenameToken.Val) + + // Parse the parent + parentTemplate, err := doc.template.set.FromFile(parentFilename) + if err != nil { + return nil, err.(*Error) + } + + // Keep track of things + parentTemplate.child = doc.template + doc.template.parent = parentTemplate + extendsNode.filename = parentFilename + } else { + return nil, arguments.Error("Tag 'extends' requires a template filename as string.", nil) + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Tag 'extends' does only take 1 argument.", nil) + } + + return extendsNode, nil +} + +func init() { + RegisterTag("extends", tagExtendsParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_filter.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_filter.go new file mode 100644 index 0000000..b38fd92 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_filter.go @@ -0,0 +1,95 @@ +package pongo2 + +import ( + "bytes" +) + +type nodeFilterCall struct { + name string + paramExpr IEvaluator +} + +type tagFilterNode struct { + position *Token + bodyWrapper *NodeWrapper + filterChain []*nodeFilterCall +} + +func (node *tagFilterNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + temp := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB size + + err := node.bodyWrapper.Execute(ctx, temp) + if err != nil { + return err + } + + value := AsValue(temp.String()) + + for _, call := range node.filterChain { + var param *Value + if call.paramExpr != nil { + param, err = call.paramExpr.Evaluate(ctx) + if err != nil { + return err + } + } else { + param = AsValue(nil) + } + value, err = ApplyFilter(call.name, value, param) + if err != nil { + return ctx.Error(err.Error(), node.position) + } + } + + writer.WriteString(value.String()) + + return nil +} + +func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + filterNode := &tagFilterNode{ + position: start, + } + + wrapper, _, err := doc.WrapUntilTag("endfilter") + if err != nil { + return nil, err + } + filterNode.bodyWrapper = wrapper + + for arguments.Remaining() > 0 { + filterCall := &nodeFilterCall{} + + nameToken := arguments.MatchType(TokenIdentifier) + if nameToken == nil { + return nil, arguments.Error("Expected a filter name (identifier).", nil) + } + filterCall.name = nameToken.Val + + if arguments.MatchOne(TokenSymbol, ":") != nil { + // Filter parameter + // NOTICE: we can't use ParseExpression() here, because it would parse the next filter "|..." as well in the argument list + expr, err := arguments.parseVariableOrLiteral() + if err != nil { + return nil, err + } + filterCall.paramExpr = expr + } + + filterNode.filterChain = append(filterNode.filterChain, filterCall) + + if arguments.MatchOne(TokenSymbol, "|") == nil { + break + } + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed filter-tag arguments.", nil) + } + + return filterNode, nil +} + +func init() { + RegisterTag("filter", tagFilterParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_firstof.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_firstof.go new file mode 100644 index 0000000..5b2888e --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_firstof.go @@ -0,0 +1,49 @@ +package pongo2 + +type tagFirstofNode struct { + position *Token + args []IEvaluator +} + +func (node *tagFirstofNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + for _, arg := range node.args { + val, err := arg.Evaluate(ctx) + if err != nil { + return err + } + + if val.IsTrue() { + if ctx.Autoescape && !arg.FilterApplied("safe") { + val, err = ApplyFilter("escape", val, nil) + if err != nil { + return err + } + } + + writer.WriteString(val.String()) + return nil + } + } + + return nil +} + +func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + firstofNode := &tagFirstofNode{ + position: start, + } + + for arguments.Remaining() > 0 { + node, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + firstofNode.args = append(firstofNode.args, node) + } + + return firstofNode, nil +} + +func init() { + RegisterTag("firstof", tagFirstofParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_for.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_for.go new file mode 100644 index 0000000..5b0b555 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_for.go @@ -0,0 +1,159 @@ +package pongo2 + +type tagForNode struct { + key string + value string // only for maps: for key, value in map + objectEvaluator IEvaluator + reversed bool + sorted bool + + bodyWrapper *NodeWrapper + emptyWrapper *NodeWrapper +} + +type tagForLoopInformation struct { + Counter int + Counter0 int + Revcounter int + Revcounter0 int + First bool + Last bool + Parentloop *tagForLoopInformation +} + +func (node *tagForNode) Execute(ctx *ExecutionContext, writer TemplateWriter) (forError *Error) { + // Backup forloop (as parentloop in public context), key-name and value-name + forCtx := NewChildExecutionContext(ctx) + parentloop := forCtx.Private["forloop"] + + // Create loop struct + loopInfo := &tagForLoopInformation{ + First: true, + } + + // Is it a loop in a loop? + if parentloop != nil { + loopInfo.Parentloop = parentloop.(*tagForLoopInformation) + } + + // Register loopInfo in public context + forCtx.Private["forloop"] = loopInfo + + obj, err := node.objectEvaluator.Evaluate(forCtx) + if err != nil { + return err + } + + obj.IterateOrder(func(idx, count int, key, value *Value) bool { + // There's something to iterate over (correct type and at least 1 item) + + // Update loop infos and public context + forCtx.Private[node.key] = key + if value != nil { + forCtx.Private[node.value] = value + } + loopInfo.Counter = idx + 1 + loopInfo.Counter0 = idx + if idx == 1 { + loopInfo.First = false + } + if idx+1 == count { + loopInfo.Last = true + } + loopInfo.Revcounter = count - idx // TODO: Not sure about this, have to look it up + loopInfo.Revcounter0 = count - (idx + 1) // TODO: Not sure about this, have to look it up + + // Render elements with updated context + err := node.bodyWrapper.Execute(forCtx, writer) + if err != nil { + forError = err + return false + } + return true + }, func() { + // Nothing to iterate over (maybe wrong type or no items) + if node.emptyWrapper != nil { + err := node.emptyWrapper.Execute(forCtx, writer) + if err != nil { + forError = err + } + } + }, node.reversed, node.sorted) + + return forError +} + +func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + forNode := &tagForNode{} + + // Arguments parsing + var valueToken *Token + keyToken := arguments.MatchType(TokenIdentifier) + if keyToken == nil { + return nil, arguments.Error("Expected an key identifier as first argument for 'for'-tag", nil) + } + + if arguments.Match(TokenSymbol, ",") != nil { + // Value name is provided + valueToken = arguments.MatchType(TokenIdentifier) + if valueToken == nil { + return nil, arguments.Error("Value name must be an identifier.", nil) + } + } + + if arguments.Match(TokenKeyword, "in") == nil { + return nil, arguments.Error("Expected keyword 'in'.", nil) + } + + objectEvaluator, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + forNode.objectEvaluator = objectEvaluator + forNode.key = keyToken.Val + if valueToken != nil { + forNode.value = valueToken.Val + } + + if arguments.MatchOne(TokenIdentifier, "reversed") != nil { + forNode.reversed = true + } + + if arguments.MatchOne(TokenIdentifier, "sorted") != nil { + forNode.sorted = true + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed for-loop arguments.", nil) + } + + // Body wrapping + wrapper, endargs, err := doc.WrapUntilTag("empty", "endfor") + if err != nil { + return nil, err + } + forNode.bodyWrapper = wrapper + + if endargs.Count() > 0 { + return nil, endargs.Error("Arguments not allowed here.", nil) + } + + if wrapper.Endtag == "empty" { + // if there's an else in the if-statement, we need the else-Block as well + wrapper, endargs, err = doc.WrapUntilTag("endfor") + if err != nil { + return nil, err + } + forNode.emptyWrapper = wrapper + + if endargs.Count() > 0 { + return nil, endargs.Error("Arguments not allowed here.", nil) + } + } + + return forNode, nil +} + +func init() { + RegisterTag("for", tagForParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_if.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_if.go new file mode 100644 index 0000000..3eeaf3b --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_if.go @@ -0,0 +1,76 @@ +package pongo2 + +type tagIfNode struct { + conditions []IEvaluator + wrappers []*NodeWrapper +} + +func (node *tagIfNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + for i, condition := range node.conditions { + result, err := condition.Evaluate(ctx) + if err != nil { + return err + } + + if result.IsTrue() { + return node.wrappers[i].Execute(ctx, writer) + } + // Last condition? + if len(node.conditions) == i+1 && len(node.wrappers) > i+1 { + return node.wrappers[i+1].Execute(ctx, writer) + } + } + return nil +} + +func tagIfParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + ifNode := &tagIfNode{} + + // Parse first and main IF condition + condition, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + ifNode.conditions = append(ifNode.conditions, condition) + + if arguments.Remaining() > 0 { + return nil, arguments.Error("If-condition is malformed.", nil) + } + + // Check the rest + for { + wrapper, tagArgs, err := doc.WrapUntilTag("elif", "else", "endif") + if err != nil { + return nil, err + } + ifNode.wrappers = append(ifNode.wrappers, wrapper) + + if wrapper.Endtag == "elif" { + // elif can take a condition + condition, err = tagArgs.ParseExpression() + if err != nil { + return nil, err + } + ifNode.conditions = append(ifNode.conditions, condition) + + if tagArgs.Remaining() > 0 { + return nil, tagArgs.Error("Elif-condition is malformed.", nil) + } + } else { + if tagArgs.Count() > 0 { + // else/endif can't take any conditions + return nil, tagArgs.Error("Arguments not allowed here.", nil) + } + } + + if wrapper.Endtag == "endif" { + break + } + } + + return ifNode, nil +} + +func init() { + RegisterTag("if", tagIfParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifchanged.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifchanged.go new file mode 100644 index 0000000..45296a0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifchanged.go @@ -0,0 +1,116 @@ +package pongo2 + +import ( + "bytes" +) + +type tagIfchangedNode struct { + watchedExpr []IEvaluator + lastValues []*Value + lastContent []byte + thenWrapper *NodeWrapper + elseWrapper *NodeWrapper +} + +func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + if len(node.watchedExpr) == 0 { + // Check against own rendered body + + buf := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB + err := node.thenWrapper.Execute(ctx, buf) + if err != nil { + return err + } + + bufBytes := buf.Bytes() + if !bytes.Equal(node.lastContent, bufBytes) { + // Rendered content changed, output it + writer.Write(bufBytes) + node.lastContent = bufBytes + } + } else { + nowValues := make([]*Value, 0, len(node.watchedExpr)) + for _, expr := range node.watchedExpr { + val, err := expr.Evaluate(ctx) + if err != nil { + return err + } + nowValues = append(nowValues, val) + } + + // Compare old to new values now + changed := len(node.lastValues) == 0 + + for idx, oldVal := range node.lastValues { + if !oldVal.EqualValueTo(nowValues[idx]) { + changed = true + break // we can stop here because ONE value changed + } + } + + node.lastValues = nowValues + + if changed { + // Render thenWrapper + err := node.thenWrapper.Execute(ctx, writer) + if err != nil { + return err + } + } else { + // Render elseWrapper + err := node.elseWrapper.Execute(ctx, writer) + if err != nil { + return err + } + } + } + + return nil +} + +func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + ifchangedNode := &tagIfchangedNode{} + + for arguments.Remaining() > 0 { + // Parse condition + expr, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + ifchangedNode.watchedExpr = append(ifchangedNode.watchedExpr, expr) + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Ifchanged-arguments are malformed.", nil) + } + + // Wrap then/else-blocks + wrapper, endargs, err := doc.WrapUntilTag("else", "endifchanged") + if err != nil { + return nil, err + } + ifchangedNode.thenWrapper = wrapper + + if endargs.Count() > 0 { + return nil, endargs.Error("Arguments not allowed here.", nil) + } + + if wrapper.Endtag == "else" { + // if there's an else in the if-statement, we need the else-Block as well + wrapper, endargs, err = doc.WrapUntilTag("endifchanged") + if err != nil { + return nil, err + } + ifchangedNode.elseWrapper = wrapper + + if endargs.Count() > 0 { + return nil, endargs.Error("Arguments not allowed here.", nil) + } + } + + return ifchangedNode, nil +} + +func init() { + RegisterTag("ifchanged", tagIfchangedParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifequal.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifequal.go new file mode 100644 index 0000000..103f1c7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifequal.go @@ -0,0 +1,78 @@ +package pongo2 + +type tagIfEqualNode struct { + var1, var2 IEvaluator + thenWrapper *NodeWrapper + elseWrapper *NodeWrapper +} + +func (node *tagIfEqualNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + r1, err := node.var1.Evaluate(ctx) + if err != nil { + return err + } + r2, err := node.var2.Evaluate(ctx) + if err != nil { + return err + } + + result := r1.EqualValueTo(r2) + + if result { + return node.thenWrapper.Execute(ctx, writer) + } + if node.elseWrapper != nil { + return node.elseWrapper.Execute(ctx, writer) + } + return nil +} + +func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + ifequalNode := &tagIfEqualNode{} + + // Parse two expressions + var1, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + var2, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + ifequalNode.var1 = var1 + ifequalNode.var2 = var2 + + if arguments.Remaining() > 0 { + return nil, arguments.Error("ifequal only takes 2 arguments.", nil) + } + + // Wrap then/else-blocks + wrapper, endargs, err := doc.WrapUntilTag("else", "endifequal") + if err != nil { + return nil, err + } + ifequalNode.thenWrapper = wrapper + + if endargs.Count() > 0 { + return nil, endargs.Error("Arguments not allowed here.", nil) + } + + if wrapper.Endtag == "else" { + // if there's an else in the if-statement, we need the else-Block as well + wrapper, endargs, err = doc.WrapUntilTag("endifequal") + if err != nil { + return nil, err + } + ifequalNode.elseWrapper = wrapper + + if endargs.Count() > 0 { + return nil, endargs.Error("Arguments not allowed here.", nil) + } + } + + return ifequalNode, nil +} + +func init() { + RegisterTag("ifequal", tagIfEqualParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifnotequal.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifnotequal.go new file mode 100644 index 0000000..0d287d3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ifnotequal.go @@ -0,0 +1,78 @@ +package pongo2 + +type tagIfNotEqualNode struct { + var1, var2 IEvaluator + thenWrapper *NodeWrapper + elseWrapper *NodeWrapper +} + +func (node *tagIfNotEqualNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + r1, err := node.var1.Evaluate(ctx) + if err != nil { + return err + } + r2, err := node.var2.Evaluate(ctx) + if err != nil { + return err + } + + result := !r1.EqualValueTo(r2) + + if result { + return node.thenWrapper.Execute(ctx, writer) + } + if node.elseWrapper != nil { + return node.elseWrapper.Execute(ctx, writer) + } + return nil +} + +func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + ifnotequalNode := &tagIfNotEqualNode{} + + // Parse two expressions + var1, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + var2, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + ifnotequalNode.var1 = var1 + ifnotequalNode.var2 = var2 + + if arguments.Remaining() > 0 { + return nil, arguments.Error("ifequal only takes 2 arguments.", nil) + } + + // Wrap then/else-blocks + wrapper, endargs, err := doc.WrapUntilTag("else", "endifnotequal") + if err != nil { + return nil, err + } + ifnotequalNode.thenWrapper = wrapper + + if endargs.Count() > 0 { + return nil, endargs.Error("Arguments not allowed here.", nil) + } + + if wrapper.Endtag == "else" { + // if there's an else in the if-statement, we need the else-Block as well + wrapper, endargs, err = doc.WrapUntilTag("endifnotequal") + if err != nil { + return nil, err + } + ifnotequalNode.elseWrapper = wrapper + + if endargs.Count() > 0 { + return nil, endargs.Error("Arguments not allowed here.", nil) + } + } + + return ifnotequalNode, nil +} + +func init() { + RegisterTag("ifnotequal", tagIfNotEqualParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_import.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_import.go new file mode 100644 index 0000000..7e0d6a0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_import.go @@ -0,0 +1,84 @@ +package pongo2 + +import ( + "fmt" +) + +type tagImportNode struct { + position *Token + filename string + macros map[string]*tagMacroNode // alias/name -> macro instance +} + +func (node *tagImportNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + for name, macro := range node.macros { + func(name string, macro *tagMacroNode) { + ctx.Private[name] = func(args ...*Value) *Value { + return macro.call(ctx, args...) + } + }(name, macro) + } + return nil +} + +func tagImportParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + importNode := &tagImportNode{ + position: start, + macros: make(map[string]*tagMacroNode), + } + + filenameToken := arguments.MatchType(TokenString) + if filenameToken == nil { + return nil, arguments.Error("Import-tag needs a filename as string.", nil) + } + + importNode.filename = doc.template.set.resolveFilename(doc.template, filenameToken.Val) + + if arguments.Remaining() == 0 { + return nil, arguments.Error("You must at least specify one macro to import.", nil) + } + + // Compile the given template + tpl, err := doc.template.set.FromFile(importNode.filename) + if err != nil { + return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, start) + } + + for arguments.Remaining() > 0 { + macroNameToken := arguments.MatchType(TokenIdentifier) + if macroNameToken == nil { + return nil, arguments.Error("Expected macro name (identifier).", nil) + } + + asName := macroNameToken.Val + if arguments.Match(TokenKeyword, "as") != nil { + aliasToken := arguments.MatchType(TokenIdentifier) + if aliasToken == nil { + return nil, arguments.Error("Expected macro alias name (identifier).", nil) + } + asName = aliasToken.Val + } + + macroInstance, has := tpl.exportedMacros[macroNameToken.Val] + if !has { + return nil, arguments.Error(fmt.Sprintf("Macro '%s' not found (or not exported) in '%s'.", macroNameToken.Val, + importNode.filename), macroNameToken) + } + + importNode.macros[asName] = macroInstance + + if arguments.Remaining() == 0 { + break + } + + if arguments.Match(TokenSymbol, ",") == nil { + return nil, arguments.Error("Expected ','.", nil) + } + } + + return importNode, nil +} + +func init() { + RegisterTag("import", tagImportParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_include.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_include.go new file mode 100644 index 0000000..6d619fd --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_include.go @@ -0,0 +1,146 @@ +package pongo2 + +type tagIncludeNode struct { + tpl *Template + filenameEvaluator IEvaluator + lazy bool + only bool + filename string + withPairs map[string]IEvaluator + ifExists bool +} + +func (node *tagIncludeNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + // Building the context for the template + includeCtx := make(Context) + + // Fill the context with all data from the parent + if !node.only { + includeCtx.Update(ctx.Public) + includeCtx.Update(ctx.Private) + } + + // Put all custom with-pairs into the context + for key, value := range node.withPairs { + val, err := value.Evaluate(ctx) + if err != nil { + return err + } + includeCtx[key] = val + } + + // Execute the template + if node.lazy { + // Evaluate the filename + filename, err := node.filenameEvaluator.Evaluate(ctx) + if err != nil { + return err + } + + if filename.String() == "" { + return ctx.Error("Filename for 'include'-tag evaluated to an empty string.", nil) + } + + // Get include-filename + includedFilename := ctx.template.set.resolveFilename(ctx.template, filename.String()) + + includedTpl, err2 := ctx.template.set.FromFile(includedFilename) + if err2 != nil { + // if this is ReadFile error, and "if_exists" flag is enabled + if node.ifExists && err2.(*Error).Sender == "fromfile" { + return nil + } + return err2.(*Error) + } + err2 = includedTpl.ExecuteWriter(includeCtx, writer) + if err2 != nil { + return err2.(*Error) + } + return nil + } + // Template is already parsed with static filename + err := node.tpl.ExecuteWriter(includeCtx, writer) + if err != nil { + return err.(*Error) + } + return nil +} + +type tagIncludeEmptyNode struct{} + +func (node *tagIncludeEmptyNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + return nil +} + +func tagIncludeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + includeNode := &tagIncludeNode{ + withPairs: make(map[string]IEvaluator), + } + + if filenameToken := arguments.MatchType(TokenString); filenameToken != nil { + // prepared, static template + + // "if_exists" flag + ifExists := arguments.Match(TokenIdentifier, "if_exists") != nil + + // Get include-filename + includedFilename := doc.template.set.resolveFilename(doc.template, filenameToken.Val) + + // Parse the parent + includeNode.filename = includedFilename + includedTpl, err := doc.template.set.FromFile(includedFilename) + if err != nil { + // if this is ReadFile error, and "if_exists" token presents we should create and empty node + if err.(*Error).Sender == "fromfile" && ifExists { + return &tagIncludeEmptyNode{}, nil + } + return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, filenameToken) + } + includeNode.tpl = includedTpl + } else { + // No String, then the user wants to use lazy-evaluation (slower, but possible) + filenameEvaluator, err := arguments.ParseExpression() + if err != nil { + return nil, err.updateFromTokenIfNeeded(doc.template, filenameToken) + } + includeNode.filenameEvaluator = filenameEvaluator + includeNode.lazy = true + includeNode.ifExists = arguments.Match(TokenIdentifier, "if_exists") != nil // "if_exists" flag + } + + // After having parsed the filename we're gonna parse the with+only options + if arguments.Match(TokenIdentifier, "with") != nil { + for arguments.Remaining() > 0 { + // We have at least one key=expr pair (because of starting "with") + keyToken := arguments.MatchType(TokenIdentifier) + if keyToken == nil { + return nil, arguments.Error("Expected an identifier", nil) + } + if arguments.Match(TokenSymbol, "=") == nil { + return nil, arguments.Error("Expected '='.", nil) + } + valueExpr, err := arguments.ParseExpression() + if err != nil { + return nil, err.updateFromTokenIfNeeded(doc.template, keyToken) + } + + includeNode.withPairs[keyToken.Val] = valueExpr + + // Only? + if arguments.Match(TokenIdentifier, "only") != nil { + includeNode.only = true + break // stop parsing arguments because it's the last option + } + } + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed 'include'-tag arguments.", nil) + } + + return includeNode, nil +} + +func init() { + RegisterTag("include", tagIncludeParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_lorem.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_lorem.go new file mode 100644 index 0000000..8a152b3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_lorem.go @@ -0,0 +1,131 @@ +package pongo2 + +import ( + "math/rand" + "strings" + "time" +) + +var ( + tagLoremParagraphs = strings.Split(tagLoremText, "\n") + tagLoremWords = strings.Fields(tagLoremText) +) + +type tagLoremNode struct { + position *Token + count int // number of paragraphs + method string // w = words, p = HTML paragraphs, b = plain-text (default is b) + random bool // does not use the default paragraph "Lorem ipsum dolor sit amet, ..." +} + +func (node *tagLoremNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + switch node.method { + case "b": + if node.random { + for i := 0; i < node.count; i++ { + if i > 0 { + writer.WriteString("\n") + } + par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))] + writer.WriteString(par) + } + } else { + for i := 0; i < node.count; i++ { + if i > 0 { + writer.WriteString("\n") + } + par := tagLoremParagraphs[i%len(tagLoremParagraphs)] + writer.WriteString(par) + } + } + case "w": + if node.random { + for i := 0; i < node.count; i++ { + if i > 0 { + writer.WriteString(" ") + } + word := tagLoremWords[rand.Intn(len(tagLoremWords))] + writer.WriteString(word) + } + } else { + for i := 0; i < node.count; i++ { + if i > 0 { + writer.WriteString(" ") + } + word := tagLoremWords[i%len(tagLoremWords)] + writer.WriteString(word) + } + } + case "p": + if node.random { + for i := 0; i < node.count; i++ { + if i > 0 { + writer.WriteString("\n") + } + writer.WriteString("") + par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))] + writer.WriteString(par) + writer.WriteString("
") + } + } else { + for i := 0; i < node.count; i++ { + if i > 0 { + writer.WriteString("\n") + } + writer.WriteString("") + par := tagLoremParagraphs[i%len(tagLoremParagraphs)] + writer.WriteString(par) + writer.WriteString("
") + + } + } + default: + panic("unsupported method") + } + + return nil +} + +func tagLoremParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + loremNode := &tagLoremNode{ + position: start, + count: 1, + method: "b", + } + + if countToken := arguments.MatchType(TokenNumber); countToken != nil { + loremNode.count = AsValue(countToken.Val).Integer() + } + + if methodToken := arguments.MatchType(TokenIdentifier); methodToken != nil { + if methodToken.Val != "w" && methodToken.Val != "p" && methodToken.Val != "b" { + return nil, arguments.Error("lorem-method must be either 'w', 'p' or 'b'.", nil) + } + + loremNode.method = methodToken.Val + } + + if arguments.MatchOne(TokenIdentifier, "random") != nil { + loremNode.random = true + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed lorem-tag arguments.", nil) + } + + return loremNode, nil +} + +func init() { + rand.Seed(time.Now().Unix()) + + RegisterTag("lorem", tagLoremParser) +} + +const tagLoremText = `Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat. +Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.` diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_macro.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_macro.go new file mode 100644 index 0000000..18a2c3c --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_macro.go @@ -0,0 +1,149 @@ +package pongo2 + +import ( + "bytes" + "fmt" +) + +type tagMacroNode struct { + position *Token + name string + argsOrder []string + args map[string]IEvaluator + exported bool + + wrapper *NodeWrapper +} + +func (node *tagMacroNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + ctx.Private[node.name] = func(args ...*Value) *Value { + return node.call(ctx, args...) + } + + return nil +} + +func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value { + argsCtx := make(Context) + + for k, v := range node.args { + if v == nil { + // User did not provided a default value + argsCtx[k] = nil + } else { + // Evaluate the default value + valueExpr, err := v.Evaluate(ctx) + if err != nil { + ctx.Logf(err.Error()) + return AsSafeValue(err.Error()) + } + + argsCtx[k] = valueExpr + } + } + + if len(args) > len(node.argsOrder) { + // Too many arguments, we're ignoring them and just logging into debug mode. + err := ctx.Error(fmt.Sprintf("Macro '%s' called with too many arguments (%d instead of %d).", + node.name, len(args), len(node.argsOrder)), nil).updateFromTokenIfNeeded(ctx.template, node.position) + + ctx.Logf(err.Error()) // TODO: This is a workaround, because the error is not returned yet to the Execution()-methods + return AsSafeValue(err.Error()) + } + + // Make a context for the macro execution + macroCtx := NewChildExecutionContext(ctx) + + // Register all arguments in the private context + macroCtx.Private.Update(argsCtx) + + for idx, argValue := range args { + macroCtx.Private[node.argsOrder[idx]] = argValue.Interface() + } + + var b bytes.Buffer + err := node.wrapper.Execute(macroCtx, &b) + if err != nil { + return AsSafeValue(err.updateFromTokenIfNeeded(ctx.template, node.position).Error()) + } + + return AsSafeValue(b.String()) +} + +func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + macroNode := &tagMacroNode{ + position: start, + args: make(map[string]IEvaluator), + } + + nameToken := arguments.MatchType(TokenIdentifier) + if nameToken == nil { + return nil, arguments.Error("Macro-tag needs at least an identifier as name.", nil) + } + macroNode.name = nameToken.Val + + if arguments.MatchOne(TokenSymbol, "(") == nil { + return nil, arguments.Error("Expected '('.", nil) + } + + for arguments.Match(TokenSymbol, ")") == nil { + argNameToken := arguments.MatchType(TokenIdentifier) + if argNameToken == nil { + return nil, arguments.Error("Expected argument name as identifier.", nil) + } + macroNode.argsOrder = append(macroNode.argsOrder, argNameToken.Val) + + if arguments.Match(TokenSymbol, "=") != nil { + // Default expression follows + argDefaultExpr, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + macroNode.args[argNameToken.Val] = argDefaultExpr + } else { + // No default expression + macroNode.args[argNameToken.Val] = nil + } + + if arguments.Match(TokenSymbol, ")") != nil { + break + } + if arguments.Match(TokenSymbol, ",") == nil { + return nil, arguments.Error("Expected ',' or ')'.", nil) + } + } + + if arguments.Match(TokenKeyword, "export") != nil { + macroNode.exported = true + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed macro-tag.", nil) + } + + // Body wrapping + wrapper, endargs, err := doc.WrapUntilTag("endmacro") + if err != nil { + return nil, err + } + macroNode.wrapper = wrapper + + if endargs.Count() > 0 { + return nil, endargs.Error("Arguments not allowed here.", nil) + } + + if macroNode.exported { + // Now register the macro if it wants to be exported + _, has := doc.template.exportedMacros[macroNode.name] + if has { + return nil, doc.Error(fmt.Sprintf("Another macro with name '%s' already exported.", macroNode.name), start) + } + doc.template.exportedMacros[macroNode.name] = macroNode + } + + return macroNode, nil +} + +func init() { + RegisterTag("macro", tagMacroParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_now.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_now.go new file mode 100644 index 0000000..d9fa4a3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_now.go @@ -0,0 +1,50 @@ +package pongo2 + +import ( + "time" +) + +type tagNowNode struct { + position *Token + format string + fake bool +} + +func (node *tagNowNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + var t time.Time + if node.fake { + t = time.Date(2014, time.February, 05, 18, 31, 45, 00, time.UTC) + } else { + t = time.Now() + } + + writer.WriteString(t.Format(node.format)) + + return nil +} + +func tagNowParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + nowNode := &tagNowNode{ + position: start, + } + + formatToken := arguments.MatchType(TokenString) + if formatToken == nil { + return nil, arguments.Error("Expected a format string.", nil) + } + nowNode.format = formatToken.Val + + if arguments.MatchOne(TokenIdentifier, "fake") != nil { + nowNode.fake = true + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed now-tag arguments.", nil) + } + + return nowNode, nil +} + +func init() { + RegisterTag("now", tagNowParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_set.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_set.go new file mode 100644 index 0000000..be121c1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_set.go @@ -0,0 +1,50 @@ +package pongo2 + +type tagSetNode struct { + name string + expression IEvaluator +} + +func (node *tagSetNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + // Evaluate expression + value, err := node.expression.Evaluate(ctx) + if err != nil { + return err + } + + ctx.Private[node.name] = value + return nil +} + +func tagSetParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + node := &tagSetNode{} + + // Parse variable name + typeToken := arguments.MatchType(TokenIdentifier) + if typeToken == nil { + return nil, arguments.Error("Expected an identifier.", nil) + } + node.name = typeToken.Val + + if arguments.Match(TokenSymbol, "=") == nil { + return nil, arguments.Error("Expected '='.", nil) + } + + // Variable expression + keyExpression, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + node.expression = keyExpression + + // Remaining arguments + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed 'set'-tag arguments.", nil) + } + + return node, nil +} + +func init() { + RegisterTag("set", tagSetParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_spaceless.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_spaceless.go new file mode 100644 index 0000000..4fa851b --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_spaceless.go @@ -0,0 +1,54 @@ +package pongo2 + +import ( + "bytes" + "regexp" +) + +type tagSpacelessNode struct { + wrapper *NodeWrapper +} + +var tagSpacelessRegexp = regexp.MustCompile(`(?U:(<.*>))([\t\n\v\f\r ]+)(?U:(<.*>))`) + +func (node *tagSpacelessNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + b := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB + + err := node.wrapper.Execute(ctx, b) + if err != nil { + return err + } + + s := b.String() + // Repeat this recursively + changed := true + for changed { + s2 := tagSpacelessRegexp.ReplaceAllString(s, "$1$3") + changed = s != s2 + s = s2 + } + + writer.WriteString(s) + + return nil +} + +func tagSpacelessParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + spacelessNode := &tagSpacelessNode{} + + wrapper, _, err := doc.WrapUntilTag("endspaceless") + if err != nil { + return nil, err + } + spacelessNode.wrapper = wrapper + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed spaceless-tag arguments.", nil) + } + + return spacelessNode, nil +} + +func init() { + RegisterTag("spaceless", tagSpacelessParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ssi.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ssi.go new file mode 100644 index 0000000..09c2325 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_ssi.go @@ -0,0 +1,68 @@ +package pongo2 + +import ( + "io/ioutil" +) + +type tagSSINode struct { + filename string + content string + template *Template +} + +func (node *tagSSINode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + if node.template != nil { + // Execute the template within the current context + includeCtx := make(Context) + includeCtx.Update(ctx.Public) + includeCtx.Update(ctx.Private) + + err := node.template.execute(includeCtx, writer) + if err != nil { + return err.(*Error) + } + } else { + // Just print out the content + writer.WriteString(node.content) + } + return nil +} + +func tagSSIParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + SSINode := &tagSSINode{} + + if fileToken := arguments.MatchType(TokenString); fileToken != nil { + SSINode.filename = fileToken.Val + + if arguments.Match(TokenIdentifier, "parsed") != nil { + // parsed + temporaryTpl, err := doc.template.set.FromFile(doc.template.set.resolveFilename(doc.template, fileToken.Val)) + if err != nil { + return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, fileToken) + } + SSINode.template = temporaryTpl + } else { + // plaintext + buf, err := ioutil.ReadFile(doc.template.set.resolveFilename(doc.template, fileToken.Val)) + if err != nil { + return nil, (&Error{ + Sender: "tag:ssi", + ErrorMsg: err.Error(), + }).updateFromTokenIfNeeded(doc.template, fileToken) + } + SSINode.content = string(buf) + } + } else { + return nil, arguments.Error("First argument must be a string.", nil) + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed SSI-tag argument.", nil) + } + + return SSINode, nil +} + +func init() { + RegisterTag("ssi", tagSSIParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_templatetag.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_templatetag.go new file mode 100644 index 0000000..164b4dc --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_templatetag.go @@ -0,0 +1,45 @@ +package pongo2 + +type tagTemplateTagNode struct { + content string +} + +var templateTagMapping = map[string]string{ + "openblock": "{%", + "closeblock": "%}", + "openvariable": "{{", + "closevariable": "}}", + "openbrace": "{", + "closebrace": "}", + "opencomment": "{#", + "closecomment": "#}", +} + +func (node *tagTemplateTagNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + writer.WriteString(node.content) + return nil +} + +func tagTemplateTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + ttNode := &tagTemplateTagNode{} + + if argToken := arguments.MatchType(TokenIdentifier); argToken != nil { + output, found := templateTagMapping[argToken.Val] + if !found { + return nil, arguments.Error("Argument not found", argToken) + } + ttNode.content = output + } else { + return nil, arguments.Error("Identifier expected.", nil) + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed templatetag-tag argument.", nil) + } + + return ttNode, nil +} + +func init() { + RegisterTag("templatetag", tagTemplateTagParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_widthratio.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_widthratio.go new file mode 100644 index 0000000..70c9c3e --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_widthratio.go @@ -0,0 +1,83 @@ +package pongo2 + +import ( + "fmt" + "math" +) + +type tagWidthratioNode struct { + position *Token + current, max IEvaluator + width IEvaluator + ctxName string +} + +func (node *tagWidthratioNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + current, err := node.current.Evaluate(ctx) + if err != nil { + return err + } + + max, err := node.max.Evaluate(ctx) + if err != nil { + return err + } + + width, err := node.width.Evaluate(ctx) + if err != nil { + return err + } + + value := int(math.Ceil(current.Float()/max.Float()*width.Float() + 0.5)) + + if node.ctxName == "" { + writer.WriteString(fmt.Sprintf("%d", value)) + } else { + ctx.Private[node.ctxName] = value + } + + return nil +} + +func tagWidthratioParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + widthratioNode := &tagWidthratioNode{ + position: start, + } + + current, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + widthratioNode.current = current + + max, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + widthratioNode.max = max + + width, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + widthratioNode.width = width + + if arguments.MatchOne(TokenKeyword, "as") != nil { + // Name follows + nameToken := arguments.MatchType(TokenIdentifier) + if nameToken == nil { + return nil, arguments.Error("Expected name (identifier).", nil) + } + widthratioNode.ctxName = nameToken.Val + } + + if arguments.Remaining() > 0 { + return nil, arguments.Error("Malformed widthratio-tag arguments.", nil) + } + + return widthratioNode, nil +} + +func init() { + RegisterTag("widthratio", tagWidthratioParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/tags_with.go b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_with.go new file mode 100644 index 0000000..32b3c1c --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/tags_with.go @@ -0,0 +1,88 @@ +package pongo2 + +type tagWithNode struct { + withPairs map[string]IEvaluator + wrapper *NodeWrapper +} + +func (node *tagWithNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { + //new context for block + withctx := NewChildExecutionContext(ctx) + + // Put all custom with-pairs into the context + for key, value := range node.withPairs { + val, err := value.Evaluate(ctx) + if err != nil { + return err + } + withctx.Private[key] = val + } + + return node.wrapper.Execute(withctx, writer) +} + +func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { + withNode := &tagWithNode{ + withPairs: make(map[string]IEvaluator), + } + + if arguments.Count() == 0 { + return nil, arguments.Error("Tag 'with' requires at least one argument.", nil) + } + + wrapper, endargs, err := doc.WrapUntilTag("endwith") + if err != nil { + return nil, err + } + withNode.wrapper = wrapper + + if endargs.Count() > 0 { + return nil, endargs.Error("Arguments not allowed here.", nil) + } + + // Scan through all arguments to see which style the user uses (old or new style). + // If we find any "as" keyword we will enforce old style; otherwise we will use new style. + oldStyle := false // by default we're using the new_style + for i := 0; i < arguments.Count(); i++ { + if arguments.PeekN(i, TokenKeyword, "as") != nil { + oldStyle = true + break + } + } + + for arguments.Remaining() > 0 { + if oldStyle { + valueExpr, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + if arguments.Match(TokenKeyword, "as") == nil { + return nil, arguments.Error("Expected 'as' keyword.", nil) + } + keyToken := arguments.MatchType(TokenIdentifier) + if keyToken == nil { + return nil, arguments.Error("Expected an identifier", nil) + } + withNode.withPairs[keyToken.Val] = valueExpr + } else { + keyToken := arguments.MatchType(TokenIdentifier) + if keyToken == nil { + return nil, arguments.Error("Expected an identifier", nil) + } + if arguments.Match(TokenSymbol, "=") == nil { + return nil, arguments.Error("Expected '='.", nil) + } + valueExpr, err := arguments.ParseExpression() + if err != nil { + return nil, err + } + withNode.withPairs[keyToken.Val] = valueExpr + } + } + + return withNode, nil +} + +func init() { + RegisterTag("with", tagWithParser) +} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template.go b/Godeps/_workspace/src/github.com/flosch/pongo2/template.go new file mode 100644 index 0000000..74bd30b --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template.go @@ -0,0 +1,193 @@ +package pongo2 + +import ( + "bytes" + "fmt" + "io" +) + +type TemplateWriter interface { + io.Writer + WriteString(string) (int, error) +} + +type templateWriter struct { + w io.Writer +} + +func (tw *templateWriter) WriteString(s string) (int, error) { + return tw.w.Write([]byte(s)) +} + +func (tw *templateWriter) Write(b []byte) (int, error) { + return tw.w.Write(b) +} + +type Template struct { + set *TemplateSet + + // Input + isTplString bool + name string + tpl string + size int + + // Calculation + tokens []*Token + parser *Parser + + // first come, first serve (it's important to not override existing entries in here) + level int + parent *Template + child *Template + blocks map[string]*NodeWrapper + exportedMacros map[string]*tagMacroNode + + // Output + root *nodeDocument +} + +func newTemplateString(set *TemplateSet, tpl []byte) (*Template, error) { + return newTemplate(set, "From: {{ comment.Author.Name }} ({{ comment.Author.Validated|yesno:"validated,not validated,unknown validation status" }})
+ + {% if complex.is_admin(comment.Author) %} +This user is an admin (verify: {{ comment.Author.Is_admin }})!
+ {% else %} +This user is not admin!
+ {% endif %} + +Written {{ comment.Date }}
+{{ comment.Text|striptags }}
+ {% endfor %} + + + \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/complex.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/complex.tpl.out new file mode 100644 index 0000000..7fa3e1d --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/complex.tpl.out @@ -0,0 +1,50 @@ + + + + + +Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.
+From: user1 (validated)
+ + +This user is not admin!
+ + +Written 2014-06-10 15:30:15 +0000 UTC
+"pongo2 is nice!"
+ +From: user2 (validated)
+ + +This user is an admin (verify: True)!
+ + +Written 2011-03-21 08:37:56.000000012 +0000 UTC
+comment2 with unsafe tags in it
+ +From: user3 (not validated)
+ + +This user is not admin!
+ + +Written 2014-06-10 15:30:15 +0000 UTC
+hello! there
+ + + + \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/cycle.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/cycle.tpl new file mode 100644 index 0000000..967d82f --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/cycle.tpl @@ -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 }}' \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/cycle.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/cycle.tpl.out new file mode 100644 index 0000000..b966fb3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/cycle.tpl.out @@ -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' \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/empty.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/empty.tpl new file mode 100644 index 0000000..e69de29 diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/empty.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/empty.tpl.out new file mode 100644 index 0000000..e69de29 diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/expressions.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/expressions.tpl new file mode 100644 index 0000000..caada14 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/expressions.tpl @@ -0,0 +1,69 @@ +integers and complex expressions +{{ 10-100 }} +{{ -(10-100) }} +{{ -(-(10-100)) }} +{{ -1 * (-(-(10-100))) }} +{{ -1 * (-(-(10-100)) ^ 2) ^ 3 + 3 * (5 - 17) + 1 + 2 }} + +floats +{{ 5.5 }} +{{ 5.172841 }} +{{ 5.5 - 1.5 == 4 }} +{{ 5.5 - 1.5 == 4.0 }} + +mul/div +{{ 2 * 5 }} +{{ 2 * 5.0 }} +{{ 2 * 0 }} +{{ 2.5 * 5.3 }} +{{ 1/2 }} +{{ 1/2.0 }} +{{ 1/0.000001 }} + +logic expressions +{{ !true }} +{{ !(true || false) }} +{{ true || false }} +{{ true or false }} +{{ false or false }} +{{ false || false }} +{{ true && (true && (true && (true && (1 == 1 || false)))) }} + +float comparison +{{ 5.5 <= 5.5 }} +{{ 5.5 < 5.5 }} +{{ 5.5 > 5.5 }} +{{ 5.5 >= 5.5 }} + +remainders +{{ (simple.number+7)%7 }} +{{ (simple.number+7)%7 == 0 }} +{{ (simple.number+7)%6 }} + +in/not in +{{ 5 in simple.intmap }} +{{ 2 in simple.intmap }} +{{ 7 in simple.intmap }} +{{ !(5 in simple.intmap) }} +{{ not(7 in simple.intmap) }} +{{ 1 in simple.multiple_item_list }} +{{ 4 in simple.multiple_item_list }} +{{ !(4 in simple.multiple_item_list) }} +{{ "Hello" in simple.misc_list }} +{{ "Hello2" in simple.misc_list }} +{{ 99 in simple.misc_list }} +{{ False in simple.misc_list }} + +issue #48 (associativity for infix operators) +{{ 34/3*3 }} +{{ 10 + 24 / 6 / 2 }} +{{ 6 - 4 - 2 }} + +issue #64 (uint comparison with int const) +{{ simple.uint }} +{{ simple.uint == 8 }} +{{ simple.uint == 9 }} +{{ simple.uint >= 8 }} +{{ simple.uint <= 8 }} +{{ simple.uint < 8 }} +{{ simple.uint > 8 }} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/expressions.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/expressions.tpl.out new file mode 100644 index 0000000..d710fc8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/expressions.tpl.out @@ -0,0 +1,69 @@ +integers and complex expressions +-90 +90 +-90 +90 +531440999967.000000 + +floats +5.500000 +5.172841 +False +True + +mul/div +10 +10.000000 +0 +13.250000 +0 +0.500000 +1000000.000000 + +logic expressions +False +False +True +True +False +False +True + +float comparison +True +False +False +True + +remainders +0 +True +1 + +in/not in +True +True +False +False +True +True +False +True +True +False +True +False + +issue #48 (associativity for infix operators) +33 +12 +0 + +issue #64 (uint comparison with int const) +8 +True +False +True +True +False +False \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/extends.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/extends.tpl new file mode 100644 index 0000000..7216d05 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/extends.tpl @@ -0,0 +1,3 @@ +{% extends "inheritance/base.tpl" %} + +{% block content %}Extends' content{% endblock %} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/extends.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/extends.tpl.out new file mode 100644 index 0000000..4c535c7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/extends.tpl.out @@ -0,0 +1 @@ +Start#This is base's bodyExtends' content#End \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-compilation.err b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-compilation.err new file mode 100644 index 0000000..cc5c8cb --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-compilation.err @@ -0,0 +1,5 @@ +{{ }} +{{ (1 - 1 }} +{{ 1|float: }} +{{ "test"|non_existent_filter }} +{{ "test"|"test" }} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-compilation.err.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-compilation.err.out new file mode 100644 index 0000000..3562fae --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-compilation.err.out @@ -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\. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-execution.err b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-execution.err new file mode 100644 index 0000000..1fc53f2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-execution.err @@ -0,0 +1,4 @@ +{{ -(true || false) }} +{{ simple.func_add("test", 5) }} +{% for item in simple.multiple_item_list %} {{ simple.func_add("test", 5) }} {% endfor %} +{{ simple.func_variadic_sum_int("foo") }} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-execution.err.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-execution.err.out new file mode 100644 index 0000000..a960607 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters-execution.err.out @@ -0,0 +1,4 @@ +.*where: execution.*Negative sign on a non\-number expression +.*Function input argument 0 of 'simple.func_add' must be of type int or \*pongo2.Value \(not string\). +.*Function input argument 0 of 'simple.func_add' must be of type int or \*pongo2.Value \(not string\). +.*Function variadic input argument of 'simple.func_variadic_sum_int' must be of type int or \*pongo2.Value \(not string\). diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters.tpl new file mode 100644 index 0000000..c15468a --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/filters.tpl @@ -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 +{{ "" %} +{% firstof doesnotexist ""|safe %} +{% firstof doesnotexist simple.uint 42 %} +{% firstof doesnotexist "test" simple.number 42 %} +{% firstof %} +{% firstof "test" "test2" %} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/firstof.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/firstof.tpl.out new file mode 100644 index 0000000..5ae55ad --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/firstof.tpl.out @@ -0,0 +1,7 @@ +42 +<script>alert('xss');</script> + +8 +test + +test \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/for.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/for.tpl new file mode 100644 index 0000000..d14e632 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/for.tpl @@ -0,0 +1,27 @@ +{% for comment in complex.comments %}[{{ forloop.Counter }} {{ forloop.Counter0 }} {{ forloop.First }} {{ forloop.Last }} {{ forloop.Revcounter }} {{ forloop.Revcounter0 }}] {{ comment.Author.Name }} + +{# nested loop #} +{% for char in comment.Text %}{{forloop.Parentloop.Counter0}}.{{forloop.Counter0}}:{{ char|safe }} {% endfor %} + +{% endfor %} + +reversed +'{% for item in simple.multiple_item_list reversed %}{{ item }} {% endfor %}' + +sorted string map +'{% for key in simple.strmap sorted %}{{ key }} {% endfor %}' + +sorted int map +'{% for key in simple.intmap sorted %}{{ key }} {% endfor %}' + +sorted int list +'{% for key in simple.unsorted_int_list sorted %}{{ key }} {% endfor %}' + +reversed sorted int list +'{% for key in simple.unsorted_int_list reversed sorted %}{{ key }} {% endfor %}' + +reversed sorted string map +'{% for key in simple.strmap reversed sorted %}{{ key }} {% endfor %}' + +reversed sorted int map +'{% for key in simple.intmap reversed sorted %}{{ key }} {% endfor %}' diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/for.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/for.tpl.out new file mode 100644 index 0000000..27c7120 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/for.tpl.out @@ -0,0 +1,37 @@ +[1 0 True False 3 2] user1 + + +0.0:" 0.1:p 0.2:o 0.3:n 0.4:g 0.5:o 0.6:2 0.7: 0.8:i 0.9:s 0.10: 0.11:n 0.12:i 0.13:c 0.14:e 0.15:! 0.16:" + +[2 1 False False 2 1] user2 + + +1.0:c 1.1:o 1.2:m 1.3:m 1.4:e 1.5:n 1.6:t 1.7:2 1.8: 1.9:w 1.10:i 1.11:t 1.12:h 1.13: 1.14:< 1.15:s 1.16:c 1.17:r 1.18:i 1.19:p 1.20:t 1.21:> 1.22:u 1.23:n 1.24:s 1.25:a 1.26:f 1.27:e 1.28:< 1.29:/ 1.30:s 1.31:c 1.32:r 1.33:i 1.34:p 1.35:t 1.36:> 1.37: 1.38:t 1.39:a 1.40:g 1.41:s 1.42: 1.43:i 1.44:n 1.45: 1.46:i 1.47:t + +[3 2 False True 1 0] user3 + + +2.0:< 2.1:b 2.2:> 2.3:h 2.4:e 2.5:l 2.6:l 2.7:o 2.8:! 2.9:< 2.10:/ 2.11:b 2.12:> 2.13: 2.14:t 2.15:h 2.16:e 2.17:r 2.18:e + + + +reversed +'55 34 21 13 8 5 3 2 1 1 ' + +sorted string map +'aab abc bcd gh ukq zab ' + +sorted int map +'1 2 5 ' + +sorted int list +'1 22 192 249 581 8271 9999 1828591 ' + +reversed sorted int list +'1828591 9999 8271 581 249 192 22 1 ' + +reversed sorted string map +'zab ukq gh bcd abc aab ' + +reversed sorted int map +'5 2 1 ' diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/function_calls_wrapper.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/function_calls_wrapper.tpl new file mode 100644 index 0000000..85b870a --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/function_calls_wrapper.tpl @@ -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) }} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/function_calls_wrapper.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/function_calls_wrapper.tpl.out new file mode 100644 index 0000000..924e466 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/function_calls_wrapper.tpl.out @@ -0,0 +1,11 @@ +79 +79 +hello +hello, john doe +5 + 42 is 49 +0 +1 +205 +0 +2 +108 \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/if.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/if.tpl new file mode 100644 index 0000000..c434c3f --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/if.tpl @@ -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 %} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/if.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/if.tpl.out new file mode 100644 index 0000000..bf931be --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/if.tpl.out @@ -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 \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/ifchanged.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/ifchanged.tpl new file mode 100644 index 0000000..0282925 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/ifchanged.tpl @@ -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 %} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/ifchanged.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/ifchanged.tpl.out new file mode 100644 index 0000000..3f186cb --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/ifchanged.tpl.out @@ -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 diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/includes.helper b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/includes.helper new file mode 100644 index 0000000..b66db23 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/includes.helper @@ -0,0 +1 @@ +I'm {{ what_am_i }}{{ number }} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/includes.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/includes.tpl new file mode 100644 index 0000000..2394ee9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/includes.tpl @@ -0,0 +1,7 @@ +Start '{% include "includes.helper" %}' End +Start '{% include "includes.helper" if_exists %}' End +Start '{% include "includes.helper" with what_am_i=simple.name only %}' End +Start '{% include "includes.helper" with what_am_i=simple.name %}' End +Start '{% include simple.included_file|lower with number=7 what_am_i="guest" %}' End +Start '{% include "includes.helper.not_exists" if_exists %}' End +Start '{% include simple.included_file_not_exists if_exists with number=7 what_am_i="guest" %}' End \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/includes.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/includes.tpl.out new file mode 100644 index 0000000..61d9318 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/includes.tpl.out @@ -0,0 +1,7 @@ +Start 'I'm 11' End +Start 'I'm 11' End +Start 'I'm john doe' End +Start 'I'm john doe11' End +Start 'I'm guest7' End +Start '' End +Start '' End \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/base.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/base.tpl new file mode 100644 index 0000000..2b06d32 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/base.tpl @@ -0,0 +1,3 @@ +{% extends "inheritance2/skeleton.tpl" %} + +{% block body %}This is base's body{% block content %}Default content{% endblock %}{% endblock %} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/base2.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/base2.tpl new file mode 100644 index 0000000..5ebad5f --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/base2.tpl @@ -0,0 +1 @@ +{% include "doesnotexist.tpl" %} diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/cycle_include.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/cycle_include.tpl new file mode 100644 index 0000000..4b5d7b9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/cycle_include.tpl @@ -0,0 +1 @@ +Included '{{ cycleitem }}'. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/inheritance2/skeleton.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/inheritance2/skeleton.tpl new file mode 100644 index 0000000..c07cde6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/inheritance/inheritance2/skeleton.tpl @@ -0,0 +1 @@ +Start#{% block body %}Default body{% endblock %}#End \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/issues.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/issues.tpl new file mode 100644 index 0000000..e69de29 diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/issues.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/issues.tpl.out new file mode 100644 index 0000000..e69de29 diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/lorem.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/lorem.tpl new file mode 100644 index 0000000..f6b52dd --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/lorem.tpl @@ -0,0 +1,9 @@ +----- +{% lorem %} +----- +{% lorem 10 %} +----- +{% lorem 3 p %} +----- +{% lorem 100 w %} +----- \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/lorem.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/lorem.tpl.out new file mode 100644 index 0000000..286a148 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/lorem.tpl.out @@ -0,0 +1,20 @@ +----- +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. +----- +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. +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. +----- +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.
+----- +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 +----- \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-compilation.err b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-compilation.err new file mode 100644 index 0000000..baf3e6e --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-compilation.err @@ -0,0 +1 @@ +{% macro test_override() export %}{% endmacro %}{% macro test_override() export %}{% endmacro %} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-compilation.err.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-compilation.err.out new file mode 100644 index 0000000..a9ad8f1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-compilation.err.out @@ -0,0 +1 @@ +.*Another macro with name 'test_override' already exported. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-execution.err b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-execution.err new file mode 100644 index 0000000..ef7872c --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-execution.err @@ -0,0 +1 @@ +{% macro number() export %}No number here.{% endmacro %}{{ number() }} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-execution.err.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-execution.err.out new file mode 100644 index 0000000..4d4067b --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro-execution.err.out @@ -0,0 +1 @@ +.*Context key name 'number' clashes with macro 'number'. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro.helper b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro.helper new file mode 100644 index 0000000..d9809fd --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro.helper @@ -0,0 +1,2 @@ +{% macro imported_macro(foo) export %}Hey {{ foo }}!
{% endmacro %} +{% macro imported_macro_void() export %}Hello mate!
{% endmacro %} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro.tpl new file mode 100644 index 0000000..3c4d931 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro.tpl @@ -0,0 +1,30 @@ +Begin +{% macro greetings(to, from=simple.name, name2="guest") %} +Greetings to {{ to }} from {{ from }}. Howdy, {% if name2 == "guest" %}anonymous guest{% else %}{{ name2 }}{% endif %}! +{% endmacro %} +{{ greetings() }} +{{ greetings(10) }} +{{ greetings("john") }} +{{ greetings("john", "michelle") }} +{{ greetings("john", "michelle", "johann") }} +{{ greetings("john", "michelle", "johann", "foobar") }} + +{% macro test2(loop, value) %}map[{{ loop.Counter0 }}] = {{ value }}{% endmacro %} +{% for item in simple.misc_list %} +{{ test2(forloop, item) }}{% endfor %} + +issue #39 (deactivate auto-escape of macros) +{% macro html_test(name) %} +Hello {{ name }}.
+{% endmacro %} +{{ html_test("Max") }} + +Importing macros +{% import "macro.helper" imported_macro, imported_macro as renamed_macro, imported_macro as html_test %} +{{ imported_macro("User1") }} +{{ renamed_macro("User2") }} +{{ html_test("Max") }} + +Chaining macros{% import "macro2.helper" greeter_macro %} +{{ greeter_macro() }} +End \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro.tpl.out new file mode 100644 index 0000000..1bb9274 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro.tpl.out @@ -0,0 +1,44 @@ +Begin + + +Greetings to from john doe. Howdy, anonymous guest! + + +Greetings to 10 from john doe. Howdy, anonymous guest! + + +Greetings to john from john doe. Howdy, anonymous guest! + + +Greetings to john from michelle. Howdy, anonymous guest! + + +Greetings to john from michelle. Howdy, johann! + +[Error (where: execution) in template_tests/macro.tpl | Line 2 Col 4 near 'macro'] Macro 'greetings' called with too many arguments (4 instead of 3). + + + +map[0] = Hello +map[1] = 99 +map[2] = 3.140000 +map[3] = good + +issue #39 (deactivate auto-escape of macros) + + +Hello Max.
+ + +Importing macros + +Hey User1!
+Hey User2!
+Hey Max!
+ +Chaining macros + + +One greeting:Hey Dirk!
-Hello mate!
+ +End \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro2.helper b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro2.helper new file mode 100644 index 0000000..faa89c3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/macro2.helper @@ -0,0 +1,4 @@ +{% macro greeter_macro() export %} +{% import "macro.helper" imported_macro, imported_macro_void %} +One greeting: {{ imported_macro("Dirk") }} - {{ imported_macro_void() }} +{% endmacro %} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/now.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/now.tpl new file mode 100644 index 0000000..99ddae8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/now.tpl @@ -0,0 +1,2 @@ +{# The 'fake' argument exists to have tests for the now-tag; it will set the time to a specific date instead of now #} +{% now "Mon Jan 2 15:04:05 -0700 MST 2006" fake %} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/now.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/now.tpl.out new file mode 100644 index 0000000..4a8a624 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/now.tpl.out @@ -0,0 +1,2 @@ + +Wed Feb 5 18:31:45 +0000 UTC 2014 \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/pongo2ctx.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/pongo2ctx.tpl new file mode 100644 index 0000000..a3715f7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/pongo2ctx.tpl @@ -0,0 +1 @@ +{{ pongo2.version }} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/pongo2ctx.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/pongo2ctx.tpl.out new file mode 100644 index 0000000..9001211 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/pongo2ctx.tpl.out @@ -0,0 +1 @@ +dev \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox-compilation.err b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox-compilation.err new file mode 100644 index 0000000..fd59c3b --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox-compilation.err @@ -0,0 +1,3 @@ +{{ "hello"|banned_filter }} +{% banned_tag %} +{% include "../../test_not_existent" %} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox-compilation.err.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox-compilation.err.out new file mode 100644 index 0000000..cbc56ef --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox-compilation.err.out @@ -0,0 +1,3 @@ +.*Usage of filter 'banned_filter' is not allowed \(sandbox restriction active\). +.*Usage of tag 'banned_tag' is not allowed \(sandbox restriction active\). +\[Error \(where: fromfile\) | Line 1 Col 12 near '../../test_not_existent'\] open : no such file or directory \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox.tpl new file mode 100644 index 0000000..5a58c75 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox.tpl @@ -0,0 +1,3 @@ +{{ "hello"|unbanned_filter }} +{% unbanned_tag %} +{% include temp_file %} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox.tpl.out new file mode 100644 index 0000000..a60ed32 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/sandbox.tpl.out @@ -0,0 +1,3 @@ +hello +hello +Hello from pongo2 \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/set.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/set.tpl new file mode 100644 index 0000000..09e7b2d --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/set.tpl @@ -0,0 +1,5 @@ +{% set new_var = "hello" %}{{ new_var }} +{% block content %}{% set new_var = "world" %}{{ new_var }}{% endblock %} +{{ new_var }}{% for item in simple.misc_list %} +{% set new_var = item %}{{ new_var }}{% endfor %} +{{ new_var }} \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/set.tpl.out b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/set.tpl.out new file mode 100644 index 0000000..bede53d --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/set.tpl.out @@ -0,0 +1,8 @@ +hello +world +world +Hello +99 +3.140000 +good +world \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/spaceless.tpl b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/spaceless.tpl new file mode 100644 index 0000000..5659e81 --- /dev/null +++ b/Godeps/_workspace/src/github.com/flosch/pongo2/template_tests/spaceless.tpl @@ -0,0 +1,18 @@ +{% spaceless %} ++ This is a test! Mail me at + + + mail@example.tld + +
+ ++ + Yep! + +
+ ++ This is a test! Mail me at + + + mail@example.tld +
+ + Yep! + +