1
0
Fork 0
mirror of https://github.com/Luzifer/promcertcheck.git synced 2024-11-09 16:30:04 +00:00

Update vendored libs

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2018-06-04 11:29:58 +02:00
parent dd7d942f30
commit ce6a18fbf3
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
1137 changed files with 37335 additions and 167090 deletions

76
Gopkg.lock generated
View file

@ -11,43 +11,37 @@
branch = "master" branch = "master"
name = "github.com/beorn7/perks" name = "github.com/beorn7/perks"
packages = ["quantile"] packages = ["quantile"]
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9" revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]] [[projects]]
branch = "master"
name = "github.com/flosch/pongo2" name = "github.com/flosch/pongo2"
packages = ["."] packages = ["."]
revision = "1f4be1efe3b3529b7e58861f75d70120a9567dc4" revision = "5e81b817a0c48c1c57cdf1a9056cf76bdee02ca9"
version = "v3.0"
[[projects]] [[projects]]
branch = "master"
name = "github.com/golang/protobuf" name = "github.com/golang/protobuf"
packages = ["proto"] packages = ["proto"]
revision = "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9" revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
version = "v1.1.0"
[[projects]] [[projects]]
name = "github.com/gorilla/context" name = "github.com/gorilla/context"
packages = ["."] packages = ["."]
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a" revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1" version = "v1.1.1"
[[projects]] [[projects]]
name = "github.com/gorilla/mux" name = "github.com/gorilla/mux"
packages = ["."] packages = ["."]
revision = "24fca303ac6da784b9e8269f724ddeb0b2eea5e7" revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.5.0" version = "v1.6.2"
[[projects]]
branch = "master"
name = "github.com/juju/errors"
packages = ["."]
revision = "c7d06af17c68cd34c835053720b21f6549d9b0ee"
[[projects]] [[projects]]
name = "github.com/matttproud/golang_protobuf_extensions" name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"] packages = ["pbutil"]
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c" revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c"
version = "v1.0.0" version = "v1.0.1"
[[projects]] [[projects]]
name = "github.com/prometheus/client_golang" name = "github.com/prometheus/client_golang"
@ -59,65 +53,77 @@
branch = "master" branch = "master"
name = "github.com/prometheus/client_model" name = "github.com/prometheus/client_model"
packages = ["go"] packages = ["go"]
revision = "6f3806018612930941127f2a7c6c453ba2c527d2" revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/prometheus/common" name = "github.com/prometheus/common"
packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"] packages = [
revision = "e3fb1a1acd7605367a2b378bc2e2f893c05174b7" "expfmt",
"internal/bitbucket.org/ww/goautoneg",
"model"
]
revision = "7600349dcfe1abd18d72d3a1770870d9800a7801"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "github.com/prometheus/procfs" name = "github.com/prometheus/procfs"
packages = [".","xfs"] packages = [
revision = "a6e9df898b1336106c743392c48ee0b71f5c4efa" ".",
"internal/util",
"nfs",
"xfs"
]
revision = "94663424ae5ae9856b40a9f170762b4197024661"
[[projects]] [[projects]]
name = "github.com/robfig/cron" name = "github.com/robfig/cron"
packages = ["."] packages = ["."]
revision = "b024fc5ea0e34bc3f83d9941c8d60b0622bfaca4" revision = "b41be1df696709bb6395fe435af20370037c0b4c"
version = "v1" version = "v1.1"
[[projects]] [[projects]]
name = "github.com/sirupsen/logrus" name = "github.com/sirupsen/logrus"
packages = ["."] packages = ["."]
revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e" revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.3" version = "v1.0.5"
[[projects]] [[projects]]
name = "github.com/spf13/pflag" name = "github.com/spf13/pflag"
packages = ["."] packages = ["."]
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.0" version = "v1.0.1"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/crypto" name = "golang.org/x/crypto"
packages = ["ssh/terminal"] packages = ["ssh/terminal"]
revision = "bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8" revision = "df8d4716b3472e4a531c33cedbe537dae921a1a9"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/sys" name = "golang.org/x/sys"
packages = ["unix","windows"] packages = [
revision = "8eb05f94d449fdf134ec24630ce69ada5b469c1c" "unix",
"windows"
]
revision = "c11f84a56e43e20a78cee75a7c034031ecf57d1f"
[[projects]] [[projects]]
branch = "v2" branch = "v2"
name = "gopkg.in/validator.v2" name = "gopkg.in/validator.v2"
packages = ["."] packages = ["."]
revision = "460c83432a98c35224a6fe352acf8b23e067ad06" revision = "135c24b11c19e52befcae2ec3fca5d9b78c4e98e"
[[projects]] [[projects]]
branch = "v2"
name = "gopkg.in/yaml.v2" name = "gopkg.in/yaml.v2"
packages = ["."] packages = ["."]
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f" revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "f88f786b9ff5e99b5589aea171923fd8d9dcef123a4d6be68d7659b923e8dc1e" inputs-digest = "9e9921ed070369ec5c66299d9403b1d7dd184332dbf7f7e36c5d1ff493b88216"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View file

@ -1,7 +1,6 @@
# Gopkg.toml example # Gopkg.toml example
# #
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation. # for detailed Gopkg.toml documentation.
# #
# required = ["github.com/user/thing/cmd/thing"] # required = ["github.com/user/thing/cmd/thing"]
@ -17,8 +16,13 @@
# source = "github.com/myfork/project2" # source = "github.com/myfork/project2"
# #
# [[override]] # [[override]]
# name = "github.com/x/y" # name = "github.com/x/y"
# version = "2.4.0" # version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]] [[constraint]]
@ -27,17 +31,24 @@
[[constraint]] [[constraint]]
name = "github.com/flosch/pongo2" name = "github.com/flosch/pongo2"
branch = "master" version = "3.0.0"
[[constraint]] [[constraint]]
name = "github.com/gorilla/mux" name = "github.com/gorilla/mux"
version = "1.6.2"
[[constraint]] [[constraint]]
name = "github.com/prometheus/client_golang" name = "github.com/prometheus/client_golang"
version = "0.8.0"
[[constraint]] [[constraint]]
name = "github.com/robfig/cron" name = "github.com/robfig/cron"
version = "1.1.0"
[[constraint]] [[constraint]]
name = "github.com/sirupsen/logrus" name = "github.com/sirupsen/logrus"
version = "1.0.3" version = "1.0.5"
[prune]
go-tests = true
unused-packages = true

View file

@ -1,70 +0,0 @@
package rconfig
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing bool parsing", func() {
type t struct {
Test1 bool `default:"true"`
Test2 bool `default:"false" flag:"test2"`
Test3 bool `default:"true" flag:"test3,t"`
Test4 bool `flag:"test4"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--test2",
"-t",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test1).To(Equal(true))
Expect(cfg.Test2).To(Equal(true))
Expect(cfg.Test3).To(Equal(true))
Expect(cfg.Test4).To(Equal(false))
})
})
var _ = Describe("Testing to set bool from ENV with default", func() {
type t struct {
Test1 bool `default:"true" env:"TEST1"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{}
})
JustBeforeEach(func() {
os.Unsetenv("TEST1")
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test1).To(Equal(true))
})
})

View file

@ -1,41 +0,0 @@
package rconfig
import (
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Duration", func() {
type t struct {
Test time.Duration `flag:"duration"`
TestS time.Duration `flag:"other-duration,o"`
TestDef time.Duration `default:"30h"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--duration=23s", "-o", "45m",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal(23 * time.Second))
Expect(cfg.TestS).To(Equal(45 * time.Minute))
Expect(cfg.TestDef).To(Equal(30 * time.Hour))
})
})

View file

@ -1,56 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing errors", func() {
It("should not accept string as int", func() {
Expect(parse(&struct {
A int `default:"a"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string as float", func() {
Expect(parse(&struct {
A float32 `default:"a"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string as uint", func() {
Expect(parse(&struct {
A uint `default:"a"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string as uint in sub-struct", func() {
Expect(parse(&struct {
B struct {
A uint `default:"a"`
}
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string slice as int slice", func() {
Expect(parse(&struct {
A []int `default:"a,bn"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept variables not being pointers", func() {
cfg := struct {
A string `default:"a"`
}{}
Expect(parse(cfg, []string{})).To(HaveOccurred())
})
It("should not accept variables not being pointers to structs", func() {
cfg := "test"
Expect(parse(cfg, []string{})).To(HaveOccurred())
})
})

View file

@ -1,37 +0,0 @@
package rconfig
import (
"fmt"
"os"
)
func ExampleParse() {
// We're building an example configuration with a sub-struct to be filled
// by the Parse command.
config := struct {
Username string `default:"unknown" flag:"user,u" description:"Your name"`
Details struct {
Age int `default:"25" flag:"age" description:"Your age"`
}
}{}
// To have more relieable results we're setting os.Args to a known value.
// In real-life use cases you wouldn't do this but parse the original
// commandline arguments.
os.Args = []string{
"example",
"--user=Luzifer",
}
Parse(&config)
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
config.Username,
config.Details.Age)
// You can also show an usage message for your user
Usage()
// Output:
// Hello Luzifer, happy birthday for your 25th birthday.
}

View file

@ -1,44 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing float parsing", func() {
type t struct {
Test32 float32 `flag:"float32"`
Test32P float32 `flag:"float32p,3"`
Test64 float64 `flag:"float64"`
Test64P float64 `flag:"float64p,6"`
TestDef float32 `default:"66.256"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--float32=5.5", "-3", "6.6",
"--float64=7.7", "-6", "8.8",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test32).To(Equal(float32(5.5)))
Expect(cfg.Test32P).To(Equal(float32(6.6)))
Expect(cfg.Test64).To(Equal(float64(7.7)))
Expect(cfg.Test64P).To(Equal(float64(8.8)))
Expect(cfg.TestDef).To(Equal(float32(66.256)))
})
})

View file

@ -1,128 +0,0 @@
package rconfig
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing general parsing", func() {
type t struct {
Test string `default:"foo" env:"shell" flag:"shell" description:"Test"`
Test2 string `default:"blub" env:"testvar" flag:"testvar,t" description:"Test"`
DefaultFlag string `default:"goo"`
SadFlag string
}
type tValidated struct {
Test string `flag:"test" default:"" validate:"nonzero"`
}
var (
err error
args []string
cfg t
)
Context("with defined arguments", func() {
BeforeEach(func() {
cfg = t{}
args = []string{
"--shell=test23",
"-t", "bla",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have parsed the expected values", func() {
Expect(cfg.Test).To(Equal("test23"))
Expect(cfg.Test2).To(Equal("bla"))
Expect(cfg.SadFlag).To(Equal(""))
Expect(cfg.DefaultFlag).To(Equal("goo"))
})
})
Context("with no arguments", func() {
BeforeEach(func() {
cfg = t{}
args = []string{}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the default value", func() {
Expect(cfg.Test).To(Equal("foo"))
})
})
Context("with no arguments and set env", func() {
BeforeEach(func() {
cfg = t{}
args = []string{}
os.Setenv("shell", "test546")
})
AfterEach(func() {
os.Unsetenv("shell")
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the value from env", func() {
Expect(cfg.Test).To(Equal("test546"))
})
})
Context("with additional arguments", func() {
BeforeEach(func() {
cfg = t{}
args = []string{
"--shell=test23",
"-t", "bla",
"positional1", "positional2",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have parsed the expected values", func() {
Expect(cfg.Test).To(Equal("test23"))
Expect(cfg.Test2).To(Equal("bla"))
Expect(cfg.SadFlag).To(Equal(""))
Expect(cfg.DefaultFlag).To(Equal("goo"))
})
It("should have detected the positional arguments", func() {
Expect(Args()).To(Equal([]string{"positional1", "positional2"}))
})
})
Context("making use of the validator package", func() {
var cfgValidated tValidated
BeforeEach(func() {
cfgValidated = tValidated{}
args = []string{}
})
JustBeforeEach(func() {
err = parseAndValidate(&cfgValidated, args)
})
It("should have errored", func() { Expect(err).To(HaveOccurred()) })
})
})

View file

@ -1,54 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing int parsing", func() {
type t struct {
Test int `flag:"int"`
TestP int `flag:"intp,i"`
Test8 int8 `flag:"int8"`
Test8P int8 `flag:"int8p,8"`
Test32 int32 `flag:"int32"`
Test32P int32 `flag:"int32p,3"`
Test64 int64 `flag:"int64"`
Test64P int64 `flag:"int64p,6"`
TestDef int8 `default:"66"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--int=1", "-i", "2",
"--int8=3", "-8", "4",
"--int32=5", "-3", "6",
"--int64=7", "-6", "8",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal(1))
Expect(cfg.TestP).To(Equal(2))
Expect(cfg.Test8).To(Equal(int8(3)))
Expect(cfg.Test8P).To(Equal(int8(4)))
Expect(cfg.Test32).To(Equal(int32(5)))
Expect(cfg.Test32P).To(Equal(int32(6)))
Expect(cfg.Test64).To(Equal(int64(7)))
Expect(cfg.Test64P).To(Equal(int64(8)))
Expect(cfg.TestDef).To(Equal(int8(66)))
})
})

View file

@ -1,40 +0,0 @@
package rconfig_test
import (
"os"
. "github.com/Luzifer/rconfig"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing os.Args", func() {
type t struct {
A string `default:"a" flag:"a"`
}
var (
err error
cfg t
)
JustBeforeEach(func() {
err = Parse(&cfg)
})
Context("With only valid arguments", func() {
BeforeEach(func() {
cfg = t{}
os.Args = []string{"--a=bar"}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.A).To(Equal("bar"))
})
})
})

View file

@ -1,87 +0,0 @@
package rconfig
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Precedence", func() {
type t struct {
A int `default:"1" vardefault:"a" env:"a" flag:"avar,a" description:"a"`
}
var (
err error
cfg t
args []string
vardefaults map[string]string
)
JustBeforeEach(func() {
cfg = t{}
SetVariableDefaults(vardefaults)
err = parse(&cfg, args)
})
Context("Provided: Flag, Env, Default, VarDefault", func() {
BeforeEach(func() {
args = []string{"-a", "5"}
os.Setenv("a", "8")
vardefaults = map[string]string{
"a": "3",
}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the flag value", func() {
Expect(cfg.A).To(Equal(5))
})
})
Context("Provided: Env, Default, VarDefault", func() {
BeforeEach(func() {
args = []string{}
os.Setenv("a", "8")
vardefaults = map[string]string{
"a": "3",
}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the env value", func() {
Expect(cfg.A).To(Equal(8))
})
})
Context("Provided: Default, VarDefault", func() {
BeforeEach(func() {
args = []string{}
os.Unsetenv("a")
vardefaults = map[string]string{
"a": "3",
}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the vardefault value", func() {
Expect(cfg.A).To(Equal(3))
})
})
Context("Provided: Default", func() {
BeforeEach(func() {
args = []string{}
os.Unsetenv("a")
vardefaults = map[string]string{}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the default value", func() {
Expect(cfg.A).To(Equal(1))
})
})
})

View file

@ -1,13 +0,0 @@
package rconfig_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestRconfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Rconfig Suite")
}

View file

@ -1,51 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing slices", func() {
type t struct {
Int []int `default:"1,2,3" flag:"int"`
String []string `default:"a,b,c" flag:"string"`
IntP []int `default:"1,2,3" flag:"intp,i"`
StringP []string `default:"a,b,c" flag:"stringp,s"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--int=4,5", "-s", "hallo,welt",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values for int-slice", func() {
Expect(len(cfg.Int)).To(Equal(2))
Expect(cfg.Int).To(Equal([]int{4, 5}))
Expect(cfg.Int).NotTo(Equal([]int{5, 4}))
})
It("should have the expected values for int-shorthand-slice", func() {
Expect(len(cfg.IntP)).To(Equal(3))
Expect(cfg.IntP).To(Equal([]int{1, 2, 3}))
})
It("should have the expected values for string-slice", func() {
Expect(len(cfg.String)).To(Equal(3))
Expect(cfg.String).To(Equal([]string{"a", "b", "c"}))
})
It("should have the expected values for string-shorthand-slice", func() {
Expect(len(cfg.StringP)).To(Equal(2))
Expect(cfg.StringP).To(Equal([]string{"hallo", "welt"}))
})
})

View file

@ -1,36 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing sub-structs", func() {
type t struct {
Test string `default:"blubb"`
Sub struct {
Test string `default:"Hallo"`
}
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal("blubb"))
Expect(cfg.Sub.Test).To(Equal("Hallo"))
})
})

View file

@ -1,59 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing uint parsing", func() {
type t struct {
Test uint `flag:"int"`
TestP uint `flag:"intp,i"`
Test8 uint8 `flag:"int8"`
Test8P uint8 `flag:"int8p,8"`
Test16 uint16 `flag:"int16"`
Test16P uint16 `flag:"int16p,1"`
Test32 uint32 `flag:"int32"`
Test32P uint32 `flag:"int32p,3"`
Test64 uint64 `flag:"int64"`
Test64P uint64 `flag:"int64p,6"`
TestDef uint8 `default:"66"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--int=1", "-i", "2",
"--int8=3", "-8", "4",
"--int32=5", "-3", "6",
"--int64=7", "-6", "8",
"--int16=9", "-1", "10",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal(uint(1)))
Expect(cfg.TestP).To(Equal(uint(2)))
Expect(cfg.Test8).To(Equal(uint8(3)))
Expect(cfg.Test8P).To(Equal(uint8(4)))
Expect(cfg.Test32).To(Equal(uint32(5)))
Expect(cfg.Test32P).To(Equal(uint32(6)))
Expect(cfg.Test64).To(Equal(uint64(7)))
Expect(cfg.Test64P).To(Equal(uint64(8)))
Expect(cfg.Test16).To(Equal(uint16(9)))
Expect(cfg.Test16P).To(Equal(uint16(10)))
Expect(cfg.TestDef).To(Equal(uint8(66)))
})
})

View file

@ -1,122 +0,0 @@
package rconfig
import (
"io/ioutil"
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing variable defaults", func() {
type t struct {
MySecretValue string `default:"secret" env:"foo" vardefault:"my_secret_value"`
MyUsername string `default:"luzifer" vardefault:"username"`
SomeVar string `flag:"var" description:"some variable"`
IntVar int64 `vardefault:"int_var" default:"23"`
}
var (
err error
cfg t
args = []string{}
vardefaults = map[string]string{
"my_secret_value": "veryverysecretkey",
"unkownkey": "hi there",
"int_var": "42",
}
)
BeforeEach(func() {
cfg = t{}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
Context("With manually provided variables", func() {
BeforeEach(func() {
SetVariableDefaults(vardefaults)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(42)))
Expect(cfg.MySecretValue).To(Equal("veryverysecretkey"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from YAML data", func() {
BeforeEach(func() {
yamlData := []byte("---\nmy_secret_value: veryverysecretkey\nunknownkey: hi there\nint_var: 42\n")
SetVariableDefaults(VarDefaultsFromYAML(yamlData))
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(42)))
Expect(cfg.MySecretValue).To(Equal("veryverysecretkey"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from YAML file", func() {
var tmp *os.File
BeforeEach(func() {
tmp, _ = ioutil.TempFile("", "")
yamlData := "---\nmy_secret_value: veryverysecretkey\nunknownkey: hi there\nint_var: 42\n"
tmp.WriteString(yamlData)
SetVariableDefaults(VarDefaultsFromYAMLFile(tmp.Name()))
})
AfterEach(func() {
tmp.Close()
os.Remove(tmp.Name())
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(42)))
Expect(cfg.MySecretValue).To(Equal("veryverysecretkey"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from invalid YAML data", func() {
BeforeEach(func() {
yamlData := []byte("---\nmy_secret_value = veryverysecretkey\nunknownkey = hi there\nint_var = 42\n")
SetVariableDefaults(VarDefaultsFromYAML(yamlData))
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(23)))
Expect(cfg.MySecretValue).To(Equal("secret"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from non existent YAML file", func() {
BeforeEach(func() {
file := "/tmp/this_file_should_not_exist_146e26723r"
SetVariableDefaults(VarDefaultsFromYAMLFile(file))
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(23)))
Expect(cfg.MySecretValue).To(Equal("secret"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
})

View file

@ -1,2 +0,0 @@
*.test
*.prof

View file

@ -1,31 +0,0 @@
# Perks for Go (golang.org)
Perks contains the Go package quantile that computes approximate quantiles over
an unbounded data stream within low memory and CPU bounds.
For more information and examples, see:
http://godoc.org/github.com/bmizerany/perks
A very special thank you and shout out to Graham Cormode (Rutgers University),
Flip Korn (AT&T LabsResearch), S. Muthukrishnan (Rutgers University), and
Divesh Srivastava (AT&T LabsResearch) for their research and publication of
[Effective Computation of Biased Quantiles over Data Streams](http://www.cs.rutgers.edu/~muthu/bquant.pdf)
Thank you, also:
* Armon Dadgar (@armon)
* Andrew Gerrand (@nf)
* Brad Fitzpatrick (@bradfitz)
* Keith Rarick (@kr)
FAQ:
Q: Why not move the quantile package into the project root?
A: I want to add more packages to perks later.
Copyright (C) 2013 Blake Mizerany
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.

View file

@ -1,26 +0,0 @@
package histogram
import (
"math/rand"
"testing"
)
func BenchmarkInsert10Bins(b *testing.B) {
b.StopTimer()
h := New(10)
b.StartTimer()
for i := 0; i < b.N; i++ {
f := rand.ExpFloat64()
h.Insert(f)
}
}
func BenchmarkInsert100Bins(b *testing.B) {
b.StopTimer()
h := New(100)
b.StartTimer()
for i := 0; i < b.N; i++ {
f := rand.ExpFloat64()
h.Insert(f)
}
}

View file

@ -1,108 +0,0 @@
// Package histogram provides a Go implementation of BigML's histogram package
// for Clojure/Java. It is currently experimental.
package histogram
import (
"container/heap"
"math"
"sort"
)
type Bin struct {
Count int
Sum float64
}
func (b *Bin) Update(x *Bin) {
b.Count += x.Count
b.Sum += x.Sum
}
func (b *Bin) Mean() float64 {
return b.Sum / float64(b.Count)
}
type Bins []*Bin
func (bs Bins) Len() int { return len(bs) }
func (bs Bins) Less(i, j int) bool { return bs[i].Mean() < bs[j].Mean() }
func (bs Bins) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] }
func (bs *Bins) Push(x interface{}) {
*bs = append(*bs, x.(*Bin))
}
func (bs *Bins) Pop() interface{} {
return bs.remove(len(*bs) - 1)
}
func (bs *Bins) remove(n int) *Bin {
if n < 0 || len(*bs) < n {
return nil
}
x := (*bs)[n]
*bs = append((*bs)[:n], (*bs)[n+1:]...)
return x
}
type Histogram struct {
res *reservoir
}
func New(maxBins int) *Histogram {
return &Histogram{res: newReservoir(maxBins)}
}
func (h *Histogram) Insert(f float64) {
h.res.insert(&Bin{1, f})
h.res.compress()
}
func (h *Histogram) Bins() Bins {
return h.res.bins
}
type reservoir struct {
n int
maxBins int
bins Bins
}
func newReservoir(maxBins int) *reservoir {
return &reservoir{maxBins: maxBins}
}
func (r *reservoir) insert(bin *Bin) {
r.n += bin.Count
i := sort.Search(len(r.bins), func(i int) bool {
return r.bins[i].Mean() >= bin.Mean()
})
if i < 0 || i == r.bins.Len() {
// TODO(blake): Maybe use an .insert(i, bin) instead of
// performing the extra work of a heap.Push.
heap.Push(&r.bins, bin)
return
}
r.bins[i].Update(bin)
}
func (r *reservoir) compress() {
for r.bins.Len() > r.maxBins {
minGapIndex := -1
minGap := math.MaxFloat64
for i := 0; i < r.bins.Len()-1; i++ {
gap := gapWeight(r.bins[i], r.bins[i+1])
if minGap > gap {
minGap = gap
minGapIndex = i
}
}
prev := r.bins[minGapIndex]
next := r.bins.remove(minGapIndex + 1)
prev.Update(next)
}
}
func gapWeight(prev, next *Bin) float64 {
return next.Mean() - prev.Mean()
}

View file

@ -1,38 +0,0 @@
package histogram
import (
"math/rand"
"testing"
)
func TestHistogram(t *testing.T) {
const numPoints = 1e6
const maxBins = 3
h := New(maxBins)
for i := 0; i < numPoints; i++ {
f := rand.ExpFloat64()
h.Insert(f)
}
bins := h.Bins()
if g := len(bins); g > maxBins {
t.Fatalf("got %d bins, wanted <= %d", g, maxBins)
}
for _, b := range bins {
t.Logf("%+v", b)
}
if g := count(h.Bins()); g != numPoints {
t.Fatalf("binned %d points, wanted %d", g, numPoints)
}
}
func count(bins Bins) int {
binCounts := 0
for _, b := range bins {
binCounts += b.Count
}
return binCounts
}

View file

@ -1,63 +0,0 @@
package quantile
import (
"testing"
)
func BenchmarkInsertTargeted(b *testing.B) {
b.ReportAllocs()
s := NewTargeted(Targets)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertTargetedSmallEpsilon(b *testing.B) {
s := NewTargeted(TargetsSmallEpsilon)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertBiased(b *testing.B) {
s := NewLowBiased(0.01)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkInsertBiasedSmallEpsilon(b *testing.B) {
s := NewLowBiased(0.0001)
b.ResetTimer()
for i := float64(0); i < float64(b.N); i++ {
s.Insert(i)
}
}
func BenchmarkQuery(b *testing.B) {
s := NewTargeted(Targets)
for i := float64(0); i < 1e6; i++ {
s.Insert(i)
}
b.ResetTimer()
n := float64(b.N)
for i := float64(0); i < n; i++ {
s.Query(i / n)
}
}
func BenchmarkQuerySmallEpsilon(b *testing.B) {
s := NewTargeted(TargetsSmallEpsilon)
for i := float64(0); i < 1e6; i++ {
s.Insert(i)
}
b.ResetTimer()
n := float64(b.N)
for i := float64(0); i < n; i++ {
s.Query(i / n)
}
}

View file

@ -1,121 +0,0 @@
// +build go1.1
package quantile_test
import (
"bufio"
"fmt"
"log"
"os"
"strconv"
"time"
"github.com/beorn7/perks/quantile"
)
func Example_simple() {
ch := make(chan float64)
go sendFloats(ch)
// Compute the 50th, 90th, and 99th percentile.
q := quantile.NewTargeted(map[float64]float64{
0.50: 0.005,
0.90: 0.001,
0.99: 0.0001,
})
for v := range ch {
q.Insert(v)
}
fmt.Println("perc50:", q.Query(0.50))
fmt.Println("perc90:", q.Query(0.90))
fmt.Println("perc99:", q.Query(0.99))
fmt.Println("count:", q.Count())
// Output:
// perc50: 5
// perc90: 16
// perc99: 223
// count: 2388
}
func Example_mergeMultipleStreams() {
// Scenario:
// We have multiple database shards. On each shard, there is a process
// collecting query response times from the database logs and inserting
// them into a Stream (created via NewTargeted(0.90)), much like the
// Simple example. These processes expose a network interface for us to
// ask them to serialize and send us the results of their
// Stream.Samples so we may Merge and Query them.
//
// NOTES:
// * These sample sets are small, allowing us to get them
// across the network much faster than sending the entire list of data
// points.
//
// * For this to work correctly, we must supply the same quantiles
// a priori the process collecting the samples supplied to NewTargeted,
// even if we do not plan to query them all here.
ch := make(chan quantile.Samples)
getDBQuerySamples(ch)
q := quantile.NewTargeted(map[float64]float64{0.90: 0.001})
for samples := range ch {
q.Merge(samples)
}
fmt.Println("perc90:", q.Query(0.90))
}
func Example_window() {
// Scenario: We want the 90th, 95th, and 99th percentiles for each
// minute.
ch := make(chan float64)
go sendStreamValues(ch)
tick := time.NewTicker(1 * time.Minute)
q := quantile.NewTargeted(map[float64]float64{
0.90: 0.001,
0.95: 0.0005,
0.99: 0.0001,
})
for {
select {
case t := <-tick.C:
flushToDB(t, q.Samples())
q.Reset()
case v := <-ch:
q.Insert(v)
}
}
}
func sendStreamValues(ch chan float64) {
// Use your imagination
}
func flushToDB(t time.Time, samples quantile.Samples) {
// Use your imagination
}
// This is a stub for the above example. In reality this would hit the remote
// servers via http or something like it.
func getDBQuerySamples(ch chan quantile.Samples) {}
func sendFloats(ch chan<- float64) {
f, err := os.Open("exampledata.txt")
if err != nil {
log.Fatal(err)
}
sc := bufio.NewScanner(f)
for sc.Scan() {
b := sc.Bytes()
v, err := strconv.ParseFloat(string(b), 64)
if err != nil {
log.Fatal(err)
}
ch <- v
}
if sc.Err() != nil {
log.Fatal(sc.Err())
}
close(ch)
}

View file

@ -77,15 +77,20 @@ func NewHighBiased(epsilon float64) *Stream {
// is guaranteed to be within (Quantile±Epsilon). // is guaranteed to be within (Quantile±Epsilon).
// //
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties. // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
func NewTargeted(targets map[float64]float64) *Stream { func NewTargeted(targetMap map[float64]float64) *Stream {
// Convert map to slice to avoid slow iterations on a map.
// ƒ is called on the hot path, so converting the map to a slice
// beforehand results in significant CPU savings.
targets := targetMapToSlice(targetMap)
ƒ := func(s *stream, r float64) float64 { ƒ := func(s *stream, r float64) float64 {
var m = math.MaxFloat64 var m = math.MaxFloat64
var f float64 var f float64
for quantile, epsilon := range targets { for _, t := range targets {
if quantile*s.n <= r { if t.quantile*s.n <= r {
f = (2 * epsilon * r) / quantile f = (2 * t.epsilon * r) / t.quantile
} else { } else {
f = (2 * epsilon * (s.n - r)) / (1 - quantile) f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
} }
if f < m { if f < m {
m = f m = f
@ -96,6 +101,25 @@ func NewTargeted(targets map[float64]float64) *Stream {
return newStream(ƒ) return newStream(ƒ)
} }
type target struct {
quantile float64
epsilon float64
}
func targetMapToSlice(targetMap map[float64]float64) []target {
targets := make([]target, 0, len(targetMap))
for quantile, epsilon := range targetMap {
t := target{
quantile: quantile,
epsilon: epsilon,
}
targets = append(targets, t)
}
return targets
}
// Stream computes quantiles for a stream of float64s. It is not thread-safe by // Stream computes quantiles for a stream of float64s. It is not thread-safe by
// design. Take care when using across multiple goroutines. // design. Take care when using across multiple goroutines.
type Stream struct { type Stream struct {

View file

@ -1,215 +0,0 @@
package quantile
import (
"math"
"math/rand"
"sort"
"testing"
)
var (
Targets = map[float64]float64{
0.01: 0.001,
0.10: 0.01,
0.50: 0.05,
0.90: 0.01,
0.99: 0.001,
}
TargetsSmallEpsilon = map[float64]float64{
0.01: 0.0001,
0.10: 0.001,
0.50: 0.005,
0.90: 0.001,
0.99: 0.0001,
}
LowQuantiles = []float64{0.01, 0.1, 0.5}
HighQuantiles = []float64{0.99, 0.9, 0.5}
)
const RelativeEpsilon = 0.01
func verifyPercsWithAbsoluteEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for quantile, epsilon := range Targets {
n := float64(len(a))
k := int(quantile * n)
if k < 1 {
k = 1
}
lower := int((quantile - epsilon) * n)
if lower < 1 {
lower = 1
}
upper := int(math.Ceil((quantile + epsilon) * n))
if upper > len(a) {
upper = len(a)
}
w, min, max := a[k-1], a[lower-1], a[upper-1]
if g := s.Query(quantile); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", quantile, w, min, max, g)
}
}
}
func verifyLowPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for _, qu := range LowQuantiles {
n := float64(len(a))
k := int(qu * n)
lowerRank := int((1 - RelativeEpsilon) * qu * n)
upperRank := int(math.Ceil((1 + RelativeEpsilon) * qu * n))
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
if g := s.Query(qu); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
}
}
}
func verifyHighPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) {
sort.Float64s(a)
for _, qu := range HighQuantiles {
n := float64(len(a))
k := int(qu * n)
lowerRank := int((1 - (1+RelativeEpsilon)*(1-qu)) * n)
upperRank := int(math.Ceil((1 - (1-RelativeEpsilon)*(1-qu)) * n))
w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1]
if g := s.Query(qu); g < min || g > max {
t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g)
}
}
}
func populateStream(s *Stream) []float64 {
a := make([]float64, 0, 1e5+100)
for i := 0; i < cap(a); i++ {
v := rand.NormFloat64()
// Add 5% asymmetric outliers.
if i%20 == 0 {
v = v*v + 1
}
s.Insert(v)
a = append(a, v)
}
return a
}
func TestTargetedQuery(t *testing.T) {
rand.Seed(42)
s := NewTargeted(Targets)
a := populateStream(s)
verifyPercsWithAbsoluteEpsilon(t, a, s)
}
func TestTargetedQuerySmallSampleSize(t *testing.T) {
rand.Seed(42)
s := NewTargeted(TargetsSmallEpsilon)
a := []float64{1, 2, 3, 4, 5}
for _, v := range a {
s.Insert(v)
}
verifyPercsWithAbsoluteEpsilon(t, a, s)
// If not yet flushed, results should be precise:
if !s.flushed() {
for φ, want := range map[float64]float64{
0.01: 1,
0.10: 1,
0.50: 3,
0.90: 5,
0.99: 5,
} {
if got := s.Query(φ); got != want {
t.Errorf("want %f for φ=%f, got %f", want, φ, got)
}
}
}
}
func TestLowBiasedQuery(t *testing.T) {
rand.Seed(42)
s := NewLowBiased(RelativeEpsilon)
a := populateStream(s)
verifyLowPercsWithRelativeEpsilon(t, a, s)
}
func TestHighBiasedQuery(t *testing.T) {
rand.Seed(42)
s := NewHighBiased(RelativeEpsilon)
a := populateStream(s)
verifyHighPercsWithRelativeEpsilon(t, a, s)
}
// BrokenTestTargetedMerge is broken, see Merge doc comment.
func BrokenTestTargetedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewTargeted(Targets)
s2 := NewTargeted(Targets)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyPercsWithAbsoluteEpsilon(t, a, s1)
}
// BrokenTestLowBiasedMerge is broken, see Merge doc comment.
func BrokenTestLowBiasedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewLowBiased(RelativeEpsilon)
s2 := NewLowBiased(RelativeEpsilon)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyLowPercsWithRelativeEpsilon(t, a, s2)
}
// BrokenTestHighBiasedMerge is broken, see Merge doc comment.
func BrokenTestHighBiasedMerge(t *testing.T) {
rand.Seed(42)
s1 := NewHighBiased(RelativeEpsilon)
s2 := NewHighBiased(RelativeEpsilon)
a := populateStream(s1)
a = append(a, populateStream(s2)...)
s1.Merge(s2.Samples())
verifyHighPercsWithRelativeEpsilon(t, a, s2)
}
func TestUncompressed(t *testing.T) {
q := NewTargeted(Targets)
for i := 100; i > 0; i-- {
q.Insert(float64(i))
}
if g := q.Count(); g != 100 {
t.Errorf("want count 100, got %d", g)
}
// Before compression, Query should have 100% accuracy.
for quantile := range Targets {
w := quantile * 100
if g := q.Query(quantile); g != w {
t.Errorf("want %f, got %f", w, g)
}
}
}
func TestUncompressedSamples(t *testing.T) {
q := NewTargeted(map[float64]float64{0.99: 0.001})
for i := 1; i <= 100; i++ {
q.Insert(float64(i))
}
if g := q.Samples().Len(); g != 100 {
t.Errorf("want count 100, got %d", g)
}
}
func TestUncompressedOne(t *testing.T) {
q := NewTargeted(map[float64]float64{0.99: 0.01})
q.Insert(3.14)
if g := q.Query(0.90); g != 3.14 {
t.Error("want PI, got", g)
}
}
func TestDefaults(t *testing.T) {
if g := NewTargeted(map[float64]float64{0.99: 0.001}).Query(0.99); g != 0 {
t.Errorf("want 0, got %f", g)
}
}

View file

@ -1,90 +0,0 @@
package topk
import (
"sort"
)
// http://www.cs.ucsb.edu/research/tech_reports/reports/2005-23.pdf
type Element struct {
Value string
Count int
}
type Samples []*Element
func (sm Samples) Len() int {
return len(sm)
}
func (sm Samples) Less(i, j int) bool {
return sm[i].Count < sm[j].Count
}
func (sm Samples) Swap(i, j int) {
sm[i], sm[j] = sm[j], sm[i]
}
type Stream struct {
k int
mon map[string]*Element
// the minimum Element
min *Element
}
func New(k int) *Stream {
s := new(Stream)
s.k = k
s.mon = make(map[string]*Element)
s.min = &Element{}
// Track k+1 so that less frequenet items contended for that spot,
// resulting in k being more accurate.
return s
}
func (s *Stream) Insert(x string) {
s.insert(&Element{x, 1})
}
func (s *Stream) Merge(sm Samples) {
for _, e := range sm {
s.insert(e)
}
}
func (s *Stream) insert(in *Element) {
e := s.mon[in.Value]
if e != nil {
e.Count++
} else {
if len(s.mon) < s.k+1 {
e = &Element{in.Value, in.Count}
s.mon[in.Value] = e
} else {
e = s.min
delete(s.mon, e.Value)
e.Value = in.Value
e.Count += in.Count
s.min = e
}
}
if e.Count < s.min.Count {
s.min = e
}
}
func (s *Stream) Query() Samples {
var sm Samples
for _, e := range s.mon {
sm = append(sm, e)
}
sort.Sort(sort.Reverse(sm))
if len(sm) < s.k {
return sm
}
return sm[:s.k]
}

View file

@ -1,57 +0,0 @@
package topk
import (
"fmt"
"math/rand"
"sort"
"testing"
)
func TestTopK(t *testing.T) {
stream := New(10)
ss := []*Stream{New(10), New(10), New(10)}
m := make(map[string]int)
for _, s := range ss {
for i := 0; i < 1e6; i++ {
v := fmt.Sprintf("%x", int8(rand.ExpFloat64()))
s.Insert(v)
m[v]++
}
stream.Merge(s.Query())
}
var sm Samples
for x, s := range m {
sm = append(sm, &Element{x, s})
}
sort.Sort(sort.Reverse(sm))
g := stream.Query()
if len(g) != 10 {
t.Fatalf("got %d, want 10", len(g))
}
for i, e := range g {
if sm[i].Value != e.Value {
t.Errorf("at %d: want %q, got %q", i, sm[i].Value, e.Value)
}
}
}
func TestQuery(t *testing.T) {
queryTests := []struct {
value string
expected int
}{
{"a", 1},
{"b", 2},
{"c", 2},
}
stream := New(2)
for _, tt := range queryTests {
stream.Insert(tt.value)
if n := len(stream.Query()); n != tt.expected {
t.Errorf("want %d, got %d", tt.expected, n)
}
}
}

View file

@ -7,7 +7,6 @@
_obj _obj
_test _test
.idea .idea
.vscode
# Architecture specific extensions/prefixes # Architecture specific extensions/prefixes
*.[568vq] *.[568vq]

View file

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

View file

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

View file

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

View file

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

View file

@ -1,68 +0,0 @@
TODO:
* What are filters?
* List+explain all existing filters (pongo2 + pongo2-addons)
Implemented filters so far which needs documentation:
* escape
* safe
* escapejs
* add
* addslashes
* capfirst
* center
* cut
* date
* default
* default_if_none
* divisibleby
* first
* floatformat
* get_digit
* iriencode
* join
* last
* length
* length_is
* linebreaks
* linebreaksbr
* linenumbers
* ljust
* lower
* make_list
* phone2numeric
* pluralize
* random
* removetags
* rjust
* slice
* stringformat
* striptags
* time
* title
* truncatechars
* truncatechars_html
* truncatewords
* truncatewords_html
* upper
* urlencode
* urlize
* urlizetrunc
* wordcount
* wordwrap
* yesno
* filesizeformat*
* slugify*
* truncatesentences*
* truncatesentences_html*
* markdown*
* intcomma*
* ordinal*
* naturalday*
* timesince*
* timeuntil*
* naturaltime*
Filters marked with * are available through [pongo2-addons](https://github.com/flosch/pongo2-addons).

View file

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

View file

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

View file

@ -1,31 +0,0 @@
TODO:
* What are tags?
* List+explain all existing tags (pongo2 + pongo2-addons)
Implemented tags so far which needs documentation:
* autoescape
* block
* comment
* cycle
* extends
* filter
* firstof
* for
* if
* ifchanged
* ifequal
* ifnotequal
* import
* include
* lorem
* macro
* now
* set
* spaceless
* ssi
* templatetag
* verbatim
* widthratio
* with

View file

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

View file

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,29 +0,0 @@
package pongo2_test
import (
"testing"
"github.com/flosch/pongo2"
)
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)
}
}

View file

@ -1,534 +0,0 @@
package pongo2_test
import (
"bytes"
"fmt"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
"github.com/flosch/pongo2"
)
var adminList = []string{"user2"}
var time1 = time.Date(2014, 06, 10, 15, 30, 15, 0, time.UTC)
var time2 = time.Date(2011, 03, 21, 8, 37, 56, 12, time.UTC)
type post struct {
Text string
Created time.Time
}
type user struct {
Name string
Validated bool
}
type comment struct {
Author *user
Date time.Time
Text string
}
func isAdmin(u *user) bool {
for _, a := range adminList {
if a == u.Name {
return true
}
}
return false
}
func (u *user) Is_admin() *pongo2.Value {
return pongo2.AsValue(isAdmin(u))
}
func (u *user) Is_admin2() bool {
return isAdmin(u)
}
func (p *post) String() string {
return ":-)"
}
/*
* Start setup sandbox
*/
type tagSandboxDemoTag struct {
}
func (node *tagSandboxDemoTag) Execute(ctx *pongo2.ExecutionContext, writer pongo2.TemplateWriter) *pongo2.Error {
writer.WriteString("hello")
return nil
}
func tagSandboxDemoTagParser(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) {
return &tagSandboxDemoTag{}, nil
}
func BannedFilterFn(in *pongo2.Value, params *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
return in, nil
}
func init() {
pongo2.DefaultSet.Debug = true
pongo2.RegisterFilter("banned_filter", BannedFilterFn)
pongo2.RegisterFilter("unbanned_filter", BannedFilterFn)
pongo2.RegisterTag("banned_tag", tagSandboxDemoTagParser)
pongo2.RegisterTag("unbanned_tag", tagSandboxDemoTagParser)
pongo2.DefaultSet.BanFilter("banned_filter")
pongo2.DefaultSet.BanTag("banned_tag")
f, err := ioutil.TempFile("/tmp/", "pongo2_")
if err != nil {
panic("cannot write to /tmp/")
}
f.Write([]byte("Hello from pongo2"))
pongo2.DefaultSet.Globals["temp_file"] = f.Name()
}
/*
* End setup sandbox
*/
var tplContext = pongo2.Context{
"number": 11,
"simple": map[string]interface{}{
"number": 42,
"name": "john doe",
"included_file": "INCLUDES.helper",
"included_file_not_exists": "INCLUDES.helper.not_exists",
"nil": nil,
"uint": uint(8),
"float": float64(3.1415),
"str": "string",
"chinese_hello_world": "你好世界",
"bool_true": true,
"bool_false": false,
"newline_text": `this is a text
with a new line in it`,
"long_text": `This is a simple text.
This too, as a paragraph.
Right?
Yep!`,
"escape_js_test": `escape sequences \r\n\'\" special chars "?!=$<>`,
"one_item_list": []int{99},
"multiple_item_list": []int{1, 1, 2, 3, 5, 8, 13, 21, 34, 55},
"unsorted_int_list": []int{192, 581, 22, 1, 249, 9999, 1828591, 8271},
"fixed_item_list": [...]int{1, 2, 3, 4},
"misc_list": []interface{}{"Hello", 99, 3.14, "good"},
"escape_text": "This is \\a Test. \"Yep\". 'Yep'.",
"xss": "<script>alert(\"uh oh\");</script>",
"intmap": map[int]string{
1: "one",
5: "five",
2: "two",
},
"strmap": map[string]string{
"abc": "def",
"bcd": "efg",
"zab": "cde",
"gh": "kqm",
"ukq": "qqa",
"aab": "aba",
},
"func_add": func(a, b int) int {
return a + b
},
"func_add_iface": func(a, b interface{}) interface{} {
return a.(int) + b.(int)
},
"func_variadic": func(msg string, args ...interface{}) string {
return fmt.Sprintf(msg, args...)
},
"func_variadic_sum_int": func(args ...int) int {
// Create a sum
s := 0
for _, i := range args {
s += i
}
return s
},
"func_variadic_sum_int2": func(args ...*pongo2.Value) *pongo2.Value {
// Create a sum
s := 0
for _, i := range args {
s += i.Integer()
}
return pongo2.AsValue(s)
},
},
"complex": map[string]interface{}{
"is_admin": isAdmin,
"post": post{
Text: "<h2>Hello!</h2><p>Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.</p>",
Created: time2,
},
"comments": []*comment{
&comment{
Author: &user{
Name: "user1",
Validated: true,
},
Date: time1,
Text: "\"pongo2 is nice!\"",
},
&comment{
Author: &user{
Name: "user2",
Validated: true,
},
Date: time2,
Text: "comment2 with <script>unsafe</script> tags in it",
},
&comment{
Author: &user{
Name: "user3",
Validated: false,
},
Date: time1,
Text: "<b>hello!</b> there",
},
},
"comments2": []*comment{
&comment{
Author: &user{
Name: "user1",
Validated: true,
},
Date: time2,
Text: "\"pongo2 is nice!\"",
},
&comment{
Author: &user{
Name: "user1",
Validated: true,
},
Date: time1,
Text: "comment2 with <script>unsafe</script> tags in it",
},
&comment{
Author: &user{
Name: "user3",
Validated: false,
},
Date: time1,
Text: "<b>hello!</b> there",
},
},
},
}
func TestTemplates(t *testing.T) {
// Add a global to the default set
pongo2.Globals["this_is_a_global_variable"] = "this is a global text"
matches, err := filepath.Glob("./template_tests/*.tpl")
if err != nil {
t.Fatal(err)
}
for idx, match := range matches {
t.Logf("[Template %3d] Testing '%s'", idx+1, match)
tpl, err := pongo2.FromFile(match)
if err != nil {
t.Fatalf("Error on FromFile('%s'): %s", match, err.Error())
}
testFilename := fmt.Sprintf("%s.out", match)
testOut, rerr := ioutil.ReadFile(testFilename)
if rerr != nil {
t.Fatalf("Error on ReadFile('%s'): %s", testFilename, rerr.Error())
}
tplOut, err := tpl.ExecuteBytes(tplContext)
if err != nil {
t.Fatalf("Error on Execute('%s'): %s", match, err.Error())
}
if bytes.Compare(testOut, tplOut) != 0 {
t.Logf("Template (rendered) '%s': '%s'", match, tplOut)
errFilename := filepath.Base(fmt.Sprintf("%s.error", match))
err := ioutil.WriteFile(errFilename, []byte(tplOut), 0600)
if err != nil {
t.Fatalf(err.Error())
}
t.Logf("get a complete diff with command: 'diff -ya %s %s'", testFilename, errFilename)
t.Errorf("Failed: test_out != tpl_out for %s", match)
}
}
}
func TestExecutionErrors(t *testing.T) {
//debug = true
matches, err := filepath.Glob("./template_tests/*-execution.err")
if err != nil {
t.Fatal(err)
}
for idx, match := range matches {
t.Logf("[Errors %3d] Testing '%s'", idx+1, match)
testData, err := ioutil.ReadFile(match)
tests := strings.Split(string(testData), "\n")
checkFilename := fmt.Sprintf("%s.out", match)
checkData, err := ioutil.ReadFile(checkFilename)
if err != nil {
t.Fatalf("Error on ReadFile('%s'): %s", checkFilename, err.Error())
}
checks := strings.Split(string(checkData), "\n")
if len(checks) != len(tests) {
t.Fatal("Template lines != Checks lines")
}
for idx, test := range tests {
if strings.TrimSpace(test) == "" {
continue
}
if strings.TrimSpace(checks[idx]) == "" {
t.Fatalf("[%s Line %d] Check is empty (must contain an regular expression).",
match, idx+1)
}
tpl, err := pongo2.FromString(test)
if err != nil {
t.Fatalf("Error on FromString('%s'): %s", test, err.Error())
}
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.Logf("[Errors %3d] Testing '%s'", idx+1, match)
testData, err := ioutil.ReadFile(match)
tests := strings.Split(string(testData), "\n")
checkFilename := fmt.Sprintf("%s.out", match)
checkData, err := ioutil.ReadFile(checkFilename)
if err != nil {
t.Fatalf("Error on ReadFile('%s'): %s", checkFilename, err.Error())
}
checks := strings.Split(string(checkData), "\n")
if len(checks) != len(tests) {
t.Fatal("Template lines != Checks lines")
}
for idx, test := range tests {
if strings.TrimSpace(test) == "" {
continue
}
if strings.TrimSpace(checks[idx]) == "" {
t.Fatalf("[%s Line %d] Check is empty (must contain an regular expression).",
match, idx+1)
}
_, err = pongo2.FromString(test)
if err == nil {
t.Fatalf("[%s | Line %d] Expected error for (got none): %s", match, idx+1, tests[idx])
}
re := regexp.MustCompile(fmt.Sprintf("^%s$", checks[idx]))
if !re.MatchString(err.Error()) {
t.Fatalf("[%s | Line %d] Error for '%s' (err = '%s') does not match the (regexp-)check: %s",
match, idx+1, test, err.Error(), checks[idx])
}
}
}
}
func TestBaseDirectory(t *testing.T) {
mustStr := "Hello from template_tests/base_dir_test/"
fs := pongo2.MustNewLocalFileSystemLoader("")
s := pongo2.NewSet("test set with base directory", fs)
s.Globals["base_directory"] = "template_tests/base_dir_test/"
if err := fs.SetBaseDir(s.Globals["base_directory"].(string)); err != nil {
t.Fatal(err)
}
matches, err := filepath.Glob("./template_tests/base_dir_test/subdir/*")
if err != nil {
t.Fatal(err)
}
for _, match := range matches {
match = strings.Replace(match, "template_tests/base_dir_test/", "", -1)
tpl, err := s.FromFile(match)
if err != nil {
t.Fatal(err)
}
out, err := tpl.Execute(nil)
if err != nil {
t.Fatal(err)
}
if out != mustStr {
t.Errorf("%s: out ('%s') != mustStr ('%s')", match, out, mustStr)
}
}
}
func BenchmarkCache(b *testing.B) {
cacheSet := pongo2.NewSet("cache set", pongo2.MustNewLocalFileSystemLoader(""))
for i := 0; i < b.N; i++ {
tpl, err := cacheSet.FromCache("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkCacheDebugOn(b *testing.B) {
cacheDebugSet := pongo2.NewSet("cache set", pongo2.MustNewLocalFileSystemLoader(""))
cacheDebugSet.Debug = true
for i := 0; i < b.N; i++ {
tpl, err := cacheDebugSet.FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkExecuteComplexWithSandboxActive(b *testing.B) {
tpl, err := pongo2.FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkCompileAndExecuteComplexWithSandboxActive(b *testing.B) {
buf, err := ioutil.ReadFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
preloadedTpl := string(buf)
b.ResetTimer()
for i := 0; i < b.N; i++ {
tpl, err := pongo2.FromString(preloadedTpl)
if err != nil {
b.Fatal(err)
}
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkParallelExecuteComplexWithSandboxActive(b *testing.B) {
tpl, err := pongo2.FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
err := tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
}
})
}
func BenchmarkExecuteComplexWithoutSandbox(b *testing.B) {
s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader(""))
tpl, err := s.FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkCompileAndExecuteComplexWithoutSandbox(b *testing.B) {
buf, err := ioutil.ReadFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
preloadedTpl := string(buf)
s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader(""))
b.ResetTimer()
for i := 0; i < b.N; i++ {
tpl, err := s.FromString(preloadedTpl)
if err != nil {
b.Fatal(err)
}
err = tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkParallelExecuteComplexWithoutSandbox(b *testing.B) {
s := pongo2.NewSet("set without sandbox", pongo2.MustNewLocalFileSystemLoader(""))
tpl, err := s.FromFile("template_tests/complex.tpl")
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
err := tpl.ExecuteWriterUnbuffered(tplContext, ioutil.Discard)
if err != nil {
b.Fatal(err)
}
}
})
}

View file

@ -1,103 +0,0 @@
package pongo2_test
import (
"testing"
"github.com/flosch/pongo2"
. "gopkg.in/check.v1"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct {
tpl *pongo2.Template
}
var (
_ = Suite(&TestSuite{})
testSuite2 = pongo2.NewSet("test suite 2", pongo2.MustNewLocalFileSystemLoader(""))
)
func parseTemplate(s string, c pongo2.Context) string {
t, err := testSuite2.FromString(s)
if err != nil {
panic(err)
}
out, err := t.Execute(c)
if err != nil {
panic(err)
}
return out
}
func parseTemplateFn(s string, c pongo2.Context) func() {
return func() {
parseTemplate(s, c)
}
}
func (s *TestSuite) TestMisc(c *C) {
// Must
// TODO: Add better error message (see issue #18)
c.Check(
func() { pongo2.Must(testSuite2.FromFile("template_tests/inheritance/base2.tpl")) },
PanicMatches,
`\[Error \(where: fromfile\) in .*template_tests/inheritance/doesnotexist.tpl | Line 1 Col 12 near 'doesnotexist.tpl'\] open .*template_tests/inheritance/doesnotexist.tpl: no such file or directory`,
)
// Context
c.Check(parseTemplateFn("", pongo2.Context{"'illegal": nil}), PanicMatches, ".*not a valid identifier.*")
// Registers
c.Check(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)
}

View file

@ -21,8 +21,6 @@ package pongo2
import ( import (
"fmt" "fmt"
"github.com/juju/errors"
) )
type INodeTag interface { type INodeTag interface {
@ -55,81 +53,80 @@ func init() {
tags = make(map[string]*tag) tags = make(map[string]*tag)
} }
// Registers a new tag. You usually want to call this // Registers a new tag. If there's already a tag with the same
// name, RegisterTag will panic. You usually want to call this
// function in the tag's init() function: // function in the tag's init() function:
// http://golang.org/doc/effective_go.html#init // http://golang.org/doc/effective_go.html#init
// //
// See http://www.florian-schlachter.de/post/pongo2/ for more about // See http://www.florian-schlachter.de/post/pongo2/ for more about
// writing filters and tags. // writing filters and tags.
func RegisterTag(name string, parserFn TagParser) error { func RegisterTag(name string, parserFn TagParser) {
_, existing := tags[name] _, existing := tags[name]
if existing { if existing {
return errors.Errorf("tag with name '%s' is already registered", name) panic(fmt.Sprintf("Tag with name '%s' is already registered.", name))
} }
tags[name] = &tag{ tags[name] = &tag{
name: name, name: name,
parser: parserFn, parser: parserFn,
} }
return nil
} }
// Replaces an already registered tag with a new implementation. Use this // Replaces an already registered tag with a new implementation. Use this
// function with caution since it allows you to change existing tag behaviour. // function with caution since it allows you to change existing tag behaviour.
func ReplaceTag(name string, parserFn TagParser) error { func ReplaceTag(name string, parserFn TagParser) {
_, existing := tags[name] _, existing := tags[name]
if !existing { if !existing {
return errors.Errorf("tag with name '%s' does not exist (therefore cannot be overridden)", name) panic(fmt.Sprintf("Tag with name '%s' does not exist (therefore cannot be overridden).", name))
} }
tags[name] = &tag{ tags[name] = &tag{
name: name, name: name,
parser: parserFn, parser: parserFn,
} }
return nil
} }
// Tag = "{%" IDENT ARGS "%}" // Tag = "{%" IDENT ARGS "%}"
func (p *Parser) parseTagElement() (INodeTag, *Error) { func (p *Parser) parseTagElement() (INodeTag, *Error) {
p.Consume() // consume "{%" p.Consume() // consume "{%"
tokenName := p.MatchType(TokenIdentifier) token_name := p.MatchType(TokenIdentifier)
// Check for identifier // Check for identifier
if tokenName == nil { if token_name == nil {
return nil, p.Error("Tag name must be an identifier.", nil) return nil, p.Error("Tag name must be an identifier.", nil)
} }
// Check for the existing tag // Check for the existing tag
tag, exists := tags[tokenName.Val] tag, exists := tags[token_name.Val]
if !exists { if !exists {
// Does not exists // Does not exists
return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", tokenName.Val), tokenName) return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", token_name.Val), token_name)
} }
// Check sandbox tag restriction // Check sandbox tag restriction
if _, isBanned := p.template.set.bannedTags[tokenName.Val]; isBanned { if _, is_banned := p.template.set.bannedTags[token_name.Val]; is_banned {
return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", tokenName.Val), tokenName) return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", token_name.Val), token_name)
} }
var argsToken []*Token args_token := make([]*Token, 0)
for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 { for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 {
// Add token to args // Add token to args
argsToken = append(argsToken, p.Current()) args_token = append(args_token, p.Current())
p.Consume() // next token p.Consume() // next token
} }
// EOF? // EOF?
if p.Remaining() == 0 { if p.Remaining() == 0 {
return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.lastToken) return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.last_token)
} }
p.Match(TokenSymbol, "%}") p.Match(TokenSymbol, "%}")
argParser := newParser(p.name, argsToken, p.template) arg_parser := newParser(p.name, args_token, p.template)
if len(argsToken) == 0 { if len(args_token) == 0 {
// This is done to have nice EOF error messages // This is done to have nice EOF error messages
argParser.lastToken = tokenName arg_parser.last_token = token_name
} }
p.template.level++ p.template.level++
defer func() { p.template.level-- }() defer func() { p.template.level-- }()
return tag.parser(p, tokenName, argParser) return tag.parser(p, token_name, arg_parser)
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,157 +0,0 @@
package pongo2
import (
"bytes"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"github.com/juju/errors"
)
// LocalFilesystemLoader represents a local filesystem loader with basic
// BaseDirectory capabilities. The access to the local filesystem is unrestricted.
type LocalFilesystemLoader struct {
baseDir string
}
// MustNewLocalFileSystemLoader creates a new LocalFilesystemLoader instance
// and panics if there's any error during instantiation. The parameters
// are the same like NewLocalFileSystemLoader.
func MustNewLocalFileSystemLoader(baseDir string) *LocalFilesystemLoader {
fs, err := NewLocalFileSystemLoader(baseDir)
if err != nil {
log.Panic(err)
}
return fs
}
// NewLocalFileSystemLoader creates a new LocalFilesystemLoader and allows
// templatesto be loaded from disk (unrestricted). If any base directory
// is given (or being set using SetBaseDir), this base directory is being used
// for path calculation in template inclusions/imports. Otherwise the path
// is calculated based relatively to the including template's path.
func NewLocalFileSystemLoader(baseDir string) (*LocalFilesystemLoader, error) {
fs := &LocalFilesystemLoader{}
if baseDir != "" {
if err := fs.SetBaseDir(baseDir); err != nil {
return nil, err
}
}
return fs, nil
}
// SetBaseDir sets the template's base directory. This directory will
// be used for any relative path in filters, tags and From*-functions to determine
// your template. See the comment for NewLocalFileSystemLoader as well.
func (fs *LocalFilesystemLoader) SetBaseDir(path string) error {
// Make the path absolute
if !filepath.IsAbs(path) {
abs, err := filepath.Abs(path)
if err != nil {
return err
}
path = abs
}
// Check for existence
fi, err := os.Stat(path)
if err != nil {
return err
}
if !fi.IsDir() {
return errors.Errorf("The given path '%s' is not a directory.", path)
}
fs.baseDir = path
return nil
}
// Get reads the path's content from your local filesystem.
func (fs *LocalFilesystemLoader) Get(path string) (io.Reader, error) {
buf, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return bytes.NewReader(buf), nil
}
// Abs resolves a filename relative to the base directory. Absolute paths are allowed.
// When there's no base dir set, the absolute path to the filename
// will be calculated based on either the provided base directory (which
// might be a path of a template which includes another template) or
// the current working directory.
func (fs *LocalFilesystemLoader) Abs(base, name string) string {
if filepath.IsAbs(name) {
return name
}
// Our own base dir has always priority; if there's none
// we use the path provided in base.
var err error
if fs.baseDir == "" {
if base == "" {
base, err = os.Getwd()
if err != nil {
panic(err)
}
return filepath.Join(base, name)
}
return filepath.Join(filepath.Dir(base), name)
}
return filepath.Join(fs.baseDir, name)
}
// SandboxedFilesystemLoader is still WIP.
type SandboxedFilesystemLoader struct {
*LocalFilesystemLoader
}
// NewSandboxedFilesystemLoader creates a new sandboxed local file system instance.
func NewSandboxedFilesystemLoader(baseDir string) (*SandboxedFilesystemLoader, error) {
fs, err := NewLocalFileSystemLoader(baseDir)
if err != nil {
return nil, err
}
return &SandboxedFilesystemLoader{
LocalFilesystemLoader: fs,
}, nil
}
// Move sandbox to a virtual fs
/*
if len(set.SandboxDirectories) > 0 {
defer func() {
// Remove any ".." or other crap
resolvedPath = filepath.Clean(resolvedPath)
// Make the path absolute
absPath, err := filepath.Abs(resolvedPath)
if err != nil {
panic(err)
}
resolvedPath = absPath
// Check against the sandbox directories (once one pattern matches, we're done and can allow it)
for _, pattern := range set.SandboxDirectories {
matched, err := filepath.Match(pattern, resolvedPath)
if err != nil {
panic("Wrong sandbox directory match pattern (see http://golang.org/pkg/path/filepath/#Match).")
}
if matched {
// OK!
return
}
}
// No pattern matched, we have to log+deny the request
set.logf("Access attempt outside of the sandbox directories (blocked): '%s'", resolvedPath)
resolvedPath = ""
}()
}
*/

View file

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

View file

@ -1,10 +0,0 @@
{{ "<script>alert('xss');</script>" }}
{% autoescape off %}
{{ "<script>alert('xss');</script>" }}
{% endautoescape %}
{% autoescape on %}
{{ "<script>alert('xss');</script>" }}
{% endautoescape %}
{% autoescape off %}
{{ "<script>alert('xss');</script>"|escape }}
{% endautoescape %}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,50 +0,0 @@
empty 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

View file

@ -1,39 +0,0 @@
empty 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

View file

@ -1,32 +0,0 @@
{# A more complex template using pongo2 (fully django-compatible template) #}
<!DOCTYPE html>
<html>
<head>
<title>My blog page</title>
</head>
<body>
<h1>Blogpost</h1>
<div id="content">
{{ complex.post.Text|safe }}
</div>
<h1>Comments</h1>
{% for comment in complex.comments %}
<h2>{{ forloop.Counter }}. Comment ({{ forloop.Revcounter}} comment{{ forloop.Revcounter|pluralize:"s" }} left)</h2>
<p>From: {{ comment.Author.Name }} ({{ comment.Author.Validated|yesno:"validated,not validated,unknown validation status" }})</p>
{% if complex.is_admin(comment.Author) %}
<p>This user is an admin (verify: {{ comment.Author.Is_admin }})!</p>
{% else %}
<p>This user is not admin!</p>
{% endif %}
<p>Written {{ comment.Date }}</p>
<p>{{ comment.Text|striptags }}</p>
{% endfor %}
</body>
</html>

View file

@ -1,50 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>My blog page</title>
</head>
<body>
<h1>Blogpost</h1>
<div id="content">
<h2>Hello!</h2><p>Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.</p>
</div>
<h1>Comments</h1>
<h2>1. Comment (3 comments left)</h2>
<p>From: user1 (validated)</p>
<p>This user is not admin!</p>
<p>Written 2014-06-10 15:30:15 +0000 UTC</p>
<p>&quot;pongo2 is nice!&quot;</p>
<h2>2. Comment (2 comments left)</h2>
<p>From: user2 (validated)</p>
<p>This user is an admin (verify: True)!</p>
<p>Written 2011-03-21 08:37:56.000000012 +0000 UTC</p>
<p>comment2 with unsafe tags in it</p>
<h2>3. Comment (1 comment left)</h2>
<p>From: user3 (not validated)</p>
<p>This user is not admin!</p>
<p>Written 2014-06-10 15:30:15 +0000 UTC</p>
<p>hello! there</p>
</body>
</html>

View file

@ -1,22 +0,0 @@
{% for item in simple.multiple_item_list %}
'{% cycle "item1" simple.name simple.number %}'
{% endfor %}
{% for item in simple.multiple_item_list %}
'{% cycle "item1" simple.name simple.number as cycleitem %}'
May I present the cycle item again: '{{ cycleitem }}'
{% endfor %}
{% for item in simple.multiple_item_list %}
'{% cycle "item1" simple.name simple.number as cycleitem silent %}'
May I present the cycle item: '{{ cycleitem }}'
{% endfor %}
{% for item in simple.multiple_item_list %}
'{% cycle "item1" simple.name simple.number as cycleitem silent %}'
May I present the cycle item: '{{ cycleitem }}'
{% include "inheritance/cycle_include.tpl" %}
{% endfor %}
'{% cycle "item1" simple.name simple.number as cycleitem %}'
'{% cycle cycleitem %}'
'{% cycle "item1" simple.name simple.number as cycleitem silent %}'
'{{ cycleitem }}'
'{% cycle cycleitem %}'
'{{ cycleitem }}'

View file

@ -1,130 +0,0 @@
'item1'
'john doe'
'42'
'item1'
'john doe'
'42'
'item1'
'john doe'
'42'
'item1'
'item1'
May I present the cycle item again: 'item1'
'john doe'
May I present the cycle item again: 'john doe'
'42'
May I present the cycle item again: '42'
'item1'
May I present the cycle item again: 'item1'
'john doe'
May I present the cycle item again: 'john doe'
'42'
May I present the cycle item again: '42'
'item1'
May I present the cycle item again: 'item1'
'john doe'
May I present the cycle item again: 'john doe'
'42'
May I present the cycle item again: '42'
'item1'
May I present the cycle item again: 'item1'
''
May I present the cycle item: 'item1'
''
May I present the cycle item: 'john doe'
''
May I present the cycle item: '42'
''
May I present the cycle item: 'item1'
''
May I present the cycle item: 'john doe'
''
May I present the cycle item: '42'
''
May I present the cycle item: 'item1'
''
May I present the cycle item: 'john doe'
''
May I present the cycle item: '42'
''
May I present the cycle item: 'item1'
''
May I present the cycle item: 'item1'
Included 'item1'.
''
May I present the cycle item: 'john doe'
Included 'john doe'.
''
May I present the cycle item: '42'
Included '42'.
''
May I present the cycle item: 'item1'
Included 'item1'.
''
May I present the cycle item: 'john doe'
Included 'john doe'.
''
May I present the cycle item: '42'
Included '42'.
''
May I present the cycle item: 'item1'
Included 'item1'.
''
May I present the cycle item: 'john doe'
Included 'john doe'.
''
May I present the cycle item: '42'
Included '42'.
''
May I present the cycle item: 'item1'
Included 'item1'.
'item1'
'john doe'
''
'item1'
''
'john doe'

View file

@ -1,69 +0,0 @@
integers and complex expressions
{{ 10-100 }}
{{ -(10-100) }}
{{ -(-(10-100)) }}
{{ -1 * (-(-(10-100))) }}
{{ -1 * (-(-(10-100)) ^ 2) ^ 3 + 3 * (5 - 17) + 1 + 2 }}
floats
{{ 5.5 }}
{{ 5.172841 }}
{{ 5.5 - 1.5 == 4 }}
{{ 5.5 - 1.5 == 4.0 }}
mul/div
{{ 2 * 5 }}
{{ 2 * 5.0 }}
{{ 2 * 0 }}
{{ 2.5 * 5.3 }}
{{ 1/2 }}
{{ 1/2.0 }}
{{ 1/0.000001 }}
logic expressions
{{ !true }}
{{ !(true || false) }}
{{ true || false }}
{{ true or false }}
{{ false or false }}
{{ false || false }}
{{ true && (true && (true && (true && (1 == 1 || false)))) }}
float comparison
{{ 5.5 <= 5.5 }}
{{ 5.5 < 5.5 }}
{{ 5.5 > 5.5 }}
{{ 5.5 >= 5.5 }}
remainders
{{ (simple.number+7)%7 }}
{{ (simple.number+7)%7 == 0 }}
{{ (simple.number+7)%6 }}
in/not in
{{ 5 in simple.intmap }}
{{ 2 in simple.intmap }}
{{ 7 in simple.intmap }}
{{ !(5 in simple.intmap) }}
{{ not(7 in simple.intmap) }}
{{ 1 in simple.multiple_item_list }}
{{ 4 in simple.multiple_item_list }}
{{ !(4 in simple.multiple_item_list) }}
{{ "Hello" in simple.misc_list }}
{{ "Hello2" in simple.misc_list }}
{{ 99 in simple.misc_list }}
{{ False in simple.misc_list }}
issue #48 (associativity for infix operators)
{{ 34/3*3 }}
{{ 10 + 24 / 6 / 2 }}
{{ 6 - 4 - 2 }}
issue #64 (uint comparison with int const)
{{ simple.uint }}
{{ simple.uint == 8 }}
{{ simple.uint == 9 }}
{{ simple.uint >= 8 }}
{{ simple.uint <= 8 }}
{{ simple.uint < 8 }}
{{ simple.uint > 8 }}

View file

@ -1,69 +0,0 @@
integers and complex expressions
-90
90
-90
90
531440999967.000000
floats
5.500000
5.172841
False
True
mul/div
10
10.000000
0
13.250000
0
0.500000
1000000.000000
logic expressions
False
False
True
True
False
False
True
float comparison
True
False
False
True
remainders
0
True
1
in/not in
True
True
False
False
True
True
False
True
True
False
True
False
issue #48 (associativity for infix operators)
33
12
0
issue #64 (uint comparison with int const)
8
True
False
True
True
False
False

View file

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

View file

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

View file

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

View file

@ -1 +0,0 @@
Start#This is base's bodyDefault contentextends-level-1#End

View file

@ -1,3 +0,0 @@
{% extends "extends_super.tpl" %}
{% block content %}{{ block.Super }}extends-level-2{% endblock %}

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