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

View file

@ -1,7 +1,6 @@
# 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.
#
# required = ["github.com/user/thing/cmd/thing"]
@ -17,8 +16,13 @@
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
@ -27,17 +31,24 @@
[[constraint]]
name = "github.com/flosch/pongo2"
branch = "master"
version = "3.0.0"
[[constraint]]
name = "github.com/gorilla/mux"
version = "1.6.2"
[[constraint]]
name = "github.com/prometheus/client_golang"
version = "0.8.0"
[[constraint]]
name = "github.com/robfig/cron"
version = "1.1.0"
[[constraint]]
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).
//
// 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 {
var m = math.MaxFloat64
var f float64
for quantile, epsilon := range targets {
if quantile*s.n <= r {
f = (2 * epsilon * r) / quantile
for _, t := range targets {
if t.quantile*s.n <= r {
f = (2 * t.epsilon * r) / t.quantile
} else {
f = (2 * epsilon * (s.n - r)) / (1 - quantile)
f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
}
if f < m {
m = f
@ -96,6 +101,25 @@ func NewTargeted(targets map[float64]float64) *Stream {
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
// design. Take care when using across multiple goroutines.
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
_test
.idea
.vscode
# Architecture specific extensions/prefixes
*.[568vq]

View file

@ -1,13 +1,12 @@
language: go
go:
- 1.7
- 1.3
- tip
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 gopkg.in/check.v1
- go get github.com/juju/errors
script:
- go test -v -covermode=count -coverprofile=coverage.out -bench . -cpu 1,4
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN || true'

View file

@ -1,9 +1,8 @@
# [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.svg)](https://godoc.org/github.com/flosch/pongo2)
[![GoDoc](https://godoc.org/github.com/flosch/pongo2?status.png)](https://godoc.org/github.com/flosch/pongo2)
[![Build Status](https://travis-ci.org/flosch/pongo2.svg?branch=master)](https://travis-ci.org/flosch/pongo2)
[![Coverage Status](https://coveralls.io/repos/flosch/pongo2/badge.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/)
[![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.
* Function signature for tag execution changed: not taking a `bytes.Buffer` anymore; instead `Execute()`-functions are now taking a `TemplateWriter` interface.
* Function signature for tag and filter parsing/execution changed (`error` return type changed to `*Error`).
* `INodeEvaluator` has been removed and got replaced by `IEvaluator`. You can change your existing tags/filters by simply replacing the interface.
* Two new helper functions: [`RenderTemplateFile()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateFile) and [`RenderTemplateString()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateString).
@ -106,7 +104,7 @@ If you're using the `master`-branch of pongo2, you might be interested in this s
## How you can help
* Write [filters](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3) / [tags](https://github.com/flosch/pongo2/blob/master/tags.go#L4) (see [tutorial](https://www.florian-schlachter.de/post/pongo2/)) by forking pongo2 and sending pull requests
* Write/improve code tests (use the following command to see what tests are missing: `go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out` or have a look on [gocover.io/github.com/flosch/pongo2](http://gocover.io/github.com/flosch/pongo2))
* Write/improve code tests (use the following command to see what tests are missing: `go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out`)
* Write/improve template tests (see the `template_tests/` directory)
* Write middleware, libraries and websites using pongo2. :-)
@ -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).
## Blog post series
* [pongo2 v3 released](https://www.florian-schlachter.de/post/pongo2-v3/)
* [pongo2 v2 released](https://www.florian-schlachter.de/post/pongo2-v2/)
* [pongo2 1.0 released](https://www.florian-schlachter.de/post/pongo2-10/) [August 8th 2014]
* [pongo2 playground](https://www.florian-schlachter.de/post/pongo2-playground/) [August 1st 2014]
@ -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.
* [macaron-pongo2](https://github.com/macaron-contrib/pongo2) - pongo2 support for [Macaron](https://github.com/Unknwon/macaron), a modular web framework.
* [ginpongo2](https://github.com/ngerakines/ginpongo2) - middleware for [gin](github.com/gin-gonic/gin) to use pongo2 templates
* [Build'n support for Iris' template engine](https://github.com/kataras/iris)
* [pongo2gin](https://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
* [pongo2-trans](https://github.com/fromYukki/pongo2trans) - `trans`-tag implementation for internationalization
Please add your project to this list and send me a pull request when you've developed something nice for pongo2.
# API-usage examples

View file

@ -1,14 +1,13 @@
package pongo2
import (
"fmt"
"regexp"
"github.com/juju/errors"
)
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.
// Currently, context["pongo2"] contains the following keys:
@ -25,15 +24,14 @@ func (c Context) checkForValidIdentifiers() *Error {
for k, v := range c {
if !reIdentifiers.MatchString(k) {
return &Error{
Sender: "checkForValidIdentifiers",
OrigError: errors.Errorf("context-key '%s' (value: '%+v') is not a valid identifier", k, v),
Sender: "checkForValidIdentifiers",
ErrorMsg: fmt.Sprintf("Context-key '%s' (value: '%+v') is not a valid identifier.", k, v),
}
}
}
return nil
}
// Update updates this context with the key/value-pairs from another context.
func (c Context) Update(other Context) Context {
for k, v := range other {
c[k] = v
@ -41,8 +39,6 @@ func (c Context) Update(other Context) Context {
return c
}
// ExecutionContext contains all data important for the current rendering state.
//
// If you're writing a custom tag, your tag's Execute()-function will
// have access to the ExecutionContext. This struct stores anything
// about the current rendering process's Context including
@ -101,10 +97,6 @@ func NewChildExecutionContext(parent *ExecutionContext) *ExecutionContext {
}
func (ctx *ExecutionContext) Error(msg string, token *Token) *Error {
return ctx.OrigError(errors.New(msg), token)
}
func (ctx *ExecutionContext) OrigError(err error, token *Token) *Error {
filename := ctx.template.name
var line, col int
if token != nil {
@ -115,13 +107,13 @@ func (ctx *ExecutionContext) OrigError(err error, token *Token) *Error {
col = token.Col
}
return &Error{
Template: ctx.template,
Filename: filename,
Line: line,
Column: col,
Token: token,
Sender: "execution",
OrigError: err,
Template: ctx.template,
Filename: filename,
Line: line,
Column: col,
Token: token,
Sender: "execution",
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"
)
// 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
// tag or filter) fill this object with as much information as you have.
// Make sure "Sender" is always given (if you're returning an error within
// a filter, make Sender equals 'filter:yourfilter'; same goes for tags: 'tag:mytag').
// It's okay if you only fill in ErrorMsg if you don't have any other details at hand.
type Error struct {
Template *Template
Filename string
Line int
Column int
Token *Token
Sender string
OrigError error
Template *Template
Filename string
Line int
Column int
Token *Token
Sender string
ErrorMsg string
}
func (e *Error) updateFromTokenIfNeeded(template *Template, t *Token) *Error {
@ -54,14 +54,14 @@ func (e *Error) Error() string {
}
}
s += "] "
s += e.OrigError.Error()
s += e.ErrorMsg
return s
}
// RawLine returns the affected line from the original template, if available.
func (e *Error) RawLine() (line string, available bool, outErr error) {
// Returns the affected line from the original template, if available.
func (e *Error) RawLine() (line string, available bool) {
if e.Line <= 0 || e.Filename == "<string>" {
return "", false, nil
return "", false
}
filename := e.Filename
@ -70,22 +70,17 @@ func (e *Error) RawLine() (line string, available bool, outErr error) {
}
file, err := os.Open(filename)
if err != nil {
return "", false, err
panic(err)
}
defer func() {
err := file.Close()
if err != nil && outErr == nil {
outErr = err
}
}()
defer file.Close()
scanner := bufio.NewScanner(file)
l := 0
for scanner.Scan() {
l++
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 (
"fmt"
"github.com/juju/errors"
)
// FilterFunction is the type filter functions must fulfil
type FilterFunction func(in *Value, param *Value) (out *Value, err *Error)
var filters map[string]FilterFunction
@ -15,38 +12,32 @@ func init() {
filters = make(map[string]FilterFunction)
}
// FilterExists returns true if the given filter is already registered
func FilterExists(name string) bool {
_, existing := filters[name]
return existing
}
// RegisterFilter registers a new filter. If there's already a filter with the same
// Registers a new filter. If there's already a filter with the same
// name, RegisterFilter will panic. You usually want to call this
// function in the filter's init() function:
// http://golang.org/doc/effective_go.html#init
//
// See http://www.florian-schlachter.de/post/pongo2/ for more about
// writing filters and tags.
func RegisterFilter(name string, fn FilterFunction) error {
if FilterExists(name) {
return errors.Errorf("filter with name '%s' is already registered", name)
func RegisterFilter(name string, fn FilterFunction) {
_, existing := filters[name]
if existing {
panic(fmt.Sprintf("Filter with name '%s' is already registered.", name))
}
filters[name] = fn
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.
func ReplaceFilter(name string, fn FilterFunction) error {
if !FilterExists(name) {
return errors.Errorf("filter with name '%s' does not exist (therefore cannot be overridden)", name)
func ReplaceFilter(name string, fn FilterFunction) {
_, existing := filters[name]
if !existing {
panic(fmt.Sprintf("Filter with name '%s' does not exist (therefore cannot be overridden).", name))
}
filters[name] = fn
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 {
val, err := ApplyFilter(name, value, param)
if err != nil {
@ -55,14 +46,13 @@ func MustApplyFilter(name string, value *Value, param *Value) *Value {
return val
}
// ApplyFilter applies a filter to a given value using the given parameters.
// Returns a *pongo2.Value or an error.
// Applies a filter to a given value using the given parameters. Returns a *pongo2.Value or an error.
func ApplyFilter(name string, value *Value, param *Value) (*Value, *Error) {
fn, existing := filters[name]
if !existing {
return nil, &Error{
Sender: "applyfilter",
OrigError: errors.Errorf("Filter with name '%s' not found.", name),
Sender: "applyfilter",
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)
}
filteredValue, err := fc.filterFunc(v, param)
filtered_value, err := fc.filterFunc(v, param)
if err != nil {
return nil, err.updateFromTokenIfNeeded(ctx.template, fc.token)
}
return filteredValue, nil
return filtered_value, nil
}
// Filter = IDENT | IDENT ":" FilterArg | IDENT "|" Filter
func (p *Parser) parseFilter() (*filterCall, *Error) {
identToken := p.MatchType(TokenIdentifier)
ident_token := p.MatchType(TokenIdentifier)
// Check filter ident
if identToken == nil {
if ident_token == nil {
return nil, p.Error("Filter name must be an identifier.", nil)
}
filter := &filterCall{
token: identToken,
name: identToken.Val,
token: ident_token,
name: ident_token.Val,
}
// Get the appropriate filter function and bind it
filterFn, exists := filters[identToken.Val]
filterFn, exists := filters[ident_token.Val]
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

View file

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

View file

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

View file

@ -1,13 +1,17 @@
package pongo2
import (
"bytes"
)
// The root document
type nodeDocument struct {
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 {
err := n.Execute(ctx, writer)
err := n.Execute(ctx, buffer)
if err != nil {
return err
}

View file

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

View file

@ -1,13 +1,17 @@
package pongo2
import (
"bytes"
)
type NodeWrapper struct {
Endtag string
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 {
err := n.Execute(ctx, writer)
err := n.Execute(ctx, buffer)
if err != nil {
return err
}

View file

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

View file

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

View file

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

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 (
"fmt"
"github.com/juju/errors"
)
type INodeTag interface {
@ -55,81 +53,80 @@ func init() {
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:
// http://golang.org/doc/effective_go.html#init
//
// See http://www.florian-schlachter.de/post/pongo2/ for more about
// writing filters and tags.
func RegisterTag(name string, parserFn TagParser) error {
func RegisterTag(name string, parserFn TagParser) {
_, existing := tags[name]
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{
name: name,
parser: parserFn,
}
return nil
}
// Replaces an already registered tag with a new implementation. Use this
// function with caution since it allows you to change existing tag behaviour.
func ReplaceTag(name string, parserFn TagParser) error {
func ReplaceTag(name string, parserFn TagParser) {
_, existing := tags[name]
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{
name: name,
parser: parserFn,
}
return nil
}
// Tag = "{%" IDENT ARGS "%}"
func (p *Parser) parseTagElement() (INodeTag, *Error) {
p.Consume() // consume "{%"
tokenName := p.MatchType(TokenIdentifier)
token_name := p.MatchType(TokenIdentifier)
// Check for identifier
if tokenName == nil {
if token_name == nil {
return nil, p.Error("Tag name must be an identifier.", nil)
}
// Check for the existing tag
tag, exists := tags[tokenName.Val]
tag, exists := tags[token_name.Val]
if !exists {
// Does not exists
return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", 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
if _, isBanned := p.template.set.bannedTags[tokenName.Val]; isBanned {
return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", tokenName.Val), tokenName)
if _, is_banned := p.template.set.bannedTags[token_name.Val]; is_banned {
return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", token_name.Val), token_name)
}
var argsToken []*Token
args_token := make([]*Token, 0)
for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 {
// Add token to args
argsToken = append(argsToken, p.Current())
args_token = append(args_token, p.Current())
p.Consume() // next token
}
// EOF?
if p.Remaining() == 0 {
return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.lastToken)
return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.last_token)
}
p.Match(TokenSymbol, "%}")
argParser := newParser(p.name, argsToken, p.template)
if len(argsToken) == 0 {
arg_parser := newParser(p.name, args_token, p.template)
if len(args_token) == 0 {
// This is done to have nice EOF error messages
argParser.lastToken = tokenName
arg_parser.last_token = token_name
}
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
import (
"bytes"
)
type tagAutoescapeNode struct {
wrapper *NodeWrapper
autoescape bool
}
func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
old := ctx.Autoescape
ctx.Autoescape = node.autoescape
err := node.wrapper.Execute(ctx, writer)
err := node.wrapper.Execute(ctx, buffer)
if err != nil {
return err
}
@ -20,22 +24,22 @@ func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, writer TemplateWri
}
func tagAutoescapeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
autoescapeNode := &tagAutoescapeNode{}
autoescape_node := &tagAutoescapeNode{}
wrapper, _, err := doc.WrapUntilTag("endautoescape")
if err != nil {
return nil, err
}
autoescapeNode.wrapper = wrapper
autoescape_node.wrapper = wrapper
modeToken := arguments.MatchType(TokenIdentifier)
if modeToken == nil {
mode_token := arguments.MatchType(TokenIdentifier)
if mode_token == nil {
return nil, arguments.Error("A mode is required for autoescape-tag.", nil)
}
if modeToken.Val == "on" {
autoescapeNode.autoescape = true
} else if modeToken.Val == "off" {
autoescapeNode.autoescape = false
if mode_token.Val == "on" {
autoescape_node.autoescape = true
} else if mode_token.Val == "off" {
autoescape_node.autoescape = false
} else {
return nil, arguments.Error("Only 'on' or 'off' is valid as an autoescape-mode.", nil)
}
@ -44,7 +48,7 @@ func tagAutoescapeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag
return nil, arguments.Error("Malformed autoescape-tag arguments.", nil)
}
return autoescapeNode, nil
return autoescape_node, nil
}
func init() {

View file

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

View file

@ -1,16 +1,20 @@
package pongo2
import (
"bytes"
)
type tagCommentNode struct{}
func (node *tagCommentNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
func (node *tagCommentNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
return nil
}
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)
err := doc.SkipUntilTag("endcomment")
_, _, err := doc.WrapUntilTag("endcomment")
if err != nil {
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 commentNode, nil
return comment_node, nil
}
func init() {

View file

@ -1,5 +1,9 @@
package pongo2
import (
"bytes"
)
type tagCycleValue struct {
node *tagCycleNode
value *Value
@ -9,7 +13,7 @@ type tagCycleNode struct {
position *Token
args []IEvaluator
idx int
asName string
as_name string
silent bool
}
@ -17,7 +21,7 @@ func (cv *tagCycleValue) String() 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)]
node.idx++
@ -42,30 +46,30 @@ func (node *tagCycleNode) Execute(ctx *ExecutionContext, writer TemplateWriter)
t.value = val
if !t.node.silent {
writer.WriteString(val.String())
buffer.WriteString(val.String())
}
} else {
// Regular call
cycleValue := &tagCycleValue{
cycle_value := &tagCycleValue{
node: node,
value: val,
}
if node.asName != "" {
ctx.Private[node.asName] = cycleValue
if node.as_name != "" {
ctx.Private[node.as_name] = cycle_value
}
if !node.silent {
writer.WriteString(val.String())
buffer.WriteString(val.String())
}
}
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) {
cycleNode := &tagCycleNode{
cycle_node := &tagCycleNode{
position: start,
}
@ -74,19 +78,19 @@ func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er
if err != nil {
return nil, err
}
cycleNode.args = append(cycleNode.args, node)
cycle_node.args = append(cycle_node.args, node)
if arguments.MatchOne(TokenKeyword, "as") != nil {
// as
nameToken := arguments.MatchType(TokenIdentifier)
if nameToken == nil {
name_token := arguments.MatchType(TokenIdentifier)
if name_token == 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 {
cycleNode.silent = true
cycle_node.silent = true
}
// 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 cycleNode, nil
return cycle_node, nil
}
func init() {

View file

@ -1,15 +1,19 @@
package pongo2
import (
"bytes"
)
type tagExtendsNode struct {
filename string
}
func (node *tagExtendsNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
func (node *tagExtendsNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
return nil
}
func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
extendsNode := &tagExtendsNode{}
extends_node := &tagExtendsNode{}
if doc.template.level > 1 {
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)
}
if filenameToken := arguments.MatchType(TokenString); filenameToken != nil {
if filename_token := arguments.MatchType(TokenString); filename_token != nil {
// prepared, static template
// 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
parentTemplate, err := doc.template.set.FromFile(parentFilename)
parent_template, err := doc.template.set.FromFile(parent_filename)
if err != nil {
return nil, err.(*Error)
}
// Keep track of things
parentTemplate.child = doc.template
doc.template.parent = parentTemplate
extendsNode.filename = parentFilename
parent_template.child = doc.template
doc.template.parent = parent_template
extends_node.filename = parent_filename
} else {
return nil, arguments.Error("Tag 'extends' requires a template filename as string.", nil)
}
@ -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 extendsNode, nil
return extends_node, nil
}
func init() {

View file

@ -5,8 +5,8 @@ import (
)
type nodeFilterCall struct {
name string
paramExpr IEvaluator
name string
param_expr IEvaluator
}
type tagFilterNode struct {
@ -15,7 +15,7 @@ type tagFilterNode struct {
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
err := node.bodyWrapper.Execute(ctx, temp)
@ -27,8 +27,8 @@ func (node *tagFilterNode) Execute(ctx *ExecutionContext, writer TemplateWriter)
for _, call := range node.filterChain {
var param *Value
if call.paramExpr != nil {
param, err = call.paramExpr.Evaluate(ctx)
if call.param_expr != nil {
param, err = call.param_expr.Evaluate(ctx)
if err != nil {
return err
}
@ -41,13 +41,13 @@ func (node *tagFilterNode) Execute(ctx *ExecutionContext, writer TemplateWriter)
}
}
writer.WriteString(value.String())
buffer.WriteString(value.String())
return nil
}
func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
filterNode := &tagFilterNode{
filter_node := &tagFilterNode{
position: start,
}
@ -55,16 +55,16 @@ func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *E
if err != nil {
return nil, err
}
filterNode.bodyWrapper = wrapper
filter_node.bodyWrapper = wrapper
for arguments.Remaining() > 0 {
filterCall := &nodeFilterCall{}
nameToken := arguments.MatchType(TokenIdentifier)
if nameToken == nil {
name_token := arguments.MatchType(TokenIdentifier)
if name_token == 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 {
// Filter parameter
@ -73,10 +73,10 @@ func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *E
if err != nil {
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 {
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 filterNode, nil
return filter_node, nil
}
func init() {

View file

@ -1,11 +1,15 @@
package pongo2
import (
"bytes"
)
type tagFirstofNode struct {
position *Token
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 {
val, err := arg.Evaluate(ctx)
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
}
}
@ -29,7 +33,7 @@ func (node *tagFirstofNode) Execute(ctx *ExecutionContext, writer TemplateWriter
}
func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
firstofNode := &tagFirstofNode{
firstof_node := &tagFirstofNode{
position: start,
}
@ -38,10 +42,10 @@ func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *
if err != nil {
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() {

View file

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

View file

@ -1,11 +1,15 @@
package pongo2
import (
"bytes"
)
type tagIfNode struct {
conditions []IEvaluator
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 {
result, err := condition.Evaluate(ctx)
if err != nil {
@ -13,25 +17,26 @@ func (node *tagIfNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Er
}
if result.IsTrue() {
return node.wrappers[i].Execute(ctx, writer)
}
// Last condition?
if len(node.conditions) == i+1 && len(node.wrappers) > i+1 {
return node.wrappers[i+1].Execute(ctx, writer)
return node.wrappers[i].Execute(ctx, buffer)
} else {
// Last condition?
if len(node.conditions) == i+1 && len(node.wrappers) > i+1 {
return node.wrappers[i+1].Execute(ctx, buffer)
}
}
}
return nil
}
func tagIfParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ifNode := &tagIfNode{}
if_node := &tagIfNode{}
// Parse first and main IF condition
condition, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
ifNode.conditions = append(ifNode.conditions, condition)
if_node.conditions = append(if_node.conditions, condition)
if arguments.Remaining() > 0 {
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
for {
wrapper, tagArgs, err := doc.WrapUntilTag("elif", "else", "endif")
wrapper, tag_args, err := doc.WrapUntilTag("elif", "else", "endif")
if err != nil {
return nil, err
}
ifNode.wrappers = append(ifNode.wrappers, wrapper)
if_node.wrappers = append(if_node.wrappers, wrapper)
if wrapper.Endtag == "elif" {
// elif can take a condition
condition, err = tagArgs.ParseExpression()
condition, err := tag_args.ParseExpression()
if err != nil {
return nil, err
}
ifNode.conditions = append(ifNode.conditions, condition)
if_node.conditions = append(if_node.conditions, condition)
if tagArgs.Remaining() > 0 {
return nil, tagArgs.Error("Elif-condition is malformed.", nil)
if tag_args.Remaining() > 0 {
return nil, tag_args.Error("Elif-condition is malformed.", nil)
}
} else {
if tagArgs.Count() > 0 {
if tag_args.Count() > 0 {
// 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() {

View file

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

View file

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

View file

@ -1,12 +1,16 @@
package pongo2
import (
"bytes"
)
type tagIfNotEqualNode struct {
var1, var2 IEvaluator
thenWrapper *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)
if err != nil {
return err
@ -19,16 +23,17 @@ func (node *tagIfNotEqualNode) Execute(ctx *ExecutionContext, writer TemplateWri
result := !r1.EqualValueTo(r2)
if result {
return node.thenWrapper.Execute(ctx, writer)
}
if node.elseWrapper != nil {
return node.elseWrapper.Execute(ctx, writer)
return node.thenWrapper.Execute(ctx, buffer)
} else {
if node.elseWrapper != nil {
return node.elseWrapper.Execute(ctx, buffer)
}
}
return nil
}
func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ifnotequalNode := &tagIfNotEqualNode{}
ifnotequal_node := &tagIfNotEqualNode{}
// Parse two expressions
var1, err := arguments.ParseExpression()
@ -39,19 +44,19 @@ func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag
if err != nil {
return nil, err
}
ifnotequalNode.var1 = var1
ifnotequalNode.var2 = var2
ifnotequal_node.var1 = var1
ifnotequal_node.var2 = var2
if arguments.Remaining() > 0 {
return nil, arguments.Error("ifequal only takes 2 arguments.", nil)
}
// Wrap then/else-blocks
wrapper, endargs, err := doc.WrapUntilTag("else", "endifnotequal")
wrapper, endargs, err := doc.WrapUntilTag("else", "endifequal")
if err != nil {
return nil, err
}
ifnotequalNode.thenWrapper = wrapper
ifnotequal_node.thenWrapper = wrapper
if endargs.Count() > 0 {
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 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 {
return nil, err
}
ifnotequalNode.elseWrapper = wrapper
ifnotequal_node.elseWrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
}
return ifnotequalNode, nil
return ifnotequal_node, nil
}
func init() {

View file

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

View file

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

View file

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

View file

@ -6,16 +6,16 @@ import (
)
type tagMacroNode struct {
position *Token
name string
argsOrder []string
args map[string]IEvaluator
exported bool
position *Token
name string
args_order []string
args map[string]IEvaluator
exported bool
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 {
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 {
argsCtx := make(Context)
args_ctx := make(Context)
for k, v := range node.args {
if v == nil {
// User did not provided a default value
argsCtx[k] = nil
args_ctx[k] = nil
} else {
// Evaluate the default value
valueExpr, err := v.Evaluate(ctx)
value_expr, err := v.Evaluate(ctx)
if err != nil {
ctx.Logf(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.
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
return AsSafeValue(err.Error())
@ -55,10 +55,10 @@ func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value {
macroCtx := NewChildExecutionContext(ctx)
// Register all arguments in the private context
macroCtx.Private.Update(argsCtx)
macroCtx.Private.Update(args_ctx)
for idx, argValue := range args {
macroCtx.Private[node.argsOrder[idx]] = argValue.Interface()
for idx, arg_value := range args {
macroCtx.Private[node.args_order[idx]] = arg_value.Interface()
}
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) {
macroNode := &tagMacroNode{
macro_node := &tagMacroNode{
position: start,
args: make(map[string]IEvaluator),
}
nameToken := arguments.MatchType(TokenIdentifier)
if nameToken == nil {
name_token := arguments.MatchType(TokenIdentifier)
if name_token == 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 {
return nil, arguments.Error("Expected '('.", nil)
}
for arguments.Match(TokenSymbol, ")") == nil {
argNameToken := arguments.MatchType(TokenIdentifier)
if argNameToken == nil {
arg_name_token := arguments.MatchType(TokenIdentifier)
if arg_name_token == 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 {
// Default expression follows
argDefaultExpr, err := arguments.ParseExpression()
arg_default_expr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
macroNode.args[argNameToken.Val] = argDefaultExpr
macro_node.args[arg_name_token.Val] = arg_default_expr
} else {
// No default expression
macroNode.args[argNameToken.Val] = nil
macro_node.args[arg_name_token.Val] = 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 {
macroNode.exported = true
macro_node.exported = true
}
if arguments.Remaining() > 0 {
@ -126,22 +126,22 @@ func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Er
if err != nil {
return nil, err
}
macroNode.wrapper = wrapper
macro_node.wrapper = wrapper
if endargs.Count() > 0 {
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
_, has := doc.template.exportedMacros[macroNode.name]
_, has := doc.template.exported_macros[macro_node.name]
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() {

View file

@ -1,6 +1,7 @@
package pongo2
import (
"bytes"
"time"
)
@ -10,7 +11,7 @@ type tagNowNode struct {
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
if node.fake {
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()
}
writer.WriteString(t.Format(node.format))
buffer.WriteString(t.Format(node.format))
return nil
}
func tagNowParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
nowNode := &tagNowNode{
now_node := &tagNowNode{
position: start,
}
formatToken := arguments.MatchType(TokenString)
if formatToken == nil {
format_token := arguments.MatchType(TokenString)
if format_token == 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 {
nowNode.fake = true
now_node.fake = true
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed now-tag arguments.", nil)
}
return nowNode, nil
return now_node, nil
}
func init() {

View file

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

View file

@ -11,7 +11,7 @@ type tagSpacelessNode struct {
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
err := node.wrapper.Execute(ctx, b)
@ -28,25 +28,25 @@ func (node *tagSpacelessNode) Execute(ctx *ExecutionContext, writer TemplateWrit
s = s2
}
writer.WriteString(s)
buffer.WriteString(s)
return nil
}
func tagSpacelessParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
spacelessNode := &tagSpacelessNode{}
spaceless_node := &tagSpacelessNode{}
wrapper, _, err := doc.WrapUntilTag("endspaceless")
if err != nil {
return nil, err
}
spacelessNode.wrapper = wrapper
spaceless_node.wrapper = wrapper
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed spaceless-tag arguments.", nil)
}
return spacelessNode, nil
return spaceless_node, nil
}
func init() {

View file

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

View file

@ -1,5 +1,9 @@
package pongo2
import (
"bytes"
)
type tagTemplateTagNode struct {
content string
}
@ -15,20 +19,20 @@ var templateTagMapping = map[string]string{
"closecomment": "#}",
}
func (node *tagTemplateTagNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
writer.WriteString(node.content)
func (node *tagTemplateTagNode) Execute(ctx *ExecutionContext, buffer *bytes.Buffer) *Error {
buffer.WriteString(node.content)
return nil
}
func tagTemplateTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ttNode := &tagTemplateTagNode{}
tt_node := &tagTemplateTagNode{}
if argToken := arguments.MatchType(TokenIdentifier); argToken != nil {
output, found := templateTagMapping[argToken.Val]
if arg_token := arguments.MatchType(TokenIdentifier); arg_token != nil {
output, found := templateTagMapping[arg_token.Val]
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 {
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 ttNode, nil
return tt_node, nil
}
func init() {

View file

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

View file

@ -1,16 +1,20 @@
package pongo2
import (
"bytes"
)
type tagWithNode struct {
withPairs map[string]IEvaluator
wrapper *NodeWrapper
with_pairs map[string]IEvaluator
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
withctx := NewChildExecutionContext(ctx)
// 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)
if err != nil {
return err
@ -18,12 +22,12 @@ func (node *tagWithNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *
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) {
withNode := &tagWithNode{
withPairs: make(map[string]IEvaluator),
with_node := &tagWithNode{
with_pairs: make(map[string]IEvaluator),
}
if arguments.Count() == 0 {
@ -34,7 +38,7 @@ func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Err
if err != nil {
return nil, err
}
withNode.wrapper = wrapper
with_node.wrapper = wrapper
if endargs.Count() > 0 {
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).
// 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++ {
if arguments.PeekN(i, TokenKeyword, "as") != nil {
oldStyle = true
old_style = true
break
}
}
for arguments.Remaining() > 0 {
if oldStyle {
valueExpr, err := arguments.ParseExpression()
if old_style {
value_expr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
if arguments.Match(TokenKeyword, "as") == nil {
return nil, arguments.Error("Expected 'as' keyword.", nil)
}
keyToken := arguments.MatchType(TokenIdentifier)
if keyToken == nil {
key_token := arguments.MatchType(TokenIdentifier)
if key_token == nil {
return nil, arguments.Error("Expected an identifier", nil)
}
withNode.withPairs[keyToken.Val] = valueExpr
with_node.with_pairs[key_token.Val] = value_expr
} else {
keyToken := arguments.MatchType(TokenIdentifier)
if keyToken == nil {
key_token := arguments.MatchType(TokenIdentifier)
if key_token == nil {
return nil, arguments.Error("Expected an identifier", nil)
}
if arguments.Match(TokenSymbol, "=") == nil {
return nil, arguments.Error("Expected '='.", nil)
}
valueExpr, err := arguments.ParseExpression()
value_expr, err := arguments.ParseExpression()
if err != nil {
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() {

View file

@ -2,72 +2,52 @@ package pongo2
import (
"bytes"
"fmt"
"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 {
set *TemplateSet
// Input
isTplString bool
name string
tpl string
size int
is_tpl_string bool
name string
tpl string
size int
// Calculation
tokens []*Token
parser *Parser
// first come, first serve (it's important to not override existing entries in here)
level int
parent *Template
child *Template
blocks map[string]*NodeWrapper
exportedMacros map[string]*tagMacroNode
level int
parent *Template
child *Template
blocks map[string]*NodeWrapper
exported_macros map[string]*tagMacroNode
// Output
root *nodeDocument
}
func newTemplateString(set *TemplateSet, tpl []byte) (*Template, error) {
func newTemplateString(set *TemplateSet, tpl string) (*Template, error) {
return newTemplate(set, "<string>", true, tpl)
}
func newTemplate(set *TemplateSet, name string, isTplString bool, tpl []byte) (*Template, error) {
strTpl := string(tpl)
func newTemplate(set *TemplateSet, name string, is_tpl_string bool, tpl string) (*Template, error) {
// Create the template
t := &Template{
set: set,
isTplString: isTplString,
name: name,
tpl: strTpl,
size: len(strTpl),
blocks: make(map[string]*NodeWrapper),
exportedMacros: make(map[string]*tagMacroNode),
set: set,
is_tpl_string: is_tpl_string,
name: name,
tpl: tpl,
size: len(tpl),
blocks: make(map[string]*NodeWrapper),
exported_macros: make(map[string]*tagMacroNode),
}
// Tokenize it
tokens, err := lex(name, strTpl)
tokens, err := lex(name, tpl)
if err != nil {
return nil, err
}
@ -87,7 +67,11 @@ func newTemplate(set *TemplateSet, name string, isTplString bool, tpl []byte) (*
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)
parent := tpl
for parent.parent != nil {
@ -105,17 +89,17 @@ func (tpl *Template) execute(context Context, writer TemplateWriter) error {
// Check for context name syntax
err := newContext.checkForValidIdentifiers()
if err != nil {
return err
return nil, err
}
// Check for clashes with macro names
for k := range newContext {
_, has := tpl.exportedMacros[k]
for k, _ := range newContext {
_, has := tpl.exported_macros[k]
if has {
return &Error{
Filename: tpl.name,
Sender: "execution",
OrigError: errors.Errorf("context key name '%s' clashes with macro '%s'", k, k),
return nil, &Error{
Filename: tpl.name,
Sender: "execution",
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)
// Run the selected document
if err := parent.root.Execute(ctx, writer); err != nil {
return err
}
return nil
}
func (tpl *Template) newTemplateWriterAndExecute(context Context, writer io.Writer) error {
return tpl.execute(context, &templateWriter{w: writer})
}
func (tpl *Template) newBufferAndExecute(context Context) (*bytes.Buffer, error) {
// Create output buffer
// We assume that the rendered template will be 30% larger
buffer := bytes.NewBuffer(make([]byte, 0, int(float64(tpl.size)*1.3)))
if err := tpl.execute(context, buffer); err != nil {
err := parent.root.Execute(ctx, buffer)
if err != nil {
return nil, err
}
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
// is being returned.
func (tpl *Template) ExecuteWriter(context Context, writer io.Writer) error {
buf, err := tpl.newBufferAndExecute(context)
buffer, err := tpl.execute(context)
if err != nil {
return err
}
_, err = buf.WriteTo(writer)
if err != nil {
return err
l := buffer.Len()
n, werr := buffer.WriteTo(writer)
if int(n) != l {
panic(fmt.Sprintf("error on writing template: n(%d) != buffer.Len(%d)", n, l))
}
if werr != nil {
return &Error{
Filename: tpl.name,
Sender: "execution",
ErrorMsg: werr.Error(),
}
}
return nil
}
// Same as ExecuteWriter. The only difference between both functions is that
// this function might already have written parts of the generated template in the
// case of an execution error because there's no intermediate buffer involved for
// performance reasons. This is handy if you need high performance template
// generation or if you want to manage your own pool of buffers.
func (tpl *Template) ExecuteWriterUnbuffered(context Context, writer io.Writer) error {
return tpl.newTemplateWriterAndExecute(context, writer)
}
// Executes the template and returns the rendered template as a []byte
func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) {
// Execute template
buffer, err := tpl.newBufferAndExecute(context)
buffer, err := tpl.execute(context)
if err != nil {
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
func (tpl *Template) Execute(context Context) (string, error) {
// Execute template
buffer, err := tpl.newBufferAndExecute(context)
buffer, err := tpl.execute(context)
if err != nil {
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 (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"sync"
"github.com/juju/errors"
)
// TemplateLoader allows to implement a virtual file system.
type TemplateLoader interface {
// Abs calculates the path to a given template. Whenever a path must be resolved
// due to an import from another template, the base equals the parent template's path.
Abs(base, name string) string
// Get returns an io.Reader where the template's content can be read from.
Get(path string) (io.Reader, error)
}
// TemplateSet allows you to create your own group of templates with their own
// global context (which is shared among all members of the set) and their own
// configuration.
// It's useful for a separation of different kind of templates
// (e. g. web templates vs. mail templates).
// A template set allows you to create your own group of templates with their own global context (which is shared
// among all members of the set), their own configuration (like a specific base directory) and their own sandbox.
// It's useful for a separation of different kind of templates (e. g. web templates vs. mail templates).
type TemplateSet struct {
name string
loader TemplateLoader
name string
// Globals will be provided to all templates created within this template set
Globals Context
// If debug is true (default false), ExecutionContext.Logf() will work and output
// to STDOUT. Furthermore, FromCache() won't cache the templates.
// Make sure to synchronize the access to it in case you're changing this
// If debug is true (default false), ExecutionContext.Logf() will work and output to STDOUT. Furthermore,
// FromCache() won't cache the templates. Make sure to synchronize the access to it in case you're changing this
// variable during program execution (and template compilation/execution).
Debug bool
// Base directory: If you set the base directory (string is non-empty), all filename lookups in tags/filters are
// relative to this directory. If it's empty, all lookups are relative to the current filename which is importing.
baseDirectory string
// Sandbox features
// - Limit access to directories (using SandboxDirectories)
// - Disallow access to specific tags and/or filters (using BanTag() and BanFilter())
//
// 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).
// You can limit file accesses (for all tags/filters which are using pongo2's file resolver technique)
// to these sandbox directories. All default pongo2 filters/tags are respecting these restrictions.
// For example, if you only have your base directory in the list, a {% ssi "/etc/passwd" %} will not work.
// No items in SandboxDirectories means no restrictions at all.
//
// For efficiency reasons you can ban tags/filters only *before* you have added your first
// template to the set (restrictions are statically checked). After you added one, it's not possible anymore
// (for your personal security).
//
// SandboxDirectories can be changed at runtime. Please synchronize the access to it if you need to change it
// after you've added your first template to the set. You *must* use this match pattern for your directories:
// http://golang.org/pkg/path/filepath/#Match
SandboxDirectories []string
firstTemplateCreated bool
bannedTags map[string]bool
bannedFilters map[string]bool
@ -54,13 +53,11 @@ type TemplateSet struct {
templateCacheMutex sync.Mutex
}
// NewSet can be used to create sets with different kind of templates
// (e. g. web from mail templates), with different globals or
// other configurations.
func NewSet(name string, loader TemplateLoader) *TemplateSet {
// Create your own template sets to separate different kind of templates (e. g. web from mail templates) with
// different globals or other configurations (like base directories).
func NewSet(name string) *TemplateSet {
return &TemplateSet{
name: name,
loader: loader,
Globals: make(Context),
bannedTags: 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 {
name := ""
if tpl != nil && tpl.isTplString {
return path
// Use this function to set your template set's base directory. This directory will be used for any relative
// path in filters, tags and From*-functions to determine your template.
func (set *TemplateSet) SetBaseDirectory(name string) error {
// Make the path absolute
if !filepath.IsAbs(name) {
abs, err := filepath.Abs(name)
if err != nil {
return err
}
name = abs
}
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) BanTag(name string) error {
func (set *TemplateSet) BaseDirectory() string {
return set.baseDirectory
}
// Ban a specific tag for this template set. See more in the documentation for TemplateSet.
func (set *TemplateSet) BanTag(name string) {
_, has := tags[name]
if !has {
return errors.Errorf("tag '%s' not found", name)
panic(fmt.Sprintf("Tag '%s' not found.", name))
}
if set.firstTemplateCreated {
return errors.New("you cannot ban any tags after you've added your first template to your template set")
panic("You cannot ban any tags after you've added your first template to your template set.")
}
_, has = set.bannedTags[name]
if has {
return errors.Errorf("tag '%s' is already banned", name)
panic(fmt.Sprintf("Tag '%s' is already banned.", name))
}
set.bannedTags[name] = true
return nil
}
// BanFilter bans a specific filter for this template set. See more in the documentation for TemplateSet.
func (set *TemplateSet) BanFilter(name string) error {
// Ban a specific filter for this template set. See more in the documentation for TemplateSet.
func (set *TemplateSet) BanFilter(name string) {
_, has := filters[name]
if !has {
return errors.Errorf("filter '%s' not found", name)
panic(fmt.Sprintf("Filter '%s' not found.", name))
}
if set.firstTemplateCreated {
return errors.New("you cannot ban any filters after you've added your first template to your template set")
panic("You cannot ban any filters after you've added your first template to your template set.")
}
_, has = set.bannedFilters[name]
if has {
return errors.Errorf("filter '%s' is already banned", name)
panic(fmt.Sprintf("Filter '%s' is already banned.", name))
}
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.
// If TemplateSet.Debug is true (for example during development phase),
// FromCache() will not cache the template and instead recompile it on any
// call (to make changes to a template live instantaneously).
// Like FromFile(), FromCache() takes a relative path to a set base directory.
// Sandbox restrictions apply (if given).
func (set *TemplateSet) FromCache(filename string) (*Template, error) {
if set.Debug {
// Recompile on any request
return set.FromFile(filename)
}
// Cache the template
cleanedFilename := set.resolveFilename(nil, filename)
} else {
// Cache the template
cleaned_filename := set.resolveFilename(nil, filename)
set.templateCacheMutex.Lock()
defer set.templateCacheMutex.Unlock()
set.templateCacheMutex.Lock()
defer set.templateCacheMutex.Unlock()
tpl, has := set.templateCache[cleanedFilename]
tpl, has := set.templateCache[cleaned_filename]
// Cache miss
if !has {
tpl, err := set.FromFile(cleanedFilename)
if err != nil {
return nil, err
// Cache miss
if !has {
tpl, err := set.FromFile(cleaned_filename)
if err != nil {
return nil, err
}
set.templateCache[cleaned_filename] = tpl
return tpl, nil
}
set.templateCache[cleanedFilename] = tpl
// Cache hit
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) {
set.firstTemplateCreated = true
return newTemplateString(set, []byte(tpl))
}
// FromBytes loads a template from bytes and returns a Template instance.
func (set *TemplateSet) FromBytes(tpl []byte) (*Template, error) {
set.firstTemplateCreated = true
return newTemplateString(set, tpl)
}
// FromFile loads a template from a filename and returns a Template instance.
// Loads a template from a filename and returns a Template instance.
// If a base directory is set, the filename must be either relative to it
// or be an absolute path. Sandbox restrictions (SandboxDirectories) apply
// if given.
func (set *TemplateSet) FromFile(filename string) (*Template, error) {
set.firstTemplateCreated = true
fd, err := set.loader.Get(set.resolveFilename(nil, filename))
buf, err := ioutil.ReadFile(set.resolveFilename(nil, filename))
if err != nil {
return nil, &Error{
Filename: filename,
Sender: "fromfile",
OrigError: err,
Filename: filename,
Sender: "fromfile",
ErrorMsg: err.Error(),
}
}
buf, err := ioutil.ReadAll(fd)
if err != nil {
return nil, &Error{
Filename: filename,
Sender: "fromfile",
OrigError: err,
}
}
return newTemplate(set, filename, false, buf)
return newTemplate(set, filename, false, string(buf))
}
// RenderTemplateString is a shortcut and renders a template string directly.
func (set *TemplateSet) RenderTemplateString(s string, ctx Context) (string, error) {
// Shortcut; renders a template string directly. Panics when providing a
// malformed template or an error occurs during execution.
func (set *TemplateSet) RenderTemplateString(s string, ctx Context) string {
set.firstTemplateCreated = true
tpl := Must(set.FromString(s))
result, err := tpl.Execute(ctx)
if err != nil {
return "", err
panic(err)
}
return result, nil
return result
}
// RenderTemplateBytes is a shortcut and renders template bytes directly.
func (set *TemplateSet) RenderTemplateBytes(b []byte, ctx Context) (string, error) {
set.firstTemplateCreated = true
tpl := Must(set.FromBytes(b))
result, err := tpl.Execute(ctx)
if err != nil {
return "", err
}
return result, nil
}
// RenderTemplateFile is a shortcut and renders a template file directly.
func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) (string, error) {
// Shortcut; renders a template file directly. Panics when providing a
// malformed template or an error occurs during execution.
func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) string {
set.firstTemplateCreated = true
tpl := Must(set.FromFile(fn))
result, err := tpl.Execute(ctx)
if err != nil {
return "", err
panic(err)
}
return result, nil
return result
}
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)
func logf(format string, items ...interface{}) {
if debug {
@ -236,18 +279,13 @@ func logf(format string, items ...interface{}) {
var (
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
// system and is being used by the DefaultSet.
DefaultLoader = MustNewLocalFileSystemLoader("")
// DefaultSet is a set created for you for convinience reasons.
DefaultSet = NewSet("default", DefaultLoader)
// Creating a default set
DefaultSet = NewSet("default")
// Methods on the default set
FromString = DefaultSet.FromString
FromBytes = DefaultSet.FromBytes
FromFile = DefaultSet.FromFile
FromCache = DefaultSet.FromCache
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