pax_global_header00006660000000000000000000000064141417555010014515gustar00rootroot0000000000000052 comment=8c56357c2c5a35d4cfb276507631e7190f6b029e golang-github-flosch-pongo2.v4-4.0.2/000077500000000000000000000000001414175550100172555ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/.gitattributes000066400000000000000000000000161414175550100221450ustar00rootroot00000000000000* text eol=lf golang-github-flosch-pongo2.v4-4.0.2/.github/000077500000000000000000000000001414175550100206155ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/.github/workflows/000077500000000000000000000000001414175550100226525ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/.github/workflows/codeql-analysis.yml000066400000000000000000000050261414175550100264700ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. name: "CodeQL" on: push: branches: [master] pull_request: # The branches below must be a subset of the branches above branches: [master] schedule: - cron: '0 22 * * 5' jobs: analyze: name: Analyze runs-on: ubuntu-latest strategy: fail-fast: false matrix: # Override automatic language detection by changing the below list # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] language: ['go'] # Learn more... # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection steps: - name: Checkout repository uses: actions/checkout@v2 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - run: git checkout HEAD^2 if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 golang-github-flosch-pongo2.v4-4.0.2/.gitignore000066400000000000000000000007121414175550100212450ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test .idea .vscode # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe .project EBNF.txt test1.tpl pongo2_internal_test.go tpl-error.out /count.out /cover.out *.swp *.iml /cpu.out /mem.out /pongo2.test *.error /profile /coverage.out /pongo2_internal_test.ignore golang-github-flosch-pongo2.v4-4.0.2/.travis.yml000066400000000000000000000001431414175550100213640ustar00rootroot00000000000000language: go arch: - AMD64 - ppc64le os: - linux - osx go: - 1.12 script: - go test -v golang-github-flosch-pongo2.v4-4.0.2/AUTHORS000066400000000000000000000003771414175550100203340ustar00rootroot00000000000000Main author and maintainer of pongo2: * Florian Schlachter Contributors (in no specific order): * @romanoaugusto88 * @vitalbh * @blaubaer Feel free to add yourself to the list or to modify your entry if you did a contribution. golang-github-flosch-pongo2.v4-4.0.2/LICENSE000066400000000000000000000021021414175550100202550ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2013-2014 Florian Schlachter Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-flosch-pongo2.v4-4.0.2/README.md000066400000000000000000000166671414175550100205540ustar00rootroot00000000000000# [pongo](https://en.wikipedia.org/wiki/Pongo_%28genus%29)2 [![PkgGoDev](https://pkg.go.dev/badge/github.com/flosch/pongo2)](https://pkg.go.dev/github.com/flosch/pongo2) [![Build Status](https://travis-ci.org/flosch/pongo2.svg?branch=master)](https://travis-ci.org/flosch/pongo2) pongo2 is a Django-syntax like templating-language ([official website](https://www.schlachter.tech/solutions/pongo2-template-engine/)). Install/update using `go get` (no dependencies required by pongo2): ```sh go get -u github.com/flosch/pongo2/v4 ``` Please use the [issue tracker](https://github.com/flosch/pongo2/issues) if you're encountering any problems with pongo2 or if you need help with implementing tags or filters ([create a ticket!](https://github.com/flosch/pongo2/issues/new)). ## First impression of a template ```django Our admins and users {# This is a short example to give you a quick overview of pongo2's syntax. #} {% macro user_details(user, is_admin=false) %}

= 40) || (user.karma > calc_avg_karma(userlist)+5) %} class="karma-good"{% endif %}> {{ user }}

This user registered {{ user.register_date|naturaltime }}.

The user's biography:

{{ user.biography|markdown|truncatewords_html:15 }} read more

{% if is_admin %}

This user is an admin!

{% endif %}
{% endmacro %}

Our admins

{% for admin in adminlist %} {{ user_details(admin, true) }} {% endfor %}

Our members

{% for user in userlist %} {{ user_details(user) }} {% endfor %} ``` ## Features - Syntax- and feature-set-compatible with [Django 1.7](https://django.readthedocs.io/en/1.7.x/topics/templates.html) - [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) ## 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 - [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 - [Build'n support for Iris' template engine](https://github.com/kataras/iris) - [pongo2gin](https://gitlab.com/go-box/pongo2gin) - alternative renderer for [gin](github.com/gin-gonic/gin) to use pongo2 templates - [pongo2-trans](https://github.com/digitalcrab/pongo2trans) - `trans`-tag implementation for internationalization - [tpongo2](https://github.com/tango-contrib/tpongo2) - pongo2 support for [Tango](https://github.com/lunny/tango), a micro-kernel & pluggable web framework. - [p2cli](https://github.com/wrouesnel/p2cli) - command line templating utility based on pongo2 - [Pongo2echo](https://github.com/stnc/pongo2echo) - pongo2 echo framework stability renderer [stnc] - [Pongo2gin](https://github.com/stnc/pongo2gin) - pongo2 gin minimal framework stability renderer [stnc] Please add your project to this list and send me a pull request when you've developed something nice for pongo2. ## Who's using pongo2 [I'm compiling a list of pongo2 users](https://github.com/flosch/pongo2/issues/241). Add your project or company! ## 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/v4" "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) } ``` golang-github-flosch-pongo2.v4-4.0.2/context.go000066400000000000000000000066461414175550100213040ustar00rootroot00000000000000package pongo2 import ( "fmt" "regexp" "errors" ) var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$") var autoescape = true func SetAutoescape(newValue bool) { autoescape = newValue } // 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", OrigError: fmt.Errorf("context-key '%s' (value: '%+v') is not a valid identifier", k, v), } } } return nil } // Update updates this context with the key/value-pairs from another context. func (c Context) Update(other Context) Context { for k, v := range other { c[k] = v } 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: autoescape, } } 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 { return ctx.OrigError(errors.New(msg), token) } func (ctx *ExecutionContext) OrigError(err error, token *Token) *Error { filename := ctx.template.name var line, col int if token != nil { // 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", OrigError: err, } } func (ctx *ExecutionContext) Logf(format string, args ...interface{}) { ctx.template.set.logf(format, args...) } golang-github-flosch-pongo2.v4-4.0.2/doc.go000066400000000000000000000020351414175550100203510ustar00rootroot00000000000000// Package pongo2 is 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 golang-github-flosch-pongo2.v4-4.0.2/docs/000077500000000000000000000000001414175550100202055ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/docs/examples.md000066400000000000000000000000131414175550100223370ustar00rootroot00000000000000(Stub, TBA)golang-github-flosch-pongo2.v4-4.0.2/docs/filters.md000066400000000000000000000016551414175550100222060ustar00rootroot00000000000000TODO: * 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).golang-github-flosch-pongo2.v4-4.0.2/docs/index.md000066400000000000000000000000131414175550100216300ustar00rootroot00000000000000(Stub, TBA)golang-github-flosch-pongo2.v4-4.0.2/docs/macros.md000066400000000000000000000000141414175550100220060ustar00rootroot00000000000000(Stub, TBA) golang-github-flosch-pongo2.v4-4.0.2/docs/tags.md000066400000000000000000000005461414175550100214720ustar00rootroot00000000000000TODO: * 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 * withgolang-github-flosch-pongo2.v4-4.0.2/docs/template_sets.md000066400000000000000000000000131414175550100233720ustar00rootroot00000000000000(Stub, TBA)golang-github-flosch-pongo2.v4-4.0.2/docs/write_filters.md000066400000000000000000000000001414175550100233770ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/docs/write_tags.md000066400000000000000000000000001414175550100226650ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/error.go000066400000000000000000000037161414175550100207440ustar00rootroot00000000000000package 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 OrigError error } 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.OrigError.Error() return s } // RawLine returns the affected line from the original template, if available. func (e *Error) RawLine() (line string, available bool, outErr error) { if e.Line <= 0 || e.Filename == "" { return "", false, nil } filename := e.Filename if e.Template != nil { filename = e.Template.set.resolveFilename(e.Template, e.Filename) } file, err := os.Open(filename) if err != nil { return "", false, err } defer func() { err := file.Close() if err != nil && outErr == nil { outErr = err } }() scanner := bufio.NewScanner(file) l := 0 for scanner.Scan() { l++ if l == e.Line { return scanner.Text(), true, nil } } return "", false, nil } golang-github-flosch-pongo2.v4-4.0.2/filters.go000066400000000000000000000067611414175550100212660ustar00rootroot00000000000000package pongo2 import ( "fmt" ) // FilterFunction is the type filter functions must fulfil type FilterFunction func(in *Value, param *Value) (out *Value, err *Error) var filters map[string]FilterFunction func init() { filters = make(map[string]FilterFunction) } // FilterExists returns true if the given filter is already registered func FilterExists(name string) bool { _, existing := filters[name] return existing } // RegisterFilter registers a new filter. If there's already a filter with the same // name, RegisterFilter will panic. You usually want to call this // function in the filter's init() function: // http://golang.org/doc/effective_go.html#init // // See http://www.florian-schlachter.de/post/pongo2/ for more about // writing filters and tags. func RegisterFilter(name string, fn FilterFunction) error { if FilterExists(name) { return fmt.Errorf("filter with name '%s' is already registered", name) } filters[name] = fn return nil } // ReplaceFilter replaces an already registered filter with a new implementation. Use this // function with caution since it allows you to change existing filter behaviour. func ReplaceFilter(name string, fn FilterFunction) error { if !FilterExists(name) { return fmt.Errorf("filter with name '%s' does not exist (therefore cannot be overridden)", name) } filters[name] = fn return nil } // MustApplyFilter behaves like ApplyFilter, but panics on an error. func MustApplyFilter(name string, value *Value, param *Value) *Value { val, err := ApplyFilter(name, value, param) if err != nil { panic(err) } return val } // ApplyFilter applies a filter to a given value using the given parameters. // Returns a *pongo2.Value or an error. func ApplyFilter(name string, value *Value, param *Value) (*Value, *Error) { fn, existing := filters[name] if !existing { return nil, &Error{ Sender: "applyfilter", OrigError: fmt.Errorf("Filter with name '%s' not found.", name), } } // Make sure param is a *Value if param == nil { param = AsValue(nil) } return fn(value, param) } type filterCall struct { token *Token name string parameter IEvaluator filterFunc FilterFunction } func (fc *filterCall) Execute(v *Value, ctx *ExecutionContext) (*Value, *Error) { var param *Value var err *Error if fc.parameter != nil { param, err = fc.parameter.Evaluate(ctx) if err != nil { return nil, err } } else { param = AsValue(nil) } filteredValue, err := fc.filterFunc(v, param) if err != nil { return nil, err.updateFromTokenIfNeeded(ctx.template, fc.token) } return filteredValue, nil } // Filter = IDENT | IDENT ":" FilterArg | IDENT "|" Filter func (p *Parser) parseFilter() (*filterCall, *Error) { identToken := p.MatchType(TokenIdentifier) // Check filter ident if identToken == nil { return nil, p.Error("Filter name must be an identifier.", nil) } filter := &filterCall{ token: identToken, name: identToken.Val, } // Get the appropriate filter function and bind it filterFn, exists := filters[identToken.Val] if !exists { return nil, p.Error(fmt.Sprintf("Filter '%s' does not exist.", identToken.Val), identToken) } filter.filterFunc = filterFn // Check for filter-argument (2 tokens needed: ':' ARG) if p.Match(TokenSymbol, ":") != nil { if p.Peek(TokenSymbol, "}}") != nil { return nil, p.Error("Filter parameter required after ':'.", nil) } // Get filter argument expression v, err := p.parseVariableOrLiteral() if err != nil { return nil, err } filter.parameter = v } return filter, nil } golang-github-flosch-pongo2.v4-4.0.2/filters_builtin.go000066400000000000000000000542071414175550100230120ustar00rootroot00000000000000package pongo2 /* Filters that are provided through github.com/flosch/pongo2-addons: ------------------------------------------------------------------ filesizeformat slugify timesince timeuntil Filters that won't be added: ---------------------------- get_static_prefix (reason: web-framework specific) pprint (reason: python-specific) static (reason: web-framework specific) Reconsideration (not implemented yet): -------------------------------------- force_escape (reason: not yet needed since this is the behaviour of pongo2's escape filter) safeseq (reason: same reason as `force_escape`) unordered_list (python-specific; not sure whether needed or not) dictsort (python-specific; maybe one could add a filter to sort a list of structs by a specific field name) dictsortreversed (see dictsort) */ import ( "bytes" "fmt" "math/rand" "net/url" "regexp" "strconv" "strings" "time" "unicode/utf8" "errors" ) func init() { rand.Seed(time.Now().Unix()) RegisterFilter("escape", filterEscape) RegisterFilter("safe", filterSafe) RegisterFilter("escapejs", filterEscapejs) RegisterFilter("add", filterAdd) RegisterFilter("addslashes", filterAddslashes) RegisterFilter("capfirst", filterCapfirst) RegisterFilter("center", filterCenter) RegisterFilter("cut", filterCut) RegisterFilter("date", filterDate) RegisterFilter("default", filterDefault) RegisterFilter("default_if_none", filterDefaultIfNone) RegisterFilter("divisibleby", filterDivisibleby) RegisterFilter("first", filterFirst) RegisterFilter("floatformat", filterFloatformat) RegisterFilter("get_digit", filterGetdigit) RegisterFilter("iriencode", filterIriencode) RegisterFilter("join", filterJoin) RegisterFilter("last", filterLast) RegisterFilter("length", filterLength) RegisterFilter("length_is", filterLengthis) RegisterFilter("linebreaks", filterLinebreaks) RegisterFilter("linebreaksbr", filterLinebreaksbr) RegisterFilter("linenumbers", filterLinenumbers) RegisterFilter("ljust", filterLjust) RegisterFilter("lower", filterLower) RegisterFilter("make_list", filterMakelist) RegisterFilter("phone2numeric", filterPhone2numeric) RegisterFilter("pluralize", filterPluralize) RegisterFilter("random", filterRandom) RegisterFilter("removetags", filterRemovetags) RegisterFilter("rjust", filterRjust) RegisterFilter("slice", filterSlice) RegisterFilter("split", filterSplit) RegisterFilter("stringformat", filterStringformat) RegisterFilter("striptags", filterStriptags) RegisterFilter("time", filterDate) // time uses filterDate (same golang-format) RegisterFilter("title", filterTitle) RegisterFilter("truncatechars", filterTruncatechars) RegisterFilter("truncatechars_html", filterTruncatecharsHTML) RegisterFilter("truncatewords", filterTruncatewords) RegisterFilter("truncatewords_html", filterTruncatewordsHTML) RegisterFilter("upper", filterUpper) RegisterFilter("urlencode", filterUrlencode) RegisterFilter("urlize", filterUrlize) RegisterFilter("urlizetrunc", filterUrlizetrunc) RegisterFilter("wordcount", filterWordcount) RegisterFilter("wordwrap", filterWordwrap) RegisterFilter("yesno", filterYesno) RegisterFilter("float", filterFloat) // pongo-specific RegisterFilter("integer", filterInteger) // pongo-specific } func filterTruncatecharsHelper(s string, newLen int) string { runes := []rune(s) if newLen < len(runes) { if newLen >= 3 { return fmt.Sprintf("%s...", string(runes[:newLen-3])) } // Not enough space for the ellipsis return string(runes[:newLen]) } return string(runes) } func filterTruncateHTMLHelper(value string, newOutput *bytes.Buffer, cond func() bool, fn func(c rune, s int, idx int) int, finalize func()) { vLen := len(value) var tagStack []string idx := 0 for idx < vLen && !cond() { c, s := utf8.DecodeRuneInString(value[idx:]) if c == utf8.RuneError { idx += s continue } if c == '<' { newOutput.WriteRune(c) idx += s // consume "<" if idx+1 < vLen { if value[idx] == '/' { // Close tag newOutput.WriteString("/") tag := "" idx++ // consume "/" for idx < vLen { c2, size2 := utf8.DecodeRuneInString(value[idx:]) if c2 == utf8.RuneError { idx += size2 continue } // End of tag found if c2 == '>' { idx++ // consume ">" break } tag += string(c2) idx += size2 } if len(tagStack) > 0 { // Ideally, the close tag is TOP of tag stack // In malformed HTML, it must not be, so iterate through the stack and remove the tag for i := len(tagStack) - 1; i >= 0; i-- { if tagStack[i] == tag { // Found the tag tagStack[i] = tagStack[len(tagStack)-1] tagStack = tagStack[:len(tagStack)-1] break } } } newOutput.WriteString(tag) newOutput.WriteString(">") } else { // Open tag tag := "" params := false for idx < vLen { c2, size2 := utf8.DecodeRuneInString(value[idx:]) if c2 == utf8.RuneError { idx += size2 continue } newOutput.WriteRune(c2) // End of tag found if c2 == '>' { idx++ // consume ">" break } if !params { if c2 == ' ' { params = true } else { tag += string(c2) } } idx += size2 } // Add tag to stack tagStack = append(tagStack, tag) } } } else { idx = fn(c, s, idx) } } finalize() for i := len(tagStack) - 1; i >= 0; i-- { tag := tagStack[i] // Close everything from the regular tag stack newOutput.WriteString(fmt.Sprintf("", tag)) } } func filterTruncatechars(in *Value, param *Value) (*Value, *Error) { s := in.String() newLen := param.Integer() return AsValue(filterTruncatecharsHelper(s, newLen)), nil } func filterTruncatecharsHTML(in *Value, param *Value) (*Value, *Error) { value := in.String() newLen := max(param.Integer()-3, 0) newOutput := bytes.NewBuffer(nil) textcounter := 0 filterTruncateHTMLHelper(value, newOutput, func() bool { return textcounter >= newLen }, func(c rune, s int, idx int) int { textcounter++ newOutput.WriteRune(c) return idx + s }, func() { if textcounter >= newLen && textcounter < len(value) { newOutput.WriteString("...") } }) return AsSafeValue(newOutput.String()), nil } func filterTruncatewords(in *Value, param *Value) (*Value, *Error) { words := strings.Fields(in.String()) n := param.Integer() if n <= 0 { return AsValue(""), nil } nlen := min(len(words), n) out := make([]string, 0, nlen) for i := 0; i < nlen; i++ { out = append(out, words[i]) } if n < len(words) { out = append(out, "...") } return AsValue(strings.Join(out, " ")), nil } func filterTruncatewordsHTML(in *Value, param *Value) (*Value, *Error) { value := in.String() newLen := max(param.Integer(), 0) newOutput := bytes.NewBuffer(nil) wordcounter := 0 filterTruncateHTMLHelper(value, newOutput, func() bool { return wordcounter >= newLen }, func(_ rune, _ int, idx int) int { // Get next word wordFound := false for idx < len(value) { c2, size2 := utf8.DecodeRuneInString(value[idx:]) if c2 == utf8.RuneError { idx += size2 continue } if c2 == '<' { // HTML tag start, don't consume it return idx } newOutput.WriteRune(c2) idx += size2 if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' { // Word ends here, stop capturing it now break } else { wordFound = true } } if wordFound { wordcounter++ } return idx }, func() { if wordcounter >= newLen { newOutput.WriteString("...") } }) return AsSafeValue(newOutput.String()), nil } func filterEscape(in *Value, param *Value) (*Value, *Error) { output := strings.Replace(in.String(), "&", "&", -1) output = strings.Replace(output, ">", ">", -1) output = strings.Replace(output, "<", "<", -1) output = strings.Replace(output, "\"", """, -1) output = strings.Replace(output, "'", "'", -1) return AsValue(output), nil } func filterSafe(in *Value, param *Value) (*Value, *Error) { return in, nil // nothing to do here, just to keep track of the safe application } func filterEscapejs(in *Value, param *Value) (*Value, *Error) { sin := in.String() var b bytes.Buffer idx := 0 for idx < len(sin) { c, size := utf8.DecodeRuneInString(sin[idx:]) if c == utf8.RuneError { idx += size continue } if c == '\\' { // Escape seq? if idx+1 < len(sin) { switch sin[idx+1] { case 'r': b.WriteString(fmt.Sprintf(`\u%04X`, '\r')) idx += 2 continue case 'n': b.WriteString(fmt.Sprintf(`\u%04X`, '\n')) idx += 2 continue /*case '\'': b.WriteString(fmt.Sprintf(`\u%04X`, '\'')) idx += 2 continue case '"': b.WriteString(fmt.Sprintf(`\u%04X`, '"')) idx += 2 continue*/ } } } if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == ' ' || c == '/' { b.WriteRune(c) } else { b.WriteString(fmt.Sprintf(`\u%04X`, c)) } idx += size } return AsValue(b.String()), nil } func filterAdd(in *Value, param *Value) (*Value, *Error) { if in.IsNumber() && param.IsNumber() { if in.IsFloat() || param.IsFloat() { return AsValue(in.Float() + param.Float()), nil } return AsValue(in.Integer() + param.Integer()), nil } // If in/param is not a number, we're relying on the // Value's String() conversion and just add them both together return AsValue(in.String() + param.String()), nil } func filterAddslashes(in *Value, param *Value) (*Value, *Error) { output := strings.Replace(in.String(), "\\", "\\\\", -1) output = strings.Replace(output, "\"", "\\\"", -1) output = strings.Replace(output, "'", "\\'", -1) return AsValue(output), nil } func filterCut(in *Value, param *Value) (*Value, *Error) { return AsValue(strings.Replace(in.String(), param.String(), "", -1)), nil } func filterLength(in *Value, param *Value) (*Value, *Error) { return AsValue(in.Len()), nil } func filterLengthis(in *Value, param *Value) (*Value, *Error) { return AsValue(in.Len() == param.Integer()), nil } func filterDefault(in *Value, param *Value) (*Value, *Error) { if !in.IsTrue() { return param, nil } return in, nil } func filterDefaultIfNone(in *Value, param *Value) (*Value, *Error) { if in.IsNil() { return param, nil } return in, nil } func filterDivisibleby(in *Value, param *Value) (*Value, *Error) { if param.Integer() == 0 { return AsValue(false), nil } return AsValue(in.Integer()%param.Integer() == 0), nil } func filterFirst(in *Value, param *Value) (*Value, *Error) { if in.CanSlice() && in.Len() > 0 { return in.Index(0), nil } return AsValue(""), nil } func filterFloatformat(in *Value, param *Value) (*Value, *Error) { val := in.Float() decimals := -1 if !param.IsNil() { // Any argument provided? decimals = param.Integer() } // if the argument is not a number (e. g. empty), the default // behaviour is trim the result trim := !param.IsNumber() if decimals <= 0 { // argument is negative or zero, so we // want the output being trimmed decimals = -decimals trim = true } if trim { // Remove zeroes if float64(int(val)) == val { return AsValue(in.Integer()), nil } } return AsValue(strconv.FormatFloat(val, 'f', decimals, 64)), nil } func filterGetdigit(in *Value, param *Value) (*Value, *Error) { i := param.Integer() l := len(in.String()) // do NOT use in.Len() here! if i <= 0 || i > l { return in, nil } return AsValue(in.String()[l-i] - 48), nil } const filterIRIChars = "/#%[]=:;$&()+,!?*@'~" func filterIriencode(in *Value, param *Value) (*Value, *Error) { var b bytes.Buffer sin := in.String() for _, r := range sin { if strings.IndexRune(filterIRIChars, r) >= 0 { b.WriteRune(r) } else { b.WriteString(url.QueryEscape(string(r))) } } return AsValue(b.String()), nil } func filterJoin(in *Value, param *Value) (*Value, *Error) { if !in.CanSlice() { return in, nil } sep := param.String() sl := make([]string, 0, in.Len()) for i := 0; i < in.Len(); i++ { sl = append(sl, in.Index(i).String()) } return AsValue(strings.Join(sl, sep)), nil } func filterLast(in *Value, param *Value) (*Value, *Error) { if in.CanSlice() && in.Len() > 0 { return in.Index(in.Len() - 1), nil } return AsValue(""), nil } func filterUpper(in *Value, param *Value) (*Value, *Error) { return AsValue(strings.ToUpper(in.String())), nil } func filterLower(in *Value, param *Value) (*Value, *Error) { return AsValue(strings.ToLower(in.String())), nil } func filterMakelist(in *Value, param *Value) (*Value, *Error) { s := in.String() result := make([]string, 0, len(s)) for _, c := range s { result = append(result, string(c)) } return AsValue(result), nil } func filterCapfirst(in *Value, param *Value) (*Value, *Error) { if in.Len() <= 0 { return AsValue(""), nil } t := in.String() r, size := utf8.DecodeRuneInString(t) return AsValue(strings.ToUpper(string(r)) + t[size:]), nil } func filterCenter(in *Value, param *Value) (*Value, *Error) { width := param.Integer() slen := in.Len() if width <= slen { return in, nil } spaces := width - slen left := spaces/2 + spaces%2 right := spaces / 2 return AsValue(fmt.Sprintf("%s%s%s", strings.Repeat(" ", left), in.String(), strings.Repeat(" ", right))), nil } func filterDate(in *Value, param *Value) (*Value, *Error) { t, isTime := in.Interface().(time.Time) if !isTime { return nil, &Error{ Sender: "filter:date", OrigError: errors.New("filter input argument must be of type 'time.Time'"), } } return AsValue(t.Format(param.String())), nil } func filterFloat(in *Value, param *Value) (*Value, *Error) { return AsValue(in.Float()), nil } func filterInteger(in *Value, param *Value) (*Value, *Error) { return AsValue(in.Integer()), nil } func filterLinebreaks(in *Value, param *Value) (*Value, *Error) { if in.Len() == 0 { return in, nil } var b bytes.Buffer // Newline =
// Double newline =

...

lines := strings.Split(in.String(), "\n") lenlines := len(lines) opened := false for idx, line := range lines { if !opened { b.WriteString("

") 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("

") opened = false } } else { b.WriteString("
") } } } if opened { b.WriteString("

") } return AsValue(b.String()), nil } func filterSplit(in *Value, param *Value) (*Value, *Error) { chunks := strings.Split(in.String(), param.String()) return AsValue(chunks), nil } func filterLinebreaksbr(in *Value, param *Value) (*Value, *Error) { return AsValue(strings.Replace(in.String(), "\n", "
", -1)), nil } func filterLinenumbers(in *Value, param *Value) (*Value, *Error) { lines := strings.Split(in.String(), "\n") output := make([]string, 0, len(lines)) for idx, line := range lines { output = append(output, fmt.Sprintf("%d. %s", idx+1, line)) } return AsValue(strings.Join(output, "\n")), nil } func filterLjust(in *Value, param *Value) (*Value, *Error) { times := param.Integer() - in.Len() if times < 0 { times = 0 } return AsValue(fmt.Sprintf("%s%s", in.String(), strings.Repeat(" ", times))), nil } func filterUrlencode(in *Value, param *Value) (*Value, *Error) { return AsValue(url.QueryEscape(in.String())), nil } // TODO: This regexp could do some work var filterUrlizeURLRegexp = regexp.MustCompile(`((((http|https)://)|www\.|((^|[ ])[0-9A-Za-z_\-]+(\.com|\.net|\.org|\.info|\.biz|\.de))))(?U:.*)([ ]+|$)`) var filterUrlizeEmailRegexp = regexp.MustCompile(`(\w+@\w+\.\w{2,4})`) func filterUrlizeHelper(input string, autoescape bool, trunc int) (string, error) { var soutErr error sout := filterUrlizeURLRegexp.ReplaceAllStringFunc(input, func(raw_url string) string { var prefix string var suffix string if strings.HasPrefix(raw_url, " ") { prefix = " " } if strings.HasSuffix(raw_url, " ") { suffix = " " } raw_url = strings.TrimSpace(raw_url) t, err := ApplyFilter("iriencode", AsValue(raw_url), nil) if err != nil { soutErr = err return "" } url := t.String() if !strings.HasPrefix(url, "http") { url = fmt.Sprintf("http://%s", url) } title := raw_url if trunc > 3 && len(title) > trunc { title = fmt.Sprintf("%s...", title[:trunc-3]) } if autoescape { t, err := ApplyFilter("escape", AsValue(title), nil) if err != nil { soutErr = err return "" } title = t.String() } return fmt.Sprintf(`%s%s%s`, prefix, url, title, suffix) }) if soutErr != nil { return "", soutErr } sout = filterUrlizeEmailRegexp.ReplaceAllStringFunc(sout, func(mail string) string { title := mail if trunc > 3 && len(title) > trunc { title = fmt.Sprintf("%s...", title[:trunc-3]) } return fmt.Sprintf(`%s`, mail, title) }) return sout, nil } func filterUrlize(in *Value, param *Value) (*Value, *Error) { autoescape := true if param.IsBool() { autoescape = param.Bool() } s, err := filterUrlizeHelper(in.String(), autoescape, -1) if err != nil { } return AsValue(s), nil } func filterUrlizetrunc(in *Value, param *Value) (*Value, *Error) { s, err := filterUrlizeHelper(in.String(), true, param.Integer()) if err != nil { return nil, &Error{ Sender: "filter:urlizetrunc", OrigError: errors.New("you cannot pass more than 2 arguments to filter 'pluralize'"), } } return AsValue(s), nil } func filterStringformat(in *Value, param *Value) (*Value, *Error) { return AsValue(fmt.Sprintf(param.String(), in.Interface())), nil } var reStriptags = regexp.MustCompile("<[^>]*?>") func filterStriptags(in *Value, param *Value) (*Value, *Error) { s := in.String() // Strip all tags s = reStriptags.ReplaceAllString(s, "") return AsValue(strings.TrimSpace(s)), nil } // https://en.wikipedia.org/wiki/Phoneword var filterPhone2numericMap = map[string]string{ "a": "2", "b": "2", "c": "2", "d": "3", "e": "3", "f": "3", "g": "4", "h": "4", "i": "4", "j": "5", "k": "5", "l": "5", "m": "6", "n": "6", "o": "6", "p": "7", "q": "7", "r": "7", "s": "7", "t": "8", "u": "8", "v": "8", "w": "9", "x": "9", "y": "9", "z": "9", } func filterPhone2numeric(in *Value, param *Value) (*Value, *Error) { sin := in.String() for k, v := range filterPhone2numericMap { sin = strings.Replace(sin, k, v, -1) sin = strings.Replace(sin, strings.ToUpper(k), v, -1) } return AsValue(sin), nil } func filterPluralize(in *Value, param *Value) (*Value, *Error) { if in.IsNumber() { // Works only on numbers if param.Len() > 0 { endings := strings.Split(param.String(), ",") if len(endings) > 2 { return nil, &Error{ Sender: "filter:pluralize", OrigError: errors.New("you cannot pass more than 2 arguments to filter 'pluralize'"), } } if len(endings) == 1 { // 1 argument if in.Integer() != 1 { return AsValue(endings[0]), nil } } else { if in.Integer() != 1 { // 2 arguments return AsValue(endings[1]), nil } return AsValue(endings[0]), nil } } else { if in.Integer() != 1 { // return default 's' return AsValue("s"), nil } } return AsValue(""), nil } return nil, &Error{ Sender: "filter:pluralize", OrigError: errors.New("filter 'pluralize' does only work on numbers"), } } func filterRandom(in *Value, param *Value) (*Value, *Error) { if !in.CanSlice() || in.Len() <= 0 { return in, nil } i := rand.Intn(in.Len()) return in.Index(i), nil } func filterRemovetags(in *Value, param *Value) (*Value, *Error) { s := in.String() tags := strings.Split(param.String(), ",") // Strip only specific tags for _, tag := range tags { re := regexp.MustCompile(fmt.Sprintf("", tag)) s = re.ReplaceAllString(s, "") } return AsValue(strings.TrimSpace(s)), nil } func filterRjust(in *Value, param *Value) (*Value, *Error) { return AsValue(fmt.Sprintf(fmt.Sprintf("%%%ds", param.Integer()), in.String())), nil } func filterSlice(in *Value, param *Value) (*Value, *Error) { comp := strings.Split(param.String(), ":") if len(comp) != 2 { return nil, &Error{ Sender: "filter:slice", OrigError: errors.New("Slice string must have the format 'from:to' [from/to can be omitted, but the ':' is required]"), } } if !in.CanSlice() { return in, nil } from := AsValue(comp[0]).Integer() to := in.Len() if from > to { from = to } vto := AsValue(comp[1]).Integer() if vto >= from && vto <= in.Len() { to = vto } return in.Slice(from, to), nil } func filterTitle(in *Value, param *Value) (*Value, *Error) { if !in.IsString() { return AsValue(""), nil } return AsValue(strings.Title(strings.ToLower(in.String()))), nil } func filterWordcount(in *Value, param *Value) (*Value, *Error) { return AsValue(len(strings.Fields(in.String()))), nil } func filterWordwrap(in *Value, param *Value) (*Value, *Error) { words := strings.Fields(in.String()) wordsLen := len(words) wrapAt := param.Integer() if wrapAt <= 0 { return in, nil } linecount := wordsLen/wrapAt + wordsLen%wrapAt lines := make([]string, 0, linecount) for i := 0; i < linecount; i++ { lines = append(lines, strings.Join(words[wrapAt*i:min(wrapAt*(i+1), wordsLen)], " ")) } return AsValue(strings.Join(lines, "\n")), nil } func filterYesno(in *Value, param *Value) (*Value, *Error) { choices := map[int]string{ 0: "yes", 1: "no", 2: "maybe", } paramString := param.String() customChoices := strings.Split(paramString, ",") if len(paramString) > 0 { if len(customChoices) > 3 { return nil, &Error{ Sender: "filter:yesno", OrigError: fmt.Errorf("You cannot pass more than 3 options to the 'yesno'-filter (got: '%s').", paramString), } } if len(customChoices) < 2 { return nil, &Error{ Sender: "filter:yesno", OrigError: fmt.Errorf("You must pass either no or at least 2 arguments to the 'yesno'-filter (got: '%s').", paramString), } } // Map to the options now choices[0] = customChoices[0] choices[1] = customChoices[1] if len(customChoices) == 3 { choices[2] = customChoices[2] } } // maybe if in.IsNil() { return AsValue(choices[2]), nil } // yes if in.IsTrue() { return AsValue(choices[0]), nil } // no return AsValue(choices[1]), nil } golang-github-flosch-pongo2.v4-4.0.2/go.mod000066400000000000000000000002721414175550100203640ustar00rootroot00000000000000module github.com/flosch/pongo2/v4 go 1.14 require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b ) golang-github-flosch-pongo2.v4-4.0.2/go.sum000066400000000000000000000012371414175550100204130ustar00rootroot00000000000000github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= golang-github-flosch-pongo2.v4-4.0.2/helpers.go000066400000000000000000000002171414175550100212460ustar00rootroot00000000000000package pongo2 func max(a, b int) int { if a > b { return a } return b } func min(a, b int) int { if a < b { return a } return b } golang-github-flosch-pongo2.v4-4.0.2/lexer.go000066400000000000000000000205671414175550100207350ustar00rootroot00000000000000package pongo2 import ( "fmt" "strings" "unicode/utf8" "errors" ) const ( TokenError = iota EOF TokenHTML TokenKeyword TokenIdentifier TokenString TokenNumber TokenSymbol ) var ( tokenSpaceChars = " \n\r\t" tokenIdentifierChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" tokenIdentifierCharsWithDigits = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789" tokenDigits = "0123456789" // Available symbols in pongo2 (within filters/tag) TokenSymbols = []string{ // 3-Char symbols "{{-", "-}}", "{%-", "-%}", // 2-Char symbols "==", ">=", "<=", "&&", "||", "{{", "}}", "{%", "%}", "!=", "<>", // 1-Char symbol "(", ")", "+", "-", "*", "<", ">", "/", "^", ",", ".", "!", "|", ":", "=", "%", } // Available keywords in pongo2 TokenKeywords = []string{"in", "and", "or", "not", "true", "false", "as", "export"} ) type TokenType int type Token struct { Filename string Typ TokenType Val string Line int Col int TrimWhitespaces bool } type lexerStateFn func() lexerStateFn type lexer struct { name string input string start int // start pos of the item pos int // current pos width int // width of last rune tokens []*Token errored bool startline int startcol int line int col int inVerbatim bool verbatimName string } func (t *Token) String() string { val := t.Val if len(val) > 1000 { val = fmt.Sprintf("%s...%s", val[:10], val[len(val)-5:]) } typ := "" switch t.Typ { case TokenHTML: typ = "HTML" case TokenError: typ = "Error" case TokenIdentifier: typ = "Identifier" case TokenKeyword: typ = "Keyword" case TokenNumber: typ = "Number" case TokenString: typ = "String" case TokenSymbol: typ = "Symbol" default: typ = "Unknown" } return fmt.Sprintf("", typ, t.Typ, val, t.Line, t.Col, t.TrimWhitespaces) } func lex(name string, input string) ([]*Token, *Error) { l := &lexer{ name: name, input: input, tokens: make([]*Token, 0, 100), line: 1, col: 1, startline: 1, startcol: 1, } l.run() if l.errored { errtoken := l.tokens[len(l.tokens)-1] return nil, &Error{ Filename: name, Line: errtoken.Line, Column: errtoken.Col, Sender: "lexer", OrigError: errors.New(errtoken.Val), } } return l.tokens, nil } func (l *lexer) value() string { return l.input[l.start:l.pos] } func (l *lexer) length() int { return l.pos - l.start } func (l *lexer) emit(t TokenType) { tok := &Token{ Filename: l.name, Typ: t, Val: l.value(), Line: l.startline, Col: l.startcol, } if t == TokenString { // Escape sequence \" in strings tok.Val = strings.Replace(tok.Val, `\"`, `"`, -1) tok.Val = strings.Replace(tok.Val, `\\`, `\`, -1) } if t == TokenSymbol && len(tok.Val) == 3 && (strings.HasSuffix(tok.Val, "-") || strings.HasPrefix(tok.Val, "-")) { tok.TrimWhitespaces = true tok.Val = strings.Replace(tok.Val, "-", "", -1) } l.tokens = append(l.tokens, tok) l.start = l.pos l.startline = l.line l.startcol = l.col } func (l *lexer) next() rune { if l.pos >= len(l.input) { l.width = 0 return EOF } r, w := utf8.DecodeRuneInString(l.input[l.pos:]) l.width = w l.pos += l.width l.col += l.width return r } func (l *lexer) backup() { l.pos -= l.width l.col -= l.width } func (l *lexer) peek() rune { r := l.next() l.backup() return r } func (l *lexer) ignore() { l.start = l.pos l.startline = l.line l.startcol = l.col } func (l *lexer) accept(what string) bool { if strings.IndexRune(what, l.next()) >= 0 { return true } l.backup() return false } func (l *lexer) acceptRun(what string) { for strings.IndexRune(what, l.next()) >= 0 { } l.backup() } func (l *lexer) errorf(format string, args ...interface{}) lexerStateFn { t := &Token{ Filename: l.name, Typ: TokenError, Val: fmt.Sprintf(format, args...), Line: l.startline, Col: l.startcol, } l.tokens = append(l.tokens, t) l.errored = true l.startline = l.line l.startcol = l.col return nil } func (l *lexer) eof() bool { return l.start >= len(l.input)-1 } func (l *lexer) run() { for { // TODO: Support verbatim tag names // https://docs.djangoproject.com/en/dev/ref/templates/builtins/#verbatim if l.inVerbatim { name := l.verbatimName if name != "" { name += " " } if strings.HasPrefix(l.input[l.pos:], fmt.Sprintf("{%% endverbatim %s%%}", name)) { // end verbatim if l.pos > l.start { l.emit(TokenHTML) } w := len("{% endverbatim %}") l.pos += w l.col += w l.ignore() l.inVerbatim = false } } else if strings.HasPrefix(l.input[l.pos:], "{% verbatim %}") { // tag if l.pos > l.start { l.emit(TokenHTML) } l.inVerbatim = true w := len("{% verbatim %}") l.pos += w l.col += w l.ignore() } if !l.inVerbatim { // Ignore single-line comments {# ... #} if strings.HasPrefix(l.input[l.pos:], "{#") { if l.pos > l.start { l.emit(TokenHTML) } l.pos += 2 // pass '{#' l.col += 2 for { switch l.peek() { case EOF: l.errorf("Single-line comment not closed.") return case '\n': l.errorf("Newline not permitted in a single-line comment.") return } if strings.HasPrefix(l.input[l.pos:], "#}") { l.pos += 2 // pass '#}' l.col += 2 break } l.next() } l.ignore() // ignore whole comment // Comment skipped continue // next token } if strings.HasPrefix(l.input[l.pos:], "{{") || // variable strings.HasPrefix(l.input[l.pos:], "{%") { // tag if l.pos > l.start { l.emit(TokenHTML) } l.tokenize() if l.errored { return } continue } } switch l.peek() { case '\n': l.line++ l.col = 0 } if l.next() == EOF { break } } if l.pos > l.start { l.emit(TokenHTML) } if l.inVerbatim { l.errorf("verbatim-tag not closed, got EOF.") } } func (l *lexer) tokenize() { for state := l.stateCode; state != nil; { state = state() } } func (l *lexer) stateCode() lexerStateFn { outer_loop: for { switch { case l.accept(tokenSpaceChars): if l.value() == "\n" { return l.errorf("Newline not allowed within tag/variable.") } l.ignore() continue case l.accept(tokenIdentifierChars): return l.stateIdentifier case l.accept(tokenDigits): return l.stateNumber case l.accept(`"'`): return l.stateString } // Check for symbol for _, sym := range TokenSymbols { if strings.HasPrefix(l.input[l.start:], sym) { l.pos += len(sym) l.col += l.length() l.emit(TokenSymbol) if sym == "%}" || sym == "-%}" || sym == "}}" || sym == "-}}" { // Tag/variable end, return after emit return nil } continue outer_loop } } break } // Normal shut down return nil } func (l *lexer) stateIdentifier() lexerStateFn { l.acceptRun(tokenIdentifierChars) l.acceptRun(tokenIdentifierCharsWithDigits) for _, kw := range TokenKeywords { if kw == l.value() { l.emit(TokenKeyword) return l.stateCode } } l.emit(TokenIdentifier) return l.stateCode } func (l *lexer) stateNumber() lexerStateFn { l.acceptRun(tokenDigits) if l.accept(tokenIdentifierCharsWithDigits) { // This seems to be an identifier starting with a number. // See https://github.com/flosch/pongo2/issues/151 return l.stateIdentifier() } /* Maybe context-sensitive number lexing? * comments.0.Text // first comment * usercomments.1.0 // second user, first comment * if (score >= 8.5) // 8.5 as a number if l.peek() == '.' { l.accept(".") if !l.accept(tokenDigits) { return l.errorf("Malformed number.") } l.acceptRun(tokenDigits) } */ l.emit(TokenNumber) return l.stateCode } func (l *lexer) stateString() lexerStateFn { quotationMark := l.value() l.ignore() l.startcol-- // we're starting the position at the first " for !l.accept(quotationMark) { switch l.next() { case '\\': // escape sequence switch l.peek() { case '"', '\\': l.next() default: return l.errorf("Unknown escape sequence: \\%c", l.peek()) } case EOF: return l.errorf("Unexpected EOF, string not closed.") case '\n': return l.errorf("Newline in string is not allowed.") } } l.backup() l.emit(TokenString) l.next() l.ignore() return l.stateCode } golang-github-flosch-pongo2.v4-4.0.2/nodes.go000066400000000000000000000004361414175550100207170ustar00rootroot00000000000000package pongo2 // The root document type nodeDocument struct { Nodes []INode } func (doc *nodeDocument) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { for _, n := range doc.Nodes { err := n.Execute(ctx, writer) if err != nil { return err } } return nil } golang-github-flosch-pongo2.v4-4.0.2/nodes_html.go000066400000000000000000000006101414175550100217350ustar00rootroot00000000000000package pongo2 import ( "strings" ) type nodeHTML struct { token *Token trimLeft bool trimRight bool } func (n *nodeHTML) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { res := n.token.Val if n.trimLeft { res = strings.TrimLeft(res, tokenSpaceChars) } if n.trimRight { res = strings.TrimRight(res, tokenSpaceChars) } writer.WriteString(res) return nil } golang-github-flosch-pongo2.v4-4.0.2/nodes_wrapper.go000066400000000000000000000004371414175550100224600ustar00rootroot00000000000000package pongo2 type NodeWrapper struct { Endtag string nodes []INode } func (wrapper *NodeWrapper) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { for _, n := range wrapper.nodes { err := n.Execute(ctx, writer) if err != nil { return err } } return nil } golang-github-flosch-pongo2.v4-4.0.2/options.go000066400000000000000000000013421414175550100212770ustar00rootroot00000000000000package pongo2 // Options allow you to change the behavior of template-engine. // You can change the options before calling the Execute method. type Options struct { // If this is set to true the first newline after a block is removed (block, not variable tag!). Defaults to false. TrimBlocks bool // If this is set to true leading spaces and tabs are stripped from the start of a line to a block. Defaults to false LStripBlocks bool } func newOptions() *Options { return &Options{ TrimBlocks: false, LStripBlocks: false, } } // Update updates this options from another options. func (opt *Options) Update(other *Options) *Options { opt.TrimBlocks = other.TrimBlocks opt.LStripBlocks = other.LStripBlocks return opt } golang-github-flosch-pongo2.v4-4.0.2/parser.go000066400000000000000000000163151414175550100211060ustar00rootroot00000000000000package pongo2 import ( "fmt" "strings" "errors" ) type INode interface { Execute(*ExecutionContext, TemplateWriter) *Error } type IEvaluator interface { INode GetPositionToken() *Token Evaluate(*ExecutionContext) (*Value, *Error) FilterApplied(name string) bool } // The parser provides you a comprehensive and easy tool to // work with the template document and arguments provided by // the user for your custom tag. // // The parser works on a token list which will be provided by pongo2. // A token is a unit you can work with. Tokens are either of type identifier, // string, number, keyword, HTML or symbol. // // (See Token's documentation for more about tokens) type Parser struct { name string idx int tokens []*Token lastToken *Token // if the parser parses a template document, here will be // a reference to it (needed to access the template through Tags) template *Template } // Creates a new parser to parse tokens. // Used inside pongo2 to parse documents and to provide an easy-to-use // parser for tag authors func newParser(name string, tokens []*Token, template *Template) *Parser { p := &Parser{ name: name, tokens: tokens, template: template, } if len(tokens) > 0 { p.lastToken = tokens[len(tokens)-1] } return p } // Consume one token. It will be gone forever. func (p *Parser) Consume() { p.ConsumeN(1) } // Consume N tokens. They will be gone forever. func (p *Parser) ConsumeN(count int) { p.idx += count } // Returns the current token. func (p *Parser) Current() *Token { return p.Get(p.idx) } // Returns the CURRENT token if the given type matches. // Consumes this token on success. func (p *Parser) MatchType(typ TokenType) *Token { if t := p.PeekType(typ); t != nil { p.Consume() return t } return nil } // Returns the CURRENT token if the given type AND value matches. // Consumes this token on success. func (p *Parser) Match(typ TokenType, val string) *Token { if t := p.Peek(typ, val); t != nil { p.Consume() return t } return nil } // Returns the CURRENT token if the given type AND *one* of // the given values matches. // Consumes this token on success. func (p *Parser) MatchOne(typ TokenType, vals ...string) *Token { for _, val := range vals { if t := p.Peek(typ, val); t != nil { p.Consume() return t } } return nil } // Returns the CURRENT token if the given type matches. // It DOES NOT consume the token. func (p *Parser) PeekType(typ TokenType) *Token { return p.PeekTypeN(0, typ) } // Returns the CURRENT token if the given type AND value matches. // It DOES NOT consume the token. func (p *Parser) Peek(typ TokenType, val string) *Token { return p.PeekN(0, typ, val) } // Returns the CURRENT token if the given type AND *one* of // the given values matches. // It DOES NOT consume the token. func (p *Parser) PeekOne(typ TokenType, vals ...string) *Token { for _, v := range vals { t := p.PeekN(0, typ, v) if t != nil { return t } } return nil } // Returns the tokens[current position + shift] token if the // given type AND value matches for that token. // DOES NOT consume the token. func (p *Parser) PeekN(shift int, typ TokenType, val string) *Token { t := p.Get(p.idx + shift) if t != nil { if t.Typ == typ && t.Val == val { return t } } return nil } // Returns the tokens[current position + shift] token if the given type matches. // DOES NOT consume the token for that token. func (p *Parser) PeekTypeN(shift int, typ TokenType) *Token { t := p.Get(p.idx + shift) if t != nil { if t.Typ == typ { return t } } return nil } // Returns the UNCONSUMED token count. func (p *Parser) Remaining() int { return len(p.tokens) - p.idx } // Returns the total token count. func (p *Parser) Count() int { return len(p.tokens) } // Returns tokens[i] or NIL (if i >= len(tokens)) func (p *Parser) Get(i int) *Token { if i < len(p.tokens) && i >= 0 { return p.tokens[i] } return nil } // Returns tokens[current-position + shift] or NIL // (if (current-position + i) >= len(tokens)) func (p *Parser) GetR(shift int) *Token { i := p.idx + shift return p.Get(i) } // Error produces a nice error message and returns an error-object. // The 'token'-argument is optional. If provided, it will take // the token's position information. If not provided, it will // automatically use the CURRENT token's position information. func (p *Parser) Error(msg string, token *Token) *Error { if token == nil { // Set current token token = p.Current() if token == nil { // Set to last token if len(p.tokens) > 0 { token = p.tokens[len(p.tokens)-1] } } } var line, col int if token != nil { line = token.Line col = token.Col } return &Error{ Template: p.template, Filename: p.name, Sender: "parser", Line: line, Column: col, Token: token, OrigError: errors.New(msg), } } // Wraps all nodes between starting tag and "{% endtag %}" and provides // one simple interface to execute the wrapped nodes. // It returns a parser to process provided arguments to the tag. func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) { wrapper := &NodeWrapper{} var tagArgs []*Token for p.Remaining() > 0 { // New tag, check whether we have to stop wrapping here if p.Peek(TokenSymbol, "{%") != nil { tagIdent := p.PeekTypeN(1, TokenIdentifier) if tagIdent != nil { // We've found a (!) end-tag found := false for _, n := range names { if tagIdent.Val == n { found = true break } } // We only process the tag if we've found an end tag if found { // Okay, endtag found. p.ConsumeN(2) // '{%' tagname for { if p.Match(TokenSymbol, "%}") != nil { // Okay, end the wrapping here wrapper.Endtag = tagIdent.Val return wrapper, newParser(p.template.name, tagArgs, p.template), nil } t := p.Current() p.Consume() if t == nil { return nil, nil, p.Error("Unexpected EOF.", p.lastToken) } tagArgs = append(tagArgs, t) } } } } // Otherwise process next element to be wrapped node, err := p.parseDocElement() if err != nil { return nil, nil, err } wrapper.nodes = append(wrapper.nodes, node) } return nil, nil, p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")), p.lastToken) } // Skips all nodes between starting tag and "{% endtag %}" func (p *Parser) SkipUntilTag(names ...string) *Error { for p.Remaining() > 0 { // New tag, check whether we have to stop wrapping here if p.Peek(TokenSymbol, "{%") != nil { tagIdent := p.PeekTypeN(1, TokenIdentifier) if tagIdent != nil { // We've found a (!) end-tag found := false for _, n := range names { if tagIdent.Val == n { found = true break } } // We only process the tag if we've found an end tag if found { // Okay, endtag found. p.ConsumeN(2) // '{%' tagname for { if p.Match(TokenSymbol, "%}") != nil { // Done skipping, exit. return nil } } } } } t := p.Current() p.Consume() if t == nil { return p.Error("Unexpected EOF.", p.lastToken) } } return p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")), p.lastToken) } golang-github-flosch-pongo2.v4-4.0.2/parser_document.go000066400000000000000000000023671414175550100230060ustar00rootroot00000000000000package pongo2 // Doc = { ( Filter | Tag | HTML ) } func (p *Parser) parseDocElement() (INode, *Error) { t := p.Current() switch t.Typ { case TokenHTML: n := &nodeHTML{token: t} left := p.PeekTypeN(-1, TokenSymbol) right := p.PeekTypeN(1, TokenSymbol) n.trimLeft = left != nil && left.TrimWhitespaces n.trimRight = right != nil && right.TrimWhitespaces p.Consume() // consume HTML element return n, nil case TokenSymbol: switch t.Val { case "{{": // parse variable variable, err := p.parseVariableElement() if err != nil { return nil, err } return variable, nil case "{%": // parse tag tag, err := p.parseTagElement() if err != nil { return nil, err } return tag, nil } } return nil, p.Error("Unexpected token (only HTML/tags/filters in templates allowed)", t) } func (tpl *Template) parse() *Error { tpl.parser = newParser(tpl.name, tpl.tokens, tpl) doc, err := tpl.parser.parseDocument() if err != nil { return err } tpl.root = doc return nil } func (p *Parser) parseDocument() (*nodeDocument, *Error) { doc := &nodeDocument{} for p.Remaining() > 0 { node, err := p.parseDocElement() if err != nil { return nil, err } doc.Nodes = append(doc.Nodes, node) } return doc, nil } golang-github-flosch-pongo2.v4-4.0.2/parser_expression.go000066400000000000000000000265301414175550100233650ustar00rootroot00000000000000package pongo2 import ( "fmt" "math" ) type Expression struct { // TODO: Add location token? expr1 IEvaluator expr2 IEvaluator opToken *Token } type relationalExpression struct { // TODO: Add location token? expr1 IEvaluator expr2 IEvaluator opToken *Token } type simpleExpression struct { negate bool negativeSign bool term1 IEvaluator term2 IEvaluator opToken *Token } type term struct { // TODO: Add location token? factor1 IEvaluator factor2 IEvaluator opToken *Token } type power struct { // TODO: Add location token? power1 IEvaluator power2 IEvaluator } func (expr *Expression) FilterApplied(name string) bool { return expr.expr1.FilterApplied(name) && (expr.expr2 == nil || (expr.expr2 != nil && expr.expr2.FilterApplied(name))) } func (expr *relationalExpression) FilterApplied(name string) bool { return expr.expr1.FilterApplied(name) && (expr.expr2 == nil || (expr.expr2 != nil && expr.expr2.FilterApplied(name))) } func (expr *simpleExpression) FilterApplied(name string) bool { return expr.term1.FilterApplied(name) && (expr.term2 == nil || (expr.term2 != nil && expr.term2.FilterApplied(name))) } func (expr *term) FilterApplied(name string) bool { return expr.factor1.FilterApplied(name) && (expr.factor2 == nil || (expr.factor2 != nil && expr.factor2.FilterApplied(name))) } func (expr *power) FilterApplied(name string) bool { return expr.power1.FilterApplied(name) && (expr.power2 == nil || (expr.power2 != nil && expr.power2.FilterApplied(name))) } func (expr *Expression) GetPositionToken() *Token { return expr.expr1.GetPositionToken() } func (expr *relationalExpression) GetPositionToken() *Token { return expr.expr1.GetPositionToken() } func (expr *simpleExpression) GetPositionToken() *Token { return expr.term1.GetPositionToken() } func (expr *term) GetPositionToken() *Token { return expr.factor1.GetPositionToken() } func (expr *power) GetPositionToken() *Token { return expr.power1.GetPositionToken() } func (expr *Expression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { value, err := expr.Evaluate(ctx) if err != nil { return err } writer.WriteString(value.String()) return nil } func (expr *relationalExpression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { value, err := expr.Evaluate(ctx) if err != nil { return err } writer.WriteString(value.String()) return nil } func (expr *simpleExpression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { value, err := expr.Evaluate(ctx) if err != nil { return err } writer.WriteString(value.String()) return nil } func (expr *term) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { value, err := expr.Evaluate(ctx) if err != nil { return err } writer.WriteString(value.String()) return nil } func (expr *power) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { value, err := expr.Evaluate(ctx) if err != nil { return err } writer.WriteString(value.String()) return nil } func (expr *Expression) Evaluate(ctx *ExecutionContext) (*Value, *Error) { v1, err := expr.expr1.Evaluate(ctx) if err != nil { return nil, err } if expr.expr2 != nil { switch expr.opToken.Val { case "and", "&&": if !v1.IsTrue() { return AsValue(false), nil } else { v2, err := expr.expr2.Evaluate(ctx) if err != nil { return nil, err } return AsValue(v2.IsTrue()), nil } case "or", "||": if v1.IsTrue() { return AsValue(true), nil } else { v2, err := expr.expr2.Evaluate(ctx) if err != nil { return nil, err } return AsValue(v2.IsTrue()), nil } default: return nil, ctx.Error(fmt.Sprintf("unimplemented: %s", expr.opToken.Val), expr.opToken) } } else { return v1, nil } } func (expr *relationalExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) { v1, err := expr.expr1.Evaluate(ctx) if err != nil { return nil, err } if expr.expr2 != nil { v2, err := expr.expr2.Evaluate(ctx) if err != nil { return nil, err } switch expr.opToken.Val { case "<=": if v1.IsFloat() || v2.IsFloat() { return AsValue(v1.Float() <= v2.Float()), nil } if v1.IsTime() && v2.IsTime() { tm1, tm2 := v1.Time(), v2.Time() return AsValue(tm1.Before(tm2) || tm1.Equal(tm2)), nil } return AsValue(v1.Integer() <= v2.Integer()), nil case ">=": if v1.IsFloat() || v2.IsFloat() { return AsValue(v1.Float() >= v2.Float()), nil } if v1.IsTime() && v2.IsTime() { tm1, tm2 := v1.Time(), v2.Time() return AsValue(tm1.After(tm2) || tm1.Equal(tm2)), nil } return AsValue(v1.Integer() >= v2.Integer()), nil case "==": return AsValue(v1.EqualValueTo(v2)), nil case ">": if v1.IsFloat() || v2.IsFloat() { return AsValue(v1.Float() > v2.Float()), nil } if v1.IsTime() && v2.IsTime() { return AsValue(v1.Time().After(v2.Time())), nil } return AsValue(v1.Integer() > v2.Integer()), nil case "<": if v1.IsFloat() || v2.IsFloat() { return AsValue(v1.Float() < v2.Float()), nil } if v1.IsTime() && v2.IsTime() { return AsValue(v1.Time().Before(v2.Time())), nil } return AsValue(v1.Integer() < v2.Integer()), nil case "!=", "<>": return AsValue(!v1.EqualValueTo(v2)), nil case "in": return AsValue(v2.Contains(v1)), nil default: return nil, ctx.Error(fmt.Sprintf("unimplemented: %s", expr.opToken.Val), expr.opToken) } } else { return v1, nil } } func (expr *simpleExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) { t1, err := expr.term1.Evaluate(ctx) if err != nil { return nil, err } result := t1 if expr.negate { result = result.Negate() } if expr.negativeSign { if result.IsNumber() { switch { case result.IsFloat(): result = AsValue(-1 * result.Float()) case result.IsInteger(): result = AsValue(-1 * result.Integer()) default: return nil, ctx.Error("Operation between a number and a non-(float/integer) is not possible", nil) } } else { return nil, ctx.Error("Negative sign on a non-number expression", expr.GetPositionToken()) } } if expr.term2 != nil { t2, err := expr.term2.Evaluate(ctx) if err != nil { return nil, err } switch expr.opToken.Val { case "+": if result.IsFloat() || t2.IsFloat() { // Result will be a float return AsValue(result.Float() + t2.Float()), nil } // Result will be an integer return AsValue(result.Integer() + t2.Integer()), nil case "-": if result.IsFloat() || t2.IsFloat() { // Result will be a float return AsValue(result.Float() - t2.Float()), nil } // Result will be an integer return AsValue(result.Integer() - t2.Integer()), nil default: return nil, ctx.Error("Unimplemented", expr.GetPositionToken()) } } return result, nil } func (expr *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) { f1, err := expr.factor1.Evaluate(ctx) if err != nil { return nil, err } if expr.factor2 != nil { f2, err := expr.factor2.Evaluate(ctx) if err != nil { return nil, err } switch expr.opToken.Val { case "*": if f1.IsFloat() || f2.IsFloat() { // Result will be float return AsValue(f1.Float() * f2.Float()), nil } // Result will be int return AsValue(f1.Integer() * f2.Integer()), nil case "/": if f1.IsFloat() || f2.IsFloat() { // Result will be float return AsValue(f1.Float() / f2.Float()), nil } // Result will be int return AsValue(f1.Integer() / f2.Integer()), nil case "%": // Result will be int return AsValue(f1.Integer() % f2.Integer()), nil default: return nil, ctx.Error("unimplemented", expr.opToken) } } else { return f1, nil } } func (expr *power) Evaluate(ctx *ExecutionContext) (*Value, *Error) { p1, err := expr.power1.Evaluate(ctx) if err != nil { return nil, err } if expr.power2 != nil { p2, err := expr.power2.Evaluate(ctx) if err != nil { return nil, err } return AsValue(math.Pow(p1.Float(), p2.Float())), nil } return p1, nil } func (p *Parser) parseFactor() (IEvaluator, *Error) { if p.Match(TokenSymbol, "(") != nil { expr, err := p.ParseExpression() if err != nil { return nil, err } if p.Match(TokenSymbol, ")") == nil { return nil, p.Error("Closing bracket expected after expression", nil) } return expr, nil } return p.parseVariableOrLiteralWithFilter() } func (p *Parser) parsePower() (IEvaluator, *Error) { pw := new(power) power1, err := p.parseFactor() if err != nil { return nil, err } pw.power1 = power1 if p.Match(TokenSymbol, "^") != nil { power2, err := p.parsePower() if err != nil { return nil, err } pw.power2 = power2 } if pw.power2 == nil { // Shortcut for faster evaluation return pw.power1, nil } return pw, nil } func (p *Parser) parseTerm() (IEvaluator, *Error) { returnTerm := new(term) factor1, err := p.parsePower() if err != nil { return nil, err } returnTerm.factor1 = factor1 for p.PeekOne(TokenSymbol, "*", "/", "%") != nil { if returnTerm.opToken != nil { // Create new sub-term returnTerm = &term{ factor1: returnTerm, } } op := p.Current() p.Consume() factor2, err := p.parsePower() if err != nil { return nil, err } returnTerm.opToken = op returnTerm.factor2 = factor2 } if returnTerm.opToken == nil { // Shortcut for faster evaluation return returnTerm.factor1, nil } return returnTerm, nil } func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) { expr := new(simpleExpression) if sign := p.MatchOne(TokenSymbol, "+", "-"); sign != nil { if sign.Val == "-" { expr.negativeSign = true } } if p.Match(TokenSymbol, "!") != nil || p.Match(TokenKeyword, "not") != nil { expr.negate = true } term1, err := p.parseTerm() if err != nil { return nil, err } expr.term1 = term1 for p.PeekOne(TokenSymbol, "+", "-") != nil { if expr.opToken != nil { // New sub expr expr = &simpleExpression{ term1: expr, } } op := p.Current() p.Consume() term2, err := p.parseTerm() if err != nil { return nil, err } expr.term2 = term2 expr.opToken = op } if expr.negate == false && expr.negativeSign == false && expr.term2 == nil { // Shortcut for faster evaluation return expr.term1, nil } return expr, nil } func (p *Parser) parseRelationalExpression() (IEvaluator, *Error) { expr1, err := p.parseSimpleExpression() if err != nil { return nil, err } expr := &relationalExpression{ expr1: expr1, } if t := p.MatchOne(TokenSymbol, "==", "<=", ">=", "!=", "<>", ">", "<"); t != nil { expr2, err := p.parseRelationalExpression() if err != nil { return nil, err } expr.opToken = t expr.expr2 = expr2 } else if t := p.MatchOne(TokenKeyword, "in"); t != nil { expr2, err := p.parseSimpleExpression() if err != nil { return nil, err } expr.opToken = t expr.expr2 = expr2 } if expr.expr2 == nil { // Shortcut for faster evaluation return expr.expr1, nil } return expr, nil } func (p *Parser) ParseExpression() (IEvaluator, *Error) { rexpr1, err := p.parseRelationalExpression() if err != nil { return nil, err } exp := &Expression{ expr1: rexpr1, } if p.PeekOne(TokenSymbol, "&&", "||") != nil || p.PeekOne(TokenKeyword, "and", "or") != nil { op := p.Current() p.Consume() expr2, err := p.ParseExpression() if err != nil { return nil, err } exp.expr2 = expr2 exp.opToken = op } if exp.expr2 == nil { // Shortcut for faster evaluation return exp.expr1, nil } return exp, nil } golang-github-flosch-pongo2.v4-4.0.2/pongo2.go000066400000000000000000000005051414175550100210100ustar00rootroot00000000000000package pongo2 // Version string const Version = "4.0.2" // Must panics, if a Template couldn't successfully parsed. This is how you // would use it: // var baseTemplate = pongo2.Must(pongo2.FromFile("templates/base.html")) func Must(tpl *Template, err error) *Template { if err != nil { panic(err) } return tpl } golang-github-flosch-pongo2.v4-4.0.2/pongo2_issues_test.go000066400000000000000000000007701414175550100234460ustar00rootroot00000000000000package pongo2_test import ( "testing" "github.com/flosch/pongo2/v4" ) func TestIssue151(t *testing.T) { tpl, err := pongo2.FromString("{{ mydict.51232_3 }}{{ 12345_123}}{{ 995189baz }}") if err != nil { t.Fatal(err) } str, err := tpl.Execute(pongo2.Context{ "mydict": map[string]string{ "51232_3": "foo", }, "12345_123": "bar", "995189baz": "baz", }) if err != nil { t.Fatal(err) } if str != "foobarbaz" { t.Fatalf("Expected output 'foobarbaz', but got '%s'.", str) } } golang-github-flosch-pongo2.v4-4.0.2/pongo2_template_test.go000066400000000000000000000454731414175550100237570ustar00rootroot00000000000000package pongo2_test import ( "bytes" "encoding/json" "errors" "fmt" "io/ioutil" "os" "path/filepath" "regexp" "strings" "testing" "time" "github.com/flosch/pongo2/v4" ) var adminList = []string{"user2"} var time1 = time.Date(2014, 06, 10, 15, 30, 15, 0, time.UTC) var time2 = time.Date(2011, 03, 21, 8, 37, 56, 12, time.UTC) type post struct { Text string Created time.Time } type user struct { Name string Validated bool } type comment struct { Author *user Date time.Time Text string } func isAdmin(u *user) bool { for _, a := range adminList { if a == u.Name { return true } } return false } func (u *user) Is_admin() *pongo2.Value { return pongo2.AsValue(isAdmin(u)) } func (u *user) Is_admin2() bool { return isAdmin(u) } func (p *post) String() string { return ":-)" } /* * Start setup sandbox */ type tagSandboxDemoTag struct { } func (node *tagSandboxDemoTag) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error { writer.WriteString("hello") return nil } func tagSandboxDemoTagParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { return &tagSandboxDemoTag{}, nil } func BannedFilterFn(in *pongo2.Value, params *pongo2.Value) (*pongo2.Value, *pongo2.Error) { return in, nil } func init() { pongo2.DefaultSet.Debug = true pongo2.RegisterFilter("banned_filter", BannedFilterFn) pongo2.RegisterFilter("unbanned_filter", BannedFilterFn) pongo2.RegisterTag("banned_tag", tagSandboxDemoTagParser) pongo2.RegisterTag("unbanned_tag", tagSandboxDemoTagParser) pongo2.DefaultSet.BanFilter("banned_filter") pongo2.DefaultSet.BanTag("banned_tag") f, err := ioutil.TempFile(os.TempDir(), "pongo2_") if err != nil { panic(fmt.Sprintf("cannot write to %s", os.TempDir())) } defer f.Close() _, err = f.Write([]byte("Hello from pongo2")) if err != nil { panic(fmt.Sprintf("cannot write to %s", os.TempDir())) } pongo2.DefaultSet.Globals["temp_file"] = f.Name() } /* * End setup sandbox */ var tplContext = pongo2.Context{ "number": 11, "simple": map[string]interface{}{ "number": 42, "name": "john doe", "included_file": "INCLUDES.helper", "included_file_not_exists": "INCLUDES.helper.not_exists", "nil": nil, "uint": uint(8), "float": float64(3.1415), "str": "string", "chinese_hello_world": "äŊ åĨŊä¸–į•Œ", "bool_true": true, "bool_false": false, "newline_text": `this is a text with a new line in it`, "long_text": `This is a simple text. This too, as a paragraph. Right? Yep!`, "escape_js_test": `escape sequences \r\n\'\" special chars "?!=$<>`, "one_item_list": []int{99}, "multiple_item_list": []int{1, 1, 2, 3, 5, 8, 13, 21, 34, 55}, "unsorted_int_list": []int{192, 581, 22, 1, 249, 9999, 1828591, 8271}, "fixed_item_list": [...]int{1, 2, 3, 4}, "misc_list": []interface{}{"Hello", 99, 3.14, "good"}, "escape_text": "This is \\a Test. \"Yep\". 'Yep'.", "xss": "", "time1": time1, "time2": time2, "intmap": map[int]string{ 1: "one", 5: "five", 2: "two", }, "strmap": map[string]string{ "abc": "def", "bcd": "efg", "zab": "cde", "gh": "kqm", "ukq": "qqa", "aab": "aba", }, "func_add": func(a, b int) int { return a + b }, "func_add_iface": func(a, b interface{}) interface{} { return a.(int) + b.(int) }, "func_variadic": func(msg string, args ...interface{}) string { return fmt.Sprintf(msg, args...) }, "func_variadic_sum_int": func(args ...int) int { // Create a sum s := 0 for _, i := range args { s += i } return s }, "func_variadic_sum_int2": func(args ...*pongo2.Value) *pongo2.Value { // Create a sum s := 0 for _, i := range args { s += i.Integer() } return pongo2.AsValue(s) }, }, "complex": map[string]interface{}{ "is_admin": isAdmin, "post": post{ Text: "

Hello!

Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.

", Created: time2, }, "comments": []*comment{ { Author: &user{ Name: "user1", Validated: true, }, Date: time1, Text: "\"pongo2 is nice!\"", }, { Author: &user{ Name: "user2", Validated: true, }, Date: time2, Text: "comment2 with tags in it", }, { Author: &user{ Name: "user3", Validated: false, }, Date: time1, Text: "hello! there", }, }, "comments2": []*comment{ { Author: &user{ Name: "user1", Validated: true, }, Date: time2, Text: "\"pongo2 is nice!\"", }, { Author: &user{ Name: "user1", Validated: true, }, Date: time1, Text: "comment2 with tags in it", }, { Author: &user{ Name: "user3", Validated: false, }, Date: time1, Text: "hello! there", }, }, }, } func TestTemplate_Functions(t *testing.T) { mydict := map[string]interface{}{ "foo": "bar", "foobar": 8379, } tests := []struct { name string template string context pongo2.Context want string errorMessage string wantErr bool }{ { name: "NoError", template: "{{ testFunc(mydict) }}", context: pongo2.Context{ "mydict": mydict, "testFunc": func(i interface{}) (string, error) { d, err := json.Marshal(i) return string(d), err }, }, want: `{"foo":"bar","foobar":8379}`, wantErr: false, }, { name: "WithError", template: "{{ testFunc(mydict) }}", context: pongo2.Context{ "mydict": mydict, "testFunc": func(i interface{}) (string, error) { return "", errors.New("something went wrong") }, }, errorMessage: "[Error (where: execution) in | Line 1 Col 4 near 'testFunc'] something went wrong", wantErr: true, }, { name: "TooMuchArguments", template: "{{ testFunc(mydict) }}", context: pongo2.Context{ "mydict": mydict, "testFunc": func(i interface{}) (string, int, error) { return "", 0, nil }, }, errorMessage: "[Error (where: execution) in | Line 1 Col 4 near 'testFunc'] 'testFunc' must have exactly 1 or 2 output arguments, the second argument must be of type error", wantErr: true, }, { name: "InvalidArguments", template: "{{ testFunc(mydict) }}", context: pongo2.Context{ "mydict": map[string]interface{}{ "foo": "bar", "foobar": 8379, }, "testFunc": func(i interface{}) (string, int) { return "", 0 }, }, errorMessage: "[Error (where: execution) in | Line 1 Col 4 near 'testFunc'] The second return value is not an error", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tpl, _ := pongo2.FromString("{{ testFunc(mydict) }}") got, err := tpl.Execute(tt.context) if err != nil { if !tt.wantErr { t.Errorf("Template.Execute() error = %v, wantErr %v", err, tt.wantErr) return } if err.Error() != tt.errorMessage { t.Errorf("Template.Execute() error = %v, expected error %v", err, tt.errorMessage) return } } if got != tt.want { t.Errorf("Template.Execute() = %v, want %v", got, tt.want) } }) } } 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.Run(fmt.Sprintf("%03d-%s", idx+1, match), func(t *testing.T) { 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()) } // Read options from file optsStr, _ := ioutil.ReadFile(fmt.Sprintf("%s.options", match)) trimBlocks := strings.Contains(string(optsStr), "TrimBlocks=true") lStripBlocks := strings.Contains(string(optsStr), "LStripBlocks=true") tpl.Options.TrimBlocks = trimBlocks tpl.Options.LStripBlocks = lStripBlocks 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()) } tplOut = testTemplateFixes.fixIfNeeded(match, tplOut) 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 TestBlockTemplates(t *testing.T) { //debug = true matches, err := filepath.Glob("./template_tests/block_render/*.tpl") if err != nil { t.Fatal(err) } for idx, match := range matches { t.Logf("[BlockTemplate %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()) } tpl_out, err := tpl.ExecuteBlocks(tplContext, []string{"content", "more_content"}) if err != nil { t.Fatalf("Error on ExecuteBlocks('%s'): %s", match, err.Error()) } if _, ok := tpl_out["content"]; !ok { t.Errorf("Failed: content not in tpl_out for %s", match) } if _, ok := tpl_out["more_content"]; !ok { t.Errorf("Failed: more_content not in tpl_out for %s", match) } testString := string(testOut[:]) joinedString := strings.Join([]string{tpl_out["content"], tpl_out["more_content"]}, "") if testString != joinedString { t.Logf("BlockTemplate (rendered) '%s': '%s'", match, tpl_out["content"]) errFilename := filepath.Base(fmt.Sprintf("%s.error", match)) err := ioutil.WriteFile(errFilename, []byte(joinedString), 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) } } } type testTemplateFixesT map[*regexp.Regexp]func(string) string func (instance testTemplateFixesT) fixIfNeeded(name string, in []byte) []byte { out := string(in) for r, f := range instance { if r.MatchString(name) { out = f(out) } } return []byte(out) } var testTemplateFixes = testTemplateFixesT{ regexp.MustCompile(`.*template_tests[/\\]macro\.tpl`): func(in string) string { out := regexp.MustCompile(`(?:\.[/\\]|)(template_tests)[/\\](macro\.tpl)`).ReplaceAllString(in, "$1/$2") return out }, } 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.Run(fmt.Sprintf("%03d-%s", idx+1, match), func(t *testing.T) { 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()) } tpl, err = pongo2.FromBytes([]byte(test)) if err != nil { t.Fatalf("Error on FromBytes('%s'): %s", test, err.Error()) } _, err = tpl.ExecuteBytes(tplContext) if err == nil { t.Fatalf("[%s Line %d] Expected error for (got none): %s", 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.Run(fmt.Sprintf("%03d-%s", idx+1, match), func(t *testing.T) { 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, fmt.Sprintf("template_tests%cbase_dir_test%c", filepath.Separator, filepath.Separator), "", -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) } } }) } func BenchmarkExecuteBlocksWithSandboxActive(b *testing.B) { blockNames := []string{"content", "more_content"} tpl, err := pongo2.FromFile("template_tests/block_render/block.tpl") if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err = tpl.ExecuteBlocks(tplContext, blockNames) if err != nil { b.Fatal(err) } } } func BenchmarkExecuteBlocksWithoutSandbox(b *testing.B) { blockNames := []string{"content", "more_content"} s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader("")) tpl, err := s.FromFile("template_tests/block_render/block.tpl") if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err = tpl.ExecuteBlocks(tplContext, blockNames) if err != nil { b.Fatal(err) } } } golang-github-flosch-pongo2.v4-4.0.2/pongo2_test.go000066400000000000000000000047211414175550100220530ustar00rootroot00000000000000package pongo2_test import ( "testing" "github.com/flosch/pongo2/v4" . "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(pongo2.RegisterFilter("escape", nil).Error(), Matches, ".*is already registered") c.Check(pongo2.RegisterTag("for", nil).Error(), Matches, ".*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.`) } func (s *TestSuite) TestImplicitExecCtx(c *C) { tpl, err := pongo2.FromString("{{ ImplicitExec }}") if err != nil { c.Fatalf("Error in FromString: %v", err) } val := "a stringy thing" res, err := tpl.Execute(pongo2.Context{ "Value": val, "ImplicitExec": func(ctx *pongo2.ExecutionContext) string { return ctx.Public["Value"].(string) }, }) if err != nil { c.Fatalf("Error executing template: %v", err) } c.Check(res, Equals, val) // The implicit ctx should not be persisted from call-to-call res, err = tpl.Execute(pongo2.Context{ "ImplicitExec": func() string { return val }, }) if err != nil { c.Fatalf("Error executing template: %v", err) } c.Check(res, Equals, val) } golang-github-flosch-pongo2.v4-4.0.2/tags.go000066400000000000000000000070111414175550100205410ustar00rootroot00000000000000package 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. 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) error { _, existing := tags[name] if existing { return fmt.Errorf("tag with name '%s' is already registered", name) } tags[name] = &tag{ name: name, parser: parserFn, } return nil } // Replaces an already registered tag with a new implementation. Use this // function with caution since it allows you to change existing tag behaviour. func ReplaceTag(name string, parserFn TagParser) error { _, existing := tags[name] if !existing { return fmt.Errorf("tag with name '%s' does not exist (therefore cannot be overridden)", name) } tags[name] = &tag{ name: name, parser: parserFn, } return nil } // Tag = "{%" IDENT ARGS "%}" func (p *Parser) parseTagElement() (INodeTag, *Error) { p.Consume() // consume "{%" 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_autoescape.go000066400000000000000000000023001414175550100227460ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_block.go000066400000000000000000000060521414175550100217170ustar00rootroot00000000000000package pongo2 import ( "bytes" "fmt" ) type tagBlockNode struct { name string } func (node *tagBlockNode) getBlockWrappers(tpl *Template) []*NodeWrapper { nodeWrappers := make([]*NodeWrapper, 0) var t *NodeWrapper for tpl != nil { t = tpl.blocks[node.name] if t != nil { nodeWrappers = append(nodeWrappers, t) } tpl = tpl.child } return nodeWrappers } 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 blockWrappers := node.getBlockWrappers(tpl) lenBlockWrappers := len(blockWrappers) if lenBlockWrappers == 0 { return ctx.Error("internal error: len(block_wrappers) == 0 in tagBlockNode.Execute()", nil) } blockWrapper := blockWrappers[lenBlockWrappers-1] ctx.Private["block"] = tagBlockInformation{ ctx: ctx, wrappers: blockWrappers[0 : lenBlockWrappers-1], } err := blockWrapper.Execute(ctx, writer) if err != nil { return err } return nil } type tagBlockInformation struct { ctx *ExecutionContext wrappers []*NodeWrapper } func (t tagBlockInformation) Super() string { lenWrappers := len(t.wrappers) if lenWrappers == 0 { return "" } superCtx := NewChildExecutionContext(t.ctx) superCtx.Private["block"] = tagBlockInformation{ ctx: t.ctx, wrappers: t.wrappers[0 : lenWrappers-1], } blockWrapper := t.wrappers[lenWrappers-1] buf := bytes.NewBufferString("") err := blockWrapper.Execute(superCtx, &templateWriter{buf}) if err != nil { return "" } return buf.String() } func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { if arguments.Count() == 0 { return nil, arguments.Error("Tag 'block' requires an identifier.", nil) } 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_comment.go000066400000000000000000000011731414175550100222660ustar00rootroot00000000000000package 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.SkipUntilTag("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) } golang-github-flosch-pongo2.v4-4.0.2/tags_cycle.go000066400000000000000000000040221414175550100217170ustar00rootroot00000000000000package 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-separated list of expressions 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_extends.go000066400000000000000000000025331414175550100222770ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_filter.go000066400000000000000000000040071414175550100221100ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_firstof.go000066400000000000000000000016361414175550100223040ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_for.go000066400000000000000000000075241414175550100214200ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_if.go000066400000000000000000000032641414175550100212250ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_ifchanged.go000066400000000000000000000050501414175550100225320ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_ifequal.go000066400000000000000000000032421414175550100222510ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_ifnotequal.go000066400000000000000000000033151414175550100227730ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_import.go000066400000000000000000000042361414175550100221410ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_include.go000066400000000000000000000102031414175550100222410ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_lorem.go000066400000000000000000000144241414175550100217450ustar00rootroot00000000000000package pongo2 import ( "fmt" "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: return ctx.OrigError(fmt.Errorf("unsupported method: %s", node.method), nil) } 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.` golang-github-flosch-pongo2.v4-4.0.2/tags_macro.go000066400000000000000000000074141414175550100217310ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_now.go000066400000000000000000000016771414175550100214400ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_set.go000066400000000000000000000020611414175550100214140ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_spaceless.go000066400000000000000000000020551414175550100226060ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_ssi.go000066400000000000000000000032271414175550100214240ustar00rootroot00000000000000package 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", OrigError: err, }).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) } golang-github-flosch-pongo2.v4-4.0.2/tags_templatetag.go000066400000000000000000000020671414175550100231360ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_widthratio.go000066400000000000000000000031671414175550100230070ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/tags_with.go000066400000000000000000000043671414175550100216070ustar00rootroot00000000000000package 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) } golang-github-flosch-pongo2.v4-4.0.2/template.go000066400000000000000000000152001414175550100214150ustar00rootroot00000000000000package pongo2 import ( "bytes" "fmt" "io" "strings" ) 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 // Options allow you to change the behavior of template-engine. // You can change the options before calling the Execute method. Options *Options } func newTemplateString(set *TemplateSet, tpl []byte) (*Template, error) { return newTemplate(set, "", true, tpl) } func newTemplate(set *TemplateSet, name string, isTplString bool, tpl []byte) (*Template, error) { strTpl := string(tpl) // Create the template t := &Template{ set: set, isTplString: isTplString, name: name, tpl: strTpl, size: len(strTpl), blocks: make(map[string]*NodeWrapper), exportedMacros: make(map[string]*tagMacroNode), Options: newOptions(), } // Copy all settings from another Options. t.Options.Update(set.Options) // Tokenize it tokens, err := lex(name, strTpl) if err != nil { return nil, err } t.tokens = tokens // For debugging purposes, show all tokens: /*for i, t := range tokens { fmt.Printf("%3d. %s\n", i, t) }*/ // Parse it err = t.parse() if err != nil { return nil, err } return t, nil } func (tpl *Template) newContextForExecution(context Context) (*Template, *ExecutionContext, error) { if tpl.Options.TrimBlocks || tpl.Options.LStripBlocks { // Issue #94 https://github.com/flosch/pongo2/issues/94 // If an application configures pongo2 template to trim_blocks, // the first newline after a template tag is removed automatically (like in PHP). prev := &Token{ Typ: TokenHTML, Val: "\n", } for _, t := range tpl.tokens { if tpl.Options.LStripBlocks { if prev.Typ == TokenHTML && t.Typ != TokenHTML && t.Val == "{%" { prev.Val = strings.TrimRight(prev.Val, "\t ") } } if tpl.Options.TrimBlocks { if prev.Typ != TokenHTML && t.Typ == TokenHTML && prev.Val == "%}" { if len(t.Val) > 0 && t.Val[0] == '\n' { t.Val = t.Val[1:len(t.Val)] } } } prev = t } } // Determine the parent to be executed (for template inheritance) parent := tpl for parent.parent != nil { parent = parent.parent } // Create context if none is given newContext := make(Context) newContext.Update(tpl.set.Globals) if context != nil { newContext.Update(context) if len(newContext) > 0 { // Check for context name syntax err := newContext.checkForValidIdentifiers() if err != nil { return parent, nil, err } // Check for clashes with macro names for k := range newContext { _, has := tpl.exportedMacros[k] if has { return parent, nil, &Error{ Filename: tpl.name, Sender: "execution", OrigError: fmt.Errorf("context key name '%s' clashes with macro '%s'", k, k), } } } } } // Create operational context ctx := newExecutionContext(parent, newContext) return parent, ctx, nil } func (tpl *Template) execute(context Context, writer TemplateWriter) error { parent, ctx, err := tpl.newContextForExecution(context) if err != nil { return err } // Run the selected document if err := parent.root.Execute(ctx, writer); err != nil { return err } return nil } func (tpl *Template) newTemplateWriterAndExecute(context Context, writer io.Writer) error { return tpl.execute(context, &templateWriter{w: writer}) } func (tpl *Template) newBufferAndExecute(context Context) (*bytes.Buffer, error) { // Create output buffer // We assume that the rendered template will be 30% larger buffer := bytes.NewBuffer(make([]byte, 0, int(float64(tpl.size)*1.3))) if err := tpl.execute(context, buffer); err != nil { return nil, err } return buffer, nil } // Executes the template with the given context and writes to writer (io.Writer) // on success. Context can be nil. Nothing is written on error; instead the error // is being returned. func (tpl *Template) ExecuteWriter(context Context, writer io.Writer) error { buf, err := tpl.newBufferAndExecute(context) if err != nil { return err } _, err = buf.WriteTo(writer) if err != nil { return err } return nil } // Same as ExecuteWriter. The only difference between both functions is that // this function might already have written parts of the generated template in the // case of an execution error because there's no intermediate buffer involved for // performance reasons. This is handy if you need high performance template // generation or if you want to manage your own pool of buffers. func (tpl *Template) ExecuteWriterUnbuffered(context Context, writer io.Writer) error { return tpl.newTemplateWriterAndExecute(context, writer) } // Executes the template and returns the rendered template as a []byte func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) { // Execute template buffer, err := tpl.newBufferAndExecute(context) if err != nil { return nil, err } return buffer.Bytes(), nil } // Executes the template and returns the rendered template as a string func (tpl *Template) Execute(context Context) (string, error) { // Execute template buffer, err := tpl.newBufferAndExecute(context) if err != nil { return "", err } return buffer.String(), nil } func (tpl *Template) ExecuteBlocks(context Context, blocks []string) (map[string]string, error) { var parents []*Template result := make(map[string]string) parent := tpl for parent != nil { parents = append(parents, parent) parent = parent.parent } for _, t := range parents { buffer := bytes.NewBuffer(make([]byte, 0, int(float64(t.size)*1.3))) _, ctx, err := t.newContextForExecution(context) if err != nil { return nil, err } for _, blockName := range blocks { if _, ok := result[blockName]; ok { continue } if blockWrapper, ok := t.blocks[blockName]; ok { bErr := blockWrapper.Execute(ctx, buffer) if bErr != nil { return nil, bErr } result[blockName] = buffer.String() buffer.Reset() } } // We have found all blocks if len(blocks) == len(result) { break } } return result, nil } golang-github-flosch-pongo2.v4-4.0.2/template_loader.go000066400000000000000000000137641414175550100227600ustar00rootroot00000000000000package pongo2 import ( "bytes" "errors" "fmt" "io" "io/ioutil" "log" "net/http" "os" "path/filepath" ) // LocalFilesystemLoader represents a local filesystem loader with basic // BaseDirectory capabilities. The access to the local filesystem is unrestricted. type LocalFilesystemLoader struct { baseDir string } // MustNewLocalFileSystemLoader creates a new LocalFilesystemLoader instance // and panics if there's any error during instantiation. The parameters // are the same like NewLocalFileSystemLoader. func MustNewLocalFileSystemLoader(baseDir string) *LocalFilesystemLoader { fs, err := NewLocalFileSystemLoader(baseDir) if err != nil { log.Panic(err) } return fs } // NewLocalFileSystemLoader creates a new LocalFilesystemLoader and allows // templatesto be loaded from disk (unrestricted). If any base directory // is given (or being set using SetBaseDir), this base directory is being used // for path calculation in template inclusions/imports. Otherwise the path // is calculated based relatively to the including template's path. func NewLocalFileSystemLoader(baseDir string) (*LocalFilesystemLoader, error) { fs := &LocalFilesystemLoader{} if baseDir != "" { if err := fs.SetBaseDir(baseDir); err != nil { return nil, err } } return fs, nil } // SetBaseDir sets the template's base directory. This directory will // be used for any relative path in filters, tags and From*-functions to determine // your template. See the comment for NewLocalFileSystemLoader as well. func (fs *LocalFilesystemLoader) SetBaseDir(path string) error { // Make the path absolute if !filepath.IsAbs(path) { abs, err := filepath.Abs(path) if err != nil { return err } path = abs } // Check for existence fi, err := os.Stat(path) if err != nil { return err } if !fi.IsDir() { return fmt.Errorf("The given path '%s' is not a directory.", path) } fs.baseDir = path return nil } // Get reads the path's content from your local filesystem. func (fs *LocalFilesystemLoader) Get(path string) (io.Reader, error) { buf, err := ioutil.ReadFile(path) if err != nil { return nil, err } return bytes.NewReader(buf), nil } // Abs resolves a filename relative to the base directory. Absolute paths are allowed. // When there's no base dir set, the absolute path to the filename // will be calculated based on either the provided base directory (which // might be a path of a template which includes another template) or // the current working directory. func (fs *LocalFilesystemLoader) Abs(base, name string) string { if filepath.IsAbs(name) { return name } // Our own base dir has always priority; if there's none // we use the path provided in base. var err error if fs.baseDir == "" { if base == "" { base, err = os.Getwd() if err != nil { panic(err) } return filepath.Join(base, name) } return filepath.Join(filepath.Dir(base), name) } return filepath.Join(fs.baseDir, name) } // SandboxedFilesystemLoader is still WIP. type SandboxedFilesystemLoader struct { *LocalFilesystemLoader } // NewSandboxedFilesystemLoader creates a new sandboxed local file system instance. func NewSandboxedFilesystemLoader(baseDir string) (*SandboxedFilesystemLoader, error) { fs, err := NewLocalFileSystemLoader(baseDir) if err != nil { return nil, err } return &SandboxedFilesystemLoader{ LocalFilesystemLoader: fs, }, nil } // Move sandbox to a virtual fs /* if len(set.SandboxDirectories) > 0 { defer func() { // Remove any ".." or other crap resolvedPath = filepath.Clean(resolvedPath) // Make the path absolute absPath, err := filepath.Abs(resolvedPath) if err != nil { panic(err) } resolvedPath = absPath // Check against the sandbox directories (once one pattern matches, we're done and can allow it) for _, pattern := range set.SandboxDirectories { matched, err := filepath.Match(pattern, resolvedPath) if err != nil { panic("Wrong sandbox directory match pattern (see http://golang.org/pkg/path/filepath/#Match).") } if matched { // OK! return } } // No pattern matched, we have to log+deny the request set.logf("Access attempt outside of the sandbox directories (blocked): '%s'", resolvedPath) resolvedPath = "" }() } */ // HttpFilesystemLoader supports loading templates // from an http.FileSystem - useful for using one of several // file-to-code generators that packs static files into // a go binary (ex: https://github.com/jteeuwen/go-bindata) type HttpFilesystemLoader struct { fs http.FileSystem baseDir string } // MustNewHttpFileSystemLoader creates a new HttpFilesystemLoader instance // and panics if there's any error during instantiation. The parameters // are the same like NewHttpFilesystemLoader. func MustNewHttpFileSystemLoader(httpfs http.FileSystem, baseDir string) *HttpFilesystemLoader { fs, err := NewHttpFileSystemLoader(httpfs, baseDir) if err != nil { log.Panic(err) } return fs } // NewHttpFileSystemLoader creates a new HttpFileSystemLoader and allows // templates to be loaded from the virtual filesystem. The path // is calculated based relatively from the root of the http.Filesystem. func NewHttpFileSystemLoader(httpfs http.FileSystem, baseDir string) (*HttpFilesystemLoader, error) { hfs := &HttpFilesystemLoader{ fs: httpfs, baseDir: baseDir, } if httpfs == nil { err := errors.New("httpfs cannot be nil") return nil, err } return hfs, nil } // Abs in this instance simply returns the filename, since // there's no potential for an unexpanded path in an http.FileSystem func (h *HttpFilesystemLoader) Abs(base, name string) string { return name } // Get returns an io.Reader where the template's content can be read from. func (h *HttpFilesystemLoader) Get(path string) (io.Reader, error) { fullPath := path if h.baseDir != "" { fullPath = fmt.Sprintf( "%s/%s", h.baseDir, fullPath, ) } return h.fs.Open(fullPath) } golang-github-flosch-pongo2.v4-4.0.2/template_sets.go000066400000000000000000000215321414175550100224600ustar00rootroot00000000000000package pongo2 import ( "fmt" "io" "io/ioutil" "log" "os" "sync" "errors" ) // TemplateLoader allows to implement a virtual file system. type TemplateLoader interface { // Abs calculates the path to a given template. Whenever a path must be resolved // due to an import from another template, the base equals the parent template's path. Abs(base, name string) string // Get returns an io.Reader where the template's content can be read from. Get(path string) (io.Reader, error) } // TemplateSet allows you to create your own group of templates with their own // global context (which is shared among all members of the set) and their own // configuration. // It's useful for a separation of different kind of templates // (e. g. web templates vs. mail templates). type TemplateSet struct { name string loaders []TemplateLoader // Globals will be provided to all templates created within this template set Globals Context // If debug is true (default false), ExecutionContext.Logf() will work and output // to STDOUT. Furthermore, FromCache() won't cache the templates. // Make sure to synchronize the access to it in case you're changing this // variable during program execution (and template compilation/execution). Debug bool // Options allow you to change the behavior of template-engine. // You can change the options before calling the Execute method. Options *Options // Sandbox features // - Disallow access to specific tags and/or filters (using BanTag() and BanFilter()) // // For efficiency reasons you can ban tags/filters only *before* you have // added your first template to the set (restrictions are statically checked). // After you added one, it's not possible anymore (for your personal security). firstTemplateCreated bool bannedTags map[string]bool bannedFilters map[string]bool // Template cache (for FromCache()) templateCache map[string]*Template templateCacheMutex sync.Mutex } // NewSet can be used to create sets with different kind of templates // (e. g. web from mail templates), with different globals or // other configurations. func NewSet(name string, loaders ...TemplateLoader) *TemplateSet { if len(loaders) == 0 { panic(fmt.Errorf("at least one template loader must be specified")) } return &TemplateSet{ name: name, loaders: loaders, Globals: make(Context), bannedTags: make(map[string]bool), bannedFilters: make(map[string]bool), templateCache: make(map[string]*Template), Options: newOptions(), } } func (set *TemplateSet) AddLoader(loaders ...TemplateLoader) { set.loaders = append(set.loaders, loaders...) } func (set *TemplateSet) resolveFilename(tpl *Template, path string) string { return set.resolveFilenameForLoader(set.loaders[0], tpl, path) } func (set *TemplateSet) resolveFilenameForLoader(loader TemplateLoader, tpl *Template, path string) string { name := "" if tpl != nil && tpl.isTplString { return path } if tpl != nil { name = tpl.name } return loader.Abs(name, path) } // BanTag bans a specific tag for this template set. See more in the documentation for TemplateSet. func (set *TemplateSet) BanTag(name string) error { _, has := tags[name] if !has { return fmt.Errorf("tag '%s' not found", name) } if set.firstTemplateCreated { return errors.New("you cannot ban any tags after you've added your first template to your template set") } _, has = set.bannedTags[name] if has { return fmt.Errorf("tag '%s' is already banned", name) } set.bannedTags[name] = true return nil } // BanFilter bans a specific filter for this template set. See more in the documentation for TemplateSet. func (set *TemplateSet) BanFilter(name string) error { _, has := filters[name] if !has { return fmt.Errorf("filter '%s' not found", name) } if set.firstTemplateCreated { return errors.New("you cannot ban any filters after you've added your first template to your template set") } _, has = set.bannedFilters[name] if has { return fmt.Errorf("filter '%s' is already banned", name) } set.bannedFilters[name] = true return nil } func (set *TemplateSet) resolveTemplate(tpl *Template, path string) (name string, loader TemplateLoader, fd io.Reader, err error) { // iterate over loaders until we appear to have a valid template for _, loader = range set.loaders { name = set.resolveFilenameForLoader(loader, tpl, path) fd, err = loader.Get(name) if err == nil { return } } return path, nil, nil, fmt.Errorf("unable to resolve template") } // CleanCache cleans the template cache. If filenames is not empty, // it will remove the template caches of those filenames. // Or it will empty the whole template cache. It is thread-safe. func (set *TemplateSet) CleanCache(filenames ...string) { set.templateCacheMutex.Lock() defer set.templateCacheMutex.Unlock() if len(filenames) == 0 { set.templateCache = make(map[string]*Template, len(set.templateCache)) } for _, filename := range filenames { delete(set.templateCache, set.resolveFilename(nil, filename)) } } // FromCache is a convenient method to cache templates. It is thread-safe // and will only compile the template associated with a filename once. // If TemplateSet.Debug is true (for example during development phase), // FromCache() will not cache the template and instead recompile it on any // call (to make changes to a template live instantaneously). func (set *TemplateSet) FromCache(filename string) (*Template, error) { if set.Debug { // Recompile on any request return set.FromFile(filename) } // Cache the template cleanedFilename := set.resolveFilename(nil, filename) set.templateCacheMutex.Lock() defer set.templateCacheMutex.Unlock() tpl, has := set.templateCache[cleanedFilename] // Cache miss if !has { tpl, err := set.FromFile(cleanedFilename) if err != nil { return nil, err } set.templateCache[cleanedFilename] = tpl return tpl, nil } // Cache hit return tpl, nil } // FromString loads a template from string and returns a Template instance. func (set *TemplateSet) FromString(tpl string) (*Template, error) { set.firstTemplateCreated = true return newTemplateString(set, []byte(tpl)) } // FromBytes loads a template from bytes and returns a Template instance. func (set *TemplateSet) FromBytes(tpl []byte) (*Template, error) { set.firstTemplateCreated = true return newTemplateString(set, tpl) } // FromFile loads a template from a filename and returns a Template instance. func (set *TemplateSet) FromFile(filename string) (*Template, error) { set.firstTemplateCreated = true _, _, fd, err := set.resolveTemplate(nil, filename) if err != nil { return nil, &Error{ Filename: filename, Sender: "fromfile", OrigError: err, } } buf, err := ioutil.ReadAll(fd) if err != nil { return nil, &Error{ Filename: filename, Sender: "fromfile", OrigError: err, } } return newTemplate(set, filename, false, buf) } // RenderTemplateString is a shortcut and renders a template string directly. func (set *TemplateSet) RenderTemplateString(s string, ctx Context) (string, error) { set.firstTemplateCreated = true tpl := Must(set.FromString(s)) result, err := tpl.Execute(ctx) if err != nil { return "", err } return result, nil } // RenderTemplateBytes is a shortcut and renders template bytes directly. func (set *TemplateSet) RenderTemplateBytes(b []byte, ctx Context) (string, error) { set.firstTemplateCreated = true tpl := Must(set.FromBytes(b)) result, err := tpl.Execute(ctx) if err != nil { return "", err } return result, nil } // RenderTemplateFile is a shortcut and renders a template file directly. func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) (string, error) { set.firstTemplateCreated = true tpl := Must(set.FromFile(fn)) result, err := tpl.Execute(ctx) if err != nil { return "", err } return result, nil } func (set *TemplateSet) logf(format string, args ...interface{}) { if set.Debug { logger.Printf(fmt.Sprintf("[template set: %s] %s", set.name, format), args...) } } // Logging function (internally used) func logf(format string, items ...interface{}) { if debug { logger.Printf(format, items...) } } var ( debug bool // internal debugging logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags|log.Lshortfile) // DefaultLoader allows the default un-sandboxed access to the local file // system and is being used by the DefaultSet. DefaultLoader = MustNewLocalFileSystemLoader("") // DefaultSet is a set created for you for convinience reasons. DefaultSet = NewSet("default", DefaultLoader) // Methods on the default set FromString = DefaultSet.FromString FromBytes = DefaultSet.FromBytes FromFile = DefaultSet.FromFile FromCache = DefaultSet.FromCache RenderTemplateString = DefaultSet.RenderTemplateString RenderTemplateFile = DefaultSet.RenderTemplateFile // Globals for the default set Globals = DefaultSet.Globals ) golang-github-flosch-pongo2.v4-4.0.2/template_tests/000077500000000000000000000000001414175550100223125ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/template_tests/autoescape.tpl000066400000000000000000000004341414175550100251650ustar00rootroot00000000000000{{ "" }} {% autoescape off %} {{ "" }} {% endautoescape %} {% autoescape on %} {{ "" }} {% endautoescape %} {% autoescape off %} {{ ""|escape }} {% endautoescape %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/autoescape.tpl.out000066400000000000000000000002751414175550100257760ustar00rootroot00000000000000<script>alert('xss');</script> <script>alert('xss');</script> <script>alert('xss');</script> golang-github-flosch-pongo2.v4-4.0.2/template_tests/base_dir_test/000077500000000000000000000000001414175550100251215ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/template_tests/base_dir_test/base.html000066400000000000000000000000371414175550100267210ustar00rootroot00000000000000Hello from {{ base_directory }}golang-github-flosch-pongo2.v4-4.0.2/template_tests/base_dir_test/subdir/000077500000000000000000000000001414175550100264115ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/template_tests/base_dir_test/subdir/include.html000066400000000000000000000000311414175550100307140ustar00rootroot00000000000000{% include "base.html" %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/base_dir_test/subdir/index.html000066400000000000000000000000321414175550100304010ustar00rootroot00000000000000{% extends "base.html" %} golang-github-flosch-pongo2.v4-4.0.2/template_tests/base_dir_test/subdir/ssi.html000066400000000000000000000000341414175550100300720ustar00rootroot00000000000000{% ssi "base.html" parsed %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/block_render/000077500000000000000000000000001414175550100247435ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/template_tests/block_render/block.tpl000066400000000000000000000001341414175550100265540ustar00rootroot00000000000000{% block content %}Render content {% endblock %} {% block more_content %}More {% endblock %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/block_render/block.tpl.out000066400000000000000000000000241414175550100273600ustar00rootroot00000000000000Render content More golang-github-flosch-pongo2.v4-4.0.2/template_tests/block_render/split.tpl000066400000000000000000000001431414175550100266150ustar00rootroot00000000000000{% extends "../inheritance/base.tpl" %} {% block more_content %} The nested content {% endblock %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/block_render/split.tpl.out000066400000000000000000000000431414175550100274220ustar00rootroot00000000000000Default content The nested content golang-github-flosch-pongo2.v4-4.0.2/template_tests/comment.tpl000066400000000000000000000021121414175550100244710ustar00rootroot00000000000000empty single line comment {# #} filled single line comment {# testing single line comment #} filled single line comment with valid tags {# testing single line comment {% if thing %}{% endif %} #} filled single line comment with invalid tags {# testing single line comment {% if thing %} #} filled single line comment with invalid syntax {# testing single line comment {% if thing('') %}wow{% endif %} #} empty block comment {% comment %}{% endcomment %} filled text single line block comment {% comment %}filled block comment {% endcomment %} empty multi line block comment {% comment %} {% endcomment %} block comment with other tags inside of it {% comment %} {{ thing_goes_here }} {% if stuff %}do stuff{% endif %} {% endcomment %} block comment with invalid tags inside of it {% comment %} {% if thing %} {% endcomment %} block comment with invalid syntax inside of it {% comment %} {% thing('') %} {% endcomment %} Regular tags between comments to verify it doesn't break in the lexer {% if hello %} {% endif %} after if {% comment %}All done{% endcomment %} end of file golang-github-flosch-pongo2.v4-4.0.2/template_tests/comment.tpl.out000066400000000000000000000010201414175550100252740ustar00rootroot00000000000000empty single line comment filled single line comment filled single line comment with valid tags filled single line comment with invalid tags filled single line comment with invalid syntax empty block comment filled text single line block comment empty multi line block comment block comment with other tags inside of it block comment with invalid tags inside of it block comment with invalid syntax inside of it Regular tags between comments to verify it doesn't break in the lexer after if end of file golang-github-flosch-pongo2.v4-4.0.2/template_tests/complex.tpl000066400000000000000000000014731414175550100245070ustar00rootroot00000000000000{# A more complex template using pongo2 (fully django-compatible template) #} My blog page

Blogpost

{{ complex.post.Text|safe }}

Comments

{% for comment in complex.comments %}

{{ forloop.Counter }}. Comment ({{ forloop.Revcounter}} comment{{ forloop.Revcounter|pluralize:"s" }} left)

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 %} golang-github-flosch-pongo2.v4-4.0.2/template_tests/complex.tpl.out000066400000000000000000000016031414175550100253100ustar00rootroot00000000000000 My blog page

Blogpost

Hello!

Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.

Comments

1. Comment (3 comments left)

From: user1 (validated)

This user is not admin!

Written 2014-06-10 15:30:15 +0000 UTC

"pongo2 is nice!"

2. Comment (2 comments left)

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

3. Comment (1 comment left)

From: user3 (not validated)

This user is not admin!

Written 2014-06-10 15:30:15 +0000 UTC

hello! there

golang-github-flosch-pongo2.v4-4.0.2/template_tests/complex_trim_blocks.tpl000066400000000000000000000014731414175550100270770ustar00rootroot00000000000000{# A more complex template using pongo2 (fully django-compatible template) #} My blog page

Blogpost

{{ complex.post.Text|safe }}

Comments

{% for comment in complex.comments %}

{{ forloop.Counter }}. Comment ({{ forloop.Revcounter}} comment{{ forloop.Revcounter|pluralize:"s" }} left)

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 %} golang-github-flosch-pongo2.v4-4.0.2/template_tests/complex_trim_blocks.tpl.options000066400000000000000000000000411414175550100305570ustar00rootroot00000000000000TrimBlocks=true LStripBlocks=truegolang-github-flosch-pongo2.v4-4.0.2/template_tests/complex_trim_blocks.tpl.out000066400000000000000000000015511414175550100277020ustar00rootroot00000000000000 My blog page

Blogpost

Hello!

Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.

Comments

1. Comment (3 comments left)

From: user1 (validated)

This user is not admin!

Written 2014-06-10 15:30:15 +0000 UTC

"pongo2 is nice!"

2. Comment (2 comments left)

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

3. Comment (1 comment left)

From: user3 (not validated)

This user is not admin!

Written 2014-06-10 15:30:15 +0000 UTC

hello! there

golang-github-flosch-pongo2.v4-4.0.2/template_tests/cycle.tpl000066400000000000000000000016211414175550100241320ustar00rootroot00000000000000{% 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 }}'golang-github-flosch-pongo2.v4-4.0.2/template_tests/cycle.tpl.out000066400000000000000000000037271414175550100247510ustar00rootroot00000000000000 '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'golang-github-flosch-pongo2.v4-4.0.2/template_tests/empty.tpl000066400000000000000000000000001414175550100241570ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/template_tests/empty.tpl.out000066400000000000000000000000001414175550100247650ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/template_tests/empty_trim_blocks.tpl000066400000000000000000000000331414175550100265550ustar00rootroot00000000000000{% if true %} {% endif %} golang-github-flosch-pongo2.v4-4.0.2/template_tests/empty_trim_blocks.tpl.options000066400000000000000000000000201414175550100302430ustar00rootroot00000000000000TrimBlocks=true golang-github-flosch-pongo2.v4-4.0.2/template_tests/empty_trim_blocks.tpl.out000066400000000000000000000000001414175550100273550ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/template_tests/expressions.tpl000066400000000000000000000025731414175550100254240ustar00rootroot00000000000000integers 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 }}golang-github-flosch-pongo2.v4-4.0.2/template_tests/expressions.tpl.out000066400000000000000000000007541414175550100262310ustar00rootroot00000000000000integers 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 Falsegolang-github-flosch-pongo2.v4-4.0.2/template_tests/extends.tpl000066400000000000000000000001271414175550100245050ustar00rootroot00000000000000{% extends "inheritance/base.tpl" %} {% block content %}Extends' content{% endblock %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/extends.tpl.out000066400000000000000000000000551414175550100253130ustar00rootroot00000000000000Start#This is base's bodyExtends' content#Endgolang-github-flosch-pongo2.v4-4.0.2/template_tests/extends_super.tpl000066400000000000000000000001471414175550100257250ustar00rootroot00000000000000{% extends "inheritance/base.tpl" %} {% block content %}{{ block.Super }}extends-level-1{% endblock %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/extends_super.tpl.out000066400000000000000000000000731414175550100265310ustar00rootroot00000000000000Start#This is base's bodyDefault contentextends-level-1#Endgolang-github-flosch-pongo2.v4-4.0.2/template_tests/extends_super2.tpl000066400000000000000000000001441414175550100260040ustar00rootroot00000000000000{% extends "extends_super.tpl" %} {% block content %}{{ block.Super }}extends-level-2{% endblock %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/extends_super2.tpl.out000066400000000000000000000001121414175550100266050ustar00rootroot00000000000000Start#This is base's bodyDefault contentextends-level-1extends-level-2#Endgolang-github-flosch-pongo2.v4-4.0.2/template_tests/filters-compilation.err000066400000000000000000000001261414175550100270070ustar00rootroot00000000000000{{ }} {{ (1 - 1 }} {{ 1|float: }} {{ "test"|non_existent_filter }} {{ "test"|"test" }}golang-github-flosch-pongo2.v4-4.0.2/template_tests/filters-compilation.err.out000066400000000000000000000003451414175550100276200ustar00rootroot00000000000000.*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\.golang-github-flosch-pongo2.v4-4.0.2/template_tests/filters-execution.err000066400000000000000000000002741414175550100265000ustar00rootroot00000000000000{{ -(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") }} golang-github-flosch-pongo2.v4-4.0.2/template_tests/filters-execution.err.out000066400000000000000000000006071414175550100273060ustar00rootroot00000000000000.*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\). golang-github-flosch-pongo2.v4-4.0.2/template_tests/filters.tpl000066400000000000000000000216111414175550100245040ustar00rootroot00000000000000add {{ 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" %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/firstof.tpl.out000066400000000000000000000001411414175550100253110ustar00rootroot0000000000000042 <script>alert('xss');</script> 8 test testgolang-github-flosch-pongo2.v4-4.0.2/template_tests/for.tpl000066400000000000000000000017311414175550100236230ustar00rootroot00000000000000{% 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 %}' golang-github-flosch-pongo2.v4-4.0.2/template_tests/for.tpl.out000066400000000000000000000017071414175550100244340ustar00rootroot00000000000000[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 ' golang-github-flosch-pongo2.v4-4.0.2/template_tests/function_calls_wrapper.tpl000066400000000000000000000010461414175550100275770ustar00rootroot00000000000000{{ 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) }}golang-github-flosch-pongo2.v4-4.0.2/template_tests/function_calls_wrapper.tpl.out000066400000000000000000000000701414175550100304010ustar00rootroot0000000000000079 79 hello hello, john doe 5 + 42 is 49 0 1 205 0 2 108golang-github-flosch-pongo2.v4-4.0.2/template_tests/if.tpl000066400000000000000000000023001414175550100234240ustar00rootroot00000000000000{% 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 simple.uint in simple.multiple_item_list %}simple.uint in simple.multiple_item_list{% endif %} {% if !0.0 %}!0.0{% endif %} {% if !0 %}!0{% endif %} {% if not complex.post %}true{% else %}false{% endif %} {% if simple.number == 43 %}no{% else %}42{% endif %} {% if simple.number < 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 %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/if.tpl.out000066400000000000000000000003421414175550100242360ustar00rootroot00000000000000true simple != nil uint != 0 float != 0.0 !simple !simple.uint !simple.float text field in complex.post 5 in simple.intmap simple.uint in simple.multiple_item_list !0.0 !0 false 42 yes yes true simple.float Elseif with no elsegolang-github-flosch-pongo2.v4-4.0.2/template_tests/ifchanged.tpl000066400000000000000000000006571414175550100247530ustar00rootroot00000000000000{% 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 %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/ifchanged.tpl.out000066400000000000000000000005351414175550100255540ustar00rootroot00000000000000 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 golang-github-flosch-pongo2.v4-4.0.2/template_tests/includes.helper000066400000000000000000000000371414175550100253210ustar00rootroot00000000000000I'm {{ what_am_i }}{{ number }}golang-github-flosch-pongo2.v4-4.0.2/template_tests/includes.tpl000066400000000000000000000007561414175550100246510ustar00rootroot00000000000000Start '{% 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" %}' Endgolang-github-flosch-pongo2.v4-4.0.2/template_tests/includes.tpl.out000066400000000000000000000002121414175550100254420ustar00rootroot00000000000000Start '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 '' Endgolang-github-flosch-pongo2.v4-4.0.2/template_tests/inheritance/000077500000000000000000000000001414175550100246035ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/template_tests/inheritance/base.tpl000066400000000000000000000002141414175550100262330ustar00rootroot00000000000000{% extends "inheritance2/skeleton.tpl" %} {% block body %}This is base's body{% block content %}Default content{% endblock %}{% endblock %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/inheritance/base2.tpl000066400000000000000000000000411414175550100263130ustar00rootroot00000000000000{% include "doesnotexist.tpl" %} golang-github-flosch-pongo2.v4-4.0.2/template_tests/inheritance/cycle_include.tpl000066400000000000000000000000331414175550100301220ustar00rootroot00000000000000Included '{{ cycleitem }}'.golang-github-flosch-pongo2.v4-4.0.2/template_tests/inheritance/inheritance2/000077500000000000000000000000001414175550100271565ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/template_tests/inheritance/inheritance2/skeleton.tpl000066400000000000000000000000641414175550100315230ustar00rootroot00000000000000Start#{% block body %}Default body{% endblock %}#Endgolang-github-flosch-pongo2.v4-4.0.2/template_tests/issues.tpl000066400000000000000000000000001414175550100243340ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/template_tests/issues.tpl.out000066400000000000000000000000001414175550100251420ustar00rootroot00000000000000golang-github-flosch-pongo2.v4-4.0.2/template_tests/lorem.tpl000066400000000000000000000001321414175550100241450ustar00rootroot00000000000000----- {% lorem %} ----- {% lorem 10 %} ----- {% lorem 3 p %} ----- {% lorem 100 w %} -----golang-github-flosch-pongo2.v4-4.0.2/template_tests/lorem.tpl.out000066400000000000000000000157141414175550100247670ustar00rootroot00000000000000----- 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 -----golang-github-flosch-pongo2.v4-4.0.2/template_tests/macro-compilation.err000066400000000000000000000001401414175550100264340ustar00rootroot00000000000000{% macro test_override() export %}{% endmacro %}{% macro test_override() export %}{% endmacro %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/macro-compilation.err.out000066400000000000000000000000721414175550100272460ustar00rootroot00000000000000.*another macro with name 'test_override' already exportedgolang-github-flosch-pongo2.v4-4.0.2/template_tests/macro-execution.err000066400000000000000000000001061414175550100261230ustar00rootroot00000000000000{% macro number() export %}No number here.{% endmacro %}{{ number() }}golang-github-flosch-pongo2.v4-4.0.2/template_tests/macro-execution.err.out000066400000000000000000000000671414175550100267370ustar00rootroot00000000000000.*context key name 'number' clashes with macro 'number'golang-github-flosch-pongo2.v4-4.0.2/template_tests/macro.helper000066400000000000000000000002221414175550100246100ustar00rootroot00000000000000{% macro imported_macro(foo) export %}

Hey {{ foo }}!

{% endmacro %} {% macro imported_macro_void() export %}

Hello mate!

{% endmacro %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/macro.tpl000066400000000000000000000017301414175550100241350ustar00rootroot00000000000000Begin {% 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() }} Endgolang-github-flosch-pongo2.v4-4.0.2/template_tests/macro.tpl.out000066400000000000000000000013101414175550100247350ustar00rootroot00000000000000Begin 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!

Endgolang-github-flosch-pongo2.v4-4.0.2/template_tests/macro2.helper000066400000000000000000000002721414175550100246770ustar00rootroot00000000000000{% macro greeter_macro() export %} {% import "macro.helper" imported_macro, imported_macro_void %} One greeting: {{ imported_macro("Dirk") }} - {{ imported_macro_void() }} {% endmacro %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/now.tpl000066400000000000000000000002511414175550100236340ustar00rootroot00000000000000{# 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 %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/now.tpl.out000066400000000000000000000000421414175550100244400ustar00rootroot00000000000000 Wed Feb 5 18:31:45 +0000 UTC 2014golang-github-flosch-pongo2.v4-4.0.2/template_tests/pongo2ctx.tpl000066400000000000000000000000241414175550100247520ustar00rootroot00000000000000{{ pongo2.version }}golang-github-flosch-pongo2.v4-4.0.2/template_tests/pongo2ctx.tpl.out000066400000000000000000000000051414175550100255570ustar00rootroot000000000000004.0.2golang-github-flosch-pongo2.v4-4.0.2/template_tests/quotes.tpl000066400000000000000000000007711414175550100243600ustar00rootroot00000000000000Variables {{ "hello" }} {{ 'hello' }} {{ "hell'o" }} Filters {{ 'Test'|slice:'1:3' }} {{ '
  • This is a long test which will be cutted after some chars.

'|truncatechars_html:25 }} {{ '

This is a long test which will be cutted after some chars.

'|truncatechars_html:25 }} Tags {% if 'Text' in complex.post %}text field in complex.post{% endif %} Functions {{ simple.func_variadic('hello') }} golang-github-flosch-pongo2.v4-4.0.2/template_tests/quotes.tpl.out000066400000000000000000000004011414175550100251540ustar00rootroot00000000000000Variables hello hello hell'o Filters es
  • This is a long test wh...

This is a long test wh...

Tags text field in complex.post Functions hello golang-github-flosch-pongo2.v4-4.0.2/template_tests/sandbox-compilation.err000066400000000000000000000001241414175550100267730ustar00rootroot00000000000000{{ "hello"|banned_filter }} {% banned_tag %} {% include "../../test_not_existent" %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/sandbox-compilation.err.out000066400000000000000000000004111414175550100276000ustar00rootroot00000000000000.*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 directorygolang-github-flosch-pongo2.v4-4.0.2/template_tests/sandbox.tpl000066400000000000000000000001101414175550100244610ustar00rootroot00000000000000{{ "hello"|unbanned_filter }} {% unbanned_tag %} {% include temp_file %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/sandbox.tpl.out000066400000000000000000000000351414175550100252750ustar00rootroot00000000000000hello hello Hello from pongo2golang-github-flosch-pongo2.v4-4.0.2/template_tests/set.tpl000066400000000000000000000004301414175550100236230ustar00rootroot00000000000000{% 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 }} {% set car=someUndefinedVar %}{{ car.Drive }}No Panicgolang-github-flosch-pongo2.v4-4.0.2/template_tests/set.tpl.out000066400000000000000000000000671414175550100244370ustar00rootroot00000000000000hello world world Hello 99 3.140000 good world No Panicgolang-github-flosch-pongo2.v4-4.0.2/template_tests/spaceless.tpl000066400000000000000000000003721414175550100250170ustar00rootroot00000000000000{% spaceless %}

This is a test! Mail me at mail@example.tld

Yep!

{% endspaceless %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/spaceless.tpl.out000066400000000000000000000003071414175550100256230ustar00rootroot00000000000000

This is a test! Mail me at mail@example.tld

Yep!

golang-github-flosch-pongo2.v4-4.0.2/template_tests/ssi.helper000066400000000000000000000000321414175550100243040ustar00rootroot00000000000000{{ number }} {{ "hello" }}golang-github-flosch-pongo2.v4-4.0.2/template_tests/ssi.tpl000066400000000000000000000000641414175550100236310ustar00rootroot00000000000000{% ssi "ssi.helper" %} {% ssi "ssi.helper" parsed %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/ssi.tpl.out000066400000000000000000000000431414175550100244340ustar00rootroot00000000000000{{ number }} {{ "hello" }} 11 hellogolang-github-flosch-pongo2.v4-4.0.2/template_tests/tag_filter.tpl000066400000000000000000000003611414175550100251530ustar00rootroot00000000000000{% filter lower %}This is a nice test; let's see whether it works. Foobar. {{ simple.xss }}{% endfilter %} {% filter truncatechars:10|lower|length %}This is a nice test; let's see whether it works. Foobar. {{ simple.number }}{% endfilter %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/tag_filter.tpl.out000066400000000000000000000001631414175550100257610ustar00rootroot00000000000000this is a nice test; let's see whether it works. foobar. <script>alert("uh oh");</script> 10golang-github-flosch-pongo2.v4-4.0.2/template_tests/tags-compilation.err000066400000000000000000000004741414175550100263030ustar00rootroot00000000000000{% if true %} {% if (1) . %}{% endif %} {% block test %}{% block test %}{% endblock %}{% endblock %} {% block test %}{% block test %}{% endblock %}{% endblock test2 %} {% block test %}{% block test2 %}{% endblock xy %}{% endblock test %} {% block test %}{% block test2 %}{% endblock test2 test3 %}{% endblock test %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/tags-compilation.err.out000066400000000000000000000005341414175550100271060ustar00rootroot00000000000000.*Unexpected EOF, expected tag elif or else or endif. .*If-condition is malformed. .*Block named 'test' already defined.* .*Name for 'endblock' must equal to 'block'\-tag's name \('test' != 'test2'\). .*Name for 'endblock' must equal to 'block'-tag's name \('test2' != 'xy'\). .*Either no or only one argument \(identifier\) allowed for 'endblock'.golang-github-flosch-pongo2.v4-4.0.2/template_tests/template_sets.tpl000066400000000000000000000000471414175550100257050ustar00rootroot00000000000000Globals {{ this_is_a_global_variable }}golang-github-flosch-pongo2.v4-4.0.2/template_tests/template_sets.tpl.out000066400000000000000000000000351414175550100265100ustar00rootroot00000000000000Globals this is a global textgolang-github-flosch-pongo2.v4-4.0.2/template_tests/templatetag.tpl000066400000000000000000000003731414175550100253450ustar00rootroot00000000000000{% templatetag openblock %} url 'entry_list' {% templatetag closeblock %} {% templatetag openvariable %}{% templatetag closevariable %} {% templatetag openbrace %}{% templatetag closebrace %} {% templatetag opencomment %}{% templatetag closecomment %}golang-github-flosch-pongo2.v4-4.0.2/template_tests/templatetag.tpl.out000066400000000000000000000000431414175550100261450ustar00rootroot00000000000000{% url 'entry_list' %} {{}} {} {##}golang-github-flosch-pongo2.v4-4.0.2/template_tests/time.tpl000066400000000000000000000012251414175550100237710ustar00rootroot00000000000000{% if simple.time1 == simple.time1 %}equal{% else %}not equal{% endif %} {% if simple.time1 > simple.time2 %}greater{% else %}not greater{% endif %} {% if simple.time2 < simple.time1 %}lower{% else %}not lower{% endif %} {% if simple.time1 >= simple.time2 %}greater or equal (greater){% else %}not greater or equal (greater){% endif %} {% if simple.time1 >= simple.time1 %}greater or equal (equal){% else %}not greater or equal (equal){% endif %} {% if simple.time2 <= simple.time1 %}lower or equal (lower){% else %}not lower or equal (lower){% endif %} {% if simple.time2 <= simple.time2 %}lower or equal (equal){% else %}not lower or equal (equal){% endif %} golang-github-flosch-pongo2.v4-4.0.2/template_tests/time.tpl.out000066400000000000000000000001661414175550100246020ustar00rootroot00000000000000equal greater lower greater or equal (greater) greater or equal (equal) lower or equal (lower) lower or equal (equal) golang-github-flosch-pongo2.v4-4.0.2/template_tests/trim_blocks.tpl000066400000000000000000000001441414175550100253420ustar00rootroot00000000000000{% if true %} Text {% endif %} {% if true %} {% endif %} ---- {% if true %} Text2 {% endif %} golang-github-flosch-pongo2.v4-4.0.2/template_tests/trim_blocks.tpl.options000066400000000000000000000000201414175550100270250ustar00rootroot00000000000000TrimBlocks=true golang-github-flosch-pongo2.v4-4.0.2/template_tests/trim_blocks.tpl.out000066400000000000000000000000231414175550100261440ustar00rootroot00000000000000Text ---- Text2 golang-github-flosch-pongo2.v4-4.0.2/template_tests/variables.tpl000066400000000000000000000004421414175550100250030ustar00rootroot00000000000000{{ 1 }} {{ -5 }} {{ "hallo" }} {{ true }} {{ false }} {{ simple.uint }} {{ simple.nil }} {{ simple.str }} {{ simple.bool_false }} {{ simple.bool_true }} {{ simple.uint }} {{ simple.uint|integer }} {{ simple.uint|float }} {{ simple.multiple_item_list.10 }} {{ simple.multiple_item_list.4 }} golang-github-flosch-pongo2.v4-4.0.2/template_tests/variables.tpl.out000066400000000000000000000000731414175550100256110ustar00rootroot000000000000001 -5 hallo True False 8 string False True 8 8 8.000000 5 golang-github-flosch-pongo2.v4-4.0.2/template_tests/verbatim.tpl000066400000000000000000000003121414175550100246400ustar00rootroot00000000000000.{{ simple.number }}{% verbatim %}